moazx commited on
Commit
22770b7
·
1 Parent(s): 2c23d2c

Implement the core eligibility logic natively in Python.

Browse files
api/models.py CHANGED
@@ -111,7 +111,7 @@ class HBVPatientInput(BaseModel):
111
  hbv_dna_level: float = Field(..., description="HBV DNA level in IU/mL", ge=0)
112
  hbeag_status: str = Field(..., description="HBeAg status: Positive / Negative")
113
  alt_level: float = Field(..., description="ALT level in U/L", ge=0)
114
- fibrosis_stage: str = Field(..., description="Fibrosis/Cirrhosis stage: F0-F1 / F2-F3 / F4")
115
  necroinflammatory_activity: str = Field(..., description="Necroinflammatory activity: A0 / A1 / A2 / A3")
116
  extrahepatic_manifestations: bool = Field(..., description="Presence of extrahepatic manifestations")
117
  immunosuppression_status: Optional[str] = Field(None, description="Immunosuppression status: None / Chemotherapy / Other")
 
111
  hbv_dna_level: float = Field(..., description="HBV DNA level in IU/mL", ge=0)
112
  hbeag_status: str = Field(..., description="HBeAg status: Positive / Negative")
113
  alt_level: float = Field(..., description="ALT level in U/L", ge=0)
114
+ fibrosis_stage: str = Field(..., description="Fibrosis/Cirrhosis stage: F0 / F1 / F2 / F3 / F4")
115
  necroinflammatory_activity: str = Field(..., description="Necroinflammatory activity: A0 / A1 / A2 / A3")
116
  extrahepatic_manifestations: bool = Field(..., description="Presence of extrahepatic manifestations")
117
  immunosuppression_status: Optional[str] = Field(None, description="Immunosuppression status: None / Chemotherapy / Other")
api/routers/hbv_assessment.py CHANGED
@@ -72,7 +72,7 @@ async def assess_patient_from_text(text_input: TextAssessmentInput) -> HBVAssess
72
  HBV DNA: 5000 IU/mL
73
  HBeAg: Positive
74
  ALT: 80 U/L
75
- Fibrosis stage: F2-F3
76
  Necroinflammatory activity: A2
77
  No extrahepatic manifestations
78
  No immunosuppression
 
72
  HBV DNA: 5000 IU/mL
73
  HBeAg: Positive
74
  ALT: 80 U/L
75
+ Fibrosis stage: F2
76
  Necroinflammatory activity: A2
77
  No extrahepatic manifestations
78
  No immunosuppression
core/hbv_assessment.py CHANGED
@@ -5,7 +5,7 @@ Evaluates patient eligibility for HBV treatment according to SASLT 2021 guidelin
5
  import logging
6
  import json
7
  import re
8
- from typing import Dict, Any
9
  from .config import get_llm
10
 
11
  logger = logging.getLogger(__name__)
@@ -203,6 +203,88 @@ Liver fibrosis is usually classified into five stages:
203
  F0—no fibrosis; F1—mild fibrosis, pericellular collagen deposits; F2—moderate fibrosis, beginning bridging fibrosis; F3—severe fibrosis, defined as presence of numerous bridges and septa; F4—cirrhosis
204
  """
205
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
206
 
207
 
208
  def assess_hbv_eligibility(patient_data: Dict[str, Any]) -> Dict[str, Any]:
@@ -219,26 +301,44 @@ def assess_hbv_eligibility(patient_data: Dict[str, Any]) -> Dict[str, Any]:
219
  - recommendations: str (comprehensive narrative with inline citations in format [SASLT 2021, Page X])
220
  """
221
  try:
222
- # # Check if HBsAg is positive (required for treatment consideration)
223
- # if patient_data.get("hbsag_status") != "Positive":
224
- # return {
225
- # "eligible": False,
226
- # "recommendations": "Patient is HBsAg negative. HBV treatment is not indicated. HBsAg positivity is required for HBV treatment consideration according to SASLT 2021 guidelines."
227
- # }
228
-
229
  # Use hardcoded SASLT 2021 guidelines instead of RAG retrieval
230
  logger.info("Using hardcoded SASLT 2021 guidelines (Pages 3, 4, 6, 7, 8, 9, 10)")
231
 
232
- # Define ALT ULN for context
233
- sex = patient_data.get("sex", "Male")
234
- #alt_uln = 35 if sex == "Male" else 25
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
235
 
236
- # Format patient data for prompt
 
237
  age = patient_data.get("age", "N/A")
