test: bound tackle freshness score
This commit is contained in:
@@ -118,7 +118,7 @@ class TackleCombinationOut(BaseModel):
|
||||
catches: int
|
||||
unique_players: int
|
||||
last_seen_at: datetime
|
||||
freshness_score: int
|
||||
freshness_score: int = Field(ge=0, le=100)
|
||||
status: str
|
||||
explanation: str
|
||||
|
||||
|
||||
@@ -2290,6 +2290,8 @@
|
||||
"type": "string"
|
||||
},
|
||||
"freshness_score": {
|
||||
"maximum": 100.0,
|
||||
"minimum": 0.0,
|
||||
"title": "Freshness Score",
|
||||
"type": "integer"
|
||||
},
|
||||
|
||||
@@ -6,6 +6,7 @@ from sqlalchemy.orm import Session
|
||||
from app.database import Base
|
||||
from app.models import CatchReport, CatchTackleComponent, Fish, ModerationStatus, SourceType, Spot, Waterbody
|
||||
from app.routers.analytics import tackle_combinations
|
||||
from app.schemas import TackleCombinationOut
|
||||
|
||||
|
||||
def test_tackle_recommendation_requires_samples_and_independent_players() -> None:
|
||||
@@ -99,3 +100,18 @@ def test_tackle_analytics_exposes_decay_and_prefers_fresher_equal_samples() -> N
|
||||
assert rows[0].freshness_score <= 100
|
||||
|
||||
engine.dispose()
|
||||
|
||||
|
||||
def test_tackle_freshness_score_contract_rejects_out_of_range_values() -> None:
|
||||
base = {
|
||||
"role": "lure", "value": "Spinner", "catches": 3, "unique_players": 2,
|
||||
"last_seen_at": datetime.now(timezone.utc), "status": "recommendation", "explanation": "ok",
|
||||
}
|
||||
assert TackleCombinationOut(**base, freshness_score=0).freshness_score == 0
|
||||
assert TackleCombinationOut(**base, freshness_score=100).freshness_score == 100
|
||||
for value in (-1, 101):
|
||||
try:
|
||||
TackleCombinationOut(**base, freshness_score=value)
|
||||
except ValueError:
|
||||
continue
|
||||
raise AssertionError(f"freshness score {value} was accepted")
|
||||
|
||||
+1
-1
@@ -72,7 +72,7 @@
|
||||
- [ ] **G04 · Crosswalk и нормализация.** Добавлен offline `gear_crosswalk`: нормализация регистра/пробелов/`е/ё`, точное имя или alias плюс совместимая категория, консервативная проверка brand/family. Неоднозначные, несовместимые, брендовые и unmatched-строки получают review-статус без canonical key; исходное значение сохраняется. Остаётся подать реальные RF4DB/RF4MAP/RF4 Posts identities и вручную подтвердить результаты.
|
||||
- [ ] **G05 · Связи с уловами и источниками.** Добавлены `catch_tackle_component` и offline `gear_components`: можно сохранять несколько unresolved/canonical компонентов с ролью, порядком, исходным значением, source identity и `raw_payload`; legacy `bait_id` не меняется. Parser сохраняет порядок оборудования из detail и разделяет bait/rig в catch-полях. Запись компонентов подключена к community import, официальному импорту и пользовательской форме; повторная обработка идемпотентна. Canonical-привязка и безопасный backfill остаются только после подтверждённого crosswalk.
|
||||
- [ ] **G06 · API и публичный каталог.** Добавлены пагинированный `/api/v1/tackle/items` с фильтрами по категории, бренду, семейству и уровню, detail endpoints для предмета и монтажа, а также ordered `tackle_components` в ответе уловов точки. Ответы показывают только канонические характеристики, provenance, timestamp проверки и `missing_fields`; рейтинг эффективности не добавляется. Публичный каталог теперь имеет detail-route `/tackle/items/:id` с 404/unavailable-различением, паспортом данных и явным списком недостающих полей; CatchList показывает ordered-компоненты, ведёт в canonical detail только при наличии `tackle_item_id`, а unresolved raw value оставляет текстом.
|
||||
- [ ] **G07 · Аналитика сочетаний и рекомендации.** Добавлен `/api/v1/analytics/tackle`: approved-наблюдения группируются по роли и исходному компоненту с фильтрами водоёма, рыбы, метода и окна; дубликаты одного улова не увеличивают счётчик, а минимум наблюдений и независимых игроков отделяет факт использования от рекомендации. Публичный `/tackle/analytics` показывает фильтры, выборку, независимых игроков и раздельные `recommendation`/`insufficient_data` states без рейтинга эффективности. Добавлен объяснимый `freshness_score` с half-life `12.5` часа: он не меняет raw-счётчики и пороги, но стабильно ставит более свежую равную выборку выше старой и виден на карточке. Остаётся дальнейшая визуальная приёмка.
|
||||
- [ ] **G07 · Аналитика сочетаний и рекомендации.** Добавлен `/api/v1/analytics/tackle`: approved-наблюдения группируются по роли и исходному компоненту с фильтрами водоёма, рыбы, метода и окна; дубликаты одного улова не увеличивают счётчик, а минимум наблюдений и независимых игроков отделяет факт использования от рекомендации. Публичный `/tackle/analytics` показывает фильтры, выборку, независимых игроков и раздельные `recommendation`/`insufficient_data` states без рейтинга эффективности. Добавлен объяснимый `freshness_score` с half-life `12.5` часа и контрактом `0..100`: он не меняет raw-счётчики и пороги, но стабильно ставит более свежую равную выборку выше старой и виден на карточке. Остаётся дальнейшая визуальная приёмка.
|
||||
- [ ] **G08 · Медиа и качество.** Добавлены отдельные reviewed-роли `tackle_card`, `tackle_detail`, `rig_diagram`, `tackle_screenshot` для `tackle`, а также CLI-параметр `--media-role`; offline audit отклоняет неизвестную роль и несовпадение роли с entity type. Существующие dimensions, MIME, SHA-256, прозрачность, aspect ratio, provenance и атомарное продвижение сохраняются. Остаётся провести реальный contact-sheet review для будущих tackle-кандидатов без автоматической публикации.
|
||||
- [ ] **G09 · Приёмка и эксплуатация.** Добавлены fixture/regression tests для crosswalk, media roles, идемпотентных компонентов, пустых результатов и многокомпонентных наблюдений; каталог `/tackle` включён в visual-matrix, narrow smoke и accessibility routes. Offline catalog/media audits и сохранение старых данных при сбое импорта проходят. TEMP-only query-plan gate для фильтра каталога и группировки сочетаний использует индексы и укладывается в 250 мс. Chromium подтвердил empty-state публичного каталога и отсутствие overflow на 320 px; обычный E2E пропускает bootstrap без явных переменных, отдельный bootstrap с токеном проходит. Остаются HTTP/browser acceptance для неоднозначных и многокомпонентных комплектов, ручной visual review и проверяемый счётчик по категориям либо явный `unknown`. Сетевые тесты не выполнять; импорт оставить opt-in, последовательным и под общим cooldown/backoff.
|
||||
|
||||
|
||||
Reference in New Issue
Block a user