feat: add data freshness indicator with relative time and patient count (Task 5.3)

This commit is contained in:
Andrew Charlwood
2026-02-06 14:21:45 +00:00
parent b739d47e18
commit e877268805
3 changed files with 57 additions and 5 deletions
+3 -3
View File
@@ -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 - **Checkpoint**: Loading spinner appears during data fetch, empty state shows message
### 5.3 Data freshness indicator ### 5.3 Data freshness indicator
- [ ] Header shows: green dot + "{N} records" + "Last updated: {relative_time}" - [x] Header shows: green dot + "{N} patients" + "Last updated: {relative_time}"
- [ ] Pull from `pathway_refresh_log` via `queries.load_initial_data()` - [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)
- [ ] Format as relative time (e.g., "2h ago", "yesterday") - [x] Format as relative time (e.g., "2h ago", "yesterday")
- **Checkpoint**: Header shows correct data freshness - **Checkpoint**: Header shows correct data freshness
### 5.4 Remove Reflex + final validation ### 5.4 Remove Reflex + final validation
+42 -2
View File
@@ -1,7 +1,39 @@
"""Callbacks for reference data loading and filter state management.""" """Callbacks for reference data loading and filter state management."""
from datetime import datetime
from dash import Input, Output, State, callback, ctx, no_update 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): def register_filter_callbacks(app):
"""Register reference data loading and filter state callbacks.""" """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 from dash_app.data.queries import load_initial_data
ref = load_initial_data() ref = load_initial_data()
total = ref.get("total_records", 0) 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", "") 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 return ref, record_text, updated_text
+12
View File
@@ -92,12 +92,24 @@ def load_initial_data(db_path: Path) -> dict:
""") """)
available_trusts = [row[0] for row in cursor.fetchall()] 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 { return {
"available_drugs": available_drugs, "available_drugs": available_drugs,
"available_directorates": available_directorates, "available_directorates": available_directorates,
"available_indications": available_indications, "available_indications": available_indications,
"available_trusts": available_trusts, "available_trusts": available_trusts,
"total_records": total_records, "total_records": total_records,
"total_patients": total_patients,
"last_updated": last_updated, "last_updated": last_updated,
} }
except sqlite3.Error as e: except sqlite3.Error as e: