Skip to content

Commit ef4b1a1

Browse files
committed
Add UI analytics, quality gates, and security docs
Introduce risk trend/history visualizations, add lint+coverage CI and pre-commit hooks, and document architecture plus PII/retention hardening guidance for recruiter-ready project quality. Made-with: Cursor
1 parent ca068a5 commit ef4b1a1

20 files changed

Lines changed: 241 additions & 78 deletions

.github/workflows/ci.yml

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,5 +26,11 @@ jobs:
2626
- name: Compile
2727
run: python -m compileall agents api models tools memory ui main.py scripts tests
2828

29-
- name: Run tests
30-
run: pytest -q
29+
- name: Lint
30+
run: ruff check .
31+
32+
- name: Format check
33+
run: ruff format --check .
34+
35+
- name: Run tests with coverage
36+
run: pytest -q --cov=. --cov-report=term-missing --cov-fail-under=60

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,3 +3,4 @@ __pycache__/
33
*.pyc
44
.DS_Store
55
logs/
6+
.coverage

.pre-commit-config.yaml

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
repos:
2+
- repo: https://github.com/astral-sh/ruff-pre-commit
3+
rev: v0.12.11
4+
hooks:
5+
- id: ruff-check
6+
args: [--fix]
7+
- id: ruff-format

README.md

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,8 @@ User Input
2929
-> Final Output (dashboard + API/chat-ready response)
3030
```
3131

32+
Detailed diagram: [`docs/architecture.md`](docs/architecture.md)
33+
3234
## Key Features
3335

3436
- **Risk prediction** from patient vitals and demographic inputs
@@ -112,6 +114,13 @@ source .venv/bin/activate
112114
pip install -r requirements.txt
113115
```
114116

117+
### 2.1) Enable local pre-commit checks (recommended)
118+
119+
```bash
120+
pre-commit install
121+
pre-commit run --all-files
122+
```
123+
115124
### 3) Configure environment variables
116125

117126
Create a `.env` file (or export directly):
@@ -252,6 +261,12 @@ Always consult qualified healthcare professionals for real medical advice.
252261
- Added structured decision audit logging (`logs/decisions.jsonl`)
253262
- Added Dockerfile + Docker Compose for reproducible local deployment
254263
- Added CI workflow (compile + test on push/PR)
264+
- Added lint + coverage quality gates in CI
265+
- Added local pre-commit hooks for code quality
266+
267+
## Security Notes
268+
269+
See [`docs/security.md`](docs/security.md) for PII handling guidance, retention policy, and hardening recommendations.
255270

256271
## Contributing
257272

agents/explainer.py

Lines changed: 11 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,9 @@ def __init__(self, model_name="gpt-4o-mini"):
1010

1111
def explain(self, patient_data, prob, feature_explanation=None, similar_cases=None):
1212
if self.client is None:
13-
return self._fallback_explanation(patient_data, prob, feature_explanation, similar_cases)
13+
return self._fallback_explanation(
14+
patient_data, prob, feature_explanation, similar_cases
15+
)
1416

