""" app.py โ€” Harivaidya Web Demo A Gradio web interface that demonstrates: 1. PHI anonymization (patient safety) 2. Medical entity extraction (AI understanding) 3. Clinical severity assessment (rule-based triage) Design philosophy: - Severity banner FIRST (most important output) - Show the JOURNEY: PHI stripped โ†’ entities found โ†’ severity assessed - Everything visible, nothing hidden To run locally: uv run python app.py Opens http://localhost:7860 ๐Ÿ•‰๏ธ Dedicated to Lord Hari and Lord Vaidyanath For every patient who ever held a report and did not know what it meant. """ import sys from pathlib import Path sys.path.insert(0, str(Path(__file__).parent / "src")) import gradio as gr from harivaidya.anonymizer import PHIAnonymizer from harivaidya.ner_pipeline import MedicalNERPipeline from harivaidya.clinical_rules import ClinicalRuleEngine # โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ # INITIALIZE MODELS (load once at startup) # โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ print("๐Ÿ•‰๏ธ Initializing Harivaidya...") print(" Loading anonymizer...") anonymizer = PHIAnonymizer() print(" Loading NER model (first time downloads ~250MB)...") ner = MedicalNERPipeline() ner.load() print(" Loading clinical rule engine...") rule_engine = ClinicalRuleEngine() print("โœ… Harivaidya ready.\n") # โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ # MAIN ANALYSIS FUNCTION # โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ def analyze_report(report_text: str): """ Returns 4 outputs: 1. Severity banner (HTML) 2. Anonymized text 3. Entity table 4. Summary markdown """ if not report_text or not report_text.strip(): return ( "
โšช Please paste a medical report to analyze.
", "", [], "No analysis performed.", ) # Layer 1: Strip PHI anon_result = anonymizer.anonymize(report_text) # Layer 2: Run NER ner_result = ner.extract(anon_result.anonymized_text) # Layer 3: Clinical rules entity_texts = [e.text for e in ner_result.entities] assessment = rule_engine.assess( text=anon_result.anonymized_text, entities=entity_texts, ) # Build severity banner severity_emojis = { "CRITICAL": "๐Ÿšจ", "MODERATE": "โš ๏ธ", "MILD": "๐Ÿ“‹", "NORMAL": "โœ…", } severity_colors = { "CRITICAL": "#CC0000", "MODERATE": "#FF9900", "MILD": "#FFCC00", "NORMAL": "#00AA00", } emoji = severity_emojis.get(assessment.severity.value, "โšช") color = severity_colors.get(assessment.severity.value, "#888888") severity_banner = f"""
{emoji}
{assessment.severity.value}
{assessment.action}
Why: {assessment.reasoning}
""" # Build entity table entity_rows = [] for entity in ner_result.entities: entity_rows.append([ entity.text, entity.label.replace("_", " ").title(), f"{entity.confidence:.1%}", ]) # Build summary red_flag_text = "" if assessment.red_flags: red_flag_text = f"\n- **๐Ÿšฉ RED FLAGS:** {', '.join(assessment.red_flags)}" summary = f""" **๐Ÿ“Š Analysis Summary** - **Severity:** {assessment.severity.value} - **Trigger findings:** {', '.join(assessment.trigger_findings) if assessment.trigger_findings else 'None'}{red_flag_text} - **PHI items stripped:** {anon_result.phi_count} - **Medical entities found:** {ner_result.total_entities} - **Processing time:** {ner_result.processing_time_ms:.0f} ms - **Audit hash:** `{anon_result.content_hash}` **๐Ÿ”’ Privacy guarantee:** The anonymized text above is what the AI actually analyzed. Your original patient data never touched the AI model. ๐Ÿ•‰๏ธ *Powered by Harivaidya โ€” Hari + Vaidyanath, the Divine Physician.* """ return ( severity_banner, anon_result.anonymized_text, entity_rows, summary, ) # โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ # EXAMPLES # โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ EXAMPLE_REPORTS = [ ["""Patient Name: Ramesh Kumar Sharma Aadhaar: 1234 5678 9012 Phone: +91 9876543210 Date: 15/11/2024 Ref. Doctor: Dr. Priya Singh FINDINGS: Right lower lobe consolidation with mild cardiomegaly. CTR 0.55. No pleural effusion. IMPRESSION: Pneumonia in right lower lobe. Mild cardiomegaly."""], ["""Patient: Suresh Patel Phone: 9988776655 Date: 10/01/2024 BLOOD TEST RESULTS: HbA1c: 8.3% (diabetic range) Fasting Glucose: 187 mg/dL Hemoglobin: 9.8 g/dL (low) Creatinine: 1.4 mg/dL (mildly elevated) INTERPRETATION: Poorly controlled diabetes with progressive anaemia and early kidney involvement."""], ["""IMPRESSION: Bilateral pneumothorax with mediastinal shift. Severe respiratory compromise. Emergency chest tube placement required. Blood pressure dropping โ€” likely tension pneumothorax."""], ["""IMPRESSION: Normal chest X-ray. Clear lung fields. No abnormalities detected. Routine screening."""], ] # โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ # GRADIO UI # โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ custom_css = """ .gradio-container { max-width: 1200px !important; font-family: 'Segoe UI', Tahoma, sans-serif; } #title { text-align: center; background: linear-gradient(135deg, #FF9933, #138808); -webkit-background-clip: text; -webkit-text-fill-color: transparent; font-size: 2.2em; font-weight: bold; } """ with gr.Blocks(title="Harivaidya โ€” Medical AI for India", css=custom_css) as demo: # Header gr.HTML("""
๐Ÿ•‰๏ธ Harivaidya โ€” เคนเคฐเคฟเคตเฅˆเคฆเฅเคฏ

