"""Smoke-тест Objective API: GetToken + 4 канонических отчёта. Запуск: cd backend OBJECTIVE_API_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// 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= 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) # Всегда пишем в /data/raw/objective_smoke/ независимо от cwd repo_root = Path(__file__).resolve().parent.parent.parent out_dir = repo_root / "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())