# src/report_generator.py from typing import Dict, List import json SYSTEM_PROMPT = """You are a clinical AI assistant specialized in radiological reporting of kidney stones. Your role is to generate preliminary AI-assisted reports based on automated detection results, grounded in provided clinical guidelines. CRITICAL RULES: 1. GROUNDING: Only make clinical claims that are directly supported by the provided clinical guideline sources. Do not introduce medical information from your general knowledge. 2. CITATIONS: When making clinical recommendations or interpretations, reference the source guidelines (e.g., "per EAU Guidelines 2026" or "based on AUA recommendations"). 3. CAUTIOUS LANGUAGE: Use appropriate radiological hedging: - "Hyperdense focus suggestive of..." instead of "Stone is..." - "Management options may include..." instead of "Treatment is..." - "Morphology pattern suggests..." instead of "Shape is..." 4. UNCERTAINTY: Clearly state limitations: - Size measurements are approximate (pixel-based estimation, assumed spacing 0.7 mm/px) - Equivalent diameter is derived from segmentation mask area, not direct caliper measurement - Density values are relative pixel intensities (0-255 PNG scale), not true Hounsfield Units - Morphology metrics (circularity, solidity) are image-based approximations - AI detection is not a substitute for radiologist interpretation 5. STRUCTURE: Follow the exact report format provided. 6. NO FABRICATION: If the provided sources do not cover a specific clinical question, acknowledge this rather than inventing information. In the REFERENCES section, list ONLY the exact source names shown in the [Source N] headers of the CLINICAL KNOWLEDGE CONTEXT. Do not add, modify, or invent any source names. 7. PROFESSIONAL TONE: Use formal clinical language appropriate for a radiology report. 8. COMPLETION: You MUST complete ALL 6 report sections without exception. Never end a response mid-sentence. If guideline context is insufficient for a section, write "Insufficient guideline context available" rather than truncating.""" REPORT_TEMPLATE = """Generate a clinical AI-assisted kidney stone detection report using the structure below. === DETECTION RESULTS (Automated Analysis) === {detection_json} === CLINICAL KNOWLEDGE CONTEXT (Retrieved from Guidelines) === {retrieved_context} === REPORT STRUCTURE === Generate the report with the following sections: **1. FINDINGS** - Number of detected hyperdense foci suggestive of urolithiasis - For each stone: equivalent diameter (mm), estimated area (mm²), image quadrant location - If multiple stones: total stone area and largest stone size **2. IMAGE CHARACTERISTICS** - For each stone: size category, shape category, relative density category - Detection confidence score - If morphology available: circularity score, solidity, eccentricity, orientation angle - Density profile: mean pixel intensity, intensity homogeneity, contrast to surrounding tissue **3. CLINICAL INTERPRETATION** - Size-based assessment of spontaneous passage likelihood (reference EAU/AUA size thresholds) - Shape-based passage prognosis: irregular or low-solidity stones may indicate increased obstruction risk - Density homogeneity assessment: note relative density pattern (cautiously, as true HU not available) - Reference specific guideline sources for each clinical claim **4. MANAGEMENT CONSIDERATIONS** - Treatment options based on size category, per retrieved guidelines - Medical expulsive therapy (MET) eligibility if applicable - Surgical options (ESWL/URS/PCNL) if applicable, noting morphology factors where relevant - Always cite the source guideline **5. LIMITATIONS & DISCLAIMERS** - This is an AI-generated preliminary assessment only - Size is derived from segmentation mask equivalent diameter (pixel-based, assumed 0.7 mm/px spacing) - Density values are relative pixel intensities from PNG images, not true Hounsfield Units - Morphology metrics are image-based approximations, not direct anatomical measurements - Clinical correlation and radiologist review required - Not a substitute for professional medical interpretation **6. REFERENCES** - List unique sources cited in the report IMPORTANT: Complete all 6 sections in full before ending your response. Generate the report now:""" NO_STONE_PROMPT = """Generate a brief AI-assisted report stating that no kidney stones were detected in the analyzed image. Include: 1. FINDINGS: State that no hyperdense foci suggestive of urolithiasis were identified 2. LIMITATIONS: AI detection has inherent false negative risk; clinical correlation needed 3. RECOMMENDATION: If clinical suspicion persists despite negative AI findings, further imaging or clinical evaluation is warranted Keep the report concise but professional.""" def format_retrieved_context(retrieved_docs: List[Dict]) -> str: """Retrieved chunk'ları LLM için format'la.""" if not retrieved_docs: return "No specific guideline context available." MAX_CONTENT_CHARS = 900 formatted = [] for i, doc in enumerate(retrieved_docs, 1): content = doc['content'] if len(content) > MAX_CONTENT_CHARS: content = content[:MAX_CONTENT_CHARS] + "..." entry = ( f"[Source {i}] {doc['source']} (Page {doc['page']})\n" f"Relevance Score: {doc['similarity_score']:.3f}\n" f"Content: {content}\n" ) formatted.append(entry) return "\n---\n".join(formatted) def format_detection_features(features: Dict) -> str: """Detection/segmentation features'ı LLM için temiz JSON olarak format'la.""" clean_features = { "stone_detected": features.get("stone_detected"), "count": features.get("count"), "multiple_stones": features.get("multiple_stones", False), } if features.get("largest_stone_mm"): clean_features["largest_stone_mm"] = features["largest_stone_mm"] if features.get("total_stone_area_mm2"): clean_features["total_stone_area_mm2"] = features["total_stone_area_mm2"] if features.get("stones"): clean_features["stones"] = [] for stone in features["stones"]: clean_stone = { "stone_id": stone["stone_id"], "size": stone["size"], "location_quadrant": stone["location"]["quadrant"], "shape": stone["shape"], "density": stone["density"], "detection_confidence": stone["detection_confidence"] } if stone.get("morphology"): clean_stone["morphology"] = stone["morphology"] clean_features["stones"].append(clean_stone) return json.dumps(clean_features, indent=2) def build_report_prompt( features: Dict, retrieved_docs: List[Dict] ) -> tuple[str, str]: """ Complete prompt'u inşa et. Returns: (system_prompt, user_prompt) tuple """ if not features.get("stone_detected", False): return SYSTEM_PROMPT, NO_STONE_PROMPT detection_json = format_detection_features(features) retrieved_context = format_retrieved_context(retrieved_docs) user_prompt = REPORT_TEMPLATE.format( detection_json=detection_json, retrieved_context=retrieved_context ) return SYSTEM_PROMPT, user_prompt if __name__ == "__main__": mock_features = { "stone_detected": True, "count": 1, "stones": [{ "stone_id": 1, "size": { "equivalent_diameter_mm": 7.2, "max_dimension_mm": 7.2, "area_mm2": 40.7, "size_category": "medium" }, "location": {"quadrant": "upper-left"}, "shape": "irregular", "density": { "category": "high", "mean_intensity": 210.4, "std_intensity": 18.2, "homogeneity": "homogeneous", "contrast_to_surrounding": 45.3 }, "detection_confidence": 0.87, "morphology": { "area_px": 831.2, "area_mm2": 40.7, "perimeter_px": 112.4, "circularity": 0.826, "solidity": 0.91, "eccentricity": 0.44, "orientation_deg": 32.1 } }], "multiple_stones": False } mock_retrieved = [ { "source": "EAU-Guidelines-2026.pdf", "page": 17, "similarity_score": 0.61, "content": "α-blockers are recommended for 5-10mm distal ureter stones..." } ] system, user = build_report_prompt(mock_features, mock_retrieved) print("=== SYSTEM PROMPT ===") print(system) print("\n=== USER PROMPT ===") print(user)