feat(provenance): importer sets image_record.artist_id, stops minting artist tags

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-05-18 21:33:55 -04:00
parent 63e7185811
commit 914cf14d9e
2 changed files with 65 additions and 29 deletions
+11 -29
View File
@@ -27,10 +27,7 @@ from ..models import (
ImportSettings,
Post,
Source,
Tag,
TagKind,
)
from ..models.tag import image_tag
from ..utils.paths import derive_subdir, derive_top_level_artist, hash_suffixed_name
from ..utils.phash import compute_phash, find_similar
from ..utils.sidecar import find_sidecar, parse_sidecar
@@ -244,7 +241,7 @@ class Importer:
artist = None
artist_name = derive_top_level_artist(source, self.import_root)
if artist_name:
artist = self._attach_artist_tag(record.id, artist_name)
artist = self._attach_artist(record, artist_name)
# Sidecar provenance (best-effort; never fails the import).
self._apply_sidecar(record, source, artist)
@@ -266,33 +263,15 @@ class Importer:
self.session.flush()
return artist
def _attach_artist_tag(self, image_id: int, artist_name: str) -> Artist:
"""Idempotently creates Artist + artist tag, attaches to image.
Returns the Artist (sidecar provenance attaches its Source to it)."""
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)
tag = self.session.execute(
select(Tag).where(Tag.name == artist_name).where(Tag.kind == TagKind.artist)
).scalar_one_or_none()
if tag is None:
tag = Tag(name=artist_name, kind=TagKind.artist)
self.session.add(tag)
if record.artist_id is None:
record.artist_id = artist.id
self.session.flush()
# Idempotent association; using a raw insert + on-conflict-do-nothing
# is cleaner here than fetching first, but for a sync session we just
# check existence.
already = self.session.execute(
select(image_tag.c.tag_id)
.where(image_tag.c.image_record_id == image_id)
.where(image_tag.c.tag_id == tag.id)
).scalar_one_or_none()
if already is None:
self.session.execute(
image_tag.insert().values(
image_record_id=image_id, tag_id=tag.id, source="auto",
)
)
return artist
@staticmethod
@@ -339,6 +318,9 @@ class Importer:
)
return
if record.artist_id is None:
record.artist_id = artist.id
platform = sd.platform or "unknown"
url = sd.post_url or f"sidecar:{platform}"
src = self.session.execute(