gendesign/data/sql/69_objective_smoke.py
lekss361 aaa5d44e77 feat(objective): integrate api.objctv.ru — auth, schema, weekly sync
backend/app/services/scrapers/objective.py:
  ObjectiveClient — GetToken (Bearer cached в Redis 25 min TTL) +
  GetReport v2 с UseDdu/UseDkp. 4 высокоуровневых метода:
  report_corpuses_summary, report_lots_summary, report_corpuses_per_flat,
  report_lots_per_flat. Retry 401/429/5xx, rate-limit 500ms, brotli.
backend/app/workers/tasks/scrape_objective.py:
  Celery task sync_objective_group — еженедельно тянет 4 канон. отчёта по
  группе, raw payload в objective_raw_reports.
celery_app.py: +include + beat «0 5 * * mon».
data/sql/68_schema_objective.sql: 6 таблиц — runs, raw_reports (jsonb),
oks (готовые ТЭП + escrow/debt), lots, lots_history (per-flat per-day),
complex_mapping (Objective ↔ domrf_kn_objects, is_reviewed workflow).
data/sql/69_objective_smoke.py: stand-alone GetToken + 4 отчёта →
data/raw/objective_smoke/<ts>/. Используется один раз чтобы понять
реальную схему payload перед написанием parser-слоя.
config: OBJECTIVE_API_KEY, OBJECTIVE_DEFAULT_GROUP=Екатеринбург,
OBJECTIVE_SYNC_CRON='0 5 * * mon'.
2026-05-07 21:04:00 +03:00

121 lines
4.3 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.

"""Smoke-тест Objective API: GetToken + 4 канонических отчёта.
Запуск:
cd backend
OBJECTIVE_API_KEY=<your_key> uv run python ../data/sql/69_objective_smoke.py
Что делает:
1. Получает Bearer-токен (кеш в Redis с TTL).
2. Тянет 4 отчёта по Екатеринбургу:
a. Сводные/Корпуса (период last 1 month)
b. Сводные/Лоты (период last 1 month)
c. Поквартирные/Корпуса
d. Поквартирные/Лоты
3. Сохраняет raw JSON в data/raw/objective_smoke/<timestamp>/
4. Печатает size + первые поля каждого отчёта (схему-прицел).
Это нужно один раз, чтобы понять реальную форму payload и доработать
парсер 70_parse_objective.py (следующий шаг — нормализация в objective_*).
"""
from __future__ import annotations
import json
import os
import sys
from datetime import date, datetime, timedelta
from pathlib import Path
# Гарантия что запускается из репо-root через `cd backend; uv run python ...`
sys.path.insert(0, str(Path(__file__).resolve().parent.parent.parent / "backend"))
from app.services.scrapers.objective import ObjectiveAuthError, ObjectiveClient
def main() -> int:
if not os.environ.get("OBJECTIVE_API_KEY"):
print(
"ERROR: переменная OBJECTIVE_API_KEY не задана. "
"Запусти: OBJECTIVE_API_KEY=<key> uv run python ../data/sql/69_objective_smoke.py"
)
return 1
end = date.today().replace(day=1)
start = (end - timedelta(days=1)).replace(day=1)
out_dir = Path("data/raw/objective_smoke") / datetime.utcnow().strftime("%Y%m%d_%H%M%S")
out_dir.mkdir(parents=True, exist_ok=True)
try:
client = ObjectiveClient()
except ObjectiveAuthError as e:
print(f"AUTH ERROR: {e}")
return 2
reports: list[tuple[str, str, dict]] = [
(
"corpuses_summary",
"Объединенные данные / Сводные / Корпуса",
dict(start_date=start, end_date=end, use_ddu=True, use_dkp=True),
),
(
"lots_summary",
"Объединенные данные / Сводные / Лоты",
dict(start_date=start, end_date=end, use_ddu=True, use_dkp=True),
),
(
"corpuses_per_flat",
"Объединенные данные / Поквартирные / Корпуса",
dict(use_ddu=True, use_dkp=True),
),
(
"lots_per_flat",
"Объединенные данные / Поквартирные / Лоты",
dict(use_ddu=True, use_dkp=True),
),
]
method_map = {
"corpuses_summary": client.report_corpuses_summary,
"lots_summary": client.report_lots_summary,
"corpuses_per_flat": client.report_corpuses_per_flat,
"lots_per_flat": client.report_lots_per_flat,
}
print(f"=== Objective smoke ({start}{end}) → {out_dir}")
for slug, label, params in reports:
print(f"\n--- {label}")
try:
data = method_map[slug](**params)
except Exception as e:
print(f"{type(e).__name__}: {e}")
continue
path = out_dir / f"{slug}.json"
text = json.dumps(data, ensure_ascii=False, indent=2)
path.write_text(text, encoding="utf-8")
size_kb = len(text) / 1024.0
# Краткий schema-probe
if isinstance(data, list):
shape = f"list len={len(data)}"
sample_keys = list(data[0].keys()) if data and isinstance(data[0], dict) else []
elif isinstance(data, dict):
shape = f"dict keys={list(data.keys())}"
sample_keys = []
else:
shape = f"{type(data).__name__}"
sample_keys = []
print(f" ✓ saved {path.name} ({size_kb:.1f} KB)")
print(f" shape: {shape}")
if sample_keys:
print(f" first row keys: {sample_keys[:25]}")
client.close()
print(f"\nDONE. Папка: {out_dir.absolute()}")
return 0
if __name__ == "__main__":
sys.exit(main())