fix(report): дата сборки в POST-ответе на fast-path из кэша (последний хвост R2 #2259) (#2347)
All checks were successful
Deploy / changes (push) Successful in 6s
Deploy / build-backend (push) Successful in 2m6s
Deploy / build-frontend (push) Successful in 3m36s
Deploy / build-worker (push) Successful in 4m3s
Deploy / deploy (push) Successful in 1m34s

This commit is contained in:
bot-backend 2026-07-03 23:44:19 +00:00
parent 5fa505b809
commit 36fe33750f
5 changed files with 22 additions and 5 deletions

View file

@ -1626,10 +1626,15 @@ def build_parcel_report(
analyze_at = _iso_or_none(getattr(analyze_run, "created_at", None))
forecast_at = _iso_or_none(getattr(forecast_run, "created_at", None))
# Готовый кэш → 200 ready (не enqueue'им повторно).
if _cached_report_result(db, cad_num) is not None:
# Готовый кэш → 200 ready (не enqueue'им повторно). Отдаём и дату генерации PDF из
# метадаты рана — чтобы fast-path кнопка показала дату без отдельного GET /status.
cached = _cached_report_result(db, cad_num)
if cached is not None:
return ReportBuildResponse(
status="ready", analyze_run_at=analyze_at, forecast_run_at=forecast_at
status="ready",
analyze_run_at=analyze_at,
forecast_run_at=forecast_at,
report_generated_at=cached.get("generated_at"),
)
# Enqueue фоновой сборки. Lazy import таски (как forecast в analyze_parcel) — атрибут

View file

@ -270,6 +270,9 @@ class ReportBuildResponse(BaseModel):
# Даты базовых ранов (ISO) — контекст, по каким данным собирается/собран отчёт.
analyze_run_at: str | None = None
forecast_run_at: str | None = None
# Дата генерации готового PDF (ISO) — только при status="ready" (кэш-хит); на
# building-ветке None (файла ещё нет). Даёт fast-path кнопке дату без GET /status.
report_generated_at: str | None = None
class ReportStatusResponse(BaseModel):

View file

@ -95,6 +95,8 @@ def test_post_report_enqueues_and_returns_202() -> None:
body = resp.json()
assert body["status"] == "building"
assert body["analyze_run_at"] is not None
# Отчёт ещё не собран → даты генерации PDF нет.
assert body["report_generated_at"] is None
delay_mock.assert_called_once_with(_CAD)
finally:
app.dependency_overrides.clear()
@ -127,7 +129,10 @@ def test_post_report_cache_hit_returns_200_ready(tmp_path: Path) -> None:
client = TestClient(app)
resp = client.post(f"/api/v1/parcels/{_CAD}/report")
assert resp.status_code == 200, resp.text
assert resp.json()["status"] == "ready"
body = resp.json()
assert body["status"] == "ready"
# Fast-path: POST-ответ несёт дату генерации PDF из метадаты рана (без GET /status).
assert body["report_generated_at"] == "2026-07-03T09:00:00+00:00"
delay_mock.assert_not_called() # кэш есть → не enqueue'им
finally:
app.dependency_overrides.clear()

View file

@ -294,7 +294,9 @@ export function useFullReport(cad: string): UseFullReportResult {
}
const reportGeneratedAt =
(uiState === "ready" ? statusData?.report_generated_at : null) ?? null;
(uiState === "ready"
? (statusData?.report_generated_at ?? enqueue.data?.report_generated_at)
: null) ?? null;
return {
uiState,

View file

@ -5255,6 +5255,8 @@ export interface components {
analyze_run_at?: string | null;
/** Forecast Run At */
forecast_run_at?: string | null;
/** Report Generated At */
report_generated_at?: string | null;
};
/**
* ReportStatusResponse