Spaces:
Sleeping
Sleeping
| """ | |
| 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 ( | |
| "<div style='text-align:center;padding:20px;color:#888'>โช Please paste a medical report to analyze.</div>", | |
| "", | |
| [], | |
| "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""" | |
| <div style="background: {color}15; border: 3px solid {color}; border-radius: 12px; padding: 20px; text-align: center;"> | |
| <div style="font-size: 48px;">{emoji}</div> | |
| <div style="font-size: 28px; font-weight: bold; color: {color}; margin: 8px 0;"> | |
| {assessment.severity.value} | |
| </div> | |
| <div style="font-size: 16px; color: #333; margin-top: 12px;"> | |
| {assessment.action} | |
| </div> | |
| <div style="font-size: 13px; color: #666; margin-top: 12px; font-style: italic;"> | |
| Why: {assessment.reasoning} | |
| </div> | |
| </div> | |
| """ | |
| # 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(""" | |
| <div id="title">๐๏ธ Harivaidya โ เคนเคฐเคฟเคตเฅเคฆเฅเคฏ</div> | |
| <p style="text-align:center; color: #888; margin-top: 0;"> | |
| AI Medical Report Analyzer for India<br> | |
| <em>Hari (Narayan) + Vaidyanath (Shiva) โ The Divine Physician</em> | |
| </p> | |
| """) | |
| # 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, | |
| ) |