diff --git a/IMPLEMENTATION_PLAN.md b/IMPLEMENTATION_PLAN.md index ffc5a41..c93c564 100644 --- a/IMPLEMENTATION_PLAN.md +++ b/IMPLEMENTATION_PLAN.md @@ -242,9 +242,9 @@ Drawer selection → update_drug_selection → app-state store → load_pathway_ - **Checkpoint**: Loading spinner appears during data fetch, empty state shows message ### 5.3 Data freshness indicator -- [ ] Header shows: green dot + "{N} records" + "Last updated: {relative_time}" -- [ ] Pull from `pathway_refresh_log` via `queries.load_initial_data()` -- [ ] Format as relative time (e.g., "2h ago", "yesterday") +- [x] Header shows: green dot + "{N} patients" + "Last updated: {relative_time}" +- [x] Pull from `pathway_refresh_log` via `queries.load_initial_data()` (uses total_patients from root node as fallback when source_row_count is 0) +- [x] Format as relative time (e.g., "2h ago", "yesterday") - **Checkpoint**: Header shows correct data freshness ### 5.4 Remove Reflex + final validation diff --git a/dash_app/callbacks/filters.py b/dash_app/callbacks/filters.py index b99096a..8352212 100644 --- a/dash_app/callbacks/filters.py +++ b/dash_app/callbacks/filters.py @@ -1,7 +1,39 @@ """Callbacks for reference data loading and filter state management.""" +from datetime import datetime from dash import Input, Output, State, callback, ctx, no_update +def _format_relative_time(iso_timestamp: str) -> str: + """Format an ISO timestamp as relative time (e.g., '2h ago', 'yesterday').""" + if not iso_timestamp: + return "Unknown" + try: + dt = datetime.fromisoformat(iso_timestamp) + now = datetime.now() + delta = now - dt + seconds = int(delta.total_seconds()) + + if seconds < 60: + return "just now" + minutes = seconds // 60 + if minutes < 60: + return f"{minutes}m ago" + hours = minutes // 60 + if hours < 24: + return f"{hours}h ago" + days = hours // 24 + if days == 1: + return "yesterday" + if days < 7: + return f"{days}d ago" + if days < 30: + weeks = days // 7 + return f"{weeks}w ago" + return dt.strftime("%d %b %Y") + except (ValueError, TypeError): + return iso_timestamp[:10] if len(iso_timestamp) >= 10 else "Unknown" + + def register_filter_callbacks(app): """Register reference data loading and filter state callbacks.""" @@ -16,10 +48,18 @@ def register_filter_callbacks(app): from dash_app.data.queries import load_initial_data ref = load_initial_data() + total = ref.get("total_records", 0) - record_text = f"{total:,} records" if total else "Data loaded" + patients = ref.get("total_patients", 0) + if total: + record_text = f"{total:,} records" + elif patients: + record_text = f"{patients:,} patients" + else: + record_text = "Data loaded" + last_updated = ref.get("last_updated", "") - updated_text = last_updated[:10] if last_updated else "Unknown" + updated_text = _format_relative_time(last_updated) return ref, record_text, updated_text diff --git a/src/data_processing/pathway_queries.py b/src/data_processing/pathway_queries.py index f8eb9ee..ab919ce 100644 --- a/src/data_processing/pathway_queries.py +++ b/src/data_processing/pathway_queries.py @@ -92,12 +92,24 @@ def load_initial_data(db_path: Path) -> dict: """) available_trusts = [row[0] for row in cursor.fetchall()] + # Total patients from default root node (fallback when source_row_count is empty) + total_patients = 0 + if not total_records: + cursor.execute(""" + SELECT value FROM pathway_nodes + WHERE level = 0 AND date_filter_id = 'all_6mo' AND chart_type = 'directory' + LIMIT 1 + """) + root_row = cursor.fetchone() + total_patients = (root_row[0] or 0) if root_row else 0 + return { "available_drugs": available_drugs, "available_directorates": available_directorates, "available_indications": available_indications, "available_trusts": available_trusts, "total_records": total_records, + "total_patients": total_patients, "last_updated": last_updated, } except sqlite3.Error as e: