All watches

Changelog

Every catalog we add to Seikobase, newest first.

New watcheswatches added to the siteDatareferences, calibers, coverageTaggingwatches assigned to collectionsModelthe data / identity modelAppwebsite featuresMethodan investigation or decision
Hide internal changes

September 2, 2026

AppInternal19:xx

The site serves every image in the light WebP format

Goal

Make every page fast and keep the heavy scan originals private.

Method
  • Serve every watch photo on the site as WebP, converted once from the archive scan on first view and reused after.
  • Cap the served size at what the page actually displays.
  • Keep the original catalog scans untouched, as the archive.
Results
  • A watch photo weighs about 7 times less than before.
  • The original scans stay complete and unmodified.
New watches16:xx

First public watches: King Seiko, the 62MAS, and the 6159 divers

Goal

Open the first fully validated collections to the public.

Method
  • Publish the seven collections we validated end to end: King Seiko 44KS, 45KS, 56KS, 52KS, and Vanac; the 62MAS; and the 6159 Diver.
  • Every watch carries a confirmed caseback reference from a named source.
Results

52 watches are now public: 47 King Seiko models, both 62MAS references (6217-8000 and 6217-8001), and all three 6159 divers (6159-7000, 6159-7001, 6159-7010).

AppInternal16:xx

Seikobase is public

Goal

Open the site to visitors, so collectors can browse and search the database without a password.

Method
  • Take down the password gate.
  • Tell search engines they can index the site, and point them at the sitemap so every public watch is found.
Results
  • The site is now open.
  • No password.
  • Search engines can index every public watch.
AppInternal14:xx

A real logo in the header, the browser tab, and social shares

Goal

Give the site a real identity, so a visitor knows where they are and a shared link looks right.

Method
  • Show the Seikobase logo in the top-right of every page, and swap between the black-text and white-text mark for light and dark theme.
  • Add a favicon, so the browser tab shows the mark.
  • Add a preview image, so a link shared on social media or in a chat shows the logo and the site name instead of a blank card.
Results

The site now shows the Seikobase logo in the header, the browser tab, and a shared-link preview.

AppInternal13:xx

Readable, stable URLs for every watch

Goal

Give every watch a URL a collector can read, remember, and share, instead of an opaque number.

Method
  • The URL of a watch is now its reference — /watch/6217-8000 for the 62MAS, /watch/6159-7010 for the hi-beat Tuna, /watch/56kss-023 for a 56KS.
  • Old numeric links (/watch/356 …) redirect to the new URL, so any bookmark still works.
  • Each page has its own title and short description, and tells search engines the canonical URL, so it can be found by its reference.
Results
  • Every public watch has a readable URL, and any old link still works.
  • Search engines can index each watch by its reference.
ModelInternal12:xx

Show only the collections we have validated

Goal

Publish only the parts of the database we can defend from a source, so a visitor never sees a collection we have not audited.

Method
  • Draw a line: a collection is public only when we have decoded it from a named source (a reference site, a catalog, or a domain expert).
  • Eight collections meet the bar today — King Seiko 1st, 44KS, 45KS, 56KS, 52KS, and Vanac; the 62MAS; and the 6159 Diver.
  • Every other collection stays private for now and will open as we complete it.
Results
  • The site shows 8 collections instead of 83.
  • About 52 watches are public, all with a confirmed reference.
  • The status page, the filter sidebar, and the grid all read the same list, so numbers agree everywhere.
Rejected

A soft rule that ranked collections by completion percent. It let unaudited Grand Seiko and quartz watches slip through, so we chose the strict allowlist instead.

AppInternal12:xx

Sitemap of every public watch

Goal

Give search engines one clean list of every page on the site, so the watches we publish can be found.

Method
  • Publish /sitemap.xml.
  • List the home page, the status page, the changelog, one page per public collection, and one page per public watch.
  • The sitemap shows the same watches the grid shows, no more and no less.
Results
  • /sitemap.xml lists the home page, the status page, the changelog, 8 collection pages, and about 52 watch pages.
  • Search engines can now index every public watch.

August 16, 2026

MethodInternal23:xx

King Seiko missing-42 hunt list; the gap is source material, not decoding

Goal

Decide whether the 42 King Seiko references we lack are recoverable from our data or need new sources.

