comicbox.formats.base.online.signals

source module comicbox.formats.base.online.signals

Per-signal scoring functions for the matcher.

Each s_* returns a value in [0, 1]. Missing inputs are treated as “uncertain, partial credit” rather than “definitely wrong” so a candidate isn’t penalised for the comic being thin on metadata.

The cover-hash signal lives in cover_hash.py (M4); this module only covers metadata signals.

Functions

  • normalize_series Lowercase, strip volume suffixes, collapse non-alphanumeric runs.

  • s_series Series-name similarity via rapidfuzz WRatio, normalized.

  • s_issue Issue number match. Integer-equal first; fall back to string-equal.

  • s_year Year match with smooth decay beyond the original 1.0/0.7/0.4 anchors.

  • s_publisher Score publisher equality after normalization.

  • s_pages Page-count match: 1 if equal, 0.7 within 10%, 0.3 within 25%, else 0.

source normalize_series(name: str)str

Lowercase, strip volume suffixes, collapse non-alphanumeric runs.

source s_series(profile: ComicProfile, candidate: Candidate)float

Series-name similarity via rapidfuzz WRatio, normalized.

source s_issue(profile: ComicProfile, candidate: Candidate)float

Issue number match. Integer-equal first; fall back to string-equal.

source s_year(profile: ComicProfile, candidate: Candidate)float

Year match with smooth decay beyond the original 1.0/0.7/0.4 anchors.

  • 1.0 on exact match
  • 0.7 at ±1 year
  • 0.4 at ±2 years
  • Linear decay from 0.4 (diff=2) to 0.0 (diff=7) — Phase F
  • 0.0 for diff ≥ 7

Missing-data handling distinguishes two cases

  • both missing: 0.5 (weak agnostic prior — we have no signal)
  • asymmetric (one side has year, other doesn’t): 0.3 (the previous 0.6 over-credited unknown candidates and let wrong-volume picks coast through the auto-write band when CV’s BasicIssue lacked a cover_date)

Phase F (2026-05-14) replaced the original cliff (0.0 for any diff ≥ 3) with linear decay. The cliff treated modern reissues of older series as having no year signal at all — fine for the common case where year discriminates between volumes, but bad when the right candidate IS the same series at a different year (e.g. The Boys 2009 fixture’s expected vol was at year=2006, gap of 3 years). Smooth decay gives the older candidate ~75% of the one-year-off signal, enough to compete with cover-hash signal when present. Beyond 7 years, the score still hits zero — the long tail of 20+ year reissues (Conan 1973 vs 2025) shouldn’t benefit from year signal.

See tasks/online-tagging/calibration-notes/2026-05-14-bigmedia-247.md for the failure-mode analysis that motivated this change.

source s_publisher(profile: ComicProfile, candidate: Candidate)float

Score publisher equality after normalization.

source s_pages(profile: ComicProfile, candidate: Candidate)float

Page-count match: 1 if equal, 0.7 within 10%, 0.3 within 25%, else 0.