mirror of
https://github.com/complexcaresolutions/dak.c2s.git
synced 2026-03-17 20:43:41 +00:00
get_vorjahr_summary returned a flat dict, but excel_export looked for
data under vj.get("summary", {}), resulting in empty Vorjahr columns.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
207 lines
7.6 KiB
Python
207 lines
7.6 KiB
Python
"""Vorjahr (previous-year) comparison service.
|
|
|
|
Provides aggregated previous-year data for Sheet 1's year-over-year columns.
|
|
Uses the yearly_summary table as a cache — if summary rows exist for the
|
|
previous year they are aggregated directly; otherwise the live cases table
|
|
is queried and the results are stored for future lookups.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import logging
|
|
|
|
from sqlalchemy.orm import Session
|
|
|
|
from app.models.report import YearlySummary
|
|
from app.services.report_service import (
|
|
FALLGRUPPEN,
|
|
calculate_sheet1_data,
|
|
calculate_sheet2_data,
|
|
calculate_sheet3_data,
|
|
calculate_sheet4_data,
|
|
)
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Public API
|
|
# ---------------------------------------------------------------------------
|
|
|
|
def get_vorjahr_summary(db: Session, jahr: int) -> dict:
|
|
"""Get previous year's aggregated data for comparison.
|
|
|
|
First checks the ``yearly_summary`` table (cached per-KW rows).
|
|
If no cached data exists, calculates live from the cases table and
|
|
persists the results for future use.
|
|
|
|
Returns::
|
|
|
|
{
|
|
"jahr": int,
|
|
"erstberatungen": int,
|
|
"ablehnungen": int,
|
|
"unterlagen": int,
|
|
"keine_rueckmeldung": int,
|
|
"gutachten": int,
|
|
"gutachten_alternative": int,
|
|
"gutachten_bestaetigung": int,
|
|
}
|
|
"""
|
|
vorjahr = jahr - 1
|
|
|
|
summaries = (
|
|
db.query(YearlySummary)
|
|
.filter(YearlySummary.jahr == vorjahr)
|
|
.all()
|
|
)
|
|
|
|
if summaries:
|
|
logger.debug("Vorjahr %d: using %d cached summary rows", vorjahr, len(summaries))
|
|
return _aggregate_summaries(vorjahr, summaries)
|
|
|
|
# No cache — calculate live and store
|
|
logger.info("Vorjahr %d: no cache, calculating from cases table", vorjahr)
|
|
return _calculate_and_cache(db, vorjahr)
|
|
|
|
|
|
def get_vorjahr_detail(db: Session, jahr: int) -> dict:
|
|
"""Get previous year's full breakdown (all sheets) for side-by-side comparison.
|
|
|
|
This always calculates live from the cases table (no caching of detail data).
|
|
|
|
Returns the same structure as ``generate_full_report`` but for *jahr - 1*.
|
|
"""
|
|
vorjahr = jahr - 1
|
|
from app.services.report_service import generate_full_report
|
|
return generate_full_report(db, vorjahr)
|
|
|
|
|
|
def refresh_vorjahr_cache(db: Session, jahr: int) -> int:
|
|
"""Recalculate and store yearly summary rows for the given year.
|
|
|
|
This is useful after historical data has been imported or corrected.
|
|
Returns the number of KW rows written.
|
|
"""
|
|
logger.info("Refreshing yearly summary cache for jahr=%d", jahr)
|
|
|
|
# Delete existing cache rows for this year
|
|
db.query(YearlySummary).filter(YearlySummary.jahr == jahr).delete()
|
|
db.flush()
|
|
|
|
return _store_summaries(db, jahr)
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Internal helpers
|
|
# ---------------------------------------------------------------------------
|
|
|
|
def _aggregate_summaries(jahr: int, summaries: list[YearlySummary]) -> dict:
|
|
"""Sum up weekly YearlySummary rows into yearly totals.
|
|
|
|
Returns a dict with a ``summary`` key so that excel_export can access
|
|
it the same way as the current-year sheet1 data (``data.get("summary")``).
|
|
"""
|
|
totals = {
|
|
"erstberatungen": sum(_safe(s.erstberatungen) for s in summaries),
|
|
"ablehnungen": sum(_safe(s.ablehnungen) for s in summaries),
|
|
"unterlagen": sum(_safe(s.unterlagen) for s in summaries),
|
|
"keine_rueckmeldung": sum(_safe(s.keine_rueckmeldung) for s in summaries),
|
|
"gutachten": sum(_safe(s.gutachten_gesamt) for s in summaries),
|
|
"gutachten_alternative": sum(_safe(s.gutachten_alternative) for s in summaries),
|
|
"gutachten_bestaetigung": sum(_safe(s.gutachten_bestaetigung) for s in summaries),
|
|
}
|
|
return {"jahr": jahr, "summary": totals}
|
|
|
|
|
|
def _calculate_and_cache(db: Session, jahr: int) -> dict:
|
|
"""Calculate from cases, store in yearly_summary, return aggregated."""
|
|
written = _store_summaries(db, jahr)
|
|
logger.info("Cached %d KW rows for jahr=%d", written, jahr)
|
|
|
|
# Re-read from cache to return consistent data
|
|
summaries = (
|
|
db.query(YearlySummary)
|
|
.filter(YearlySummary.jahr == jahr)
|
|
.all()
|
|
)
|
|
return _aggregate_summaries(jahr, summaries)
|
|
|
|
|
|
def _store_summaries(db: Session, jahr: int) -> int:
|
|
"""Calculate all sheet data and store one YearlySummary row per KW.
|
|
|
|
Returns number of KW rows written.
|
|
"""
|
|
sheet1 = calculate_sheet1_data(db, jahr)
|
|
sheet2 = calculate_sheet2_data(db, jahr)
|
|
sheet3 = calculate_sheet3_data(db, jahr)
|
|
sheet4 = calculate_sheet4_data(db, jahr)
|
|
|
|
written = 0
|
|
for i, s1_week in enumerate(sheet1["weekly"]):
|
|
kw = s1_week["kw"]
|
|
s2_week = sheet2["weekly"][i]
|
|
s3_week = sheet3["weekly"][i]
|
|
s4_week = sheet4["weekly"][i]
|
|
|
|
# Skip KWs with zero data everywhere (no need to store empty rows)
|
|
if s1_week["erstberatungen"] == 0 and s1_week["gutachten"] == 0:
|
|
continue
|
|
|
|
summary = YearlySummary(
|
|
jahr=jahr,
|
|
kw=kw,
|
|
# Sheet 1 totals
|
|
erstberatungen=s1_week["erstberatungen"],
|
|
ablehnungen=s1_week["ablehnungen"],
|
|
unterlagen=s1_week["unterlagen"],
|
|
keine_rueckmeldung=s1_week["keine_rm"],
|
|
gutachten_gesamt=s1_week["gutachten"],
|
|
# Sheet 3 gesamt
|
|
gutachten_alternative=s3_week["gesamt"]["alternative"],
|
|
gutachten_bestaetigung=s3_week["gesamt"]["bestaetigung"],
|
|
# Sheet 2: per-Fallgruppe
|
|
onko_anzahl=s2_week["onko"]["anzahl"],
|
|
onko_gutachten=s2_week["onko"]["gutachten"],
|
|
onko_keine_rm=s2_week["onko"]["keine_rm"],
|
|
kardio_anzahl=s2_week["kardio"]["anzahl"],
|
|
kardio_gutachten=s2_week["kardio"]["gutachten"],
|
|
kardio_keine_rm=s2_week["kardio"]["keine_rm"],
|
|
intensiv_anzahl=s2_week["intensiv"]["anzahl"],
|
|
intensiv_gutachten=s2_week["intensiv"]["gutachten"],
|
|
intensiv_keine_rm=s2_week["intensiv"]["keine_rm"],
|
|
galle_anzahl=s2_week["galle"]["anzahl"],
|
|
galle_gutachten=s2_week["galle"]["gutachten"],
|
|
galle_keine_rm=s2_week["galle"]["keine_rm"],
|
|
sd_anzahl=s2_week["sd"]["anzahl"],
|
|
sd_gutachten=s2_week["sd"]["gutachten"],
|
|
sd_keine_rm=s2_week["sd"]["keine_rm"],
|
|
# Sheet 3: per-Fallgruppe gutachten types
|
|
onko_alternative=s3_week["onko"]["alternative"],
|
|
onko_bestaetigung=s3_week["onko"]["bestaetigung"],
|
|
kardio_alternative=s3_week["kardio"]["alternative"],
|
|
kardio_bestaetigung=s3_week["kardio"]["bestaetigung"],
|
|
intensiv_alternative=s3_week["intensiv"]["alternative"],
|
|
intensiv_bestaetigung=s3_week["intensiv"]["bestaetigung"],
|
|
galle_alternative=s3_week["galle"]["alternative"],
|
|
galle_bestaetigung=s3_week["galle"]["bestaetigung"],
|
|
sd_alternative=s3_week["sd"]["alternative"],
|
|
sd_bestaetigung=s3_week["sd"]["bestaetigung"],
|
|
# Sheet 4: therapy changes
|
|
ta_ja=s4_week["ta_ja"],
|
|
ta_nein=s4_week["ta_nein"],
|
|
ta_diagnosekorrektur=s4_week["diagnosekorrektur"],
|
|
ta_unterversorgung=s4_week["unterversorgung"],
|
|
ta_uebertherapie=s4_week["uebertherapie"],
|
|
)
|
|
db.add(summary)
|
|
written += 1
|
|
|
|
db.commit()
|
|
return written
|
|
|
|
|
|
def _safe(val) -> int:
|
|
"""Coerce None to 0."""
|
|
return val if val is not None else 0
|