Spaces:
Sleeping
Second Life β Project Reference (LLM Context Document)
DSCI 5260 | Group 7 | Last updated: 2026-04-25 (post Session 6 β inbox, messaging, trial dashboard, document upload, dataset seeding)
Architecture Overview
Flask web app (port 5000) with two authenticated portals:
- Patient Portal (/patient): Register/login, update medical profile, get AI trial matches, connect with hospitals
- Hospital Portal (/hospital): Login, browse opt-in patients, search by condition, manage connections, edit profile
Key Files
pipeline.pyβ Core ML pipeline (data loading, feature engineering, model training, matching)app.pyβ Flask server: session auth, patient API, hospital API, pipeline APIdatabase.pyβ SQLite layer: 5 tables, auth, connections, trial interests, messagestemplates/landing.htmlβ Login/register landing pagetemplates/patient.htmlβ Patient SPA (profile, trials, connections, inbox)templates/hospital.htmlβ Hospital SPA (patients, search, my trials, inbox, connections, profile)llm.mdβ This file
Running the System
cd "E:\DSCI 5260\Project\PT"
python app.py
# open http://localhost:5000
On first run (no model_cache.pkl): loads all data files (~2-3 min), trains RF model, saves cache. On subsequent runs: loads cached model immediately.
Delete model_cache.pkl to force retrain (required after pipeline feature changes).
Demo Credentials
All hand-made patients have open_to_trials=1 and password pass123.
| Username | Password | Name | Conditions | Location |
|---|---|---|---|---|
| john_doe | pass123 | John Doe | hypertension, diabetes, MI | Boston, MA |
| jane_smith | pass123 | Jane Smith | asthma, atopic dermatitis, allergic rhinitis | Cambridge, MA |
| bob_jones | pass123 | Robert Jones | CAD, hypertension, chronic pain | Cleveland, OH |
| alice_brown | pass123 | Alice Brown | non-small cell lung cancer, stroke | Baltimore, MD |
| david_chen | pass123 | David Chen | diabetes, osteoporosis, CAD | Chicago, IL |
| Username | Password | Hospital | Location |
|---|---|---|---|
| mgh | mgh123 | Massachusetts General Hospital | Boston, MA |
| cleveland | clinic123 | Cleveland Clinic | Cleveland, OH |
| jhopkins | johns123 | Johns Hopkins Hospital | Baltimore, MD |
Dataset-Backed Patients (Synthea)
20 real Synthea patients are auto-seeded on first run from Final Patients Synthea Data/. Username format: synthea_<first 8 chars of Patient_ID>, password pass123, all open_to_trials=1. Total DB patients: 25 (5 hand-made + 20 Synthea).
List all accounts: SELECT username, first_name, last_name FROM patient_accounts ORDER BY username;
Data Sources
Patient Side (Synthea synthetic data)
Final Patients Synthea Data/final_patients_conditions.csvβ ~967K rows, 265,893 patients, 106 conditions overlapping with trials. Columns: Patient_ID, Condition_Name, Condition_End_DateFinal Patients Synthea Data/patients_details.csvβ Demographics. Columns: Patient_ID, First_Name, Last_Name, Birth_Date (DD-MM-YYYY), Gender, Race, Ethnicity, AddressFinal Patients Synthea Data/patients_medications.csvβ 213,182 patients with med data. Columns: Patient_ID, Medication_Name, Medication_End_DateFinal Patients Synthea Data/patients_observations.csvβ 23,231 patients with lab data. Columns: Patient_ID, Observation_Name
Trial Side (ClinicalTrials.gov / AACT)
Final Clinical Trails Data/trail_conditions.csvβ ~1M rows, 571,379 total trials, 34,074 with matched condition profiles. Columns: Trial_ID, Condition_Name_LowerFinal Clinical Trails Data/trail_eligibilities.csvβ Columns: Trial_ID, Gender (leading space β stripped), Minimum_Age, Maximum_Age, Eligibility_CriteriaFinal Clinical Trails Data/trail_studies.csvβ 65,292 recruiting trials. Columns: Trial_ID, Brief_Title, Overall_Status, Phase, Start_Date, EnrollmentFinal Clinical Trails Data/trail_facilities.csvβ 189,274 trials with US state geo data. Columns: Trial_ID, Facility_Name, Facility_City, Facility_State (full names), Facility_CountryFinal Clinical Trails Data/trail_brief_summaries.csvβ Columns: Trial_ID, Brief_SummaryFinal Clinical Trails Data/trail_interventions.csvβ 196,865 trials with drug intervention data. Columns: Trial_ID, Intervention_Type, Intervention_NameFinal Clinical Trails Data/trail_countries.csvβ Not used for geo scoring (superseded by facility-level state data)Final Clinical Trails Data/trail_keywords.csvβ Columns: Trial_ID, Keyword_Name_Lower
MIMIC-IV Demo (code-level validation only, not in UI)
mimic-iv-clinical-database-demo-2.2/hosp/patients.csv.gzmimic-iv-clinical-database-demo-2.2/hosp/diagnoses_icd.csv.gzmimic-iv-clinical-database-demo-2.2/hosp/d_icd_diagnoses.csv.gz
SQLite Database (secondlife.db)
Tables
patient_accounts(id, username, password_hash, synthea_id, first_name, last_name,
dob, gender, address, conditions TEXT DEFAULT '[]',
medications TEXT DEFAULT '[]', documents TEXT DEFAULT '[]',
open_to_trials INTEGER DEFAULT 0, created_at)
hospital_accounts(id, username, password_hash, hospital_name, location,
research_conditions TEXT DEFAULT '[]', created_at)
patient_trial_interests(id, patient_id, trial_id, trial_title, match_score,
status DEFAULT 'interested', created_at,
UNIQUE(patient_id, trial_id))
connections(id, patient_id, hospital_id, trial_id, trial_title,
initiated_by DEFAULT 'patient', status DEFAULT 'pending',
message, created_at,
UNIQUE(patient_id, hospital_id, trial_id))
-- Post-schema index handles NULL trial_id:
-- CREATE UNIQUE INDEX idx_conn_unique ON connections(patient_id, hospital_id, COALESCE(trial_id, ''))
connection_messages(id, connection_id, sender_role TEXT, -- 'patient' or 'hospital'
sender_id TEXT, body TEXT,
created_at, is_read INTEGER DEFAULT 0)
-- Index: idx_msgs_conn ON connection_messages(connection_id, created_at)
JSON fields (conditions, medications, documents, research_conditions) are stored as TEXT and parsed via _row_to_dict().
Key database.py Functions
| Function | Purpose |
|---|---|
get_open_patients_for_hospital(hid, condition_filter, include_connected) |
Returns open_to_trials=1 patients; when include_connected=False excludes already-connected patients |
get_connection_messages(connection_id) |
All messages for a connection, ASC order |
create_connection_message(connection_id, sender_role, sender_id, body) |
Insert message, returns dict |
mark_messages_read(connection_id, reader_role) |
Mark all messages from the other role as read |
unread_count(connection_id, reader_role) |
Count of unread messages from the other role |
get_hospital_inbox_threads(hospital_id) |
All threads with last_message, last_message_at, unread_count; sorted by activity |
get_patient_inbox_threads(patient_id) |
Same for patient side |
_seed_dataset_patients(c, max_patients=20) |
Seeds Synthea patients from CSV on first run |
Feature Engineering (17 features in FEATURE_COLS)
| Feature | Description |
|---|---|
| condition_overlap | Raw count of shared conditions |
| jaccard_similarity | overlap / union |
| overlap_ratio_trial | overlap / len(trial_conditions) |
| overlap_ratio_patient | overlap / len(patient_conditions) |
| condition_rarity_score | mean(1/log2(n_trials_per_cond+2)), normalised 0-1 |
| trial_specificity | 1 / trial_condition_count |
| condition_burden | total_patient_conds / 10 |
| active_ratio | active_conds / total_conds |
| resolved_ratio | resolved_conds / total_conds |
| age_distance | normalised distance outside age range (0 if within) |
| age_centered | position within age range (-1 to +1) |
| age_compatibility | 1.0 in range, decays over 30-year gap |
| gender_compatibility | 1.0 match/all, 0.1 mismatch |
| geo_feasibility | State-level: 1.0 same state, 0.75 other US state, 0.5 no data |
| med_compatibility | Keyword overlap: patient meds vs trial drug interventions |
| lab_availability | Patient observation/lab type coverage (0-1, normalised by 20) |
| data_completeness | fraction of key fields present |
Flask API Routes
Auth (no login required)
POST /auth/patient/registerβ {success, patient} or {error}POST /auth/patient/loginβ {success, patient} or {error}POST /auth/hospital/registerβ {success, hospital} or {error}POST /auth/hospital/loginβ {success, hospital} or {error}POST /auth/logoutβ {success}
Patient API (requires patient session)
GET /api/patient/profileβ patient dict (no password_hash)POST /api/patient/profileβ updated patient dict; allowed fields: first_name, last_name, dob, gender, address, conditions, medications, open_to_trialsGET /api/patient/matchesβ {results: [...trials], total}GET /api/patient/interestsβ {interests: [...]}POST /api/patient/interestβ {success}; body: {trial_id, trial_title, match_score}DELETE /api/patient/interest/<trial_id>β {success}GET /api/patient/connectionsβ {connections: [...]} joined with hospital_namePOST /api/patient/connectβ {success, connection} or 409 if duplicate; body: {hospital_id, trial_id, trial_title, message}GET /api/patient/hospitals-for-trial?trial_id=NCT...β {hospitals: [...]} tiered matching (see below)GET /api/patient/connections/<cid>/messagesβ {messages: [...]}; marks hospital messages readPOST /api/patient/connections/<cid>/messagesβ {success, message}; body: {body}GET /api/patient/inboxβ {threads: [...]} each with last_message, last_message_at, unread_count, hospital_nameGET /api/patient/documentsβ {documents: [...]}POST /api/patient/documentsβ {success, document}; multipart/form-data file upload (max 10 MB, .pdf/.docx/.doc/.txt/.png/.jpg/.jpeg)DELETE /api/patient/documents/<doc_id>β {success}; removes file from disk and DBGET /api/patient/documents/<doc_id>/downloadβ file download (as_attachment)
Hospital API (requires hospital session)
GET /api/hospital/profileβ hospital dict (no password_hash)POST /api/hospital/profileβ updated hospital dict; allowed fields: hospital_name, location, research_conditionsGET /api/hospital/patients?condition=&include_connected=β {patients: [...]} open_to_trials=1;include_connected=trueto include already-connected patients (used by Search tab)POST /api/hospital/connectβ {success, connection} or 409 if duplicate; body: {patient_id, trial_id, trial_title, message}GET /api/hospital/connectionsβ {connections: [...]} joined with patient fieldsPUT /api/hospital/connections/<cid>/statusβ {success}; body: {status: pending|accepted|rejected|completed}GET /api/hospital/connections/<cid>/messagesβ {messages: [...]}; marks patient messages readPOST /api/hospital/connections/<cid>/messagesβ {success, message}; body: {body}GET /api/hospital/inboxβ {threads: [...]} each with last_message, last_message_at, unread_count, first_name, last_nameGET /api/hospital/trialsβ {trials: [...]} active trials matched to hospital profile (see below)
Shared
GET /api/statusβ {ready, stats} or {ready: false, message}GET /api/conditions/autocomplete?q=...β {results: [...]}
Hospital Trial Dashboard (/api/hospital/trials)
pipeline.trials_for_hospital(hospital_name, location, research_conditions, top_k=20) β reverse of patient matching: given a hospital's profile, find active clinical trials it is most relevant to.
| Tier | Match condition | match_reason field |
|---|---|---|
| 1 | Jaccard(hospital name tokens, trial facility name tokens) β₯ 0.25 | "name matched to trial site" |
| 2 | Hospital state matches a US trial facility state | "in same state as trial site" |
| 3 | Hospital research_conditions overlaps trial conditions |
"researches related conditions" |
Active-only filter (is_active check) applied at every tier. Returns list of dicts:
trial_id, title, phase, status, summary, location, facility_name, n_sites, match_tier, match_reason
Tiered Hospital Matching (/api/patient/hospitals-for-trial)
For each trial, hospitals in the DB are scored and returned in tier order (Tier 1 first):
| Tier | Match condition | match_reason field |
UI label |
|---|---|---|---|
| 1 | Jaccard(hospital name tokens, any trial facility name tokens) β₯ 0.25 | "verified site on this trial" | Green β Verified Trial Sites |
| 2 | Hospital state (from "City, ST" location) matches a trial US facility state | "in same state as a trial site" | Grey β Related Hospitals |
| 3 | Hospital research_conditions overlaps trial conditions |
"researches related conditions" | Grey β Related Hospitals |
| 4 | Fallback β trial has no facility/state data at all | "" | Grey β Related Hospitals |
The patient connect modal groups Tier 1 hospitals under a green "VERIFIED TRIAL SITES" header and Tiers 2-4 under a grey "RELATED HOSPITALS β not confirmed trial sites" header.
Pipeline lookups used:
pipeline.trial_facility_tokens[trial_id]β list of frozensets of significant words from facility namespipeline.trial_us_states[trial_id]β set of US state full names (e.g. {"Massachusetts"})pipeline.trial_profiles[trial_id]["conditions"]β set of condition strings
Stopword set for facility name tokenisation (same in pipeline.py and app.py): hospital, medical, center, centre, clinic, university, health, care, healthcare, system, institute, foundation, research, general, regional, national, community, services, department, division, college, school, the, of, and, at, for, in, a, an, is, by
Trial Match Result Fields
Each item in /api/patient/matches results:
- trial_id, title, phase, status, min_age, max_age, sex, enrollment, start_date
- eligibility_probability (0-100, calibrated RF probability Γ 100)
- match_score (0-100, rule-based: overlap_ratio weighted)
- combined_score (0-100, 0.6 Γ eligibility + 0.4 Γ match_score)
- age_compatibility, gender_compatibility, geo_feasibility, med_compatibility (all 0-100)
- condition_rarity_score (0-1)
- overlap_conditions (list of conditions shared with patient)
- trial_conditions (all trial conditions)
- criteria (eligibility criteria text, truncated 500 chars)
- summary (brief summary, truncated 400 chars)
- facility_name (lead US facility name, or "" if not available)
- location (lead US facility city/state/country string)
- n_sites (total facility count for this trial)
- interest_status (null | 'interested' | 'withdrawn', from patient_trial_interests)
Data Privacy Model
- Hospitals see only patients with open_to_trials=1 (name, age, gender, conditions)
- Full details accessible only after patient-initiated connection
- Hospital cannot contact a patient unless patient is open to trials
- Connection record: patient_id, hospital_id, trial_id, initiated_by, status, message
- Hospital can also initiate connections with opt-in patients from the hospital portal
XSS Prevention
All user-controlled strings use DOM API (never innerHTML for user data):
function escH(s) { // text content in innerHTML contexts
const d = document.createElement('div');
d.appendChild(document.createTextNode(String(s||'')));
return d.innerHTML;
}
function escA(s) { // HTML attribute values
return String(s||'').replace(/&/g,'&').replace(/"/g,'"')
.replace(/</g,'<').replace(/>/g,'>');
}
Event listeners use addEventListener only. Tags and cards built via createElement + textContent.
ML Model
- Random Forest (n_estimators=200, max_depth=12, class_weight="balanced")
- CalibratedClassifierCV (isotonic, cv=3) for probability calibration
- GroupShuffleSplit (patient-level, 80/20, no leakage) for train/test split
- GroupKFold (5-fold, patient-level) for cross-validation
- Training sample: 3000 patients Γ 30 trials each + random negatives
- Cache:
model_cache.pkl(delete to force retrain)
Actual Metrics (from verified live run, 2026-04-25)
| Metric | Value |
|---|---|
| Accuracy | 85.15% |
| AUC-ROC | 0.5976 |
| CV AUC (5-fold) | 0.5992 Β± 0.0052 |
| F1 | 0.9168 |
| Precision | 0.8518 |
| Recall | 0.9925 |
| Brier score | 0.1263 |
| Avg precision | 0.8540 |
| Train size | 90,994 pairs |
| Test size | 22,681 pairs |
| Positive label rate | 82.1% |
Note on AUC: The 82.1% positive rate in pseudo-labels (weighted 6-feature labelling threshold at 0.5) makes the classification task easy to solve trivially β high accuracy/recall but lower AUC. To improve AUC, the pseudo-label threshold should be raised (e.g. 0.6) or positive/negative sampling balanced more aggressively.
Feature Importance (Random Forest, ranked)
| Rank | Feature | Importance |
|---|---|---|
| 1 | age_distance | 0.2040 |
| 2 | age_compatibility | 0.1691 |
| 3 | gender_compatibility | 0.1288 |
| 4 | age_centered | 0.0791 |
| 5 | jaccard_similarity | 0.0751 |
| 6 | condition_rarity_score | 0.0621 |
| 7 | overlap_ratio_trial | 0.0519 |
| 8 | overlap_ratio_patient | 0.0512 |
| 9 | condition_overlap | 0.0396 |
| 10 | condition_burden | 0.0274 |
| 11 | resolved_ratio | 0.0263 |
| 12 | active_ratio | 0.0258 |
| 13 | lab_availability | 0.0208 |
| 14 | geo_feasibility | 0.0158 |
| 15 | med_compatibility | 0.0144 |
| 16 | trial_specificity | 0.0087 |
| 17 | data_completeness | 0.0000 |
MIMIC-IV Validation (code-level only, not in UI)
- 100 demo patients, ~90% match rate after 3-tier ICD β condition mapping
- Call:
pipeline.validate_mimic()β list of {subject_id, mapped_conditions, n_matches, top_match} - 3-tier mapping: exact β substring containment β word-overlap β₯ 75%
- Not exposed via any Flask route
All Bug Fixes by Session
Session 3 Fixes (2026-04-25) β Two-portal foundation
Fix 1 β patient_id not passed to match_patient() (CRITICAL)
Before: pipeline.match_patient(conditions, age, gender, top_k=20)
After: pipeline.match_patient(conditions, age, gender, top_k=20, patient_id=session["patient_id"], address=address)
Without this, patient-specific medication keywords and lab scores defaulted to empty / 0.3 for all users β med_compatibility and lab_availability were effectively constants.
Fix 2 β Pseudo-label used only 3 features (HIGH)
Before: AND gate on age/gender/condition β geo/med/lab had near-zero training influence. After: 6-feature weighted score with 15% random noise:
score = (
0.30 * float(row["age_compatibility"] > 0.6) +
0.15 * float(row["gender_compatibility"] > 0.5) +
0.25 * float(row["jaccard_similarity"] > 0.05) +
0.10 * float(row["geo_feasibility"]) +
0.10 * float(row["med_compatibility"]) +
0.10 * float(row["lab_availability"])
)
base = int(score >= 0.5)
# 15% hash-deterministic noise for realism
Fix 3 β geo_feasibility was country-level heuristic (MEDIUM)
Before: Float from trail_countries.csv (1.0 US, 0.7 multi-national, 0.35 non-US). No patient location.
After: State-level matching using trail_facilities.csv + patient address regex:
def _geo_score(patient_state_full, trial_states):
if not trial_states: return 0.5 # no US facility data β neutral
if patient_state_full in trial_states: return 1.0
return 0.75 # other US state
Fix 4 β NameError trial_geo in _compute_features return dict
"geo_feasibility": float(trial_geo) β "geo_feasibility": geo_feasibility
Fix 5 β XSS in condition tag onclick handlers (MEDIUM)
addConditionTag('${c}') broke for conditions with apostrophes (e.g. "alzheimer's disease").
Fixed with DOM-based makeTag() using textContent + addEventListener. No inline onclick anywhere.
Fix 6 β Duplicate connection prevention (was: no guard)
connectionstable: addedUNIQUE(patient_id, hospital_id, trial_id)schema constraintinit_db(): runsCREATE UNIQUE INDEX IF NOT EXISTS idx_conn_unique ON connections(patient_id, hospital_id, COALESCE(trial_id, ''))to handle NULL trial_id and backfill existing DBscreate_connection(): pre-checksconnection_exists()before insert; returnsNoneon duplicate/api/patient/connectand/api/hospital/connect: return 409 whencreate_connection()returns None
Fix 7 β Hospital patient feed showed already-contacted patients (was: no exclusion)
get_open_patients_for_hospital() now uses:
WHERE open_to_trials=1
AND id NOT IN (SELECT DISTINCT patient_id FROM connections WHERE hospital_id=?)
Fix 8 β Demo seed: john_doe starts with open_to_trials=1
Hospital portal was empty on a fresh database. _seed_demo_data() now seeds john_doe with open_to_trials=1.
Fix 9 β Hospital registration silently ignored research_conditions
templates/landing.html hospital register form now collects comma-separated research conditions and sends them as a parsed lowercase array to the backend.
Fix 10 β Trial cards only showed site count, not facility name or location
pipeline.py match_patient() now extracts facility_name from Facility_Name column; prefers US facilities. Patient portal detail grid shows "Lead Site" and "Location" when available.
Session 4 Fixes (2026-04-25) β Hospital matching overhaul + profile editing
Fix 11 β Hospital suggestion logic replaced (was: research_conditions overlap only)
Complete replacement of /api/patient/hospitals-for-trial:
Before: looped all hospitals, included any whose research_conditions overlapped trial conditions. No tier concept, no facility data used.
After: 4-tier system using two new pipeline lookups:
pipeline.trial_facility_tokens[trial_id]β built fromFacility_Namecolumn in trail_facilities.csv, US rows only. Each facility name tokenised by stripping stopwords + words < 3 chars.pipeline.trial_us_states[trial_id]β set of full US state names for the trial
Helper functions in app.py:
def _hospital_name_tokens(name: str) -> frozenset:
# strips stopwords, keeps words β₯ 3 chars
...
def _facility_match_score(h_tokens: frozenset, facility_token_list: list) -> float:
# best Jaccard score against any facility in the trial
...
Each hospital gets one tier assigned and a match_reason + match_tier in the response.
Result list sorted by match_tier ascending (best first).
Fix 12 β Hospital portal had no profile editing
POST /api/hospital/profile added (was GET-only). database.py update_hospital_profile() added. templates/hospital.html now has a My Profile tab with editable hospital name, location, and research condition tags. On save, the navbar hospital name updates live without a page reload.
Fix 13 β Model disclaimer missing from patient trial results
templates/patient.html trial results section now shows an alert above results:
"Match percentages are predictions from a model trained on synthetic patient data and rule-based labels β not validated clinical eligibility determinations. Always consult a healthcare provider before enrolling in any trial."
Session 5 Fixes (2026-04-25) β Modal UX + bug fixes
Fix 14 β Patient connect modal showed all hospitals in one flat list
Before: All hospitals (all tiers) in a single flat list, sorted by tier, with coloured badges as the only visual distinction.
After: Modal renders two visually separated sections:
- "VERIFIED TRIAL SITES" (green
sec-head) β Tier 1 hospitals only - "RELATED HOSPITALS β not confirmed trial sites" (grey
sec-headwith inline subtitle) β Tiers 2, 3, 4
Both sections only render if they have entries. Click delegation on the outer #hospitalList wrapper still works for both sections.
Fix 15 β Close button invisible on hospital profile condition tags
hospital.html renderProfTags(): btn-close-white (white X) on bg-info text-dark badge (light blue background) β btn-close (dark X). The X was invisible before.
Fix 16 β Login/register forms required mouse click, no Enter key support
templates/landing.html: Added _onEnter(inputId, fn) helper and wired Enter key on all login and register inputs (both patient and hospital portals). Works on username field too (not just password).
Fix 17 β System ready banner never auto-cleared on slow boot
templates/patient.html: checkStatus() was called once at DOMContentLoaded and never again. If the pipeline was still training when the user opened the page, the yellow banner persisted even after the pipeline finished.
After: startStatusPoll() starts a setInterval (5s) when the initial check finds ready: false. The interval clears itself once ready: true is received.
checkStatus().then(() => { if (!sysReady) startStatusPoll(); });
Live Run Verification (2026-04-25)
End-to-end test results after full retrain with no model_cache.pkl:
| Test | Result |
|---|---|
| Landing page GET / | 200 OK |
| Patient login john_doe/pass123 | OK β returns patient JSON |
| Hospital login mgh/mgh123 | OK β returns hospital JSON |
| Pipeline ready (api/status) | ready: true |
| /api/patient/matches for john_doe | 20 results, all score fields populated |
| Top match geo score for Greece trial | 50% (no US facility β correct) |
| Hospital browses open_to_trials patients | 25 patients visible (5 demo + 20 Synthea) |
| Hospital condition search ?condition=hypertension | results including Synthea patients |
| Hospital β patient connect (POST) | OK, status=pending |
| Patient sees hospital connection (GET) | 1 connection, hospital_name present |
| /api/conditions/autocomplete?q=hyper | ["hypertension"] |
| Duplicate connect attempt | 409 error |
| Hospital profile save | navbar name updates live |
| Patient connect modal | Two sections render correctly |
| /api/hospital/trials for mgh | Active trials with match tiers |
| /api/hospital/inbox | Threads with unread counts |
| /api/patient/inbox | Threads with hospital names |
| Patient document upload | File saved, metadata in DB |
| Inbox Synthea seeding | 25 patients total confirmed |
Session 6 Fixes (2026-04-25) β Messaging, inbox, trial dashboard, document upload, dataset seeding
Fix 18 β Hospital trial dashboard (My Trials tab)
Before: Hospital portal had no way to see which clinical trials were relevant to it.
After: New "My Trials" nav tab in hospital.html. Calls GET /api/hospital/trials β pipeline.trials_for_hospital(). Active-only filter at all 3 tiers. Cards show status badge, phase, match tier, facility name, summary excerpt, and a "View on ClinicalTrials.gov β" link.
Active-only filter: if not self.trial_profiles[trial_id].get("is_active", False): continue at each tier loop in pipeline.py.
Fix 19 β Messaging (chat in connections)
Before: Connections table had no messaging. Patients and hospitals could only see connection status.
After:
- New
connection_messagestable withsender_role,sender_id,body,is_read. GET/POST /api/patient/connections/<cid>/messagesandGET/POST /api/hospital/connections/<cid>/messages.- Both portals have a messages modal (
#msgModal) opened by a Chat button in the Connections table. mark_messages_read()called on GET to auto-mark messages as read when the recipient opens the thread.
Fix 20 β Dedicated Inbox tab (both portals)
Before: Chat only accessible from the My Connections table row β no inbox overview.
After: New "Inbox" nav tab in both hospital.html and patient.html.
- Calls
GET /api/hospital/inboxorGET /api/patient/inbox. - Backed by
get_hospital_inbox_threads()/get_patient_inbox_threads()β SQL subqueries aggregate last_message, last_message_at, unread_count per thread. - Threads sorted by most recent activity (Python-side sort on
last_message_at or created_at). - Unread count badge on nav tab button updates when inbox loads.
- "Open" button reuses the existing
openMsgModal()and messages modal.
Fix 21 β Document upload (patient portal)
Before: Patient profile had no file upload section.
After: "My Documents" card added to patient profile tab. 4 routes:
POST /api/patient/documentsβ werkzeugsecure_filename, 10 MB limit, allowed extensions:.pdf/.docx/.doc/.txt/.png/.jpg/.jpeg. Saves touploads/patient_docs/<patient_id>/. Metadata stored as JSON array inpatient_accounts.documents.DELETE /api/patient/documents/<doc_id>β removes file from disk and metadata from DB.GET /api/patient/documents/<doc_id>/downloadβ serves file as attachment.app.config["MAX_CONTENT_LENGTH"] = 10 * 1024 * 1024enforced Flask-side.
Fix 22 β Dataset-backed patient seeding
Before: Only hand-made demo patients in DB (john_doe only had open_to_trials=1 initially). Hospital search returned 0 results on fresh DB.
After: _seed_dataset_patients(c, max_patients=20) in database.py seeds 20 real Synthea patients from CSV files on first run. Skips deceased patients (Death_Date not empty). Reads up to 8 conditions + 6 medications per patient. Username: synthea_<first8chars_of_Patient_ID>, password pass123, open_to_trials=1.
All 5 hand-made demo patients also set to open_to_trials=1. Total: 25 patients in DB.
Fix 23 β Hospital search include_connected toggle
Before: Hospital Search tab also excluded already-connected patients, same as Available Patients tab β making it useless for re-searching.
After: Search tab adds include_connected=true query param. GET /api/hospital/patients?include_connected=true bypasses the exclusion subquery. Available Patients tab retains strict exclusion. Toggle checkbox in Search tab UI.
Fix 24 β Template auto-reload
Before: app.run(debug=False, use_reloader=False) β template edits required server restart to take effect.
After: app.config["TEMPLATES_AUTO_RELOAD"] = True added after other config lines. Templates now reload on every request without enabling full debug mode or the reloader.
Known Issues / Future Improvements
High pseudo-label positive rate (82.1%) β lowers AUC-ROC to ~0.60. Fix: raise label threshold from 0.5 to 0.6, or explicitly sample equal positive/negative pairs.
data_completeness feature importance = 0 β nearly constant across training pairs (all synthetic patients have complete data). Consider removing from FEATURE_COLS.
Fuzzy condition matching not implemented β only exact condition name overlaps used (106 conditions). Substring/semantic fuzzy matching would expand coverage significantly.
lab_availability coverage is low (8.7%) β observations file is sparse. Consider normalising denominator to the subset that has any lab data.
Hospital portal does not rank patients by match quality β listed in insertion order. Could rank by condition overlap with the hospital's research_conditions.
No email/notification system β connection requests visible only inside the portal.
No automated tests β syntax checking only (
python -m py_compile). Key flows to cover: patient registration β profile update β match β connect; hospital registration β patient browse β connect β status update; duplicate connection rejection.Inbox badge not auto-refreshed β unread count badge only updates when the user clicks the Inbox tab. No real-time push; would require polling or WebSockets.
Document access control β uploaded files are served from disk by doc_id only; no additional hospital-side access to patient documents (by design β privacy model). Hospital sees document count in patient profile only after connection.
Synthea patients have Synthea-style names (e.g. "Geovany567 Reichert456") β cosmetically odd but functionally correct. No fix needed for demo.