63 lines
2.8 KiB
Python
63 lines
2.8 KiB
Python
"""Duplicate clusters, their members, and not-duplicate negative links.
|
|
|
|
A cluster is a reviewable grouping produced by a detection ``method`` (exact,
|
|
pixel, or perceptual) at a ``confidence`` band. Exact/pixel clusters can be
|
|
decided automatically; perceptual clusters stay open for human review. Every
|
|
member carries the evidence behind its inclusion. Negative links record
|
|
``not_duplicate`` decisions so rescans do not re-suggest a rejected pair.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
from datetime import datetime
|
|
|
|
from sqlalchemy import DateTime, ForeignKey, Integer, String, func
|
|
from sqlalchemy.orm import Mapped, mapped_column
|
|
|
|
from photo_pipeline.db import Base
|
|
|
|
|
|
class DuplicateCluster(Base):
|
|
__tablename__ = "duplicate_clusters"
|
|
|
|
id: Mapped[str] = mapped_column(String, primary_key=True)
|
|
method: Mapped[str] = mapped_column(String, nullable=False) # exact|pixel|perceptual
|
|
confidence: Mapped[str] = mapped_column(String, nullable=False) # band name
|
|
state: Mapped[str] = mapped_column(String, nullable=False) # open|decided|dismissed|reopened
|
|
decision: Mapped[str | None] = mapped_column(String) # canonical|not_duplicate|deferred
|
|
canonical_asset_id: Mapped[str | None] = mapped_column(ForeignKey("assets.id"))
|
|
version: Mapped[int] = mapped_column(Integer, nullable=False, default=1)
|
|
created_at: Mapped[datetime] = mapped_column(
|
|
DateTime(timezone=True), nullable=False, server_default=func.now()
|
|
)
|
|
updated_at: Mapped[datetime] = mapped_column(
|
|
DateTime(timezone=True), nullable=False, server_default=func.now(), onupdate=func.now()
|
|
)
|
|
decided_at: Mapped[datetime | None] = mapped_column(DateTime(timezone=True))
|
|
|
|
|
|
class DuplicateMember(Base):
|
|
__tablename__ = "duplicate_members"
|
|
|
|
cluster_id: Mapped[str] = mapped_column(
|
|
ForeignKey("duplicate_clusters.id"), primary_key=True
|
|
)
|
|
asset_id: Mapped[str] = mapped_column(ForeignKey("assets.id"), primary_key=True)
|
|
role: Mapped[str] = mapped_column(String, nullable=False, default="member") # member|canonical|variant
|
|
distance: Mapped[int | None] = mapped_column(Integer) # phash distance to representative
|
|
evidence: Mapped[str | None] = mapped_column(String) # JSON blob explaining inclusion
|
|
added_at: Mapped[datetime] = mapped_column(
|
|
DateTime(timezone=True), nullable=False, server_default=func.now()
|
|
)
|
|
|
|
|
|
class DuplicateNegativeLink(Base):
|
|
__tablename__ = "duplicate_negative_links"
|
|
|
|
asset_a: Mapped[str] = mapped_column(ForeignKey("assets.id"), primary_key=True)
|
|
asset_b: Mapped[str] = mapped_column(ForeignKey("assets.id"), primary_key=True)
|
|
reason: Mapped[str | None] = mapped_column(String)
|
|
created_at: Mapped[datetime] = mapped_column(
|
|
DateTime(timezone=True), nullable=False, server_default=func.now()
|
|
)
|