From 13e04e6c66a6f0776d75dc98e8ac8de492a33f83 Mon Sep 17 00:00:00 2001 From: IK Date: Fri, 11 Sep 2026 07:36:21 +0700 Subject: [PATCH] A12: Add meaningful changes/provenance to ImportRecordEvent, skip events for unchanged data --- apps/api/app/importer.py | 28 +++++++++++++++++++++++----- apps/api/app/models.py | 4 +++- apps/api/tests/test_importer.py | 3 ++- 3 files changed, 28 insertions(+), 7 deletions(-) diff --git a/apps/api/app/importer.py b/apps/api/app/importer.py index 426b8b6..3ceac74 100644 --- a/apps/api/app/importer.py +++ b/apps/api/app/importer.py @@ -189,13 +189,31 @@ def _import_records_locked(session: Session, *, url: str, region: str, category: if report is None: report = CatchReport(fish=fish, waterbody=waterbody, bait=bait, spot=None, weight_g=raw.weight_g, caught_at=caught, reported_at=now, player_name=raw.player, source_type=SourceType.official_record, source_url=url, source_external_id=key, source_confidence=100, moderation_status=ModerationStatus.approved, raw_payload=payload) session.add(report) - session.add(ImportRecordEvent(catch_report=report, import_run=run, event_type="created", created_at=now)) + session.add(ImportRecordEvent( + catch_report=report, import_run=run, event_type="created", created_at=now, + changes={"weight_g": raw.weight_g, "player": raw.player, "record_date": raw.record_date.isoformat()}, + provenance={"source_system": "rf4-official", "source_url": url, "source_external_id": key}, + )) run.rows_created += 1 else: - report.raw_payload = payload - report.source_url = url - session.add(ImportRecordEvent(catch_report=report, import_run=run, event_type="updated", created_at=now)) - run.rows_updated += 1 + # A12: Only create event if actual values changed + old_payload = (report.raw_payload or {}) + new_payload = asdict(raw) | {"record_date": raw.record_date.isoformat()} + changed_fields = {} + for field in ("weight_g", "player", "waterbody", "bait", "record_date"): + old_val = old_payload.get(field) + new_val = new_payload.get(field) + if old_val != new_val: + changed_fields[field] = {"old": old_val, "new": new_val} + if changed_fields: + report.raw_payload = payload + report.source_url = url + session.add(ImportRecordEvent( + catch_report=report, import_run=run, event_type="updated", created_at=now, + changes=changed_fields, + provenance={"source_system": "rf4-official", "source_url": url, "source_external_id": key}, + )) + run.rows_updated += 1 run.status = ImportStatus.success run.finished_at = datetime.now(timezone.utc) session.commit() diff --git a/apps/api/app/models.py b/apps/api/app/models.py index a504494..ac1ff38 100644 --- a/apps/api/app/models.py +++ b/apps/api/app/models.py @@ -216,12 +216,14 @@ class ExternalEntityAlias(Base): class ImportRecordEvent(Base): - """Track per-record import events for D09 revision history.""" + """Track per-record import events for D09 revision history with provenance.""" __tablename__ = "import_record_event" id: Mapped[uuid.UUID] = mapped_column(primary_key=True, default=uuid.uuid4) catch_report_id: Mapped[uuid.UUID] = mapped_column(ForeignKey("catch_report.id"), index=True) import_run_id: Mapped[uuid.UUID] = mapped_column(ForeignKey("official_record_import.id"), index=True) event_type: Mapped[str] = mapped_column(String(20)) # created/updated/deleted + changes: Mapped[dict | None] = mapped_column(JSON, default=None) # what fields changed + provenance: Mapped[dict | None] = mapped_column(JSON, default=None) # source system, external_id created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True)) catch_report: Mapped[CatchReport] = relationship() import_run: Mapped[OfficialRecordImport] = relationship() diff --git a/apps/api/tests/test_importer.py b/apps/api/tests/test_importer.py index 27b8eff..b555e4c 100644 --- a/apps/api/tests/test_importer.py +++ b/apps/api/tests/test_importer.py @@ -47,7 +47,8 @@ def test_parser_and_import_are_idempotent() -> None: first = import_records(db, url="fixture://records", region="RU", category="records", html=html) second = import_records(db, url="fixture://records", region="RU", category="records", html=html) assert (first.rows_created, first.rows_updated) == (2, 0) - assert (second.rows_created, second.rows_updated) == (0, 2) + # A12: Second import of identical data creates no events (no fields changed) + assert (second.rows_created, second.rows_updated) == (0, 0) assert db.scalar(select(func.count()).select_from(CatchReport).where(CatchReport.source_type == SourceType.official_record)) == 2 assert db.scalar(select(func.count()).select_from(OfficialRecordImport)) == 2