Method
  • For every King Seiko reference on kingseiko.info that we do not hold, search all pipeline/data/extractions JSON and the DB for its JDM code.
  • Confirm which 1969-1976 catalogs we have and which are extracted.
  • Save the gap and the acquisition targets to research/king_seiko_missing.md.
Results
  • All 1969-1976 KS-year catalogs are present, extracted, and in the DB (we captured 100+ KS appearances from the 1972-75 volumes).
  • Of 57 missing codes, 50 appear nowhere in our data; the 7 that do appear are in page text with zero watch rows.
  • So the 42 missing references are not pictured in our catalog volumes — hunt, not decode.
  • King Seiko is complete to the limit of our catalogs: 47 of 89.
Rejected

Re-decoding or re-extracting our current catalogs to recover them — the watches are not in the volumes we have. Acquisition targets: the 1972 Vanac brochure (most of the 18 Vanac), a 1971 catalog (52KS), more 1974-75 volumes.

ModelInternal23:xx

Identity model: collapse to two tiers (caseback is the identity 1966-1998)

Goal

Fix the identity model so identified means a real, findable reference, per an expert review.

Method
  • An external Seiko-expert review confirmed the caliber-case caseback reference is the primary physical identity for every watch 1966-1998; the sales/order code is a secondary cross-reference.
  • Collapse the 3-tier model to 2: case code before 1966, caseback from 1966 on.
  • A watch held only by a sales code is caseback pending, not identified.
  • Update NAME_SQL and the eras in lib/db.ts, the Status page, research/identification-methodology.md, and save research/identification-expert-review.md.
  • Size the recovery: 2,075 sales-code-only models (518 need the case, 1,199 need the caliber, 606 need both).
Results
  • Right-name share drops from 67.32% to 54.33% — the honest number.
  • 2,075 models reclassified from named to caseback pending, shown on Status.
  • The sales code is now a secondary cross-reference, never the primary identity.
Rejected

Keeping the sales code as the 1975-98 identity. It is a retailer ordering code, not the physical identity; sales-code-only watches are not findable (AQC834 vs the caseback 2320-5420).

DataInternal23:xx

Recover 502 casebacks for sales-code watches from the extraction captions

Goal

Turn caseback-pending modern watches into real, findable references by recovering the missing half.

Method
  • The missing half is usually still in the extraction caption raw_text — the case in a <NNN NNN> bracket (numeric like <314 014> for vintage, alphanumeric like <5G7-5J 2> for modern quartz), the caliber as NNNN NN-ishi — that the DB build dropped.
  • recover_caseback_from_captions.py joins the caption to the row by NORMALIZED sales code (HDF 928 == HDF928), scanning every text block (caption + loose entries), and rebuilds caliber-design0.
  • A strict format guard rejects OCR junk: a false caliber 17QS (17 jewels QS) and truncated 3-digit calibers (137 from a misread 3137) were caught before writing.
  • Boley confirms where it covers the caliber; own-caption evidence is trusted otherwise.
  • Dry-run, back up, apply, idempotent.
Results
  • 502 models gained a caseback across the passes.
  • Caseback pending 2075 -> 1573.
  • Right-name 54.33% -> 57.42%.
  • e.g. HDF928 -> 6810-3140, MNC824 -> 4120-8020, SABR001 -> 5P31-5A90.
Rejected

Web / Jules Borel lookup by sales code (JDM codes not indexed). Sibling self-join and own-DB-refs (~70; the refs column dropped the bracket). Page-level entries (they strip the bracket). The remaining 1,573 have no case in our extraction at all.

watchdb.sqlite.bak-20260816-155537-scodecaseback

MethodInternal23:xx

External sales-code lookup is a dead end (JDM codes not indexed)

Goal

Test whether an external source can map a JDM sales code to its caseback for the 1,891 remaining caseback-pending watches.

Method

Search the web and Jules Borel for sample JDM sales codes (AAL010, ACD021, ATR020, AQS023), and by caliber + line.

Results
  • Searching a sales code returns only generic line pages — the JDM ordering codes are not indexed anywhere (confirms the earlier LinkUp result and the expert note).
  • Searching by caliber returns real casebacks but many per caliber (9520 -> 5110/5120/5130/5140/5190), with no public concordance to pin which one a sales code is.
  • Jules Borel is keyed by caliber-case, not by sales code.
  • The web validates our recovered casebacks (9520-5130, 2435-3010 are real) but cannot map a sales code to its caseback.
Rejected

