fix(naming): let trailing bare IMDb ids anchor group identity (#113)

* fix(naming): let trailing bare IMDb ids anchor group identity

hasStructuredIDAnchor only consulted ParseStructuredFolderIDs, so folders
tagged with an unbracketed trailing id ("Eggs Run (2021) tt8049994") still
went ambiguous on title conflicts. ParseFolderIDs now returns only explicit
evidence (structured tags plus unambiguous tt-prefixed ids), so use it for
the anchor check. Covers the last 3 ambiguous-with-IDs groups on dev.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* fix(naming): persist file-stem trailing IMDb ids into group identity

InferGroupIdentity anchored on file-level trailing bare IMDb ids via
hasStructuredIDAnchor but extracted file-stem IDs with
ParseStructuredFolderIDs, so the id that justified resolving a title
conflict never reached GroupIdentity/ScannedMediaGroup and downstream
matching saw a resolved group without it. Use ParseFolderIDs for the
file-stem extraction to mirror the anchor check.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Quick
2026-06-09 17:12:05 -04:00
committed by GitHub
co-authored by Claude Fable 5
parent ce9830cd02
commit 0d0d7bab1e
2 changed files with 51 additions and 6 deletions
+9 -6
View File
@@ -60,13 +60,15 @@ func InferGroupIdentity(filePath string, libraryType string, assignment RootAssi
populateMovieGroupIdentity(cleanFilePath, assignment, idAnchored, &group)
}
// ID persistence must mirror hasStructuredIDAnchor: any evidence strong
// enough to anchor identity must also reach downstream matching.
if ids := ParseFolderIDs(filepath.Base(group.ObservedRootPath)); ids != nil {
group.TmdbID = ids.TmdbID
group.ImdbID = ids.ImdbID
group.TvdbID = ids.TvdbID
}
if group.TmdbID == "" || group.ImdbID == "" || group.TvdbID == "" {
if ids := ParseStructuredFolderIDs(strings.TrimSuffix(filepath.Base(cleanFilePath), filepath.Ext(cleanFilePath))); ids != nil {
if ids := ParseFolderIDs(strings.TrimSuffix(filepath.Base(cleanFilePath), filepath.Ext(cleanFilePath))); ids != nil {
if group.TmdbID == "" {
group.TmdbID = ids.TmdbID
}
@@ -249,15 +251,16 @@ func populateSeriesGroupIdentity(filePath string, libraryType string, assignment
}
// hasStructuredIDAnchor reports whether the observed root folder or the file
// name carries explicit structured provider IDs (e.g. {tmdb-694938},
// [tvdbid-81189]). Bare trailing numerics are deliberately excluded — only an
// explicit tag is strong enough to anchor identity over a title conflict.
// name carries explicit provider IDs — structured tags (e.g. {tmdb-694938},
// [tvdbid-81189]) or an unambiguous tt-prefixed IMDb id ("Eggs Run (2021)
// tt8049994"). ParseFolderIDs returns only such explicit evidence, which is
// strong enough to anchor identity over a title conflict.
func hasStructuredIDAnchor(filePath, observedRootPath string) bool {
if ParseStructuredFolderIDs(filepath.Base(observedRootPath)) != nil {
if ParseFolderIDs(filepath.Base(observedRootPath)) != nil {
return true
}
baseNoExt := strings.TrimSuffix(filepath.Base(filePath), filepath.Ext(filePath))
return ParseStructuredFolderIDs(baseNoExt) != nil
return ParseFolderIDs(baseNoExt) != nil
}
func deriveObservedRootPath(filePath, candidateRoot string) string {
+42
View File
@@ -30,6 +30,48 @@ func TestInferGroupIdentity_StructuredIDsResolveMovieTitleConflict(t *testing.T)
}
}
// An unbracketed trailing IMDb id anchors identity the same way a structured
// tag does — the tt prefix is unambiguous. Mirrors the dev-library case
// "Eggs Run (2021) tt8049994" whose file carries a different release title.
func TestInferGroupIdentity_TrailingImdbIDResolvesMovieTitleConflict(t *testing.T) {
group := InferGroupIdentity(
"/movies-int/es/Eggs Run (2021) tt8049994/Huevitos Congelados (2022) [WEBDL-1080p]-GRP.mkv",
"movies",
RootAssignment{
RootPath: "/movies-int/es/Eggs Run (2021) tt8049994",
InferredType: "movie",
},
)
if got, want := group.State, "resolved"; got != want {
t.Fatalf("State = %q, want %q", got, want)
}
if got, want := group.ImdbID, "tt8049994"; got != want {
t.Fatalf("ImdbID = %q, want %q", got, want)
}
}
// A trailing IMDb id on the file stem (folder carries none) must both anchor
// the identity and be persisted into the group, so downstream matching sees
// the provider ID that justified the resolution.
func TestInferGroupIdentity_FileLevelTrailingImdbIDAnchorsAndPersists(t *testing.T) {
group := InferGroupIdentity(
"/movies/On Fire (2024)/Soul on Fire tt28078628.mkv",
"movies",
RootAssignment{
RootPath: "/movies/On Fire (2024)",
InferredType: "movie",
},
)
if got, want := group.State, "resolved"; got != want {
t.Fatalf("State = %q, want %q", got, want)
}
if got, want := group.ImdbID, "tt28078628"; got != want {
t.Fatalf("ImdbID = %q, want %q", got, want)
}
}
// Without provider IDs the same folder/file title conflict must still be
// flagged ambiguous — there is nothing to anchor the identity.
func TestInferGroupIdentity_TitleConflictWithoutIDsStaysAmbiguous(t *testing.T) {