238
  pregnancy_status = patient_data.get("pregnancy_status", "N/A")
239
  hbsag_status = patient_data.get("hbsag_status", "N/A")
240
  duration_hbsag = patient_data.get("duration_hbsag_months", "N/A")
241
- hbv_dna = patient_data.get("hbv_dna_level", 0)
242
  hbeag_status = patient_data.get("hbeag_status", "N/A")
243
  alt_level = patient_data.get("alt_level", 0)
244
  fibrosis_stage = patient_data.get("fibrosis_stage", "N/A")
@@ -248,8 +348,11 @@ def assess_hbv_eligibility(patient_data: Dict[str, Any]) -> Dict[str, Any]:
248
  coinfections = patient_data.get("coinfections", [])
249
  family_history = patient_data.get("family_history_cirrhosis_hcc", False)
250
  comorbidities = patient_data.get("other_comorbidities", [])
251
-
252
- # Create prompt for LLM to analyze patient against guidelines
 
 
 
253
  analysis_prompt = f"""You are an HBV treatment eligibility assessment system. Analyze the patient data against the SASLT 2021 guidelines.
254
  PATIENT DATA:
255
  - Sex: {sex}
@@ -257,9 +360,10 @@ PATIENT DATA:
257
  - Pregnancy Status: {pregnancy_status}
258
  - HBsAg Status: {hbsag_status}
259
  - HBsAg Duration: {duration_hbsag} months
260
- - HBV DNA Level: {hbv_dna:,.0f} IU/mL
 
261
  - HBeAg Status: {hbeag_status}
262
- - ALT Level: {alt_level}
263
  - Fibrosis Stage: {fibrosis_stage}
264
  - Necroinflammatory Activity: {necroinflammatory}
265
  - Extrahepatic Manifestations: {extrahepatic}
@@ -268,14 +372,21 @@ PATIENT DATA:
268
  - Family History (Cirrhosis/HCC): {family_history}
269
  - Other Comorbidities: {', '.join(comorbidities) if comorbidities else 'None'}
270
 
 
 
 
 
 
 
 
271
 
272
  SASLT 2021 GUIDELINES (Retrieved Context):
273
  {SASLT_GUIDELINES}
274
- Based STRICTLY on the SASLT 2021 guidelines and criteria provided above, assess this patient's eligibility for HBV antiviral treatment.
275
  You MUST respond with a valid JSON object in this exact format:
276
  {{
277
- "eligible": true or false,
278
- "recommendations": "Comprehensive assessment with inline citations"
279
  }}
280
  IMPORTANT JSON FORMATTING:
281
  - Return ONLY valid JSON without markdown code blocks.
@@ -283,25 +394,16 @@ IMPORTANT JSON FORMATTING:
283
  - Do NOT include literal newline characters. Use \\n for every new bullet or line.
284
  - Use SINGLE \\n between lines. Do NOT use \\n\\n (double newlines) anywhere.
285
 
286
- CRITICAL ELIGIBILITY HIERARCHY:
287
- 1. **Check Special Populations FIRST (Pages 9-10):** "Eligible" means eligible for ANY antiviral intervention (treatment OR prophylaxis).
288
- * If patient is **HBsAg-positive** AND **Immunosuppressed** (Page 9): Set **"eligible": true"** (for prophylaxis).
289
- * If patient has **HBV-HIV coinfection** (Page 9): Set **"eligible": true"** (for ART).
290
- * If patient is **Pregnant** with **HBV DNA > 100,000 IU/mL** (Page 10): Set **"eligible": true"** (for prophylaxis).
291
- * If patient has **HBV-HCV coinfection** AND meets HBV treatment criteria (Page 9): Set **"eligible": true"**.
292
- 2. **Check Standard Criteria SECOND (Page 6):**
293
- * If *not* eligible based on special populations, check standard criteria (HBV DNA, ALT, fibrosis, age, family history).
294
- * If any Page 6 criteria are met (e.g., Cirrhosis, or HBV DNA > 2,000 + ALT > ULN + moderate fibrosis, etc.): Set **"eligible": true"**.
295
- 3. **If NO criteria from (1) or (2) are met:** Set **"eligible": false"**.
296
- 4. The "eligible" flag MUST be consistent with the "Eligibility and Rationale" bullet. Do not contradict yourself.
297
 
298
  STRUCTURE AND CONTENT OF "recommendations" (CONCISE & ORGANIZED):
299
  - Use ONLY these sections in this exact order, each as a header followed by 1-3 concise bullets:
300
- 1. "Eligibility and Rationale:" (1-2 bullets max, MUST state the primary reason for eligibility/ineligibility)
301
- 2. "Treatment Recommendations:" (1-3 bullets: first-line drugs if eligible, or "Treatment not indicated" if not eligible)
302
- 3. "Monitoring and Follow-up:" (1-2 bullets)
303
- 4. "Special Considerations:" (0-2 bullets, ONLY if applicable and *not* the primary reason for eligibility)
304
- 5. "References:" (1 line listing pages cited)
305
  - OMIT "Additional Notes" and any other sections.
306
  - Keep each bullet to ONE sentence (max 25 words per bullet).
307
  - Total output: aim for 8-12 bullets maximum across all sections.
@@ -312,8 +414,7 @@ BULLETING AND CITATIONS RULES:
312
  - Only cite pages 6–10 that actually contain the information.
313
 
314
  STRICT ACCURACY AND CONSISTENCY RULES:
315
- - **NO CONTRADICTIONS:** The "eligible" flag MUST match the rationale. Follow the CRITICAL ELIGIBILITY HIERARCHY.
316
- - **Rationale First:** The Rationale MUST state the primary reason. If eligible due to immunosuppression, state that, even if Page 6 criteria are not met.
317
  - **Use ONLY the provided SASLT 2021 content;** do NOT add external knowledge.
318
  - **BREVITY:** Each bullet = 1 sentence, max 25 words. Total = 8-12 bullets max.
319
 
@@ -323,39 +424,6 @@ PAGE-TO-TOPIC MAPPING GUIDANCE (for correct citations):
323
  - Page 8: Treatment drugs/regimens (ETV, TDF, TAF), agents not recommended.
324
  - Page 9: Special populations (HBV-HCV, HBV-HDV, HBV-HIV, Immunocompromised).
325
  - Page 10: Pregnancy-related recommendations.
326
-
327
- ---
328
- EXAMPLE OUTPUT (ELIGIBLE - STANDARD CRITERIA) - Use SINGLE \\n only:
329
- {{
330
- "eligible": true,
331
- "recommendations": "Eligibility and Rationale:\\n- Eligible: HBV DNA > 2,000 IU/mL, ALT > ULN, moderate fibrosis (Grade A) [SASLT 2021, Page 6]\\nTreatment Recommendations:\\n- Start monotherapy with ETV, TDF, or TAF (Grade A) [SASLT 2021, Page 8]\\nMonitoring and Follow-up:\\n- Monitor treatment response per SASLT protocol [SASLT 2021, Page 7]\\nReferences:\\n- Pages 6, 7, 8: Treatment criteria, drugs, monitoring"
332
- }}
333
- ---
334
- EXAMPLE OUTPUT (NOT ELIGIBLE - STANDARD CRITERIA) - Use SINGLE \\n only:
335
- {{
336
- "eligible": false,
337
- "recommendations": "Eligibility and Rationale:\\n- Not eligible: HBV DNA < 2,000 IU/mL, ALT ≤ ULN, no significant fibrosis [SASLT 2021, Page 6]\\nTreatment Recommendations:\\n- Treatment not indicated at this time [SASLT 2021, Page 6]\\nMonitoring and Follow-up:\\n- Monitor every 6-12 months (HBeAg-negative, HBV DNA < 2,000 IU/mL) (Grade B) [SASLT 2021, Page 7]\\nReferences:\\n- Pages 6, 7: Treatment criteria, monitoring protocols"
338
- }}
339
- ---
340
- EXAMPLE OUTPUT (ELIGIBLE - IMMUNOSUPPRESSION) - Use SINGLE \\n only:
341
- {{
342
- "eligible": true,
343
- "recommendations": "Eligibility and Rationale:\\n- Eligible: HBsAg-positive patient requires prophylaxis for immunosuppressive therapy (Grade A) [SASLT 2021, Page 9]\\nTreatment Recommendations:\\n- Start antiviral prophylaxis (e.g., ETV, TDF, TAF) [SASLT 2021, Page 8, 9]\\nMonitoring and Follow-up:\\n- Continue prophylaxis for ≥6 months after immunosuppression (12 for anti-CD20) [SASLT 2021, Page 9]\\nReferences:\\n- Pages 8, 9: Prophylaxis criteria, drug options, monitoring"
344
- }}
345
- ---
346
- EXAMPLE OUTPUT (ELIGIBLE - HIV COINFECTION) - Use SINGLE \\n only:
347
- {{
348
- "eligible": true,
349
- "recommendations": "Eligibility and Rationale:\\n- Eligible: Patient has HBV-HIV coinfection and should start ART (Grade A) [SASLT 2021, Page 9]\\nTreatment Recommendations:\\n- ART regimen must include TDF- or TAF-based therapy (Grade A) [SASLT 2021, Page 9]\\nMonitoring and Follow-up:\\n- Monitor patient closely after ART initiation for immune reconstitution [SASLT 2021, Page 9]\\nReferences:\\n- Page 9: HBV-HIV coinfection management, ART regimens"
350
- }}
351
- ---
352
- CRITICAL REQUIREMENTS:
353
- 1. Base assessment ONLY on SASLT 2021 guidelines provided.
354
- 2. Follow the CRITICAL ELIGIBILITY HIERARCHY to avoid contradictions.
355
- 3. Keep output SHORT: 8-12 bullets total, 1 sentence per bullet (max 25 words).
356
- 4. Use ONLY the 5 sections listed above.
357
- 5. Cite exact page at end of each bullet: [SASLT 2021, Page X].
358
- 6. Return ONLY valid JSON, no markdown, no extra text.
359
  """