Web / Jules Borel lookup by sales code. The reliable remaining sources are a structured JDM concordance (Plus9Time 1991 catalog, 1981 USA microfiche) or re-extracting page-level text from the catalogs we already have.

MethodInternal23:xx

Save the Plus9Time Seiko case-material codes reference

Goal

Keep the Seiko case-material code table for decoding catalog case-material codes.

Method

Save the Plus9Time case-material-code table (built from the 1991 JDM catalog Vol.1) to research/seiko-case-material-codes.md: abbreviation, description, and case-back stamping.

Results

research/seiko-case-material-codes.md added (~180 codes).

ModelInternal23:xx

Make the two-tier identity consistent across all docs and Status

Goal

Remove the leftover three-tier language after adopting the two-tier identity model.

Method
  • Collapse the README identity table and the Status Names-present-by-era section to two rows: family case code before 1966, caliber-case caseback for 1966-1998.
  • Add the two-tier note (sales code is a secondary cross-reference, caseback-pending is not identified) to README Part V, pipeline/docs/PIPELINE.md, and the Status intro.
Results
  • README, the Status page, PIPELINE.md, and identification-methodology.md now all state two tiers.
  • The Status era section shows two rows, not three.
  • typecheck clean.
AppInternal22:xx

Rebrand the header to seikobase.com (top right)

Goal

Name the site seikobase.com.

Method

Replace the Vintage Seiko — WatchDB wordmark with seikobase.com, placed top-right in the header as the home link.

Results

The header now reads seikobase.com, top-right.

DataInternal21:xx

King Seiko: decode JDM codes from the source-of-truth tables

Goal

Recover and complete the King Seiko sub-collections from the authoritative reference tables.

Method
  • Build research/king_seiko.md and research/king_seiko_refs.csv from kingseiko.info (the source of truth): 86 distinct casebacks across 44/45/56/52KS and Vanac, each keyed by its JDM code.
  • build_king_seiko_refs.py normalizes the abbreviated 44-/45- casebacks and tags collection by source page (56KAW and 52KWL appear in both a base series and Vanac; only the page and number tell them apart).
  • decode_king_seiko.py joins our rows' codes (KSSK 010, 56KAW 267) to the table, giving caseback + sub-collection + caliber, so rows with no case design in the text still decode.
  • Set the registry est to the site's distinct-caseback counts (44KS 3, 45KS 10, 56KS 37, 52KS 9, Vanac 27), Confirmed.
  • Dry-run, back up, apply.
Results
  • Decoded 123 King Seiko rows (was ~0 identified mechanical).
  • Held vs est: 44KS 3/3 (100%), 45KS 8/10, 56KS 23/37, 52KS 4/9, Vanac 9/27 — 47 King Seiko models identified.
  • 154 rows still undecoded (no recognizable code, or a number not in the table).
Rejected

Decoding by the case design in our own OCR text — only 25 rows had one. Keying on the JDM code against the site table decoded 123.

watchdb.sqlite.bak-20260816-135155-decodeKS

DataInternal20:xx

Recover the 62MAS — both 6217-8000 and 6217-8001 (collection complete)

Goal

Find the 62MAS, Seiko's first diver, which held 0 identified models.

Method
  • The catalog prints the caliber as 62MAS, not 6217, so the earlier caliber search missed it.
  • Two rows carry 62MAS 010 (800-800) at 17 jewels: id 356 in the 1967 catalog and id 616 in the 1968 No.1 catalog.
  • TheSeikoGuy documents the catalog-year mapping: 1966-67 shows the small crown 6217-8000, 1968 vol.1 the big crown 6217-8001.
  • Boley lists both references.
  • Set caliber 6217, caseback_ref, and collection 62MAS: id 356 to 6217-8000, id 616 to 6217-8001.
  • Widen the 62MAS window to 1965-1968 in the registry so the rule stays reproducible.
Results
  • 62MAS now holds both references, 6217-8000 and 6217-8001: 2 of 2, 100% complete.
  • First hunt-collection closed from 0 to done.

watchdb.sqlite.bak-20260816-133136-62mas

AppInternal19:xx

Status: 6159 Diver at 100%, two-decimal headline shares

Goal

Reflect the completed 6159 collection and show precise headline percentages.

Method
  • Set 6159 Diver est to 3 (its three references 7000, 7001, 7010) and Confirmed in research/collections-logic.md; regenerate collections.ts.
  • Show the Identified, Right name, and Right collection headline shares to two decimals on the Status page.
