gendesign/backend/app/workers/tasks/full_report.py
bot-backend 034d9f9bca
Some checks failed
Deploy / changes (push) Successful in 8s
Deploy / build-backend (push) Successful in 3m12s
Deploy / build-worker (push) Successful in 4m8s
Deploy / build-frontend (push) Successful in 4m46s
Deploy / deploy (push) Has been cancelled
feat(reports): Celery-джоба полного PDF-отчёта + кэш + 3 endpoint'а (#2259 PR-D) (#2287)
2026-07-03 11:13:06 +00:00

80 lines
4.4 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"""Celery task: собрать полный PDF-отчёт ПТИЦА в фоне (эпик #2259 PR-D).
Сборка полного PDF `/site-finder/analysis/{cad}` (§1§7 + карты) занимает секунды-десятки
секунд (WeasyPrint-рендер + OSM-тайлы карт + server-side концепция), поэтому НЕ крутится
inline на синхронном эндпоинте — здесь фоновая on-demand таска, оркестрирующая
:func:`build_full_report` (full_report_pdf.py) и пишущая PDF-файл + метадата-ран.
ПОТОК: `SessionLocal()` → `build_full_report(db, cad)` → метадата-dict (pdf_path/…) →
close. Best-effort: нет analyze-рана → ValueError внутри оркестратора → ловим, warning,
return None (таска не валит воркер — отчётировать нечего). Прочий сбой → logger.exception
+ None (карты/концепция/форсайт сами best-effort внутри оркестратора).
Mirror conventions (forecast.py): `@celery_app.task`, `SessionLocal()` + try/finally close,
`logger` (не print), синхронный `def`. Очередь — дефолтная `celery` (как forecast-таска: у
неё нет `queue=` в celery_app routing → default; воркер слушает `--queues=celery,scrape_kn,geo`).
TIME LIMIT: WeasyPrint-рендер + карты могут занять до ~минуты на тяжёлом участке —
щедрый per-task лимит (time_limit=600 / soft_time_limit=540), как у forecast-таски.
REGISTRATION: добавлено в `include=[...]` celery_app.py (зеркало forecast). Beat НЕТ
таска enqueue-on-demand (триггер из POST /{cad}/report).
"""
from __future__ import annotations
import logging
from typing import Any
from app.core.db import SessionLocal
from app.services.exporters.full_report_pdf import build_full_report
from app.workers.celery_app import celery_app
logger = logging.getLogger(__name__)
@celery_app.task(
bind=True,
name="tasks.full_report.build_full_report",
time_limit=600,
soft_time_limit=540,
)
def build_full_report_task(self: Any, cad: str) -> dict[str, Any] | None:
"""Собрать полный PDF-отчёт участка в фоне и записать файл + метадата-ран.
Оркестрация целиком в :func:`build_full_report` (full_report_pdf.py): analyze-ран
(нет → ValueError → None), forecast-ран (нет → Part B «нет данных»), connection-capacity
и концепция best-effort, карты + WeasyPrint-рендер → файл на volume `/app/reports/` +
метадата-ран `report-pdf-1.0`. Кэш по (analyze_run_id, forecast_run_id): тот же ключ +
файл на месте → cache-hit, не пере-рендерит.
Args:
cad: кадастровый номер участка.
Returns:
Метадата отчёта {pdf_path, analyze_run_id, forecast_run_id, generated_at,
size_bytes} (+ `cache_hit` при кэш-хите), либо None если нет analyze-рана /
неожиданный сбой (таска failed gracefully, воркер не падает).
"""
db = SessionLocal()
try:
logger.info("build_full_report_task: старт cad=%s", cad)
result = build_full_report(db, cad)
logger.info(
"build_full_report_task: готово cad=%s path=%s cache_hit=%s",
cad,
result.get("pdf_path"),
result.get("cache_hit", False),
)
return result
except ValueError as exc:
# Нет analyze-рана — отчётировать нечего (graceful, не crash воркера).
logger.warning("build_full_report_task: нечего собирать cad=%s: %s", cad, exc)
return None
except Exception:
# Неожиданный сбой рендера/записи: traceback + None (карты/концепция/форсайт
# уже best-effort внутри оркестратора — сюда всплывает только фатальное).
logger.exception("build_full_report_task failed for cad=%s (returning None)", cad)
return None
finally:
db.close()