Catalog dedup, curation + Forge Studio (Phase 3)#32
Merged
Conversation
Find near-duplicate episodes from the Phase 2 embeddings and curate a clean,
labeled training set — plus a themed HTML app to explore it all.
forge catalog dedup -c ./forge-catalog --threshold 0.97
forge curate -c ./forge-catalog --where "overall_score > 6" \
--dedup 0.97 --dedup-policy keep-higher-quality --label approved
forge studio -c ./forge-catalog -o studio.html
Backend:
- Two new tables (bumps catalog SCHEMA_VERSION 2 -> 3, additive):
- dedup_edges — near-dup pairs as FACTS (similarity, not verdicts);
canonical order a < b.
- curation_labels — append-log of approve/reject/hold; latest-row-wins.
- forge/catalog/dedup.py: compute_dedup_edges (cosine over episode embeddings,
max over shared cameras; idempotent; O(N^2) with a logged cap) and curate
(WHERE filter + dedup policy -> labels).
- DuckDB v_curation view + v_dup_losers(threshold, policy) macro
(keep-higher-quality | keep-longer | keep-first).
- CLI: forge catalog dedup, forge curate, forge studio. The per-dataset
`forge dedup` (perceptual-hash) is unchanged — catalog dedup is namespaced.
Forge Studio (forge/catalog/studio.py + _studio_template.py):
- Generates a self-contained, themed single-page app — Overview, Corpus, Dedup
review, Snapshot — from real catalog data and embedded video thumbnails
(extracted via the readers, as data URIs). Design system matches the Forge
Studio mockup and the forge visualize dark theme. No server; one shareable
file. The Dedup tab does keep/reject with keyboard shortcuts and exports a
`forge curate` command.
Reuses the Phase 2 vectors, the readers for thumbnails, forge.io for cloud
sources, and the catalog writer/commit machinery. No existing behavior changed.
The in-repo example catalog (catalog_example_droid_100) now ships dedup_edges
(24 pairs @0.9) and curation_labels (84 approved / 16 dedup-rejected), catalog.json
bumped to v3, so curation + studio work from a fresh checkout. Excluded from the
built wheel like the rest of that folder.
Tests (tests/test_catalog_dedup.py): edge canonical order + threshold + idempotency,
v_dup_losers policies, curate approve/reject + latest-wins, and studio HTML smoke.
No existing tests change.
Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
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.
What
Find near-duplicate episodes from the Phase 2 embeddings and curate a clean, labeled training set — plus Forge Studio, a themed HTML app to explore it all.
forge catalog dedup -c ./forge-catalog --threshold 0.97 forge curate -c ./forge-catalog --where "overall_score > 6" \ --dedup 0.97 --dedup-policy keep-higher-quality --label approved forge studio -c ./forge-catalog -o studio.htmlPhase 3 of the Forge Data Engine, building on the catalog (#30) and embeddings (#31).
Backend
SCHEMA_VERSION2 → 3, additive):dedup_edges— near-dup pairs as facts (similarity, not verdicts), canonical ordera < b.curation_labels— an append-log ofapproved/rejected/held; latest-row-wins per episode.forge/catalog/dedup.py:compute_dedup_edges(cosine over episode embeddings, max over shared cameras, idempotent, O(N²) with a logged cap) andcurate(WHERE filter + dedup policy → labels).v_curationview +v_dup_losers(threshold, policy)macro (keep-higher-quality/keep-longer/keep-first).forge catalog dedup,forge curate,forge studio. The per-datasetforge dedup(perceptual-hash) is unchanged — catalog dedup is namespaced to avoid collision.Forge Studio
forge studiorenders a self-contained, themed single-page app — Overview · Corpus · Dedup review · Snapshot — from real catalog data and embedded video thumbnails (extracted via the readers, as data URIs). The design system matches the Forge Studio mockup and theforge visualizedark theme (monochrome chrome; cyan = telemetry, violet = AI-derived, amber = human decision). One shareable file, no server. The Dedup tab does keep/reject with keyboard shortcuts and exports aforge curatecommand. (Template JS validated withnode --check.)Design notes
forge.iofor cloud sources, and the catalog writer/commit machinery. No existing behavior changed; base CLI still doesn't import heavy deps.Verified on real data
Ran end-to-end on the droid_100 catalog in MinIO:
forge catalog dedup(24 pairs @0.9),forge curate(84 approved / 16 dedup-rejected),forge studio→ a 1.7 MB file with 100 episodes and 100 real thumbnails. The in-repo example catalog now shipsdedup_edges+curation_labels(catalog.json → v3), so curation + studio work from a fresh checkout (excluded from the built wheel like the rest of that folder).Testing
tests/test_catalog_dedup.py: edge canonical order + threshold + idempotency,v_dup_loserspolicies, curate approve/reject + latest-wins, studio HTML smoke. 13 tests.Not in this phase
Phase 4 —
forge snapshot create/materialize(freeze a curated set → export to LeRobot/RLDS for training). A hosted Studio service is a future productization; the catalog is zero-server by design so it stays optional.🤖 Generated with Claude Code