Results
  • 6159 Diver reads 3 / 3 · 100% on Status.
  • Headline shares now show two decimals (right name 66.56%, right collection 0.05%, identified 0.03%).
DataInternal18:xx

Recover the missing 6159-7001 (1969 300m hi-beat diver)

Goal

Add the missing 6159 reference — the 1969 300m hi-beat monocoque diver that we lacked.

Method
  • Two caliber-6159 Seiko Diver rows (1969 and 1970) carry the catalog case code 011; the 010 code is the proven 6159-7000, so 011 is the increment.
  • Confirm 6159-7001 exists in Boley independently, with its own parts (crystal 325W14GA).
  • Domain-expert confirmation that 6159-7001 is a real JDM reference.
  • Set caseback_ref = 6159-7001 on ids 2148 and 2678.
Results
  • 6159 Diver now holds its three references: 6159-7000, 6159-7001, and 6159-7010.
  • ids 2148 and 2678 recovered from caseback-less to identified.

watchdb.sqlite.bak-20260816-132228-6159-7001

AppInternal17:xx

Hide the case-material field and filter

Goal

Stop showing case material until we trust the data.

Method
  • Hide the Material filter in the sidebar and the case material row on the watch page.
  • Keep both in code, commented, so they are easy to restore once the material data is trusted.
Results

Case material no longer appears in the filter sidebar or on the watch page.

AppInternal16:xx

One page container across the app + a prominent collection callout

Goal

Give every page one container, and make a watch's collection and details easy to read.

Method
  • Add a shared Container (max-w-5xl, centered, one padding) used by the watch, changelog, and status pages.
  • Narrow the browse index to max-w-6xl (was full-bleed) with the same centering and padding; drop the 5-column grid tier.
  • On the watch page, turn the collection paragraph into a callout: accent panel, icon, collection-name heading, readable foreground text, placed first under the name.
  • Show every specification in one simple card, one per line (identity, case, dial, and caliber specs together).
Results
  • The index, watch page, changelog, and status share one container width and padding.
  • The collection reads as a clear callout, not gray small print.
  • Every watch specification sits in one card, one per line.
Rejected

A single identical width for every page. We kept the browse index one step wider (max-w-6xl) than the reading pages (max-w-5xl) for grid density.

AppInternal15:xx

Show only identified watches; unify model identity + title format

Goal

Make counted, shown, and distinct mean one thing, and display only watches that have a real model name.

Method
  • Define one identity: the model name = caseback reference, else sales code, else pre-1966 case code (caseback first, so one watch keeps one identity even when a later catalog also prints its sales code; ERA_NAME_SQL in lib/db.ts).
  • Show a watch only if it has that name; hide the rest across the grid, the API feed, and the facet counts.
  • Dedupe the catalog by that same name, so shown count equals distinct count.
  • Change the title to <model name> — <collection> (lib/identity.ts, modelName).
  • Count the facet sidebar and the Status have-column by that same identity, so a collection shows the same number everywhere.
Results
  • The app shows 7,753 identified models.
  • 6159 Diver: 2 identified models, 6159-7000 and 6159-7010, each titled e.g. 6159-7010 — 6159 Diver.
  • Un-referenced watches no longer appear anywhere.
  • Counted, shown, and distinct now agree everywhere: 6159 Diver reads 2 on the grid, the facet sidebar, and Status.
Rejected

A year-gated rule (sales code for 1975+). It split one physical watch into two cards when a later catalog also printed its sales/order code (6159-7010 vs YAQ028). Caseback-first fixes it.

AppInternal14:xx

Catalog shows distinct models, random order, collection-aware names

Goal

Show the catalog as distinct watches in random order, each named by its collection.

Method
  • Grid dedupes to one card per distinct model, keyed by caseback reference, then modern sales code, then model_key; a window function keeps the best appearance (has a crop, has a reference, earliest).
  • Every appearance stays in the DB as provenance; the watch page still lists the other catalog appearances.
  • Default order is a fresh random seed per page load, threaded through infinite scroll so the pages stay consistent.
  • Card and page titles now prefer the assigned collection over a soft hint or a raw catalog heading (lib/identity.ts).