360
 
361
 
@@ -374,6 +442,9 @@ CRITICAL REQUIREMENTS:
374
 
375
  # Extract JSON from response
376
  response_text = response.content if hasattr(response, 'content') else str(response)
 
 
 
377
 
378
  # Log LLM response
379
  logger.info(f"\n{'='*80}")
@@ -399,6 +470,14 @@ CRITICAL REQUIREMENTS:
399
  json_end = response_text.rfind('}') + 1
400
  if json_start >= 0 and json_end > json_start:
401
  json_str = response_text[json_start:json_end]
 
 
 
 
 
 
 
 
402
 
403
  # Clean the JSON string to escape control characters within string values
404
  cleaned_json_str = clean_json_string(json_str)
@@ -411,9 +490,14 @@ CRITICAL REQUIREMENTS:
411
  raise ValueError("No JSON found in response")
412
 
413
  # Validate and return result
 
 
 
 
 
414
  normalized_recs = normalize_recommendations(result.get("recommendations", ""))
415
  assessment_result = {
416
- "eligible": result.get("eligible", False),
417
  "recommendations": normalized_recs
418
  }
419
 
 
5
  import logging
6
  import json
7
  import re
8
+ from typing import Dict, Any, Tuple
9
  from .config import get_llm
10
 
11
  logger = logging.getLogger(__name__)
 
