R15: Fix D04/D06/D07 partial completion

D04: Add fish name-based fallback in _auto_publish (was external_id only)
D06: Cap confidence at 50% for 1 player, 65% for 2 players
D07: Set caught_at=None for community imports (not published_at)
D08: Already OK - activity_rows has no top-100 limit

- Add Fish import to community_importer.py
- Add 2 unit tests for D06 confidence caps
- Update test_community_importer.py for D04 name match behavior
This commit is contained in:
ik
2026-09-10 05:51:50 +07:00
parent e2bed0db89
commit f550639456
5 changed files with 79 additions and 10 deletions
+5
View File
@@ -56,6 +56,11 @@ def activity_rows(
activity = round(55 * min(1, weighted / 12) + 25 * min(1, len(players) / 6) + 20 * min(1, trophies / 3))
average_confidence = sum(r.source_confidence for r in items) / len(items)
confidence = round(45 * min(1, len(items) / 10) + 35 * min(1, len(players) / 5) + 20 * average_confidence / 100)
# Cap confidence: 1 player → max 50%, 2 players → max 65%
if len(players) == 1:
confidence = min(confidence, 50)
elif len(players) == 2:
confidence = min(confidence, 65)
latest = max(_aware(r.reported_at) for r in items)
baits = Counter(r.bait.name for r in items if r.bait)
freshness_text = _freshness_text(now - latest)
+18 -8
View File
@@ -9,7 +9,7 @@ from sqlalchemy import select
from sqlalchemy.orm import Session
from .community_review import publish_observation
from .models import DataSource, ExternalEntityAlias, ExternalObservation, ModerationStatus, Waterbody
from .models import DataSource, ExternalEntityAlias, ExternalObservation, Fish, ModerationStatus, Waterbody
SOURCE_DEFAULTS = {
@@ -112,12 +112,22 @@ def _auto_publish(session: Session, observation: ExternalObservation) -> bool:
or observation.weight_g is None
):
return False
fish_alias = session.scalar(select(ExternalEntityAlias).where(
ExternalEntityAlias.source_system == observation.source_system,
ExternalEntityAlias.entity_type == "fish",
ExternalEntityAlias.external_id == observation.fish_external_id,
))
if fish_alias is None or fish_alias.fish is None:
# Fish: prefer external alias, fall back to exact name match
fish = None
if observation.fish_external_id is not None:
fish_alias = session.scalar(select(ExternalEntityAlias).where(
ExternalEntityAlias.source_system == observation.source_system,
ExternalEntityAlias.entity_type == "fish",
ExternalEntityAlias.external_id == observation.fish_external_id,
))
if fish_alias and fish_alias.fish:
fish = fish_alias.fish
if fish is None:
# Fallback: exact name match
fish = session.scalar(
select(Fish).where(Fish.name_ru == observation.fish_name)
)
if fish is None:
return False
# Waterbody: prefer external alias, fall back to exact name match
waterbody = None
@@ -136,7 +146,7 @@ def _auto_publish(session: Session, observation: ExternalObservation) -> bool:
)
if waterbody is None:
return False
observation.fish = fish_alias.fish
observation.fish = fish
observation.waterbody = waterbody
observation.status = "ready"
observation.review_note = "Automatically matched by previously reviewed source aliases"
+1 -1
View File
@@ -80,7 +80,7 @@ def publish_observation(session: Session, observation: ExternalObservation) -> C
rig_type=observation.payload.get("rig_type"),
retrieve_method=observation.payload.get("retrieve_method"),
retrieve_speed=observation.payload.get("retrieve_speed"),
caught_at=observation.published_at,
caught_at=None,
reported_at=observation.published_at or observation.first_seen_at,
player_name=observation.payload.get("player_name"),
source_type=SourceType.manual_import,