Results
  • The catalog shows 12,836 distinct wristwatch models instead of 35,412 appearances.
  • 6159 Diver: 14 appearances collapse to 5 distinct models.
  • Tagged watches read e.g. 6159 Diver 6159-7010, and caseback-less ones read 6159 Diver 1969 instead of Special Watches 1969.
  • Arriving on the home page shows a fresh random order, not oldest-first.
Rejected

Relabeling duplicate appearances as unknown in the DB. Appearances are real provenance (the same model in another catalog year, with its own crop), not errors; we dedupe at the query layer instead, which is reversible and keeps the history.

MethodInternal13:xx

Defined the database-completion method (Part V of the README)

Goal

Define one robust, repeatable process for completing the database, so tagging never drifts.

Method
  • Adopt a conservation law: every distinct model resolves to one collection or to Unclassified, and per year window the collection counts plus Unclassified equal the total.
  • Work in vertical slices, one family end to end.
  • Names before collections, by era (case_code, caseback_ref, sales_code).
  • Assign each collection with the same six-step recipe: rule, dry-run, verify, assign, reconcile, log.
  • Keep all tagging as re-runnable rules in assign_collections.py, with a reconciliation gate after every batch.
  • Write it as Part V of the README.
Results
  • The completion approach is documented in the README (Part V).
  • No code is written yet; the spec and the scripts come next.
Rejected

Tagging the whole corpus at once, and leaving unclassified watches as NULL. We chose vertical slices and an explicit Unclassified bucket so the sum always balances.

TaggingInternal13:xx

First collection slice: 6159 Diver (Tuna) + the assign_collections tool

Goal

Build the re-runnable collection assigner and complete the first vertical slice.

Method
  • Add pipeline/scripts/assign_collections.py: registry-driven rules (caliber or name), idempotent, with a dry-run, a per-slice apply that backs up the DB first, and a conservation report by decade.
  • A model that matches two collections is left Unclassified, never guessed.
  • Verify the 6159 candidates against the catalog text, then apply.
Results
  • 6159 Diver: 7 models (14 rows) assigned, all caliber 6159, verified.
  • Dry-run map: base rules auto-assign 4,960 of 15,065 models (33%); 10,105 Unclassified; 110 ambiguous models flagged for tiebreakers.
  • Conservation balances in every decade.
  • Collections assigned in the DB: 1.
Rejected

Assuming the caliber spine is complete. Many Grand Seiko and King Seiko families hold 0 models by caliber, because their calibers were never extracted; those families need hunting or name recovery first, not tagging.

watchdb.sqlite.bak-20260816-122541-assign-6159Diver

AppInternal12:xx

Structured change log (CSV) + /changelog page

Goal

Keep one structured file we append to for every change, and show it clearly at /changelog.

Method
  • Store every change as a row in CHANGELOG.csv (date, time, type, title, goal, method, results, rejected, backup).
  • Parse the CSV at request time in apps/web/lib/changelog.ts.
  • Render each change as a card with a type badge and labeled Goal, Method, and Results sections.
  • Add a per-collection paragraph on each watch page from research/collections-paragraphs.md.
  • Rename research docs to kebab-case and add research/README.md.
  • Add CLAUDE.md with the logging rule and the change-type taxonomy.
Results
  • /changelog parses the CSV directly, so there is no regenerate step.
  • 81 of 81 collection paragraphs matched.
  • typecheck passed.
  • /changelog and /watch/176 returned HTTP 200.
Rejected

An intermediate generated TS file for the changelog. We replaced it by parsing the CSV at runtime, so appending a row needs no script.

August 15, 2026

ModelInternal21:xx

New identity model: three tiers, the 1956 floor, 83 MECE collections

Goal

Rebuild how a watch is identified, with exactly one home per watch.

Method
  • Define three identity tiers by year: 1956-1965 family + case code (case_code); 1966-1974 caliber + case built reference (caseback_ref); 1975-1998 sales code (sales_code).
  • Set a 1956 floor with a new in_catalog column (0 for year below 1956; hidden everywhere).
  • Define 83 MECE collections by caliber family.
  • Move the old line column to likely_collection; add an empty collection column.
  • Rebuild the Status page around three shares: Identified, Right name, Right collection.
Results
  • 55 pre-1956 rows hidden (the 1932 catalog).
  • Name coverage 67% (10,026 of 15,065 models); collection 0%; identified 0%.
  • Names by era: 1956-65 = 0%; 1966-74 = 12% (651 of 5,258); 1975-98 = 96% (9,375 of 9,762).
  • The collection filter now lists the real 81 collections.