203
  F0—no fibrosis; F1—mild fibrosis, pericellular collagen deposits; F2—moderate fibrosis, beginning bridging fibrosis; F3—severe fibrosis, defined as presence of numerous bridges and septa; F4—cirrhosis
204
  """
205
 
206
+ def check_eligibility_criteria(patient_data: Dict[str, Any]) -> Tuple[bool, str]:
207
+ """
208
+ Deterministically check patient eligibility based on SASLT 2021 guidelines.
209
+ Returns: (is_eligible, rationale_message)
210
+ """
211
+ hbsag_status = patient_data.get("hbsag_status", "N/A")
212
+ hbv_dna_numeric = patient_data.get("hbv_dna_level_numeric", 0.0) # Using numeric derived value
213
+ hbeag_status = patient_data.get("hbeag_status", "N/A")
214
+ alt_level = patient_data.get("alt_level", 0)
215
+ fibrosis_stage = patient_data.get("fibrosis_stage", "F0")
216
+ necroinflammatory = patient_data.get("necroinflammatory_activity", "A0")
217
+ age = patient_data.get("age", 0)
218
+ extrahepatic = patient_data.get("extrahepatic_manifestations", False)
219
+ immunosuppression = patient_data.get("immunosuppression_status", "None")
220
+ coinfections = patient_data.get("coinfections", [])
221
+ family_history = patient_data.get("family_history_cirrhosis_hcc", False)
222
+ pregnancy_status = patient_data.get("pregnancy_status", "No")
223
+
224
+ ULN = 40 # Based on KEY DEFINITIONS
225
+
226
+ # 0. Basic Requirement Check
227
+ if hbsag_status != "Positive":
228
+ return False, "Not eligible: HBsAg negative. HBsAg positivity is required for HBV treatment consideration [SASLT 2021, Page 6]."
229
+
230
+ # --- 1. Special Populations (CRITICAL ELIGIBILITY HIERARCHY: FIRST) ---
231
+
232
+ # 1.1 Immunosuppression (Page 9)
233
+ if immunosuppression != "None":
234
+ return True, f"Eligible for prophylaxis: HBsAg-positive patient receiving immunosuppressive therapy/chemotherapy (Grade A) [SASLT 2021, Page 9]."
235
+
236
+ # 1.2 HBV-HIV Coinfection (Page 9)
237
+ if "HIV" in coinfections:
238
+ return True, "Eligible for ART: HBV-HIV coinfection requires TDF- or TAF-based ART regimen (Grade A) [SASLT 2021, Page 9]."
239
+
240
+ # 1.3 Pregnancy (Page 10)
241
+ if pregnancy_status == "Yes" and hbv_dna_numeric > 100000:
242
+ return True, "Eligible for prophylaxis: Pregnant woman with HBV DNA > 100,000 IU/mL started at 24-28 weeks (Grade D) [SASLT 2021, Page 10]."
243
+
244
+ # --- 2. Standard Criteria (CRITICAL ELIGIBILITY HIERARCHY: SECOND) ---
245
+
246
+ # Helper checks
247
+ is_cirrhotic = fibrosis_stage == "F4"
248
+ has_moderate_fibrosis_or_necroinflammation = fibrosis_stage in ["F2", "F3", "F4"] or necroinflammatory in ["A2", "A3"]
249
+ is_alt_elevated = alt_level > ULN
250
+ is_alt_doubly_elevated = alt_level > (2 * ULN)
251
+
252
+ # 2.1 Cirrhosis (F4) (Grade A)
253
+ if is_cirrhotic:
254
+ return True, "Eligible: Patient has cirrhosis (F4) regardless of ALT and HBV DNA level (Grade A) [SASLT 2021, Page 6]."
255
+
256
+ # 2.2 Severe Viremia/ALT (HBV DNA > 20,000 & ALT > 2xULN) (Grade B)
257
+ if hbv_dna_numeric > 20000 and is_alt_doubly_elevated:
258
+ return True, "Eligible: HBV DNA > 20,000 IU/mL and ALT > 2xULN (Grade B) [SASLT 2021, Page 6]."
259
+
260
+ # 2.3 Active CHB (HBV DNA > 2,000 & ALT > ULN & F2/A2+) (Grade A) - Main Criterion
261
+ if hbv_dna_numeric > 2000 and is_alt_elevated and has_moderate_fibrosis_or_necroinflammation:
262
+ return True, "Eligible: HBV DNA > 2,000 IU/mL, ALT > ULN, and at least moderate fibrosis/necroinflammation (Grade A) [SASLT 2021, Page 6]."
263
+
264
+ # 2.4 HBeAg+ > 30 years (HBeAg+, Normal ALT, High DNA) (Grade D)
265
+ if hbeag_status == "Positive" and age > 30 and not is_alt_elevated and hbv_dna_numeric > 2000:
266
+ return True, "Eligible: HBeAg-positive chronic infection in patient > 30 years despite normal ALT (Grade D) [SASLT 2021, Page 6]."
267
+
268
+ # 2.5 Viremia and Moderate Fibrosis (HBV DNA > 2,000 & Moderate Fibrosis) (Implicit from Page 6)
269
+ # "Patients with HBV DNA >2,000 IU/mL and at least moderate fibrosis may initiate treatment even if ALT levels are normal."
270
+ if hbv_dna_numeric > 2000 and fibrosis_stage in ["F2", "F3", "F4"]:
271
+ return True, "Eligible: HBV DNA > 2,000 IU/mL and at least moderate fibrosis (F2+) (Grade D/Implicit) [SASLT 2021, Page 6]."
272
+
273
+ # 2.6 Family History/Extrahepatic (HBV DNA > 2,000 & ALT > ULN & History/Extrahepatic) (Grade D)
274
+ if hbv_dna_numeric > 2000 and is_alt_elevated and (family_history or extrahepatic):
275
+ return True, "Eligible: HBV DNA > 2,000 IU/mL, ALT > ULN, and family history of HCC/cirrhosis or extrahepatic manifestations (Grade D) [SASLT 2021, Page 6]."
276
+
277
+ # --- 3. Not Eligible ---
278
+
279
+ # Determine reason for non-eligibility for monitoring purposes
280
+ if hbeag_status == "Negative" and hbv_dna_numeric < 2000:
281
+ rationale = "Not eligible: HBV DNA < 2,000 IU/mL and ALT ≤ ULN; monitoring indicated [SASLT 2021, Page 6]."
282
+ elif not is_alt_elevated and not has_moderate_fibrosis_or_necroinflammation:
283
+ rationale = "Not eligible: ALT ≤ ULN and lack of significant fibrosis/necroinflammation; monitoring indicated [SASLT 2021, Page 6]."
284
+ else:
285
+ rationale = "Not eligible: Criteria not fully met (e.g., HBV DNA < 2,000 IU/mL) [SASLT 2021, Page 6]."
286
+
287
+ return False, rationale
288
 
289
 
290
  def assess_hbv_eligibility(patient_data: Dict[str, Any]) -> Dict[str, Any]:
 
301
  - recommendations: str (comprehensive narrative with inline citations in format [SASLT 2021, Page X])
302
  """
