feat(dates): filename date class — per-pattern conventions + filename-over-EXIF priority (#116) #122

Merged
cameron merged 2 commits from feature/filename-date-class into master 2026-09-25 21:16:54 +00:00
Owner

Summary

The filename date class becomes first-class (ADR-0001, ticket #116): a file whose name matches any existing pattern is in the class — the name is the original date of record and now resolves before EXIF / container dates are consulted.

resolve_date_taken / resolve_dates_batch
+  filename_class_resolved        # in-class names resolve here (before EXIF)
+    Wall (screenshot/dash/compact)  -> offset cascade (tag -> GPS -> server zone)
+    Utc  (unix-timestamp names)     -> true instant, tz_source = utc_direct
   exif_prior_resolved            # out-of-class only
   exiftool (single subprocess)   # out-of-class only (in-class files skip it)
   fs_time                        # out-of-class only
pub enum FilenameDate {
    Wall(NaiveDateTime),   // device clock, needs the cascade
    Utc(DateTime<Utc>),    // already a true instant
}
impl FilenameDate {
    fn legacy_datetime(self) -> DateTime<Utc>  // wall-as-UTC / epoch
    fn legacy_timestamp(self) -> i64           // what the legacy column stores
}

No patterns added or removed — every existing pattern keeps its regex and gains an explicit convention. The legacy date_taken value is byte-identical to the pre-migration interpretation at all seven consumer call sites.

Evidence

  • Before: filename rows resolved after EXIF/exiftool and stayed unclassified (date_utc = NULL); a Screenshot_2014-…-named file with a 2021 EXIF date showed under 2021.
  • After: five new waterfall-seam tests pin the contract:
    • filename_class_wall_without_evidence_assumes_server_zone
    • filename_class_wall_outranks_exif_and_uses_its_evidence (the inversion: 2020 EXIF ignored, 2015 filename wall used with the prior's offset tag)
    • filename_class_wall_gps_evidence_uses_capture_zone
    • filename_class_timestamp_is_utc_direct
    • in_class_video_name_resolves_without_exiftool
  • Out-of-class EXIF-wins unchanged: resolve_uses_prior_exif_when_present et al. still pass untouched.
  • cargo test: 639 lib + 716 integration, 0 failed. cargo fmt --check + cargo clippy --all-targets clean.
  • Two-axis code review (standards + spec sub-agents): spec axis clean, all acceptance criteria verified; standards axis no violations, 3 of 4 P2 judgement calls applied in the diff.

Merge Danger

Door: two-way. Revert restores the old waterfall; the legacy column is untouched for existing rows, and the new-contract columns only gain values that were NULL before.

Blast Radius: ingest + per-tick drain waterfall only. One deliberate behavior change for new in-class files that also carry EXIF: their legacy date_taken now comes from the filename (wall-as-UTC / epoch) instead of the EXIF date — that is the point of the ticket. Existing rows are never rewritten.

## Summary The filename date class becomes first-class (ADR-0001, ticket #116): a file whose name matches any existing pattern is *in the class* — the name is the original date of record and now resolves **before** EXIF / container dates are consulted. ``` resolve_date_taken / resolve_dates_batch + filename_class_resolved # in-class names resolve here (before EXIF) + Wall (screenshot/dash/compact) -> offset cascade (tag -> GPS -> server zone) + Utc (unix-timestamp names) -> true instant, tz_source = utc_direct exif_prior_resolved # out-of-class only exiftool (single subprocess) # out-of-class only (in-class files skip it) fs_time # out-of-class only ``` ```rust pub enum FilenameDate { Wall(NaiveDateTime), // device clock, needs the cascade Utc(DateTime<Utc>), // already a true instant } impl FilenameDate { fn legacy_datetime(self) -> DateTime<Utc> // wall-as-UTC / epoch fn legacy_timestamp(self) -> i64 // what the legacy column stores } ``` No patterns added or removed — every existing pattern keeps its regex and gains an explicit convention. The legacy `date_taken` value is byte-identical to the pre-migration interpretation at all seven consumer call sites. ## Evidence - **Before:** filename rows resolved *after* EXIF/exiftool and stayed unclassified (`date_utc = NULL`); a `Screenshot_2014-…`-named file with a 2021 EXIF date showed under 2021. - **After:** five new waterfall-seam tests pin the contract: - `filename_class_wall_without_evidence_assumes_server_zone` - `filename_class_wall_outranks_exif_and_uses_its_evidence` (the inversion: 2020 EXIF ignored, 2015 filename wall used with the prior's offset tag) - `filename_class_wall_gps_evidence_uses_capture_zone` - `filename_class_timestamp_is_utc_direct` - `in_class_video_name_resolves_without_exiftool` - Out-of-class EXIF-wins unchanged: `resolve_uses_prior_exif_when_present` et al. still pass untouched. - `cargo test`: 639 lib + 716 integration, 0 failed. `cargo fmt --check` + `cargo clippy --all-targets` clean. - Two-axis code review (standards + spec sub-agents): spec axis clean, all acceptance criteria verified; standards axis no violations, 3 of 4 P2 judgement calls applied in the diff. ## Merge Danger **Door:** two-way. Revert restores the old waterfall; the legacy column is untouched for existing rows, and the new-contract columns only gain values that were NULL before. **Blast Radius:** ingest + per-tick drain waterfall only. One deliberate behavior change for *new* in-class files that also carry EXIF: their legacy `date_taken` now comes from the filename (wall-as-UTC / epoch) instead of the EXIF date — that is the point of the ticket. Existing rows are never rewritten.
cameron added 2 commits 2026-09-25 20:55:40 +00:00
The wayfinding doc pointed at a .../dependencies/blocked_by subpath
that 404s, and the working call needs the blocker's owner+repo in the
JSON body (omitting them 404s with 'repository does not exist').
Verified against Gitea 1.27: POST/DELETE .../issues/<child>/dependencies
with {"index", "owner", "repo"}.
The filename date class is now first-class (ADR-0001):

- memories::extract_date_from_filename returns FilenameDate:
  Wall(NaiveDateTime) for the screenshot/dash/compact patterns,
  Utc(DateTime<Utc>) for the unix-timestamp patterns. No patterns
  added or removed; every existing pattern carries its convention.
- Waterfall inversion in both resolve_date_taken and
  resolve_dates_batch: an in-class name is the original date of
  record and resolves BEFORE EXIF / container dates are consulted
  (shared filename_class_resolved step; in-class files skip
  exiftool entirely). Wall patterns run the offset cascade over the
  kamadak prior's evidence (empty evidence -> server-zone
  assumption, so a class file always resolves); timestamp patterns
  are true instants (tz_source utc_direct, no offset).
- Legacy date_taken stays byte-identical: wall-as-UTC for wall
  patterns, epoch for timestamp patterns, via
  FilenameDate::legacy_datetime / legacy_timestamp at the seven
  consumer call sites.
- Out-of-class files keep the global EXIF-wins waterfall unchanged.
- CLAUDE.md waterfall section rewritten (step 0 = filename class;
  the EXIF-beats-filename note becomes out-of-class-only).

Tests: five new waterfall-seam tests (wall no-evidence ->
assumed_server; wall outranks EXIF with offset-tag evidence;
wall GPS evidence; timestamp utc_direct; in-class video name
resolves without exiftool) plus enum-shape assertions in every
extractor test. Code-reviewed on both axes (standards: no
violations, 3 of 4 P2 notes applied; spec: all acceptance
criteria verified, no scope creep).

716 integration + 639 lib tests pass; fmt + clippy clean.
cameron merged commit 3ac011abd7 into master 2026-09-25 21:16:54 +00:00
cameron deleted branch feature/filename-date-class 2026-09-25 21:16:54 +00:00
Sign in to join this conversation.