backups .bak-20260815-likelycol and .bak-20260815-incatalog

TaggingInternal20:xx

Collection audit: year-window check and tag fixes

Goal

Find watches tagged to a collection in a year that collection did not exist, and know per collection where we stand.

Method
  • Build a production window per collection from all-collections.md and the horological record.
  • Flag every watch whose catalog year sits outside its window.
  • Spot-check each flagged set against the raw text.
  • Move the confirmed mis-tags.
  • Set windows for 5 Actus and Skyliner.
  • Rebuild the Status page and add a missing-collections section.
Results
  • 325 watches flagged across 5 collections; 34 passed; 3 unchecked (no window).
  • Marvel after 1968 moved to Lord Marvel (28 rows).
  • Laurel from 1980 moved to Laurel Quartz (210 rows).
  • Marvel now 1956-1966 (14); Lord Marvel now 1967-1978 (57).
  • Still flagged for review: Skyliner 1969-74 (21) and 5 Actus 1983-85 (13).

backup .bak-20260815-collfix

TaggingInternal19:xx

Section headers leaked as watch names; a new tagging source

Goal

Stop catalog section headers showing as watch names, and use them to fill the empty collection.

Method
  • Detect a section-header heading (it ends in Group or Series) and stop using it as a watch name (lib/identity.ts, isSectionHeader).
  • Read the collection out of the header and write line for untagged rows, only where the header names a real collection.
  • Add a /status page.
Results
  • watch 2573 now reads Seiko 1970; zero headers leak as names.
  • Tagged: 5 Actus 6 to 370; Avenue 0 to 668; Arctura 22; Kinetic 13.
  • Reclassified 174 pre-1962 rows to family + year.
Rejected

Generic category headers (Business Group, Dress Watch Group, Calendar Group). They name a category, not a collection. Inventing a collection from them would break the never-invent-a-fact rule.

backup .bak-20260815-collections

MethodInternal18:2x

Split the stuck rows; extend the parser; find more catalogs

Goal

Find how to identify the wristwatches that still have no reference.

Method
  • Split the 4,496 stuck sales-code rows by whether the case and caliber sit in the caption text.
  • Add two text patterns: a caliber in round brackets, and a case number in a spaced bracket.
  • Check each result against Boley.
  • Audit the catalog PDF files on disk.
Results
  • Bucket A (case in text) 4,313; Bucket B 183.
  • Only 1,179 rows have both case and caliber in text; 3,134 have the case but no caliber, so the caliber is the main gap.
  • 62 new references ready to write.
  • Found 129 catalog PDFs; only 56 are in the DB, so about 73 are unused, including the Credor and luxury catalogs that hold stuck watches.
Rejected

The Plus9Time 1991 Case Codes external table. The Google Drive link is dead (error 404).

DataInternal17:0x

LinkUp API: jewel lookup works, sales-code lookup fails

Goal

Use the LinkUp API to resolve ambiguous calibers by jewel count, and sales codes to references.

Method
  • Build a client pipeline/scripts/linkup.py that caches every answer and fixes the macOS certificate error.
  • Store every answer and citation in data/sources/linkup/audit.jsonl and the table linkup_audit.
  • Move 5 (jewels): match the printed jewel count to exactly one caliber (enrich_jewels.py).
  • Move 2 (sales code): probe the reference with strict Boley rules.
Results

Move 5 done: 65 of 179 ambiguous rows resolved; wristwatch references 25,753 to 25,818.

Rejected

Move 2 stopped. Deep depth ($0.055 per query) invented text and returned generic references; standard depth found 0 of 20 rows; JDM order codes are not indexed on the web. We wrote nothing.

backup .bak-20260815-jewels

DataInternal16:4x

Reclassify pre-1962; apply the held sales-code tier

Goal

Fix the tier of pre-1962 watches and apply the held sales-code references.

Method
  • Set all 112 pre-1962 wristwatches to the tier pre_numbering, because the caliber-case system did not exist then.
  • Measure coverage only over watches that can have a reference.
  • Write the 168 held sales-code references (recover_salescode.py --include-unconfirmed).
Results
  • 31 rows corrected from the wrong tier none.
  • Coverage 70.4% (25,753 of 36,604).
  • Wristwatch references 25,585 to 25,753.

