second-life / llm.md
MrNoOne07's picture
Deploy Second Life Flask app
d77360c verified
|
Raw
History Blame Contribute Delete
32.5 kB

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 API
  • database.py β€” SQLite layer: 5 tables, auth, connections, trial interests, messages
  • templates/landing.html β€” Login/register landing page
  • templates/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_Date
  • Final Patients Synthea Data/patients_details.csv β€” Demographics. Columns: Patient_ID, First_Name, Last_Name, Birth_Date (DD-MM-YYYY), Gender, Race, Ethnicity, Address
  • Final Patients Synthea Data/patients_medications.csv β€” 213,182 patients with med data. Columns: Patient_ID, Medication_Name, Medication_End_Date
  • Final 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_Lower
  • Final Clinical Trails Data/trail_eligibilities.csv β€” Columns: Trial_ID, Gender (leading space β€” stripped), Minimum_Age, Maximum_Age, Eligibility_Criteria
  • Final Clinical Trails Data/trail_studies.csv β€” 65,292 recruiting trials. Columns: Trial_ID, Brief_Title, Overall_Status, Phase, Start_Date, Enrollment
  • Final 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_Country
  • Final Clinical Trails Data/trail_brief_summaries.csv β€” Columns: Trial_ID, Brief_Summary
  • Final Clinical Trails Data/trail_interventions.csv β€” 196,865 trials with drug intervention data. Columns: Trial_ID, Intervention_Type, Intervention_Name
  • Final 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.gz
  • mimic-iv-clinical-database-demo-2.2/hosp/diagnoses_icd.csv.gz
  • mimic-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_trials
  • GET /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_name
  • POST /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 read
  • POST /api/patient/connections/<cid>/messages β†’ {success, message}; body: {body}
  • GET /api/patient/inbox β†’ {threads: [...]} each with last_message, last_message_at, unread_count, hospital_name
  • GET /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 DB
  • GET /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_conditions
  • GET /api/hospital/patients?condition=&include_connected= β†’ {patients: [...]} open_to_trials=1; include_connected=true to 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 fields
  • PUT /api/hospital/connections/<cid>/status β†’ {success}; body: {status: pending|accepted|rejected|completed}
  • GET /api/hospital/connections/<cid>/messages β†’ {messages: [...]}; marks patient messages read
  • POST /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_name
  • GET /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 names
  • pipeline.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

  1. Hospitals see only patients with open_to_trials=1 (name, age, gender, conditions)
  2. Full details accessible only after patient-initiated connection
  3. Hospital cannot contact a patient unless patient is open to trials
  4. Connection record: patient_id, hospital_id, trial_id, initiated_by, status, message
  5. 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,'&amp;').replace(/"/g,'&quot;')
                      .replace(/</g,'&lt;').replace(/>/g,'&gt;');
}

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)

  • connections table: added UNIQUE(patient_id, hospital_id, trial_id) schema constraint
  • init_db(): runs CREATE 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 DBs
  • create_connection(): pre-checks connection_exists() before insert; returns None on duplicate
  • /api/patient/connect and /api/hospital/connect: return 409 when create_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 from Facility_Name column 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-head with 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_messages table with sender_role, sender_id, body, is_read.
  • GET/POST /api/patient/connections/<cid>/messages and GET/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/inbox or GET /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 β€” werkzeug secure_filename, 10 MB limit, allowed extensions: .pdf/.docx/.doc/.txt/.png/.jpg/.jpeg. Saves to uploads/patient_docs/<patient_id>/. Metadata stored as JSON array in patient_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 * 1024 enforced 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

  1. 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.

  2. data_completeness feature importance = 0 β€” nearly constant across training pairs (all synthetic patients have complete data). Consider removing from FEATURE_COLS.

  3. Fuzzy condition matching not implemented β€” only exact condition name overlaps used (106 conditions). Substring/semantic fuzzy matching would expand coverage significantly.

  4. lab_availability coverage is low (8.7%) β€” observations file is sparse. Consider normalising denominator to the subset that has any lab data.

  5. Hospital portal does not rank patients by match quality β€” listed in insertion order. Could rank by condition overlap with the hospital's research_conditions.

  6. No email/notification system β€” connection requests visible only inside the portal.

  7. 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.

  8. 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.

  9. 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.

  10. Synthea patients have Synthea-style names (e.g. "Geovany567 Reichert456") β€” cosmetically odd but functionally correct. No fix needed for demo.