feat: explain activity data quality
This commit is contained in:
@@ -68,7 +68,7 @@ def activity_rows(
|
||||
average_weight_g=round(sum(r.weight_g for r in items) / len(items)),
|
||||
max_weight_g=max(r.weight_g for r in items), last_confirmed_at=latest,
|
||||
activity_score=activity, confidence_score=confidence,
|
||||
explanation=f"{len(items)} свежих уловов от {len(players)} игроков. Последнее подтверждение {freshness_text}." + (" Данных мало." if len(items) < 3 else ""),
|
||||
explanation=_explanation(len(items), len(players), freshness_text, activity, confidence),
|
||||
))
|
||||
return sorted(result, key=lambda row: (row.activity_score, row.last_confirmed_at), reverse=True)
|
||||
|
||||
@@ -82,3 +82,27 @@ def _freshness_text(delta: timedelta) -> str:
|
||||
if minutes < 60:
|
||||
return f"{minutes} мин. назад"
|
||||
return f"{minutes // 60} ч. назад"
|
||||
|
||||
|
||||
def _explanation(catches: int, players: int, freshness: str, activity: int, confidence: int) -> str:
|
||||
activity_label = "Высокая" if activity >= 60 else "Средняя" if activity >= 40 else "Низкая"
|
||||
confidence_label = "высокая" if confidence >= 70 else "средняя" if confidence >= 40 else "низкая"
|
||||
summary = (
|
||||
f"{activity_label} активность: {_count(catches, 'свежий улов', 'свежих улова', 'свежих уловов')} "
|
||||
f"от {_count(players, 'игрока', 'игроков', 'игроков')}. "
|
||||
f"Последнее подтверждение {freshness}. Уверенность {confidence_label}."
|
||||
)
|
||||
return summary + (" Данных мало: нужно хотя бы 3 наблюдения." if catches < 3 else "")
|
||||
|
||||
|
||||
def _count(value: int, one: str, few: str, many: str) -> str:
|
||||
remainder = value % 100
|
||||
if 11 <= remainder <= 14:
|
||||
word = many
|
||||
elif value % 10 == 1:
|
||||
word = one
|
||||
elif 2 <= value % 10 <= 4:
|
||||
word = few
|
||||
else:
|
||||
word = many
|
||||
return f"{value} {word}"
|
||||
|
||||
@@ -78,7 +78,7 @@ def _spot_or_404(db: Session, spot_id: UUID) -> Spot:
|
||||
@app.get("/api/v1/spots/{spot_id}", response_model=SpotOut)
|
||||
def spot_detail(spot_id: UUID, db: Db) -> SpotOut:
|
||||
spot = _spot_or_404(db, spot_id)
|
||||
reports = list(db.scalars(select(CatchReport).options(joinedload(CatchReport.bait)).where(CatchReport.spot_id == spot.id, CatchReport.moderation_status == ModerationStatus.approved)))
|
||||
reports = list(db.scalars(select(CatchReport).options(joinedload(CatchReport.bait)).where(CatchReport.spot_id == spot.id, CatchReport.moderation_status == ModerationStatus.approved, CatchReport.deleted_at.is_(None))))
|
||||
now = datetime.now(timezone.utc)
|
||||
def count_since(delta: timedelta) -> int:
|
||||
return sum(_aware(r.reported_at) >= now - delta for r in reports)
|
||||
@@ -89,7 +89,7 @@ def spot_detail(spot_id: UUID, db: Db) -> SpotOut:
|
||||
@app.get("/api/v1/spots/{spot_id}/catches", response_model=list[CatchOut])
|
||||
def spot_catches(spot_id: UUID, db: Db, limit: int = Query(50, ge=1, le=100), offset: int = Query(0, ge=0)) -> list[CatchOut]:
|
||||
_spot_or_404(db, spot_id)
|
||||
reports = list(db.scalars(select(CatchReport).options(joinedload(CatchReport.fish), joinedload(CatchReport.bait)).where(CatchReport.spot_id == spot_id, CatchReport.moderation_status == ModerationStatus.approved).order_by(CatchReport.reported_at.desc()).offset(offset).limit(limit)))
|
||||
reports = list(db.scalars(select(CatchReport).options(joinedload(CatchReport.fish), joinedload(CatchReport.bait)).where(CatchReport.spot_id == spot_id, CatchReport.moderation_status == ModerationStatus.approved, CatchReport.deleted_at.is_(None)).order_by(CatchReport.reported_at.desc()).offset(offset).limit(limit)))
|
||||
return [CatchOut(id=r.id, fish=r.fish.name_ru, weight_g=r.weight_g, bait=r.bait.name if r.bait else None, player_name=r.player_name, caught_at=r.caught_at, reported_at=r.reported_at, retrieve_method=r.retrieve_method, retrieve_speed=r.retrieve_speed) for r in reports]
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user