Spaces:
Sleeping
Sleeping
Implement the core eligibility logic natively in Python.
Browse files- api/models.py +1 -1
- api/routers/hbv_assessment.py +1 -1
- core/hbv_assessment.py +156 -72
- core/text_parser.py +3 -3
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
|
| 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
|
| 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 |
-
#
|
| 233 |
-
|
| 234 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 235 |
|
| 236 |
-
#
|
|
|
|
| 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 |
-
#
|
|
|
|
|
|
|
|
|
|
| 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
|
|
|
|
| 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
|
| 275 |
You MUST respond with a valid JSON object in this exact format:
|
| 276 |
{{
|
| 277 |
-
|
| 278 |
-
|
| 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
|
| 287 |
-
|
| 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 |
-
|
| 301 |
-
|
| 302 |
-
|
| 303 |
-
|
| 304 |
-
|
| 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
|
| 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",
|
| 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
|
| 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",
|
| 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
|
| 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",
|