backup .bak-20260815-moves34

DataInternal16:3x

Sales-code self-join (safe tier)

Goal

Recover references by copying within the DB, using only safe matches.

Method
  • recover_salescode.py. A sales code is a unique model id; if the same code carries a reference on another page, copy that reference.
  • Use only codes that map to one reference.
  • Write only the matches that are also in Boley.
Results
  • 4,920 stuck rows have a sales code; 423 resolve by self-join (255 are also in Boley; 168 are self-consistent only).
  • Wrote the safe 255; all gained parts.
  • Coverage 25,330 to 25,585 (69.0% to 69.7%).
Rejected

Held the 168 self-consistent-only rows, because they are not proven outside our own data.

backup .bak-20260815-salescode

DataInternal16:1x

Unified recovery pass (code grammar + JDM-0 + OCR repair)

Goal

Recover 1966-1975 references that are stored in the text but not decoded.

Method
  • recover_refs.py: one Boley load, one DB scan, one write; every method checked against Boley's 38,142 references.
  • code_grammar: the LM/KS/GS formula (56LMW to 5606) plus family+case that is unique in Boley.
  • variant_jdm0: a Boley group resolved by the JDM region-0 rule.
  • ocr_repair: a garbled caliber next to its own case bracket.
  • Attach parts in the same pass with concurrent workers.
Results
  • 279 references, all confirmed in Boley, all gained parts.
  • variant_jdm0 holds fully: 0 of 238 break; 59 resolve; 179 stay ambiguous.
  • Coverage 25,051 to 25,330 (68.2% to 69.0%).
  • Boley-linked 18,598 to 18,877.
  • The pass takes about 1 second.
Rejected

The numeric code form (61-5A). Family 61 has many calibers, so it is not safe.

backup .bak-20260815-recover

MethodInternal15:5x

Investigation: the gap is not a picture problem

Goal

Test whether a vision pass on the crop images can recover references.

Method
  • Inspect 10 crop images and check what the dial shows.
  • Size two reverse strategies: OCR repair of the caliber, and sales code to caliber lookup.
Results
  • The crop shows the watch only; the reference is never on the dial. The reference was printed in the page caption.
  • A vision pass on the crops finds nothing for identification.
  • Sized: OCR repair 2,526 rows (140 confirmed in a first test); sales code to caliber 5,060 rows.
Rejected

The vision-on-crops approach for identification.

DataInternal15:19

Caseback backfill from stored text

Goal

Raise strict caseback_ref coverage for wristwatches (start 23,659 of 36,716, 64%).

Method
  • backfill_caseback.py, dry-run first, then --apply.
  • Recover a missing caliber or case only from evidence tied to the same row: the 56LMW grammar, the row's own refs, or a bracket tied to the row's caliber.
  • Re-link parts with boley_relink.py.
Results
  • 1,392 references.
  • Coverage 23,659 to 25,051 (64% to 68.2%).
  • 1,282 watches gained parts; Boley-linked 17,316 to 18,598.
  • Online check of 10 new references: every caliber real; 7 exact references confirmed; 3 not found exactly but each real; 0 fakes.
Rejected

A loose scan of the whole page. On multi-watch pages it took a neighbour's case number (6117 010, 012, 022 all became 6117-6010). It offered about 2,000 rows but made wrong ids. Precision before recall.

backup .bak-20260815-caseback

August 8, 2026

AppInternal17:48

Simpler client UI (for collectors)

Goal

Remove jargon from the client so collectors read it easily.

Method
  • Show a reference (5606-7000) or a name (Marvel 1956) as the title; neither shows Unidentified.
  • Show case materials as words and movements as Automatic, Quartz, or Hand-wound (lib/labels.ts).
  • Remove the per-field confidence badges, the Identity filter, and the Caliber filter.
  • Keep Collection, Movement, Dial colour, Case colour, Shape, and Material.
Results
  • Cleaner card and detail titles.
  • The detail page shows Reference, Collection, Caliber, Year, and plain attributes.
  • The logo links home.
DataInternal17:37

boley.de case-parts harvest + cross-reference (backend only)

Goal

Get an independent reference database to confirm our reconstructed references.

Method
  • harvest_boley.py (polite, concurrent, checkpointed) pulls the full boley.de Seiko case-parts database.
  • A build_webdb.py post-pass checks our caseback_ref against Boley and attaches part numbers.
  • Strip the source from every field in lib/db.ts so it never reaches the client.
