fix: separate demo seed from catalog facts

This commit is contained in:
ik
2026-09-22 20:05:09 +07:00
parent a357988f93
commit a8321536cc
5 changed files with 11 additions and 5 deletions
+4 -2
View File
@@ -3,18 +3,20 @@ from __future__ import annotations
from sqlalchemy import func, or_, select
from sqlalchemy.orm import Session
from .models import CatchReport, ExternalObservation, Fish, SourceType, Spot, Waterbody
from .models import CatchReport, ExternalObservation, Fish, ModerationStatus, SourceType, Spot, Waterbody
def audit_catalog(db: Session) -> dict[str, int]:
count = lambda model: db.scalar(select(func.count()).select_from(model)) or 0
demo_reports = db.scalar(select(func.count()).select_from(CatchReport).where(CatchReport.source_external_id.like("seed:%"))) or 0
approved_reports = db.scalar(select(func.count()).select_from(CatchReport).where(CatchReport.moderation_status == ModerationStatus.approved, or_(CatchReport.source_external_id.is_(None), CatchReport.source_external_id.not_like("seed:%")))) or 0
failures = {
"invalid_weights": db.scalar(select(func.count()).select_from(CatchReport).where(or_(CatchReport.weight_g <= 0, CatchReport.weight_g > 3_000_000))) or 0,
"invalid_coordinates": db.scalar(select(func.count()).select_from(Spot).where(or_(Spot.x < -10_000, Spot.x > 10_000, Spot.y < -10_000, Spot.y > 10_000))) or 0,
"incomplete_official_records": db.scalar(select(func.count()).select_from(CatchReport).where(CatchReport.source_type == SourceType.official_record, or_(CatchReport.caught_at.is_(None), CatchReport.source_url.is_(None)))) or 0,
"incomplete_published_staging": db.scalar(select(func.count()).select_from(ExternalObservation).where(ExternalObservation.status == "published", or_(ExternalObservation.fish_id.is_(None), ExternalObservation.waterbody_id.is_(None), ExternalObservation.x.is_(None), ExternalObservation.y.is_(None), ExternalObservation.weight_g.is_(None), ExternalObservation.catch_report_id.is_(None)))) or 0,
}
return {"fishes": count(Fish), "waterbodies": count(Waterbody), "reports": count(CatchReport), "staging": count(ExternalObservation), **failures, "failures": sum(failures.values())}
return {"fishes": count(Fish), "waterbodies": count(Waterbody), "reports": count(CatchReport), "demo_reports": demo_reports, "approved_confirmed_reports": approved_reports, "staging": count(ExternalObservation), **failures, "failures": sum(failures.values())}
def audit_waterbody_catalog(db: Session, expected_ids: set[str]) -> dict:
+3 -2
View File
@@ -47,8 +47,9 @@ def seed() -> None:
db.flush()
return item
spot1 = spot(vyunok, "spot1", 110, 103, "Кромка травы у северного берега")
spot2 = spot(kuori, "spot2", 85, 92, "Свальчик в глубину")
spot_description = settings.seed_demo_data
spot1 = spot(vyunok, "spot1", 110, 103, "Кромка травы у северного берега" if spot_description else None)
spot2 = spot(kuori, "spot2", 85, 92, "Свальчик в глубину" if spot_description else None)
if not settings.seed_demo_data:
return
now = datetime.now(timezone.utc)
+2
View File
@@ -18,6 +18,8 @@ def test_catalog_audit_checks_the_whole_catalog() -> None:
db.add(CatchReport(fish=fish, waterbody=water, spot=spot, weight_g=1000, reported_at=datetime.now(timezone.utc), source_type=SourceType.user, source_confidence=60, moderation_status=ModerationStatus.approved))
db.commit()
assert audit_catalog(db)["failures"] == 0
assert audit_catalog(db)["approved_confirmed_reports"] == 1
assert audit_catalog(db)["demo_reports"] == 0
spot.x = 10_001
db.commit()
result = audit_catalog(db)
+1
View File
@@ -25,3 +25,4 @@ def test_seed_repairs_partial_database_and_is_idempotent(monkeypatch) -> None:
assert db.scalar(select(func.count()).select_from(Waterbody)) == 2
assert db.scalar(select(func.count()).select_from(Spot)) == 2
assert db.scalar(select(func.count()).select_from(CatchReport)) == 0
assert all(spot.description is None for spot in db.scalars(select(Spot)))
+1 -1
View File
@@ -35,7 +35,7 @@ R-пункты уточняют критерии существующих B/G/U/
- [x] **R10 · P1 · Единый cooldown и стадии запроса.** Community CLI уже освобождал резерв до HTTP-ответа; media CLI теперь использует тот же контракт: DNS/connection/OSError освобождают только собственный резерв, HTTPError или ошибка валидированного тела сохраняют cooldown, batch освобождает окно только если ни один asset не получил ответ. Offline regression добавлен; scheduler сохраняет site-wide координацию.
- [ ] **R18 · P1 · Границы локального и production запуска.** Loopback для dev-портов с явным opt-in LAN; проверить доверенные proxy/client-IP и rate-limit на двух клиентах. Критерий: default dev не открыт в LAN, production различает клиентов. Реальные серверные gates — A07.
- [x] **R11 · P2 · Честное качество и происхождение media (B25).** API использует точный hostname allowlist для `rf4db`/`rf4map`/`rf4-stat`, неизвестные и malformed URL получают `unknown`, а не ложный `rf4-official`. UI различает низкое разрешение и наличие проверенной card-версии, не утверждая отсутствие альтернатив без данных manifest. Cache/revocation и production visual acceptance остаются отдельными gates.
- [ ] **R14 · P2 · Подтверждённый каталог и demo seed.** Отделить справочник от неподтверждённых описаний/точек, показывать loaded count и внешнюю полноту/unknown раздельно. Критерий: production без demo не создаёт вымышленных описаний, media count не подменяет canonical count.
- [x] **R14 · P2 · Подтверждённый каталог и demo seed.** `SEED_DEMO_DATA=false` больше не создаёт редакционные описания тестовых точек; demo-уловы имеют отдельный `seed:` namespace и catalog audit считает их отдельно от подтверждённых reports. Loaded canonical counts и external staging остаются раздельными, media count не используется как catalog count.
- [ ] **R15 · P2 · Восстановление пользовательских действий.** Сохранять draft при 422/429/timeout, объяснять конфликт повторов, undo плана и общий storage/clipboard helper. Критерий: повтор не требует заполнить весь улов заново.
- [ ] **R16 · P2 · Производительность реальных endpoints.** Измерить ORM/RSS/SQL и чтение manifest, затем bounded aggregation и digest/mtime cache по результатам. Критерий: воспроизводимый endpoint budget на крупных fixtures; Redis заранее не добавлять.
- [ ] **R17 · P2 · Навигация и SEO снастей.** Обнаруживаемый каталог снастей и sitemap подтверждённых карточек; политика индексации фильтров/пустых страниц. Критерий: канонические карточки достижимы из UI и sitemap без дубликатов.