A person and a year are derived rather than written #60

Closed
opened 2026-08-04 08:44:06 +02:00 by Claude · 1 comment
Collaborator

A person and a year are still derived rather than written.

The scan now writes rows for genres, music genres, studios and artists, as
upstream's validators do. PeopleValidator was left out, and years were never
in scope, so /Persons and /Years still derive their entries from the value
table.

A derived entry carries no id that survives a rescan, no sort name, no images
and no date — the same shortfall the by-name rows were written to close.

Also unexercised: this port records no ItemValues of type Studios at all
(scan/nfo.rs reads <studio> and drops it), so the studio validator is ported
and unit-tested but has never run against a library that records one.

A person and a year are still derived rather than written. The scan now writes rows for genres, music genres, studios and artists, as upstream's validators do. `PeopleValidator` was left out, and years were never in scope, so `/Persons` and `/Years` still derive their entries from the value table. A derived entry carries no id that survives a rescan, no sort name, no images and no date — the same shortfall the by-name rows were written to close. Also unexercised: this port records no `ItemValues` of type `Studios` at all (`scan/nfo.rs` reads `<studio>` and drops it), so the studio validator is ported and unit-tested but has never run against a library that records one.
Author
Collaborator

A person a scan credited is now a row. A year is not, and should not be.

People are not made by PeopleValidator. They are created at item-save time
by LibraryManager.SavePeopleMetadataAsync (LibraryManager.cs:3556), which
calls GetPerson(name) per credit and creates a Person when nothing answers.
The validator only refreshes metadata — skipping any name with no row — and
sweeps the dead ones: IsDeadPerson is !Peoples.Any(f => f.Name == e.Name)
(TranslateQuery.cs:936), skipping locked rows. Names come from the Peoples
table; a person is never an ItemValues row.

Years have no validator at all. /Years derives its entries per request:
YearsController maps each ProductionYear through GetYear, which is
CreateItemByName<Year> — created lazily by the first request that needs it.
Upstream's production database holds exactly 1 year row against 33,082
person rows. Deriving is the correct behaviour, so no year validator was
invented.

Measured against upstream's own database (33,082 person rows, 32,580 distinct
credited names), its person rows removed and the naming step run over it:

rows written 32,571
landing on an id upstream also carries 32,487 — 99.74 %
of those, SortName matching 32,477
Path matching 32,482
rescan writes 0

The 10 name disagreements are one person spelled two ways hashing to one id
(Alex Macqueen/Alex MacQueen, NFC/NFD pairs); upstream froze whichever it
saw first, we take the first in name order — same id, same row.

The 595 upstream ids we wrote nothing for all got a row from us under a
different id, and in every one of the 594 that share a path, the row our
derivation reproduces is the older one. They are duplicates the running
container made under a different metadata-path key.

The dead sweep run against upstream's untouched database would delete exactly
3 rows — precisely upstream's own IsDeadPerson set.

Left undone, and bigger than it looked: this port records no ItemValues of
type Studios because run.rs::local_metadata applies neither the <studio>
nor the <genre> an NFO parse already produces. Recording them touches the
write shape and the unchanged-detection, so the ported studio validator still
has no real data to run against. Filed separately.

Also worth knowing: named_id hashes an absolute path where the metadata
directory sits outside the data directory, which is the reference container's
layout but not a real install's. That applies to genres, studios and artists
already and is a property of the configuration rather than of this change.

A person a scan credited is now a row. A year is not, and should not be. **People are not made by `PeopleValidator`.** They are created at item-save time by `LibraryManager.SavePeopleMetadataAsync` (`LibraryManager.cs:3556`), which calls `GetPerson(name)` per credit and creates a `Person` when nothing answers. The validator only refreshes metadata — skipping any name with no row — and sweeps the dead ones: `IsDeadPerson` is `!Peoples.Any(f => f.Name == e.Name)` (`TranslateQuery.cs:936`), skipping locked rows. Names come from the `Peoples` table; a person is never an `ItemValues` row. **Years have no validator at all.** `/Years` derives its entries per request: `YearsController` maps each `ProductionYear` through `GetYear`, which is `CreateItemByName<Year>` — created lazily by the first request that needs it. Upstream's production database holds exactly **1** year row against 33,082 person rows. Deriving is the correct behaviour, so no year validator was invented. Measured against upstream's own database (33,082 person rows, 32,580 distinct credited names), its person rows removed and the naming step run over it: | | | |---|---| | rows written | 32,571 | | landing on an id upstream also carries | **32,487 — 99.74 %** | | of those, `SortName` matching | 32,477 | | `Path` matching | 32,482 | | rescan writes | **0** | The 10 name disagreements are one person spelled two ways hashing to one id (`Alex Macqueen`/`Alex MacQueen`, NFC/NFD pairs); upstream froze whichever it saw first, we take the first in name order — same id, same row. The 595 upstream ids we wrote nothing for all got a row from us under a different id, and in every one of the 594 that share a path, the row our derivation reproduces is the **older** one. They are duplicates the running container made under a different metadata-path key. The dead sweep run against upstream's untouched database would delete exactly **3** rows — precisely upstream's own `IsDeadPerson` set. Left undone, and bigger than it looked: this port records no `ItemValues` of type `Studios` because `run.rs::local_metadata` applies neither the `<studio>` nor the `<genre>` an NFO parse already produces. Recording them touches the write shape and the unchanged-detection, so the ported studio validator still has no real data to run against. Filed separately. Also worth knowing: `named_id` hashes an absolute path where the metadata directory sits outside the data directory, which is the reference container's layout but not a real install's. That applies to genres, studios and artists already and is a property of the configuration rather than of this change.
Sign in to join this conversation.
No description provided.