docs(timeline): add Person date+precision migration as foundational issue
Replace Person birthYear/deathYear integers with birthDate/deathDate + DatePrecision so known exact birthdays render precisely. Migration, re-import preservation rule, and bounded blast radius captured; becomes issue 1 the timeline's derived events depend on. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -11,7 +11,7 @@ The archive can capture, transcribe, organize, and browse letters, but the trans
|
|||||||
|
|
||||||
A **hand-curated, year-banded vertical timeline** — the "Zeitstrahl" — that weaves three layers into one chronological view:
|
A **hand-curated, year-banded vertical timeline** — the "Zeitstrahl" — that weaves three layers into one chronological view:
|
||||||
|
|
||||||
1. **Person life-events** derived from already-curated structured data (`Person.birthYear`/`deathYear`, marriage years from `PersonRelationship.fromYear`). Trusted, free, no extra entry.
|
1. **Person life-events** derived from already-curated structured data (`Person` birth/death dates, marriage years from `PersonRelationship.fromYear`). Trusted, free, no extra entry. (Requires the Person birth/death fields to move from year-integers to date + precision — see foundational issue 1.)
|
||||||
2. **Hand-curated events** the family writes — both **personal** (a move, an illness, emigration) and **historical** (a war, hyperinflation). Editorially controlled, always correct.
|
2. **Hand-curated events** the family writes — both **personal** (a move, an illness, emigration) and **historical** (a war, hyperinflation). Editorially controlled, always correct.
|
||||||
3. **Letters**, auto-placed by their existing `documentDate`, optionally hand-linked to an event to cluster them.
|
3. **Letters**, auto-placed by their existing `documentDate`, optionally hand-linked to an event to cluster them.
|
||||||
|
|
||||||
@@ -81,14 +81,35 @@ Mirrors the `Document` date model for consistency, so events and letters use one
|
|||||||
|
|
||||||
`PERSONAL` | `HISTORICAL`. Personal events render with a person/family accent; historical events with a "world" accent and muted styling so the two layers are visually separable.
|
`PERSONAL` | `HISTORICAL`. Personal events render with a person/family accent; historical events with a "world" accent and muted styling so the two layers are visually separable.
|
||||||
|
|
||||||
|
### Prerequisite: migrate `Person` birth/death to date + precision
|
||||||
|
|
||||||
|
Today `Person` stores `birthYear`/`deathYear` as `Integer`, so a known exact birthday (e.g. `1901-03-14`) has nowhere to live and derived events are stuck at year precision. This is fixed by a **foundational Person-domain migration** that the timeline depends on (and which delivers value on its own — precise dates then render on person cards, hover cards, and the Stammbaum).
|
||||||
|
|
||||||
|
**Change:** replace `birthYear`/`deathYear` (`Integer`) with:
|
||||||
|
|
||||||
|
| Field | Type | Notes |
|
||||||
|
|---|---|---|
|
||||||
|
| `birthDate` | `LocalDate` (nullable) | most precise date known |
|
||||||
|
| `birthDatePrecision` | `DatePrecision` (nullable) | `YEAR` for year-only, `DAY` for exact birthdays, etc. |
|
||||||
|
| `deathDate` | `LocalDate` (nullable) | |
|
||||||
|
| `deathDatePrecision` | `DatePrecision` (nullable) | |
|
||||||
|
|
||||||
|
**Flyway data migration:** existing `birth_year` → `birth_date = '{year}-01-01'`, `birth_date_precision = 'YEAR'` (same for death); then drop the year columns.
|
||||||
|
|
||||||
|
**Re-import preservation (ADR-025):** the canonical importer (`PersonRegisterImporter` / `tools/import-normalizer/persons_tree.py`) only carries the *year*. On re-import it must **not** clobber a hand-entered finer-than-`YEAR` date — if the existing precision is `DAY`/`MONTH`/`SEASON`, preserve it; only refresh from the spreadsheet year when the field is empty or still `YEAR`-from-import.
|
||||||
|
|
||||||
|
**Bounding the blast radius:** `PersonNodeDTO` keeps exposing an `Integer birthYear`/`deathYear` *derived* from the new date (`birthDate.getYear()`), so the Stammbaum layout (`familyForest.ts` et al.) is untouched. Display surfaces (person card, hover card) move to a shared precision-aware formatter — extend the existing `frontend/src/lib/person/personLifeDates.ts`. The person edit/new forms gain date inputs with a precision selector.
|
||||||
|
|
||||||
|
**Scope note:** `PersonRelationship.fromYear` (marriage year) stays `Integer`/`YEAR` for MVP — precise marriage dates are a later, parallel extension if wanted.
|
||||||
|
|
||||||
### Derived person-events (not persisted)
|
### Derived person-events (not persisted)
|
||||||
|
|
||||||
Assembled on read from existing curated data; never stored:
|
Assembled on read from the migrated `Person` data; never stored:
|
||||||
|
|
||||||
| Source | Derived event | `eventDate` | precision |
|
| Source | Derived event | `eventDate` | precision |
|
||||||
|---|---|---|---|
|
|---|---|---|---|
|
||||||
| `Person.birthYear` | *Geburt: {name}* | `{birthYear}-01-01` | `YEAR` |
|
| `Person.birthDate` | *Geburt: {name}* | `Person.birthDate` | `Person.birthDatePrecision` |
|
||||||
| `Person.deathYear` | *Tod: {name}* | `{deathYear}-01-01` | `YEAR` |
|
| `Person.deathDate` | *Tod: {name}* | `Person.deathDate` | `Person.deathDatePrecision` |
|
||||||
| `PersonRelationship` `SPOUSE_OF.fromYear` | *Heirat: {A} & {B}* | `{fromYear}-01-01` | `YEAR` |
|
| `PersonRelationship` `SPOUSE_OF.fromYear` | *Heirat: {A} & {B}* | `{fromYear}-01-01` | `YEAR` |
|
||||||
|
|
||||||
Emitted in the same DTO shape as a curated event, flagged `derived: true`, `type = PERSONAL`. They cannot be edited from the timeline (they are edited at their source: Person record / relationship). A marriage is derived once per `SPOUSE_OF` edge (symmetric edges are stored once — see existing relationship rules).
|
Emitted in the same DTO shape as a curated event, flagged `derived: true`, `type = PERSONAL`. They cannot be edited from the timeline (they are edited at their source: Person record / relationship). A marriage is derived once per `SPOUSE_OF` edge (symmetric edges are stored once — see existing relationship rules).
|
||||||
@@ -144,17 +165,18 @@ Input DTO `TimelineEventRequest` lives flat in the `timeline/` package. Errors u
|
|||||||
|
|
||||||
## Proposed issue breakdown (milestone "Zeitstrahl / Family Timeline")
|
## Proposed issue breakdown (milestone "Zeitstrahl / Family Timeline")
|
||||||
|
|
||||||
Ordered so each issue is independently shippable and reviewable; later issues depend on earlier ones.
|
Ordered so each issue is independently shippable and reviewable; later issues depend on earlier ones. Issue 1 is a standalone Person-domain improvement and a hard prerequisite for the timeline's derived events.
|
||||||
|
|
||||||
1. **Backend: `TimelineEvent` entity + migration** — entity, `EventType`, Flyway migration + join tables, repository. (foundation)
|
1. **Person birth/death → date + precision (foundational)** — replace `birthYear`/`deathYear` with `birthDate`/`deathDate` + precision on `Person`; Flyway data migration (year → `YYYY-01-01`, `YEAR`); update importer with re-import preservation rule; derive year in `PersonNodeDTO` (Stammbaum untouched); move person card / hover card to a precision-aware `personLifeDates.ts`; add date+precision inputs to person new/edit forms. Ships value on its own.
|
||||||
2. **Backend: TimelineEvent CRUD API** — `TimelineEventController` + `TimelineService` write methods, `TimelineEventRequest` DTO, permission gating, `GET /events/{id}`.
|
2. **Backend: `TimelineEvent` entity + migration** — entity, `EventType`, Flyway migration + join tables, repository.
|
||||||
3. **Backend: derived person-events** — assemble Geburt/Tod/Heirat from Person + relationship data via their services; unit-tested dedup.
|
3. **Backend: TimelineEvent CRUD API** — `TimelineEventController` + `TimelineService` write methods, `TimelineEventRequest` DTO, permission gating, `GET /events/{id}`.
|
||||||
4. **Backend: timeline assembly endpoint** — `GET /api/timeline` merging events + derived events + letters into `TimelineDTO`; year-bucketing, precision sort, undated bucket, filters.
|
4. **Backend: derived person-events** — assemble Geburt/Tod/Heirat from migrated Person + relationship data via their services; unit-tested dedup.
|
||||||
5. **Frontend: shared date-label helper + types** — `dateLabel.ts`, regen API types.
|
5. **Backend: timeline assembly endpoint** — `GET /api/timeline` merging events + derived events + letters into `TimelineDTO`; year-bucketing, precision sort, undated bucket, filters.
|
||||||
6. **Frontend: global `/zeitstrahl` view** — `TimelineView`, `YearBand`, `EventCard`, `LetterCard`, server load.
|
6. **Frontend: shared date-label helper + types** — `dateLabel.ts`, regen API types.
|
||||||
7. **Frontend: filters** — `TimelineFilters` (person / generation / layer / year range).
|
7. **Frontend: global `/zeitstrahl` view** — `TimelineView`, `YearBand`, `EventCard`, `LetterCard`, server load.
|
||||||
8. **Frontend: curator event forms** — `/zeitstrahl/events/new` + `/[id]/edit`, gated, with document & person pickers.
|
8. **Frontend: filters** — `TimelineFilters` (person / generation / layer / year range).
|
||||||
9. **Frontend: per-person Lebensweg** — embed `<TimelineView personId>` on Person detail.
|
9. **Frontend: curator event forms** — `/zeitstrahl/events/new` + `/[id]/edit`, gated, with document & person pickers.
|
||||||
10. **Polish & a11y** — mobile layout at 375px, dark mode, axe checks, i18n completeness (de/en/es).
|
10. **Frontend: per-person Lebensweg** — embed `<TimelineView personId>` on Person detail.
|
||||||
|
11. **Polish & a11y** — mobile layout at 375px, dark mode, axe checks, i18n completeness (de/en/es).
|
||||||
|
|
||||||
> An ADR may be warranted for the new `timeline/` domain + entity (per `docs/CLAUDE.md`, significant data-model change). Add as the next sequential ADR number when implementation starts.
|
> An ADR may be warranted for the new `timeline/` domain + entity (per `docs/CLAUDE.md`, significant data-model change). Add as the next sequential ADR number when implementation starts.
|
||||||
|
|||||||
Reference in New Issue
Block a user