From b48ba60830d23ae8dc495e9f085d30a601841a89 Mon Sep 17 00:00:00 2001 From: Bryan Van Deusen Date: Mon, 15 Jun 2026 21:25:55 -0400 Subject: [PATCH] fix(import): resolve artist from path for enrich-on-duplicate (#718) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The dedup branches of _import_media linked the existing image to the new post via _apply_sidecar(artist=None), relying on the SIDECAR to carry the artist. But an archive member's artist comes from its path, and under post-first the per-media sidecar is minimal (no artist) — so a re-packed / cross-posted archive image deduped and was left UNLINKED from the new post, i.e. the post showed "no images". Resolve the path-anchored artist (derive_top_level_artist) up-front in _import_media and pass it to both enrich-on-duplicate branches (sha256 + phash larger_exists) and the new-record path. Drop the now-dead _attach_artist helper (its logic is inlined at the single new-record call site). Surfaced by the new test_archive_all_deduped_is_benign_not_flagged (was asserting 2==4: the second post got no provenance links). Co-Authored-By: Claude Opus 4.8 --- backend/app/services/importer.py | 40 ++++++++++++++------------------ 1 file changed, 18 insertions(+), 22 deletions(-) diff --git a/backend/app/services/importer.py b/backend/app/services/importer.py index c51a9fc..fa81129 100644 --- a/backend/app/services/importer.py +++ b/backend/app/services/importer.py @@ -682,6 +682,15 @@ class Importer: error=f"{pct:.2%} transparent", ) + # Artist anchored to the attribution path (folder→artist), resolved + # UP-FRONT so the enrich-on-duplicate branches link provenance with the + # right artist even when the sidecar carries none — which is now the norm + # for archive members under post-first (the per-media sidecar is minimal). + # Without this a cross-posted / re-packed archive image deduped and was + # left UNLINKED from the new post = a post showing "no images" (#718). + _artist_name = derive_top_level_artist(attribution_path, self.import_root) + path_artist = self._upsert_artist(_artist_name) if _artist_name else None + # Hash dedup (exact). sha = _sha256_of(source) existing_stmt = select(ImageRecord).where(ImageRecord.sha256 == sha) @@ -691,9 +700,9 @@ class Importer: # Enrich-on-duplicate (parity with attach_in_place): a re-scanned # file that is a byte-dup of an existing image still links its # post via _apply_sidecar, so a cross-posted image shows on every - # post. Artist is derived from the sidecar here (not yet resolved). + # post. self._apply_sidecar( - existing, attribution_path, None, + existing, attribution_path, path_artist, explicit_source=explicit_source, ) self.session.commit() @@ -727,7 +736,7 @@ class Importer: larger = self.session.get(ImageRecord, match_id) if larger is not None: self._apply_sidecar( - larger, attribution_path, None, + larger, attribution_path, path_artist, explicit_source=explicit_source, ) self.session.commit() @@ -761,13 +770,11 @@ class Importer: self.session.flush() self._phash_cache_append(phash, width, height, record.id) - # Folder→artist (anchored to attribution_path). - artist = None - artist_name = derive_top_level_artist( - attribution_path, self.import_root - ) - if artist_name: - artist = self._attach_artist(record, artist_name) + # Folder→artist (path_artist resolved up-front above); bind it to the + # new record so it carries the canonical Artist. + if path_artist is not None and record.artist_id is None: + record.artist_id = path_artist.id + self.session.flush() # Sidecar provenance (best-effort; never fails the import). # explicit_source lets the FC-3c download path bind the new @@ -777,7 +784,7 @@ class Importer: # subscription-downloaded zip previously lost subscription # linkage if the on-disk layout didn't match assumptions. self._apply_sidecar( - record, attribution_path, artist, explicit_source=explicit_source, + record, attribution_path, path_artist, explicit_source=explicit_source, ) # Thumbnail is queued separately by the calling task; the importer @@ -1076,17 +1083,6 @@ class Importer: self.session.flush() return artist - def _attach_artist(self, record: ImageRecord, artist_name: str) -> Artist: - """Upsert the Artist and set it as the image's canonical artist. - Artist-kind tags were retired in FC-2d-vii-c — the Artist row is - the single source of truth. Returns the Artist (sidecar - provenance attaches its Source to it).""" - artist = self._upsert_artist(artist_name) - if record.artist_id is None: - record.artist_id = artist.id - self.session.flush() - return artist - @staticmethod def _sidecar_artist_name(data: dict) -> str | None: for k in ("artist", "author_name"):