Results
  • 38,142 references and 715,476 part rows harvested.
  • 17,260 confirmed (likely to verified); 56 corrected; parts attached to 17,316 watches.
  • Checked: 0 instances of boley in the client HTML.
ModelInternal17:13

Caseback reference (caseback_ref): the collector identity

Goal

Store the caseback reference collectors use (5606-7000), not the one-digit-short catalog form.

Method
  • Derive caseback_ref = caliber + 3-digit case design + the JDM region digit 0 (derive_identity.caseback_ref).
  • Make it the main reference in vintage titles.
  • Make it searchable by exact match and FTS.
Results
  • It reproduces the exact casebacks (5606-700 to 5606-7000).
  • 25,159 watches carry one (4,620 distinct).
  • Confidence is capped at likely, because the region digit is inferred.
AppInternal17:00

One README; infinite-scroll app shell

Goal

Merge the scattered methodology docs into one README and rebuild the home page.

Method
  • Merge every methodology doc into one README.md; remove the scattered files and the client /methodology page.
  • Rebuild the home page as a fixed app shell with a pinned header and sidebar.
  • Replace pagination with infinite scroll (/api/watches feed + InfiniteGrid).
Results
  • One clean README.
  • Only the catalog grid scrolls.
  • Commit 2671bf5.
ModelInternal16:50

Monorepo migration + one canonical model_key per watch

Goal

Give each watch one canonical identity and move the repo to a monorepo.

Method
  • Add one canonical model_key per watch (with model_key_source, family_id, identity_tier).
  • Merge on the validated sales_code column; add a junk-ref guard; use a linear 3-phase build order; add audit_db.py, a read-only truth audit.
  • Date the JDM code eras from our own catalogs.
  • Convert to a pnpm/Turborepo monorepo: apps/web on the packages/ui design system, and pipeline/ outside the JS workspace.
Results
  • 40,332 appearances reconciled into 16,327 distinct SKUs and 4,941 case families (before, an inflated 20,276).
  • 200 family_id conflicts to 0; about 2,600 under-merges recovered.
  • Commit 2df9ddd.

August 7, 2026

AppInternal18:30

First browser: wristwatch UI, evidence view, EN/JP toggle

Goal

Show the extracted data to a reader, with honest confidence.

Method
  • Build a wristwatch-scoped browser with an identity block and an evidence view.
  • Show a confidence badge on each field.
  • Hide the price.
  • Add an English/Japanese toggle for labels, values, and line names.
  • Add a per-catalog confidence coverage dashboard.
  • Scope the facets to the subject and clean the URLs.
Results
  • The first browsable WatchDB.
  • We simplified it later, on 2026-08-08, to the collector model.
ModelInternal18:09

Schema gate + per-field confidence tiers

Goal

Keep the data honest, with one vocabulary and a confidence tier per field.

Method
  • Add a schema gate (schema.py): single-source vocabularies, validate_row, and a schema.json export.
  • Route rows that fail the schema to a violations table.
  • Derive sales_code and the marketing line as distinct identity fields.
  • Set a rule-based confidence tier per field, cross-checked against the caliber DB, the era, and the vision pass.
Results
  • Every field carries a confidence tier and a source.
  • Only schema-valid values reach the app.

August 6, 2026

DataInternal14:53

Catalog extraction: PDFs to one row per watch (Gemini on Google Cloud)

Goal

Turn scanned Seiko catalog PDFs into one data row per watch photo.

Method
  • Download about 129 Seiko catalog PDFs from the Internet Archive (01_download_catalogs.py).
  • Render each page to PNG with PyMuPDF at 300 DPI.
  • Read each page with Gemini 2.5 Flash on Google Cloud Vertex AI (aiplatform.googleapis.com), with an OpenRouter fallback.
  • Per page, detect each product unit by bounding box, transcribe the full page text, read each caption on its own text crop, link captions to photos with a numbered-box overlay, and cut three crops per watch.
  • Run a second vision pass with Gemini 2.5 Flash-Lite (vision_colours.py) to read dial colour, case colour, shape, and strap from each photo.
Results
  • One row per watch photo across the catalogs, each with its verbatim caption and crops.
  • The caption gives sales code, jewels, material, caliber, case number, and line; the photo gives colour and shape.
  • This is the origin of every watch in the database.