303
  try:
 
 
 
 
 
 
 
304
  # Use hardcoded SASLT 2021 guidelines instead of RAG retrieval
305
  logger.info("Using hardcoded SASLT 2021 guidelines (Pages 3, 4, 6, 7, 8, 9, 10)")
306
 
307
+ # --- Pre-processing for numeric comparison ---
308
+ hbv_dna = patient_data.get("hbv_dna_level", 0)
309
+ hbv_dna_numeric = hbv_dna
310
+ if isinstance(hbv_dna_numeric, str):
311
+ try:
312
+ cleaned = re.sub(r"[^\d\.]", "", hbv_dna_numeric)
313
+ # Attempt to parse as float, handling empty string from cleaning
314
+ hbv_dna_numeric = float(cleaned) if cleaned else 0.0
315
+ except Exception:
316
+ hbv_dna_numeric = 0.0
317
+ try:
318
+ hbv_dna_numeric = float(hbv_dna_numeric)
319
+ except (TypeError, ValueError):
320
+ hbv_dna_numeric = 0.0
321
+
322
+ patient_data["hbv_dna_level_numeric"] = hbv_dna_numeric # Add numeric value to patient data
323
+
324
+ if hbv_dna_numeric > 2000:
325
+ hbv_dna_2000_comparison = ">"
326
+ elif hbv_dna_numeric < 2000:
327
+ hbv_dna_2000_comparison = "<"
328
+ else:
329
+ hbv_dna_2000_comparison = "="
330
+ patient_data["hbv_dna_2000_comparison"] = hbv_dna_2000_comparison
331
+ logger.info(f"HBV DNA vs 2000 IU/mL comparison: {hbv_dna_numeric} {hbv_dna_2000_comparison} 2000")
332
+
333
+ # --- Deterministic Eligibility Check ---
334
+ is_eligible, rationale_message = check_eligibility_criteria(patient_data)
335
 
