fix(report): report_generated_at в POST-ответе на fast-path из кэша
All checks were successful
CI / changes (pull_request) Successful in 8s
CI Trade-In / backend-tests (pull_request) Has been skipped
CI Trade-In / frontend-checks (pull_request) Has been skipped
CI Trade-In / changes (pull_request) Successful in 8s
CI / frontend-tests (pull_request) Successful in 1m11s
CI / openapi-codegen-check (pull_request) Successful in 2m5s
CI / backend-tests (pull_request) Successful in 14m47s
All checks were successful
CI / changes (pull_request) Successful in 8s
CI Trade-In / backend-tests (pull_request) Has been skipped
CI Trade-In / frontend-checks (pull_request) Has been skipped
CI Trade-In / changes (pull_request) Successful in 8s
CI / frontend-tests (pull_request) Successful in 1m11s
CI / openapi-codegen-check (pull_request) Successful in 2m5s
CI / backend-tests (pull_request) Successful in 14m47s
Кэш-хит POST /{cad}/report возвращал status=ready без даты сборки —
кнопка «Скачать отчёт» показывала лейбл без даты до отдельного
GET /status. Заполняем report_generated_at из метадаты рана на
ready-ветке (тот же ключ generated_at, что у status — ответы согласованы),
building — None. Хук фолбэчит на enqueue-ответ. api-types +2.
Refs #2259
This commit is contained in:
parent
9289c3d69d
commit
cfa6309551
5 changed files with 22 additions and 5 deletions
|
|
@ -1626,10 +1626,15 @@ def build_parcel_report(
|
||||||
analyze_at = _iso_or_none(getattr(analyze_run, "created_at", None))
|
analyze_at = _iso_or_none(getattr(analyze_run, "created_at", None))
|
||||||
forecast_at = _iso_or_none(getattr(forecast_run, "created_at", None))
|
forecast_at = _iso_or_none(getattr(forecast_run, "created_at", None))
|
||||||
|
|
||||||
# Готовый кэш → 200 ready (не enqueue'им повторно).
|
# Готовый кэш → 200 ready (не enqueue'им повторно). Отдаём и дату генерации PDF из
|
||||||
if _cached_report_result(db, cad_num) is not None:
|
# метадаты рана — чтобы fast-path кнопка показала дату без отдельного GET /status.
|
||||||
|
cached = _cached_report_result(db, cad_num)
|
||||||
|
if cached is not None:
|
||||||
return ReportBuildResponse(
|
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) — атрибут
|
# Enqueue фоновой сборки. Lazy import таски (как forecast в analyze_parcel) — атрибут
|
||||||
|
|
|
||||||
|
|
@ -270,6 +270,9 @@ class ReportBuildResponse(BaseModel):
|
||||||
# Даты базовых ранов (ISO) — контекст, по каким данным собирается/собран отчёт.
|
# Даты базовых ранов (ISO) — контекст, по каким данным собирается/собран отчёт.
|
||||||
analyze_run_at: str | None = None
|
analyze_run_at: str | None = None
|
||||||
forecast_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):
|
class ReportStatusResponse(BaseModel):
|
||||||
|
|
|
||||||
|
|
@ -95,6 +95,8 @@ def test_post_report_enqueues_and_returns_202() -> None:
|
||||||
body = resp.json()
|
body = resp.json()
|
||||||
assert body["status"] == "building"
|
assert body["status"] == "building"
|
||||||
assert body["analyze_run_at"] is not None
|
assert body["analyze_run_at"] is not None
|
||||||
|
# Отчёт ещё не собран → даты генерации PDF нет.
|
||||||
|
assert body["report_generated_at"] is None
|
||||||
delay_mock.assert_called_once_with(_CAD)
|
delay_mock.assert_called_once_with(_CAD)
|
||||||
finally:
|
finally:
|
||||||
app.dependency_overrides.clear()
|
app.dependency_overrides.clear()
|
||||||
|
|
@ -127,7 +129,10 @@ def test_post_report_cache_hit_returns_200_ready(tmp_path: Path) -> None:
|
||||||
client = TestClient(app)
|
client = TestClient(app)
|
||||||
resp = client.post(f"/api/v1/parcels/{_CAD}/report")
|
resp = client.post(f"/api/v1/parcels/{_CAD}/report")
|
||||||
assert resp.status_code == 200, resp.text
|
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'им
|
delay_mock.assert_not_called() # кэш есть → не enqueue'им
|
||||||
finally:
|
finally:
|
||||||
app.dependency_overrides.clear()
|
app.dependency_overrides.clear()
|
||||||
|
|
|
||||||
|
|
@ -294,7 +294,9 @@ export function useFullReport(cad: string): UseFullReportResult {
|
||||||
}
|
}
|
||||||
|
|
||||||
const reportGeneratedAt =
|
const reportGeneratedAt =
|
||||||
(uiState === "ready" ? statusData?.report_generated_at : null) ?? null;
|
(uiState === "ready"
|
||||||
|
? (statusData?.report_generated_at ?? enqueue.data?.report_generated_at)
|
||||||
|
: null) ?? null;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
uiState,
|
uiState,
|
||||||
|
|
|
||||||
|
|
@ -5255,6 +5255,8 @@ export interface components {
|
||||||
analyze_run_at?: string | null;
|
analyze_run_at?: string | null;
|
||||||
/** Forecast Run At */
|
/** Forecast Run At */
|
||||||
forecast_run_at?: string | null;
|
forecast_run_at?: string | null;
|
||||||
|
/** Report Generated At */
|
||||||
|
report_generated_at?: string | null;
|
||||||
};
|
};
|
||||||
/**
|
/**
|
||||||
* ReportStatusResponse
|
* ReportStatusResponse
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue