feat: add reports center, admin drill-down, disease filter + bug fixes + perf optimization
Frontend features:
- 报表中心 (ReportsCenter): list/detail views, diagnosis breakdown chart, CSV export
- 多级行政下钻 (AdminBreadcrumb): 湖北省→武汉市→区→街道 hierarchical drill-down
- 按病种筛选 (DiseaseFilter): multi-select diagnosis filter on monitoring + reports pages
Backend:
- Add /forecast/{days} endpoint, diagnosis filter params on cases endpoints
- Add /streets aggregation endpoint, enrich reports with real case data
- Extract shared case_loader module
Bug fixes (14):
- Fix missing /risk/forecast route (404), historyApi pointing to non-existent router
- Fix min_risk filter silently ignored in insights/hotspots
- Fix type mismatches: CaseTrendResponse, CaseStatsResponse shapes
- Fix silent .catch(() => {}) swallowing errors, fetchAlerts not clearing stale state
- Fix lru_cache caching exceptions, generateReport used cachedGet for write op
- Fix missing useEffect deps in Insights, DistrictComparison, ReportsCenter
Performance (9):
- Zustand selectors across 9 components (eliminate re-render cascades)
- Fix districtCases.sort() mutating store state, inline IIFE → memo'd component
- CaseLocationMap: React.memo, race protection, correct deps
- AlertCard: stable callbacks, TimelinePlayer: useMemo, TopNav: clock isolation
- SideNav: modules array to module scope, DiseaseFilter: memoized filter
This commit is contained in:
@@ -15,14 +15,16 @@ from models import (
|
||||
ReportSummary,
|
||||
ReportSection,
|
||||
ReportRecommendation,
|
||||
DiagnosisBreakdown,
|
||||
)
|
||||
from utils.date_helpers import get_latest_date, get_available_dates
|
||||
from utils.geojson import parse_geojson_file
|
||||
from data.case_loader import get_combined_data
|
||||
|
||||
router = APIRouter(prefix="/api/reports", tags=["reports"])
|
||||
|
||||
|
||||
def calculate_report_summary(grids: List[dict], period_days: int) -> ReportSummary:
|
||||
def calculate_report_summary(grids: List[dict], period_days: int, case_data=None) -> ReportSummary:
|
||||
"""Calculate summary statistics for report"""
|
||||
if not grids:
|
||||
return ReportSummary(
|
||||
@@ -53,7 +55,10 @@ def calculate_report_summary(grids: List[dict], period_days: int) -> ReportSumma
|
||||
elif avg_risk < avg_3d * 0.95:
|
||||
trend_direction = "improving"
|
||||
|
||||
total_cases = int(len(grids) * avg_risk * 0.1 * period_days)
|
||||
if case_data is not None and len(case_data) > 0:
|
||||
total_cases = len(case_data)
|
||||
else:
|
||||
total_cases = int(len(grids) * avg_risk * 0.1 * period_days)
|
||||
|
||||
return ReportSummary(
|
||||
total_cases=total_cases,
|
||||
@@ -65,6 +70,23 @@ def calculate_report_summary(grids: List[dict], period_days: int) -> ReportSumma
|
||||
)
|
||||
|
||||
|
||||
def compute_diagnosis_breakdown(case_data) -> list:
|
||||
"""Compute diagnosis breakdown from case data. Returns list of dicts."""
|
||||
if case_data is None or len(case_data) == 0:
|
||||
return []
|
||||
breakdown = case_data.groupby(['diagnosis', 'type']).size().unstack(fill_value=0)
|
||||
if 'outpatient' not in breakdown.columns:
|
||||
breakdown['outpatient'] = 0
|
||||
if 'inpatient' not in breakdown.columns:
|
||||
breakdown['inpatient'] = 0
|
||||
breakdown['total'] = breakdown['outpatient'] + breakdown['inpatient']
|
||||
return [
|
||||
{"diagnosis": str(d), "outpatient": int(row['outpatient']),
|
||||
"inpatient": int(row['inpatient']), "total": int(row['total'])}
|
||||
for d, row in breakdown.sort_values('total', ascending=False).head(10).iterrows()
|
||||
]
|
||||
|
||||
|
||||
def generate_report_sections(summary: ReportSummary, grids: List[Dict], period_days: int) -> List[ReportSection]:
|
||||
"""Generate report sections"""
|
||||
sections = [
|
||||
@@ -259,9 +281,24 @@ async def get_report(report_id: str):
|
||||
|
||||
period_days = 1 if report_type == "daily" else 7 if report_type == "weekly" else 30
|
||||
|
||||
summary = calculate_report_summary(grids, period_days)
|
||||
# Load case data for the report's date range
|
||||
case_data = None
|
||||
try:
|
||||
case_data = get_combined_data()
|
||||
if case_data is not None and len(case_data) > 0:
|
||||
report_start = datetime.strptime(date_str, "%Y%m%d") - timedelta(days=period_days - 1)
|
||||
report_end = datetime.strptime(date_str, "%Y%m%d")
|
||||
case_data = case_data[
|
||||
(case_data['date'] >= report_start) &
|
||||
(case_data['date'] <= report_end)
|
||||
]
|
||||
except Exception:
|
||||
case_data = None
|
||||
|
||||
summary = calculate_report_summary(grids, period_days, case_data)
|
||||
sections = generate_report_sections(summary, grids, period_days)
|
||||
recommendations = generate_recommendations(summary, grids)
|
||||
diagnosis_breakdown = compute_diagnosis_breakdown(case_data)
|
||||
|
||||
metadata = ReportMetadata(
|
||||
report_id=report_id,
|
||||
@@ -285,7 +322,8 @@ async def get_report(report_id: str):
|
||||
sections=sections,
|
||||
recommendations=recommendations,
|
||||
attachments=attachments,
|
||||
timestamp=datetime.now().isoformat()
|
||||
timestamp=datetime.now().isoformat(),
|
||||
diagnosis_breakdown=[DiagnosisBreakdown(**d) for d in diagnosis_breakdown],
|
||||
)
|
||||
|
||||
|
||||
@@ -336,9 +374,24 @@ async def generate_new_report(
|
||||
|
||||
period_days = 1 if report_type == "daily" else 7 if report_type == "weekly" else 30
|
||||
|
||||
summary = calculate_report_summary(grids, period_days)
|
||||
# Load case data for the report's date range
|
||||
case_data = None
|
||||
try:
|
||||
case_data = get_combined_data()
|
||||
if case_data is not None and len(case_data) > 0:
|
||||
report_start = datetime.strptime(date, "%Y%m%d") - timedelta(days=period_days - 1)
|
||||
report_end = datetime.strptime(date, "%Y%m%d")
|
||||
case_data = case_data[
|
||||
(case_data['date'] >= report_start) &
|
||||
(case_data['date'] <= report_end)
|
||||
]
|
||||
except Exception:
|
||||
case_data = None
|
||||
|
||||
summary = calculate_report_summary(grids, period_days, case_data)
|
||||
sections = generate_report_sections(summary, grids, period_days)
|
||||
recommendations = generate_recommendations(summary, grids)
|
||||
diagnosis_breakdown = compute_diagnosis_breakdown(case_data)
|
||||
|
||||
metadata = ReportMetadata(
|
||||
report_id=report_id,
|
||||
@@ -362,7 +415,8 @@ async def generate_new_report(
|
||||
sections=sections,
|
||||
recommendations=recommendations,
|
||||
attachments=attachments,
|
||||
timestamp=datetime.now().isoformat()
|
||||
timestamp=datetime.now().isoformat(),
|
||||
diagnosis_breakdown=[DiagnosisBreakdown(**d) for d in diagnosis_breakdown],
|
||||
)
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user