123 lines
4.5 KiB
Python
123 lines
4.5 KiB
Python
"""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)
|
||
# Всегда пишем в <repo>/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())
|