336
+ # --- Prepare LLM Prompt with Deterministic Result ---
337
+ sex = patient_data.get("sex", "Male")
338
  age = patient_data.get("age", "N/A")
339
  pregnancy_status = patient_data.get("pregnancy_status", "N/A")
340
  hbsag_status = patient_data.get("hbsag_status", "N/A")
341
  duration_hbsag = patient_data.get("duration_hbsag_months", "N/A")
 
342
  hbeag_status = patient_data.get("hbeag_status", "N/A")
343
  alt_level = patient_data.get("alt_level", 0)
344
  fibrosis_stage = patient_data.get("fibrosis_stage", "N/A")
 
348
  coinfections = patient_data.get("coinfections", [])
349
  family_history = patient_data.get("family_history_cirrhosis_hcc", False)
350
  comorbidities = patient_data.get("other_comorbidities", [])
351
+
352
+ # Crucial addition: Pass the determined status and rationale to the LLM
353
+ DETERMINISTIC_STATUS = "Eligible: true" if is_eligible else "Eligible: false"
354
+ DETERMINISTIC_RATIONALE = rationale_message
355
+
356
  analysis_prompt = f"""You are an HBV treatment eligibility assessment system. Analyze the patient data against the SASLT 2021 guidelines.
357
  PATIENT DATA:
358
  - Sex: {sex}
 
360
  - Pregnancy Status: {pregnancy_status}
361
  - HBsAg Status: {hbsag_status}
362
  - HBsAg Duration: {duration_hbsag} months
363
+ - HBV DNA Level: {hbv_dna} IU/mL
364
+ - HBV DNA vs 2000 IU/mL: {hbv_dna_2000_comparison}
365
  - HBeAg Status: {hbeag_status}
366
+ - ALT Level: {alt_level}
367
  - Fibrosis Stage: {fibrosis_stage}
368
  - Necroinflammatory Activity: {necroinflammatory}
369
  - Extrahepatic Manifestations: {extrahepatic}
 
372
  - Family History (Cirrhosis/HCC): {family_history}
373
  - Other Comorbidities: {', '.join(comorbidities) if comorbidities else 'None'}
374
 
375
+ DETERMINISTIC ELIGIBILITY RESULT (LLM MUST USE THIS EXACT STATUS):
376
+ {DETERMINISTIC_STATUS}
377
+ Primary Rationale: {DETERMINISTIC_RATIONALE}
378
+
379
+ NUMERIC RULES (MUST FOLLOW EXACTLY):
380
+ - Compare integers exactly as written (e.g., 1800 < 2000 is TRUE; 1800 > 2000 is FALSE).
381
+ - Respect inequality signs from the guidelines (>, >=, <, <=) without relaxing or tightening thresholds.
382
 
383
  SASLT 2021 GUIDELINES (Retrieved Context):
384
  {SASLT_GUIDELINES}
385
+ Based STRICTLY on the SASLT 2021 guidelines and the DETERMINISTIC ELIGIBILITY RESULT, complete the JSON response.
386
  You MUST respond with a valid JSON object in this exact format:
387
  {{
388
+   "eligible": {DETERMINISTIC_STATUS.split(': ')[1]},
389
+   "recommendations": "Comprehensive assessment with inline citations"
390
  }}
391
  IMPORTANT JSON FORMATTING:
392
  - Return ONLY valid JSON without markdown code blocks.
 
394
  - Do NOT include literal newline characters. Use \\n for every new bullet or line.
395
  - Use SINGLE \\n between lines. Do NOT use \\n\\n (double newlines) anywhere.
396
 
397
+ CRITICAL INSTRUCTION:
398
+ The value of the "eligible" field MUST match the DETERMINISTIC ELIGIBILITY RESULT above.
 
 
 
 
 
 
 
 
 
399
 
400
  STRUCTURE AND CONTENT OF "recommendations" (CONCISE & ORGANIZED):
401
  - Use ONLY these sections in this exact order, each as a header followed by 1-3 concise bullets:
402
+   1. "Eligibility and Rationale:" (1-2 bullets max, **The first bullet MUST incorporate the Primary Rationale provided above.**)
403
+   2. "Treatment Recommendations:" (1-3 bullets: first-line drugs if eligible, or "Treatment not indicated" if not eligible)
404
+   3. "Monitoring and Follow-up:" (1-2 bullets)
405
+   4. "Special Considerations:" (0-2 bullets, ONLY if applicable and *not* the primary reason for eligibility)
406
+   5. "References:" (1 line listing pages cited)
407
  - OMIT "Additional Notes" and any other sections.
408
  - Keep each bullet to ONE sentence (max 25 words per bullet).
409
  - Total output: aim for 8-12 bullets maximum across all sections.
 
414
  - Only cite pages 6–10 that actually contain the information.
415
 
416
  STRICT ACCURACY AND CONSISTENCY RULES:
417
+ - **NO CONTRADICTIONS:** The "eligible" flag MUST match the rationale derived in the code.
 
418
  - **Use ONLY the provided SASLT 2021 content;** do NOT add external knowledge.
419
  - **BREVITY:** Each bullet = 1 sentence, max 25 words. Total = 8-12 bullets max.
420
 
 
424
  - Page 8: Treatment drugs/regimens (ETV, TDF, TAF), agents not recommended.
425
  - Page 9: Special populations (HBV-HCV, HBV-HDV, HBV-HIV, Immunocompromised).
426
  - Page 10: Pregnancy-related recommendations.
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
427
  """