AI Medical Report Analyzer for India
Hari (Narayan) + Vaidyanath (Shiva) โ€” The Divine Physician

""") # Input gr.Markdown("### ๐Ÿ“‹ Paste a medical report below") gr.Markdown( "Try an example below, or paste any X-ray, blood test, or clinical report. " "Patient data will be automatically stripped before AI analysis." ) input_text = gr.Textbox( label="Medical report text", lines=12, placeholder="Paste any medical report here...", ) analyze_btn = gr.Button("๐Ÿ” Analyze with Harivaidya", variant="primary", size="lg") # SEVERITY BANNER (most important output, shown first) gr.Markdown("---") gr.Markdown("### ๐ŸŽฏ Clinical Severity Assessment") output_severity = gr.HTML() # Anonymized text gr.Markdown("---") gr.Markdown("### ๐Ÿ”’ Anonymized Text (PHI Stripped)") gr.Markdown("This is what the AI actually sees โ€” all patient identifiers replaced.") output_anon = gr.Textbox( label="Anonymized version", lines=10, interactive=False, ) # Entity table gr.Markdown("### ๐Ÿง  Medical Entities Extracted") gr.Markdown("Our AI found these medical concepts automatically.") output_entities = gr.Dataframe( headers=["Entity", "Category", "Confidence"], label="Medical entities", wrap=True, ) # Summary gr.Markdown("### ๐Ÿ“Š Summary") output_summary = gr.Markdown() # Examples gr.Examples( examples=EXAMPLE_REPORTS, inputs=input_text, label="๐Ÿ’ก Try these example reports", ) # Wire up the button analyze_btn.click( fn=analyze_report, inputs=[input_text], outputs=[output_severity, output_anon, output_entities, output_summary], ) # Footer gr.Markdown(""" --- **๐Ÿ•‰๏ธ About Harivaidya** - Built with love for the people of India ๐Ÿ‡ฎ๐Ÿ‡ณ - Open source: [github.com/bapuoldtown/harivaidya](https://github.com/bapuoldtown/harivaidya) - Privacy first: PHI stripped before any AI sees your data - Free for patients, sustainable for hospitals - Dedicated to Lord Jagannath and Lord Lingaraj of Odisha """) # โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ # LAUNCH # โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ if __name__ == "__main__": demo.launch( server_name="0.0.0.0", server_port=7860, share=False, )