CI and images / lint (push) Failing after 3s
CI and images / extension-version (push) Successful in 3s
CI and images / frontend-build (push) Successful in 23s
CI and images / backend-lint-and-test (push) Successful in 32s
CI and images / integration (push) Successful in 2m19s
CI and images / sign-extension (push) Skipped
CI and images / build-web (push) Skipped
CI and images / smoke-web (push) Skipped
CI and images / promote (push) Skipped
CI and images / build-agent (push) Skipped
Counting files punished a piece for the one thing a working name is
guaranteed to do — have several exports. Measured across the operator's four
dual-platform artists: knuxy carries `p217` on four files spanning exactly two
posts, the Patreon post and the Discord drop, and ~200 comic-page tokens have
that shape. Every one scored half strength. Counting posts scores them 1.00
and still catches the habits, which span many posts rather than many files:
tamadaheijun's `comic2` spans 8, conto's `seth2` 5, `maid` 4. Knuxy's
ambiguous band drops from 268 tokens to 55.
Three false positives found the same way — by running the module over the real
library rather than reading it:
* The screenshot guard matches from the start of the stem, so it never fired
on the legacy `<post id>_media_<media id>_` era. `Screenshot 2025-07-27
182450ab` sailed through and contributed `2025-07-27` — the same-day date
collision this module's docstring exists to refuse.
* tamadaheijun names screenshots in Japanese. A guard that knows only the
English word is a guard for one artist.
* `timeline 3-0002` spans three unrelated posts. Keeping hyphens inside
tokens for `0-k`'s sake let the number survive whole, so an identity token
now has to contain a letter — which is the property behind the old
bare-year and all-digits rules anyway, and leaves `0-k`, `680lc` and `p59`
untouched.
Also stopwords for `the`, `gif`, `main`, `patreon`, `capture`, `timeline`,
each measured carrying a false match inside the admitted band.
IDENTITY_FLOOR names what a shared name must reach to propose a link with no
corroboration. At 0.75, 13 of artist 8's 15 name-sharing pairs clear it,
including the operator's own example. The two that do not are real pairs this
signal will not carry alone — the stated cost of refusing the four-post band,
where conto's `illustration9` and `maid` also sit.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LVjrnpQjRgHdvq95rASoiR
308 lines
12 KiB
Python
308 lines
12 KiB
Python
"""Milestone 388: the creator's own working name, recovered from a filename.
|
|
|
|
Two of the operator's artists post a censored or cropped teaser on Patreon and
|
|
the real release in their Discord. That pair is the one a whole-image
|
|
comparison handles WORST — a crop with a censor bar is exactly the local edit
|
|
that moves a perceptual hash and blurs a semantic embedding — and it is the
|
|
pair the creator names identically on both platforms.
|
|
|
|
Everything here was calibrated against the live library (artist 8, 520 images,
|
|
409 distinct tokens) rather than invented, so the cases carry their measured
|
|
numbers. The property the whole module serves is that a WRONG link is worse
|
|
than no link: no link leaves the operator where they already were, a wrong one
|
|
tells them two different pieces are the same piece.
|
|
"""
|
|
from collections import Counter
|
|
|
|
import pytest
|
|
|
|
from backend.app.services.post_naming import (
|
|
IDENTITY_FLOOR,
|
|
MAX_MARKER_POSTS,
|
|
MAX_TOKEN_POSTS,
|
|
marker_frequencies,
|
|
marker_overlap,
|
|
shared_identity,
|
|
text_markers,
|
|
token_frequencies,
|
|
working_name_tokens,
|
|
)
|
|
|
|
# --- what survives each platform's framing -----------------------------------
|
|
#
|
|
# Three naming eras reach this (#4002), and a token has to come out the same
|
|
# from all of them or the signal only works on whichever era it was written
|
|
# against.
|
|
|
|
|
|
@pytest.mark.parametrize(
|
|
"path, expected, era",
|
|
[
|
|
(
|
|
"/images/yellowroom/patreon/2026-09-23_170392790_Anya/01_((0-k.jpg",
|
|
"0-k",
|
|
"current Patreon: a per-post media index",
|
|
),
|
|
(
|
|
"20260923_1552424119805673604_01_0-k_base.jpg",
|
|
"0-k",
|
|
"Discord: gallery-dl's date_messageid_num_ prefix (#3999)",
|
|
),
|
|
(
|
|
"85317841_media_212565911_0071 NoHeart__c3118a69f3__c3118a69f3.jpg",
|
|
"noheart",
|
|
"legacy: flat at the artist root, post id + media id + doubled hash",
|
|
),
|
|
],
|
|
)
|
|
def test_one_working_name_survives_every_naming_era(path, expected, era):
|
|
assert expected in working_name_tokens(path), era
|
|
|
|
|
|
def test_a_hyphen_stays_inside_a_token():
|
|
"""`0-k` is a real working name on the live instance — it is the operator's
|
|
own example pair. Splitting on hyphen reduces it to two single characters
|
|
and then discards both for being too short, which silently loses the one
|
|
case this was built to catch."""
|
|
assert working_name_tokens("01_((0-k.jpg") == {"0-k"}
|
|
|
|
|
|
def test_a_work_in_progress_matches_the_piece_it_became():
|
|
"""Measured: `cnni6600 wip1` (Patreon) and `cnni6600_Base` (Discord) are
|
|
one piece. A decoration is a stage, not a name."""
|
|
assert working_name_tokens("01_cnni6600 wip1.png") == working_name_tokens(
|
|
"20250514_1372285581945471087_01_cnni6600_Base.jpg"
|
|
)
|
|
|
|
|
|
@pytest.mark.parametrize(
|
|
"path, why",
|
|
[
|
|
(
|
|
"109078417_media_334848471_Screenshot 2025-07-27 182450ab.png",
|
|
"the guard matches from the START of the stem, so while the legacy "
|
|
"prefix was left on, a legacy screenshot never looked like one — "
|
|
"and contributed `2025-07-27`, the exact same-day date collision "
|
|
"this module refuses",
|
|
),
|
|
(
|
|
"136070668_media_513155924_\u30b9\u30af\u30ea\u30fc\u30f3\u30b7\u30e7\u30c3\u30c8 2025-07-27 9.31.png",
|
|
"tamadaheijun's screenshots are named in Japanese; a guard that "
|
|
"only knows the English word is a guard for one artist",
|
|
),
|
|
(
|
|
"129421439_media_469882823_timeline 3-0002.jpg",
|
|
"keeping hyphens inside tokens for `0-k`'s sake let `3-0002` "
|
|
"survive whole, and it was MEASURED spanning three unrelated posts",
|
|
),
|
|
],
|
|
)
|
|
def test_measured_false_positives_contribute_nothing(path, why):
|
|
"""Each of these was found by running the module against the operator's
|
|
real library, not by reading it — which is the only way any of them would
|
|
have been found."""
|
|
assert working_name_tokens(path) == set(), why
|
|
|
|
|
|
def test_an_identity_token_must_contain_a_letter():
|
|
"""The property behind refusing bare years, bare numbers and date
|
|
fragments, stated once. `0-k`, `680lc` and `p59` all keep a letter."""
|
|
assert working_name_tokens("01_2025-07-27.jpg") == set()
|
|
assert working_name_tokens("01_0-k.jpg") == {"0-k"}
|
|
|
|
|
|
def test_the_literal_string_none_is_not_a_name():
|
|
"""#3999 rendered `{user[name]}` as "None" for ~1,600 files, which made it
|
|
the single most common "name" in the library and an identity for nothing."""
|
|
assert working_name_tokens("20180508_None_DCthingwhat__0123456789.jpg") == {
|
|
"dcthingwhat"
|
|
}
|
|
|
|
|
|
# --- the false positive that shaped the design -------------------------------
|
|
|
|
|
|
@pytest.mark.parametrize(
|
|
"path",
|
|
[
|
|
"01_Screenshot 2026-08-13 000004.png", # Patreon's spelling
|
|
"20260813_1234567890123_01_Screenshot_2026-08-13_032144.png", # Discord's
|
|
],
|
|
)
|
|
def test_a_screenshot_contributes_nothing_at_all(path):
|
|
"""A first pass matched these two to each other on the token `2026-08-13`.
|
|
|
|
A screenshot filename is a camera artifact carrying no working name, and
|
|
its date collides across platforms BY CONSTRUCTION: the teaser and the
|
|
release go out the same day, so their screenshot names always share one.
|
|
That is a signal firing precisely when it is least informative.
|
|
|
|
Both spellings matter. `\\b` after "shot" was the first guard and it caught
|
|
only the Patreon one, because `_` is a WORD character so
|
|
`Screenshot_2026-08-13` never presented a word boundary there. Found by
|
|
running against the live library, not by reading the regex.
|
|
"""
|
|
assert working_name_tokens(path) == set()
|
|
|
|
|
|
# --- rarity, which both signals now share ------------------------------------
|
|
|
|
|
|
def test_a_token_on_two_images_is_full_strength():
|
|
"""Two is the floor rather than one: a genuine match means the token is on
|
|
at least two files, so demanding uniqueness rejects every real pair."""
|
|
freqs = Counter({"connfront": 2})
|
|
|
|
assert shared_identity({"connfront"}, {"connfront"}, freqs) == (1.0, "connfront")
|
|
|
|
|
|
def test_a_character_name_can_never_link_two_posts():
|
|
"""THE false-positive guard, and the reason the cap is set where it is.
|
|
Measured on artist 8: `anya` is on 8 images, `riju` 9, `undyne` 8, `bea` 16.
|
|
Ungated, every Anya post would match every Anya drop."""
|
|
freqs = Counter({"anya": 8})
|
|
|
|
assert shared_identity({"anya"}, {"anya"}, freqs) == (0.0, None)
|
|
|
|
|
|
def test_a_token_at_the_cap_names_nothing():
|
|
"""It decays to zero, and reporting it anyway would hand the review queue a
|
|
reason with no weight behind it — "matched on loislanetb2", with nothing
|
|
there. A token is named only while it is doing work."""
|
|
freqs = Counter({"tok": MAX_TOKEN_POSTS})
|
|
|
|
assert shared_identity({"tok"}, {"tok"}, freqs) == (0.0, None)
|
|
|
|
|
|
def test_the_rarest_shared_token_decides_not_the_count_of_them():
|
|
"""One decisive token beats three vague ones. A pair sharing a piece name
|
|
AND two habits is evidence of the piece name."""
|
|
freqs = Counter({"rare": 2, "goo": 9, "shiny": 7})
|
|
|
|
strength, token = shared_identity(
|
|
{"rare", "goo", "shiny"}, {"rare", "goo", "shiny"}, freqs
|
|
)
|
|
|
|
assert (strength, token) == (1.0, "rare")
|
|
|
|
|
|
def test_frequencies_are_counted_per_artist_not_per_library():
|
|
"""A working name belongs to the person who chose it; the same string is
|
|
one creator's piece and another's boilerplate."""
|
|
counts = token_frequencies([
|
|
["01_ConnFront.jpg"],
|
|
["20230222_1078078245695664148_01_ConnFront.jpg"],
|
|
])
|
|
|
|
assert counts["connfront"] == 2
|
|
|
|
|
|
# --- the text marker, gated the same way -------------------------------------
|
|
|
|
|
|
def test_the_same_marker_reads_the_same_through_both_platforms():
|
|
"""The operator's pair carries `\U0001F348\U0001F348` in the Patreon title and
|
|
`@everyone \U0001F348 \U0001F348` in the Discord message. Spacing is a platform's
|
|
rendering, not the creator's intent."""
|
|
assert "\U0001F348" in text_markers("Anya -- \U0001F348\U0001F348")
|
|
assert "\U0001F348" in text_markers("@everyone \U0001F348 \U0001F348")
|
|
|
|
|
|
def test_one_marker_the_creator_uses_nowhere_else_is_the_whole_signal():
|
|
"""Measured: `\U0001F317` is on exactly two of this artist's 300 posts, and they
|
|
are the pair. An earlier pass halved every total so a long body could not
|
|
out-vote a short one — which the rarity gate now does properly, and which
|
|
meant this case could reach only 0.5."""
|
|
freqs = Counter({"\U0001F317": 2})
|
|
|
|
assert marker_overlap("\U0001F317", "\U0001F317 drop", freqs) == 1.0
|
|
|
|
|
|
def test_a_marker_the_creator_uses_habitually_is_worth_nothing():
|
|
"""The bug this gate exists for. Measured on artist 8: \U0001F4A6 is in 13 of 300
|
|
posts — punctuation, about one post in twenty-five. Ungated it scored a
|
|
full 1.00 and was the DECIDING term in a proposal that proximity alone
|
|
could not carry, which is proximity wearing a hat."""
|
|
freqs = Counter({"\U0001F4A6": 13})
|
|
|
|
assert marker_overlap("Drizzle \U0001F4A6", "\U0001F4A6", freqs) == 0.0
|
|
|
|
|
|
def test_a_shared_ordinary_word_is_worth_nothing():
|
|
"""Also measured, also a real proposal: the shared "marker" between a
|
|
teaser and a drop was the word `like`, which is in 43 of this artist's
|
|
posts."""
|
|
freqs = Counter({"like": 43})
|
|
|
|
assert marker_overlap("like this", "like that", freqs) == 0.0
|
|
|
|
|
|
def test_a_symbol_outweighs_a_word_at_equal_rarity():
|
|
"""Prose shares words by accident; a creator who writes "commission" in
|
|
both posts on a Tuesday has said nothing the timestamps did not."""
|
|
freqs = Counter({"\U0001F317": 2, "commission": 2})
|
|
|
|
symbol = marker_overlap("\U0001F317", "\U0001F317", freqs)
|
|
word = marker_overlap("commission", "commission", freqs)
|
|
|
|
assert word < symbol
|
|
|
|
|
|
def test_marker_frequencies_count_posts_not_occurrences():
|
|
"""A creator who repeats an emoji six times in one body has used it once as
|
|
far as identity goes."""
|
|
counts = marker_frequencies(["\U0001F348 \U0001F348 \U0001F348 \U0001F348", "\U0001F348"])
|
|
|
|
assert counts["\U0001F348"] == 2
|
|
|
|
|
|
def test_the_marker_gate_is_tighter_than_the_filename_gate():
|
|
"""Both caps count POSTS, so they are directly comparable and the gap is a
|
|
claim: a working name is the creator's private label for one piece and may
|
|
honestly recur as they revisit it, while a marker is public decoration and
|
|
stops being evidence the moment it is reused."""
|
|
assert MAX_MARKER_POSTS < MAX_TOKEN_POSTS
|
|
|
|
|
|
def test_a_name_must_clear_the_floor_to_link_on_its_own():
|
|
"""The floor is the whole two-route design in one number: identity may
|
|
propose alone, circumstance never may. Pinned against the threshold it
|
|
guards so the two cannot drift apart silently."""
|
|
assert 0.0 < IDENTITY_FLOOR <= 1.0
|
|
assert IDENTITY_FLOOR > 0.60 # the matcher's default threshold
|
|
|
|
|
|
# --- the count is of POSTS, which is what makes the cap mean anything --------
|
|
|
|
|
|
def test_a_piece_with_many_exports_is_not_penalised_for_having_them():
|
|
"""Counting FILES punishes a piece for the one thing a working name is
|
|
guaranteed to do. Measured: knuxy carries `p217` on four files across
|
|
exactly two posts — the Patreon post and the Discord drop — and roughly
|
|
two hundred comic-page tokens have that shape. File-counting scored every
|
|
one of them at half strength."""
|
|
counts = token_frequencies([
|
|
["p217.jpg", "p217-clean.jpg"], # the Patreon post
|
|
["20240101_123456789_01_p217.jpg", "..._02_p217-clean.jpg"], # the drop
|
|
])
|
|
|
|
assert counts["p217"] == 2
|
|
assert shared_identity({"p217"}, {"p217"}, counts) == (1.0, "p217")
|
|
|
|
|
|
def test_a_name_reused_across_many_posts_is_still_caught():
|
|
"""The other half of the same property — the cap has to keep working once
|
|
the unit changes. Measured habits: tamadaheijun's `comic2` spans 8 posts,
|
|
conto's `seth2` 5."""
|
|
counts = token_frequencies([["comic2_%02d.jpg" % i] for i in range(8)])
|
|
|
|
assert counts["comic2"] == 8
|
|
assert shared_identity({"comic2"}, {"comic2"}, counts) == (0.0, None)
|
|
|
|
|
|
def test_marker_overlap_cannot_be_called_without_the_frequencies():
|
|
"""An ungated call is the bug the signature exists to make impossible to
|
|
write by accident. A default would have kept it one keyword away."""
|
|
with pytest.raises(TypeError):
|
|
marker_overlap("\U0001F348", "\U0001F348")
|