web(review): confidence-aware date guesser with combined filename + path signals

Refactor suggestDateFromPath to combine multiple signals instead of
trying patterns in priority order:

- Filename Y-M-D corroborated by path Y-M/Y-M-D → HIGH (filename-
  agrees-path). Fixes the case where a Samsung-style 20240226_xxx.jpg
  under 2024/02/ was returning the path-only 2024-02-01.
- Filename Y-M-D with no path signal → HIGH (filename-only).
- 10/13-digit Unix epoch in basename → HIGH (unix-timestamp) —
  covers WeChat (mmexport...) and FB saves.
- Path Y-M-D → HIGH (path-ymd).
- Path Y-M only → MEDIUM (path-ym-default-day, synthesised day=01).
  Sidebar row labels these "(estimated day)" so the user knows.

Filename parser now accepts `.` and space separators (covers macOS
screenshots, manual 2024.02.26 renames). Path parser accepts `.` too.
OriginalName participates as a secondary filename signal when present
and different from the on-disk basename.

Patterns we explicitly DO NOT parse, to avoid silent date flips:
DD-MM-YYYY / MM-DD-YYYY, 2-digit years, bare camera sequence numbers.

Add photoNameAndDir(p) helper next to primaryFile so RightSidebar,
BulkActionBar, photoActions, and gridKeyNav all derive {fileName,
path} the same way — fixes the bug where photo.FileName was
undefined on the single-photo detail endpoint and the basename branch
was being skipped entirely.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-05-20 12:04:48 +02:00
parent a54d90a2d9
commit c134afe023
6 changed files with 257 additions and 65 deletions

View File

@@ -25,7 +25,7 @@ import { queryClient } from '$lib/queryClient';
import { clearSelection, focusAfter } from '$lib/stores/selection.svelte';
import { push as pushUndo } from '$lib/stores/undo.svelte';
import { suggestDateFromPath } from '$lib/utils/suggestDateFromPath';
import type { PpPhoto } from '$lib/types/photoprism';
import { photoNameAndDir, type PpPhoto } from '$lib/types/photoprism';
/** Walk every cache that might hold a photo's metadata — timeline list
* (flat or infinite), review-groups bucket, per-photo detail — without
@@ -95,9 +95,14 @@ export async function acceptDateAndKeep(uids: string[]): Promise<void> {
if (uids.length === 0) return;
const { updated, errors } = await batchEdit(uids, async (id) => {
const p = cachedPhoto(id);
const iso = p ? suggestDateFromPath({ fileName: p.FileName, path: p.Path }) : null;
if (p && iso) {
await updatePhoto(p, buildTakenAtPatch(`${iso}T00:00:00Z`));
if (p) {
const { fileName, path } = photoNameAndDir(p);
const guess = suggestDateFromPath({
fileName,
originalName: p.OriginalName,
path
});
if (guess) await updatePhoto(p, buildTakenAtPatch(`${guess.iso}T00:00:00Z`));
}
await approvePhoto(id);
return id;