feat(photo-exif): suggest cluster labels from single-name Google Photos sidecars#274
Merged
Conversation
…os sidecars (#272) Adds `node face-worker.js suggest-from-sidecars`: joins the face scan's own state (single-face, single-cluster photos) against Google Photos sidecar people[] tags to vote a majority name per unlabeled cluster, gated on FACE_SEED_MIN_VOTES/FACE_SEED_MIN_FRACTION. Suggest-only — never writes a label, never touches the network, needs zero ONNX/detection.
There was a problem hiding this comment.
Pull request overview
Adds a new photo-exif face-worker subcommand that suggests names for unlabeled face clusters by voting Google Photos Takeout sidecar people[] tags against the persisted face scan state—without running detection, doing network calls, or mutating cluster/state files.
Changes:
- Add
node face-worker.js suggest-from-sidecarsto compute per-cluster majority-name suggestions from single-face / single-name sidecar votes, gated by new env thresholds. - Document the new command and thresholds in the connector README and
.env.example. - Add fixture-driven tests covering suggestion behavior, early exits, and “no writes / no posts” guarantees.
Reviewed changes
Copilot reviewed 4 out of 4 changed files in this pull request and generated 1 comment.
| File | Description |
|---|---|
| connectors/photo-exif/face-worker.js | Adds suggestFromSidecars() implementation, CLI dispatch, and env-configured vote thresholds. |
| connectors/photo-exif/test.mjs | Adds tests for sidecar-vote-based suggestions, early-exit behavior, and edge cases. |
| connectors/photo-exif/README.md | Documents the new suggest-from-sidecars subcommand and its thresholding/safety posture. |
| connectors/photo-exif/.env.example | Documents FACE_SEED_MIN_VOTES / FACE_SEED_MIN_FRACTION and the new subcommand usage. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Comment on lines
+62
to
+65
| const minVotesRaw = Number(process.env.FACE_SEED_MIN_VOTES); | ||
| const FACE_SEED_MIN_VOTES = Number.isFinite(minVotesRaw) ? minVotesRaw : 3; | ||
| const minFractionRaw = Number(process.env.FACE_SEED_MIN_FRACTION); | ||
| const FACE_SEED_MIN_FRACTION = Number.isFinite(minFractionRaw) ? minFractionRaw : 0.7; |
…-of-range Copilot review on #274 caught that Number('')+isFinite parses a blank env value as 0, which would silently disable the suggest-from-sidecars confidence gate (any single vote would then qualify). Requires both values to be a positive number, and the fraction additionally <=1.
Owner
Author
|
Resolving Copilot's review (1 comment):
|
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #272
Summary
node face-worker.js suggest-from-sidecars: joins the face scan's own state (a photo with exactly one detected face in exactly one cluster) against Google Photos sidecarpeople[]tags to vote a majority name per unlabeled cluster.FACE_STATE+ sidecars only. Suggest-only: never writescluster.label, never emits an ingest hint, never mutates the clusters/face-state files.FACE_SEED_MIN_VOTES(default 3) andFACE_SEED_MIN_FRACTION(default 0.7) so a stray mis-tag or an impure/merged cluster doesn't produce a confident wrong label.Changes
connectors/photo-exif/face-worker.js— newsuggestFromSidecars()+suggest-from-sidecarsdispatch +FACE_SEED_MIN_VOTES/FACE_SEED_MIN_FRACTIONenv thresholdsconnectors/photo-exif/.env.example— documents the new subcommand + thresholdsconnectors/photo-exif/README.md— newsuggest-from-sidecarssectionconnectors/photo-exif/test.mjs— 5 new tests: clear-majority suggestion, split vote below threshold, already-labeled cluster excluded, mutates/posts nothing,PHOTO_ROOTmissing → exit 1, no votes → clean exit 0, all-labeled early exitVerification
npm test— 27/28 passing; the 1 failure is the same pre-existing Windows/Node-v26 test-runner crash noted in feat(photo-exif): swap face stack from tfjs-node to onnxruntime-node #273, reproduces identically onmain, confirmed green on real CI (Ubuntu/Windows × Node 20/22)npm run check:boundary— clean/pre-pr-review's automated step, since direct skill invocation isn't available in this session) found no issues — this is a small, pure, network-free additionTest plan
npm testpasses (aside from the pre-existing unrelated flake noted above)npm run check:boundarypassesscan+ face pass over a Takeout library, runnode face-worker.js suggest-from-sidecarsand confirm suggestions look reasonable before confirming withlabel <id> "<name>"