428
 
429
 
 
442
 
443
  # Extract JSON from response
444
  response_text = response.content if hasattr(response, 'content') else str(response)
445
+ # Normalize and trim response text to avoid parsing issues due to leading/trailing whitespace
446
+ if isinstance(response_text, str):
447
+ response_text = response_text.strip()
448
 
449
  # Log LLM response
450
  logger.info(f"\n{'='*80}")
 
470
  json_end = response_text.rfind('}') + 1
471
  if json_start >= 0 and json_end > json_start:
472
  json_str = response_text[json_start:json_end]
473
+ # Remove invisible Unicode separators that break json.loads
474
+ # Includes: ZERO WIDTH SPACE (\u200B), ZERO WIDTH NON-JOINER (\u200C),
475
+ # ZERO WIDTH JOINER (\u200D), BYTE ORDER MARK (\uFEFF), and NO-BREAK SPACE (\u00A0)
476
+ invisible_chars = ["\u200b", "\u200c", "\u200d", "\ufeff", "\xa0"]
477
+ for ch in invisible_chars:
478
+ json_str = json_str.replace(ch, "")
479
+ # Also remove their literal forms if present
480
+ json_str = json_str.replace("\u200b", "").replace("\u200c", "").replace("\u200d", "").replace("\ufeff", "").replace("\u00a0", "")
481
 