1517
prompt = f"""
1618
Patient data: {patient_data}
@@ -23,14 +25,15 @@ def explain(self, patient_data, prob, feature_explanation=None, similar_cases=No
2325

2426
try:
2527
response = self.client.chat.completions.create(
26-
model=self.model_name,
27-
messages=[{"role": "user", "content": prompt}]
28+
model=self.model_name, messages=[{"role": "user", "content": prompt}]
2829
)
2930
return response.choices[0].message.content
3031
except Exception as exc:
3132
return f"{self._fallback_explanation(patient_data, prob, feature_explanation, similar_cases)} LLM explanation unavailable: {exc}"
3233

33-
def _fallback_explanation(self, patient_data, prob, feature_explanation=None, similar_cases=None):
34+
def _fallback_explanation(
35+
self, patient_data, prob, feature_explanation=None, similar_cases=None
36+
):
3437
age = patient_data["age"]
3538
bmi = patient_data["bmi"]
3639
bp = patient_data["bp"]
@@ -55,7 +58,9 @@ def _fallback_explanation(self, patient_data, prob, feature_explanation=None, si
5558
top_impacts = self._top_feature_text(feature_explanation)
5659
memory_text = ""
5760
if similar_cases:
58-
memory_text = f" I found {len(similar_cases)} similar prior case(s) in memory for comparison."
61+
memory_text = (
62+
f" I found {len(similar_cases)} similar prior case(s) in memory for comparison."
63+
)
5964

6065
return (
6166
f"The model estimated a {prob:.1%} risk using a calculated BMI of {bmi:.1f}. "
@@ -67,9 +72,6 @@ def _top_feature_text(self, feature_explanation):
6772
return ""
6873

6974
top_features = feature_explanation["features"][:3]
70-
parts = [
71-
f"{item['feature']} {item['direction']}"
72-
for item in top_features
73-
]
75+
parts = [f"{item['feature']} {item['direction']}" for item in top_features]
7476
method = feature_explanation.get("method", "model")
7577
return f"The {method.upper()} explanation highlights: {', '.join(parts)}. "

agents/orchestrator.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -71,5 +71,5 @@ def run(self, patient_data):
7171
"similar_cases": similar_cases,
7272
"safety": safety,
7373
"explanation": explanation,
74-
"recommendation": recommendation
74+
"recommendation": recommendation,
7575
}

agents/recommender.py

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,13 @@ def __init__(self, model_name="gpt-4o-mini"):
88
self.model_name = model_name
99
self.client = OpenAI() if os.getenv("OPENAI_API_KEY") else None
1010

11-
def recommend(self, patient_data, risk, retrieved_context=None, similar_cases=None, safety=None):
11+
def recommend(
12+
self, patient_data, risk, retrieved_context=None, similar_cases=None, safety=None
13+
):
1214
if self.client is None:
13-
return self._fallback_recommendation(patient_data, risk, retrieved_context, similar_cases, safety)
15+
return self._fallback_recommendation(
16+
patient_data, risk, retrieved_context, similar_cases, safety
17+
)
1418

1519
prompt = f"""
1620
Patient data: {patient_data}
@@ -24,14 +28,15 @@ def recommend(self, patient_data, risk, retrieved_context=None, similar_cases=No
2428

2529
try:
2630
response = self.client.chat.completions.create(
27-
model=self.model_name,
28-
messages=[{"role": "user", "content": prompt}]
31+
model=self.model_name, messages=[{"role": "user", "content": prompt}]
2932
)
3033
return response.choices[0].message.content
3134
except Exception as exc:
3235
return f"{self._fallback_recommendation(patient_data, risk, retrieved_context, similar_cases, safety)} LLM recommendation unavailable: {exc}"
3336

34-
def _fallback_recommendation(self, patient_data, risk, retrieved_context=None, similar_cases=None, safety=None):
37+
def _fallback_recommendation(
38+
self, patient_data, risk, retrieved_context=None, similar_cases=None, safety=None
39+
):
3540
glucose_note = ""
3641
if not patient_data.get("glucose_measured", True):
3742
glucose_note = " Because glucose was not measured, get an A1C or fasting blood glucose test before making clinical decisions."

agents/retriever.py

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -31,11 +31,13 @@ def retrieve(self, patient_data, risk, feature_explanation, n_results=3):
3131

3232
contexts = []
3333
for document, metadata, distance in zip(documents, metadatas, distances):
34-
contexts.append({
35-
"title": metadata.get("title", "Medical note"),
36-
"text": document,
37-
"distance": float(distance),
38-
})
34+
contexts.append(
35+
{
36+
"title": metadata.get("title", "Medical note"),
37+
"text": document,
38+
"distance": float(distance),
39+
}
40+
)
3941
return self._rerank(contexts, patient_data, risk)[:n_results]
4042

4143
def _seed_knowledge(self):
@@ -88,7 +90,10 @@ def score(item):
8890
if title == "High risk clinical review":
8991
value += 2.0 if risk == "High" else -3.0
9092
if title == "Glucose testing":
91-
if not patient_data.get("glucose_measured", True) or patient_data.get("glucose", 0) >= 140:
93+
if (
94+
not patient_data.get("glucose_measured", True)
95+
or patient_data.get("glucose", 0) >= 140
96+
):
9297
value += 1.5
9398
if title == "Blood pressure follow-up" and patient_data.get("bp", 0) >= 130:
9499
value += 1.25

api/app.py

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@
66
from fastapi import Depends, FastAPI, Header, HTTPException
77
from pydantic import BaseModel, Field
88

9-
109
PROJECT_ROOT = Path(__file__).resolve().parents[1]
1110
if str(PROJECT_ROOT) not in sys.path:
1211
sys.path.insert(0, str(PROJECT_ROOT))
@@ -15,7 +14,6 @@
1514
from main import agent
1615
from tools.audit_logger import AuditLogger
1716

18-
1917
app = FastAPI(title="Healthcare Agent API")
2018
settings = get_settings()
2119
audit_logger = AuditLogger(settings.log_dir)

docs/architecture.md

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
# Architecture Diagram
2+
3+
This diagram is designed for recruiter/interviewer walkthroughs.
4+
5+
```mermaid
6+
flowchart TD
7+
A[User Input<br/>UI or API] --> B[RiskModel<br/>Logistic Regression]
8+
B --> C[Orchestrator Agent]
9+
10+
C --> D[Explanation Agent<br/>LLM + fallback rules]
11+
C --> E[Recommendation Agent<br/>LLM + safety context]
12+
C --> F[Retrieval Agent<br/>Medical knowledge RAG]
13+
C --> G[Memory Agent<br/>ChromaDB patient memory]
14+
C --> H[Feature Explainer<br/>SHAP attributions]
15+
C --> I[Safety Guard<br/>alerts + escalation + confidence]
16+
17+
F --> J[data/medical_knowledge.jsonl]
18+
G --> K[data/chroma]
19+
C --> L[Audit Logger]
20+
L --> M[logs/decisions.jsonl]
21+
22+
C --> N[Final Response]
23+
N --> O[Streamlit Dashboard]
24+
N --> P[FastAPI /predict + /v1/predict]
25+
```
26+
27+
## Request Lifecycle (High Level)
28+
29+
1. Validate and normalize input (including BMI calculation).
30+
2. Predict probability and classify risk.
31+
3. Compute SHAP feature impacts.
32+
4. Retrieve similar past cases + relevant medical context.
33+
5. Generate explanation and recommendation.
34+
6. Apply safety assessment (alerts, escalation, confidence).
35+
7. Persist memory and write audit trail.
36+
8. Return structured response to UI/API client.

0 commit comments

Comments
 (0)