482
  # Clean the JSON string to escape control characters within string values
483
  cleaned_json_str = clean_json_string(json_str)
 
490
  raise ValueError("No JSON found in response")
491
 
492
  # Validate and return result
493
+ # Ensure the LLM didn't override the deterministic eligibility flag
494
+ if result.get("eligible") != is_eligible:
495
+ logger.warning(f"LLM contradicted deterministic eligibility. Expected: {is_eligible}, Got: {result.get('eligible')}. Overriding.")
496
+ result["eligible"] = is_eligible # Force correct eligibility status
497
+
498
  normalized_recs = normalize_recommendations(result.get("recommendations", ""))
499
  assessment_result = {
500
+ "eligible": result.get("eligible", is_eligible),
501
  "recommendations": normalized_recs
502
  }
503
 
core/text_parser.py CHANGED
@@ -39,7 +39,7 @@ Required fields:
39
  - hbv_dna_level: float (IU/mL)
40
  - hbeag_status: "Positive" or "Negative"
41
  - alt_level: float (U/L)
42
- - fibrosis_stage: "F0-F1", "F2-F3", or "F4"
43
  - necroinflammatory_activity: "A0", "A1", "A2", or "A3"
44
  - extrahepatic_manifestations: true or false
45
  - immunosuppression_status: "None", "Chemotherapy", or "Other"
@@ -56,7 +56,7 @@ IMPORTANT EXTRACTION RULES:
56
  6. For HBV DNA: Look for numbers followed by "IU/mL" or "IU/ml"
57
  7. For HBeAg: Look for "HBeAg positive" or "HBeAg negative"
58
  8. For ALT: Look for "ALT" followed by number and "U/L"
59
- 9. For fibrosis: Look for "F0", "F1", "F2", "F3", "F4" or descriptions like "significant fibrosis", "cirrhosis"
60
  10. For necroinflammatory: Look for "A0", "A1", "A2", "A3"
61
  11. For extrahepatic manifestations: Look for mentions of extrahepatic conditions
62
  12. For immunosuppression: Look for "immunosuppression", "chemotherapy", etc.
@@ -83,7 +83,7 @@ Example format:
83
  "hbv_dna_level": 5000.0,
84
  "hbeag_status": "Positive",
85
  "alt_level": 80.0,
86
- "fibrosis_stage": "F2-F3",
87
  "necroinflammatory_activity": "A2",
88
  "extrahepatic_manifestations": false,
89
  "immunosuppression_status": "None",
 
39
  - hbv_dna_level: float (IU/mL)
40
  - hbeag_status: "Positive" or "Negative"
41
  - alt_level: float (U/L)
42
+ - fibrosis_stage: "F0", "F1", "F2", "F3", or "F4"
43
  - necroinflammatory_activity: "A0", "A1", "A2", or "A3"
44
  - extrahepatic_manifestations: true or false
45
  - immunosuppression_status: "None", "Chemotherapy", or "Other"
 
56
  6. For HBV DNA: Look for numbers followed by "IU/mL" or "IU/ml"
57
  7. For HBeAg: Look for "HBeAg positive" or "HBeAg negative"
58
  8. For ALT: Look for "ALT" followed by number and "U/L"
59
+ 9. For fibrosis: Look for "F0", "F1", "F2", "F3", or "F4"
60
  10. For necroinflammatory: Look for "A0", "A1", "A2", "A3"
61
  11. For extrahepatic manifestations: Look for mentions of extrahepatic conditions
62
  12. For immunosuppression: Look for "immunosuppression", "chemotherapy", etc.
 
83
  "hbv_dna_level": 5000.0,
84
  "hbeag_status": "Positive",
85
  "alt_level": 80.0,
86
+ "fibrosis_stage": "F2",
87
  "necroinflammatory_activity": "A2",
88
  "extrahepatic_manifestations": false,
89
  "immunosuppression_status": "None",