Skip to content

Venue validity review — Naver-priority closeout

Status: Source prepared; later identity review narrows merges and extends repairs through 00598; hosted changes undeployed

Capture: 2026-09-04 · 3,492 open venues (paged REST generation, not a transaction snapshot)

Authority: docs/canon/venue-data.md

Release check: On 2026-09-05, npx supabase migration list --linked showed the linked hosted project through 00584; migrations 0058500596 remain unapplied there. No edge deploy command ran in this audit, and the linked versions of the three changed venue functions were not independently queried.

Question and decision rule

This review asks whether open venue records identify a real tennis-related place, use the right product kind, and carry metadata from the correct source. Naver Place leads every fact it publishes. Kakao, TMAP, government sources, operator pages, and review evidence corroborate or fill a gap. court_count is the exception: a government or explicit court roster leads, while Naver menu text can only corroborate.

The review combined a full deterministic corpus pass, a fresh Naver Place re-fetch for 41 high-risk rows, two independent blind kind judgements for disputed venues, and direct inspection of the four remaining unknown records. A correction is audit-pinned only when the evidence identifies the same venue and the classification is high confidence.

The captured baseline contained 2,441 full_court, 831 practice, 192 shop, 24 screen, and four unknown rows. Metadata was present for 3,219 addresses, 3,489 pins, 3,463 district links, 2,106 phones, 758 hour schedules, 515 fee schedules, 1,180 amenity lists, and 60 court counts. Those are coverage measurements, not claims that every populated value is correct. The original eleven-merge projection is superseded by the fresh September 5 identity review. Only seven merges remain; numbered-court/parent scopes are held apart, and one evacuation POI is separately excluded. Prepared migrations now extend through 00598. Any final hosted counts must come from a post-deployment capture, not an earlier arithmetic projection or local rehearsal.

Corpus findings

MeasureResult
Open venues3,492
Naver-backed venues2,767
Naver plus another source family2,281
Two or more source families2,527
C1–C9 findings before this pass171 venues
C1–C9 findings after checker corrections156 venues
C10 local-map provenance findings430 observations / 388 venues
C10 Naver Place identity findings12 observations / 12 venues
Unique venues with any C10 finding392 venues
Unique venues with any deterministic finding530 venues
Clean by the expanded deterministic checks84.8%

The net C1–C9 change from 171 to 156 is not silent data deletion. Among the predicate corrections, the checker stopped treating single-tier Sejong as if it required a district child, removing 29 false findings; it added eight real findings for court_count on non-court kinds and now also catches three stale nonzero source_count values on venues with no observations. The residual 156 are a review queue, not 156 proven-invalid venues.

The expanded C10 pass found two systemic classes: an observation can contain plausible metadata yet be attached to the wrong canonical venue, and two rows can look independent while both were manufactured from one merged seed. Among 5,033 captured Kakao, Naver Local, and TMAP observations, 4,603 retain independently checkable provider name-and-pin evidence. Another 360 fail the attachment predicate: 324 missing names, 25 name mismatches, nine generic-only names, four missing-coordinate findings, and one distance violation; three observations fail more than one predicate. The final 70 are 35 exact Kakao/Naver pairs with the canonical name, byte-identical long-decimal centroid, and identical timestamp—the legacy seed projection signature, not two independent provider pins. The review separated eight visibly wrong attachments, 402 refetchable missing/generic/synthetic rows, and 20 ambiguous aliases or sibling facilities. Two coordinate findings are exact Naver rows for canonical venues whose pins were null; their Naver pins agree with Kakao and can safely repair the venue before re-evaluation.

Naver Place needed its own accounting because the legacy scraper retained the exact Place ID and page name but omitted the page pin. Of 2,396 captured Place observations, 2,384 have a meaningful name agreement, seven have no stored page name, and five say only a generic 테니스장; none of that capture is fully pin-auditable. The prepared targeted recheck attempted 41 high-risk Place rows: 37 produced attached observations with fresh names and pins, while four record only the attempted scrape time because no attachable snapshot was produced. All 37 came from the meaningful-name cohort. Migration 00588 later adds fresh Place snapshots for three of the marker-only unknown venues and rewrites the already-attached Yeongju snapshot; those three had no captured legacy Place row, so they do not reduce the 2,384-row cohort. The 12 identity-deficient rows join the reversible refetch quarantine. If the exact-version guards pass and the prepared chain applies successfully, 2,347 named legacy rows are projected to remain active and are explicitly deferred as an exact Place-ID refetch cohort. Missing legacy pins are not misrepresented as proof that the page is wrong, and no pin or provider name is copied from the canonical row to manufacture evidence.

The 156 C1–C9 venues and 392 C10 venues are immutable pre-deployment capture measurements, with overlap between them. A subset is addressed by the prepared migrations, while other rows remain deliberate absence, conflict, or follow-up review. Because the hosted database has not been changed, the report does not subtract prepared fixes from the captured number; an exact residual requires a fresh post-deployment capture and checker run.

Among the 37 attached results, the live high-risk re-fetch matched stored Naver facts at 29/29 phones, 37/37 pins, 15/15 hours, 32/32 parking values, 34/36 addresses, 36/37 names, and 11/12 fee sets. Both address differences were deliberate 전남광주 normalisations. Kind agreed for 16/18 directly comparable rows; the two differences were reviewed rather than automatically overwritten.

Classification corrections

Two independent judges agreed that these ten commercial indoor rows are practice facilities, not regulation courts: 테니스탑, 테니스블라썸, 테니스레디, 피클테니스, 테니스랜드 창원대동, 에이테니스 동탄, 분당윔블던, 솔레아스 시흥목감, 테니스데이즈 그린, and 테니스리퍼블릭 목동. Their floor/unit evidence and photos show short training bays, lesson studios, or ball-machine lanes. 히포크라 was likewise changed from full_court to practice. 무안정구장 was changed from practice to full_court.

The migration pins these decisions with facility_kind_source = 'audit', aligns capabilities, and clears court_count wherever the final kind is not full_court.

Four formerly unknown records

Existing recordFinal kindNaver-leading identity and metadataCorroboration / deliberate deferral
더테니스shopNaver Place 13218752: exact pin, tennis-equipment category, 경기 광주시 초월읍 하오개길71번길 44-17, 0507-1403-8531, weekdays 09:00–18:00, weekend closed, parkingOperator site confirms the business. Its old screen-operation history is stale and does not make the current shop playable.
신세계테니스practiceCanonical display name 신세계스포렉스; Naver Place 16142652, phone 0507-1345-9821, weekday/weekend schedules, priced lesson menu, indoor/parking, ball machine and lessonsOperator site corroborates the facility. 신세계테니스 remains an alias. Naver's structured convenience list contains parking only; shower is stored separately as review/photo-derived evidence. “Court 2” signage is a bay label, so no court count is recorded.
테니스 TOPfull_courtNaver Place 1848278458: exact rooftop identity, 부산 기장군 정관읍 정관6로 5-20 6층 옥상, 0507-1314-4447, daily 08:00–22:00, outdoor, lighting and parkingOperator photos visibly show regulation courts, but count stays null because no government or court roster leads it. Nearby Place 1365674920 is a different business 275.9 m away and was rejected.
영주소프트테니스스포츠클럽full_courtNaver Place 1633274808 remains leading for name, category, address and 054-637-0099The current official club page and operating guide establish active operation, lessons, weekday 06:00–22:00 hours, weekend/holiday closure, and alternate phone 054-633-0099. The municipal inventory submitted 2017-11-10 records four clay courts and lighting on pages 4 and 14; its age remains explicit. Surface stays unset because it does not distinguish natural from artificial clay. A later roof-cover project, the 2022 enacted ordinance, which adds dome-court fees, and the current municipal facility listing support has_indoor = true; they do not prove all four courts are covered.

Duplicate consolidation

Coverage correction (2026-09-05): the 11 prepared closures below are a bounded adjudicated set, not a completed identity audit. The identity follow-up found 259 candidate pairs across 449 venues by checking same names at all distances, near-coincident pins with different names, and reused provider IDs in observations. Its 142 repeated-name groups include 163 measurable same-name pairs beyond the former 150 m C7 limit. These candidates require adjudication and are not additional approved merges.

Eleven obsolete aliases across ten survivor records are closed with an explicit successor_venue_id. Conflict-free observations and external references move to the survivor; same-provider observations and conflicting references stay reachable on the closed alias instead of being discarded. The survivor is then re-resolved. The affected facilities are 봉화, 당진, 횡성, 정부세종청사, 완산, 세종수질복원센터 A, 함평, 동탄, 동부생활체육, and 군산월명 (which had two obsolete siblings). Human-curated loser rows are intentionally skipped.

Two names that look count-like — 영천실내테니스장(D코트) and 청주내수하수처리장 테니스장(C코트) — are retained as valid indexed court names. They are cleaned without manufacturing a numeric count.

Integrity fixes produced by the evidence

  • A shared attachment guard now requires a meaningful provider identity and finite proximity before any Kakao, Naver, Naver Place, or TMAP metadata can enter the observation ledger. Ordinary rows use 150 m; only canonical government-coordinate seeds use 300 m. Parent-only facilities, parking/entrance POIs, competing sports, and a different lettered/numbered court fail. The provider's own name and pin survive in the payload, and the privileged observation endpoint independently reloads the canonical venue and rejects the entire batch on one bad attachment. Migration 00594 repeats the contract at the observation RPC and map-seed database boundaries. A Naver seed must carry Naver's raw name and pin; cross_verified must carry separate raw Naver and Kakao names and pins. Canonical name, pin, phone, address, category, and homepage must match the retained Naver fields when Naver publishes them; Kakao may fill only a genuine Naver gap. The crawler persists both provider categories and the Naver business homepage, and never turns an averaged centroid into two provider pins. Generic English court labels and mixed tennis/competing-sport labels fail the same attachment rule.
  • The captured historical failures are not hard-deleted. A private, access-revoked quarantine archives the complete observation and reason before removing it from active corroboration. Exact observation/time guards skip anything refreshed since the capture. At most 440 captured rows across 392 venues are eligible after the two validated pin repairs: 358 direct local-map attachment failures, 70 synthetic Kakao/Naver seed projections, and 12 Naver Place rows with no meaningful identity. If every captured row is unchanged, the number of venues with two source families falls from 2,527 to 2,227; 300 venues—including 225 captured as full_court—correctly return to unconfirmed status pending refetch. This is a trust correction, not venue removal. For the 35 synthetic-pair venues specifically, 23 retain one Naver Place family and are downgraded to Naver-only; 12 retain no provider family. Their unsupported historical centroids remain only as low-confidence refetch anchors with coordinate provenance removed, while the one exact Naver Place pin refreshed in 00585 remains medium-confidence.
  • inferCourtCount now accepts only an explicit Korean N면 expression, bounded to 1–40. Generic 코트 18, time ranges, lesson durations, indexed court letters, ranged counts, and approximate/maximum/minimum wording no longer become exact inventory counts.
  • The row invariant clears court_count for practice, screen, shop, and unknown.
  • The resolver no longer accepts a Naver-only count. The 35 canonical values whose recorded count provenance was only Naver are cleared unless a structured roster, government observation, or human decision protects them; matching Naver evidence can still raise confidence in an authoritative value.
  • The 1–40 count contract now reaches every canonical write path: scalar and roster correction validation, direct roster synchronization, the client schema/editor, the government bulk writer, both legacy generic enrichment routes, and the two legacy venue bridges. A Naver-seeded public count remains raw evidence only. A club count may seed manual field provenance only when a venue_id-null bridge row actually creates the canonical venue; linked rows and later club edits stay club-local, and the narrow creation path manufactures no identity corroboration. A club picker may retain the Naver Place ID, but only a later guarded provider refetch establishes a Naver family vote. A structured roster derives the scalar and stamps roster provenance, without relabelling 00338's legacy scalar-generated rosters as host evidence. Naver-origin or unprovenanced scalar writes fail closed instead of bypassing the resolver. Deployment likewise aborts on an existing active-roster/non-court contradiction so neither legacy side is silently promoted over the other.
  • The public-directory facet now means a government-primary seed (kspo, eshare, or std_data), not every row that happens to have a government observation and not the polluted legacy venue_type = 'public_court' field.
  • Observation emission resolves facts, applies the reviewed kind profile, and then applies the Naver display name. Both SQL and live rails use that order; the live edge path now returns a failure when either follow-up RPC fails instead of reporting partial success.
  • Observation record, quarantine, and exact-restore writers acquire canonical venue locks before attachment validation or observation mutation, in UUID order for batches. This removes the reproduced inverse-lock deadlock and prevents the canonical snapshot or archived venue state from changing while a writer waits.
  • Naver Place category and rating markers now repeat facts already present on the same raw Place observation. A Place ID alone cannot authorize arbitrary ratings: each value must exactly match its active observation and be within the database domain. Matching human kind evidence protects the coupled kind/capability/closure state and any separately authoritative count through the legacy review classifier; a stale mismatching human observation confers no protection.
  • The capture tool now requests ownership/surface fields, counts explicit boolean evidence, understands single-tier regions, and reports source-family and metadata coverage. The committed corpus generation predates those two added columns and was not relabelled as if it had been refreshed.
  • source_count is recomputed from the observation ledger for every venue, repairing the three stale nonzero values the checker exposed. New rows default to zero until an observation proves a source family, preventing both bare inserts and the capture's paged timing from being mistaken for a hand-authored source claim.

Deferred, because the evidence does not justify a value

  • Re-fetch the remaining 2,347 named legacy Naver Place observations in bounded exact-ID cohorts so each leading-source row retains a current page pin. The capture contained 2,384 such rows; 37 have a prepared attached refresh. The remaining Place ID/name agreement is useful evidence, but this capture cannot independently replay the proximity decision.
  • Do not convert missing has_indoor, has_lighting, or other booleans to false; absence of an observation is unknown. The current corpus has 251 explicit indoor observations and no explicit lighting observations.
  • Do not mass-correct venue_type or public_access. Almost every row carries the same legacy public value, so the fields cannot currently support an ownership claim. The user-visible public facet uses the narrower government-primary seed rule while an ownership-specific source contract is designed.
  • Do not populate court_count from photos or menu numbering. The database currently has no captured venue_courts rows; counts need government evidence, an explicit roster, an approved venue correction, or the narrow new-venue club-bridge assertion described above.
  • Fee and operating-hour editor contract drift is a separate product/schema slice. This audit preserves verified Naver payloads without expanding its scope into an editor redesign.

Change set and verification contract

Migrations 0058500589 record the fresh Naver observations, repair the public facet, apply kind/count integrity, adjudicate the unknowns, and consolidate duplicates. Migration 00592 closes the scalar/roster 40-court and government-provenance boundaries after the intervening decompositions, stops club-picker fields from manufacturing Naver evidence, and persists a Naver homepage marker only when the same URL exists in a guarded Naver observation. Migration 00593 performs the race-safe historical provenance quarantine and the two evidence-backed pin repairs. Migration 00594 prevents new map observations or map-origin seeds from manufacturing provider identity or coordinates, refreshes provider snapshots on valid re-ingest, requires canonical Naver-led seed fields to equal the raw Naver evidence, validates exact restoration, and removes direct client or service-role table writes around the guarded RPCs. Migration 00595 prevents a later weaker or evidence-free merge from overwriting or relabelling canonical fields already owned by a human, Naver Place, or Naver Local, including facility_kind; it gives a Naver marker authority only while a live observation contains the same normalized value, serializes same-hash and observation mutations, and rejects provider-category claims without the matching guarded raw record. Recognized government kinds remain lower-rank gap fills. Migration 00596 restricts the legacy enrichment marker path: provider IDs/categories must match active observations, each rating must equal the domain-valid value on the matching active Naver Place observation, and direct resolved-fact or classification writes are stripped before the earlier court-count and homepage guards run. Human-observation-owned classification state and authoritative counts survive the delegated legacy classifier. pgTAP covers the facet boundary, non-court count invariant, human-protection and duplicate-retention behavior, four unknowns, successor links, every server court-count boundary, quarantine access/idempotence/restoration, source-family recomputation, stale derived-phone retraction, raw seed provenance, and atomic map-observation rejection. Node and UI tests cover observation ordering, count parsing, audit-report semantics, provider attachment boundaries, edge-route rejection, and editor bounds; yarn test:scripts runs the script suites directly and is part of the normal yarn test / yarn check gate. The local migration chain through 00596 rebuilds cleanly; the eight-file focused run passes 307 assertions, the full database run passes 1,251 assertions across 81 files, and all seven script suites pass.

Deployment and rollback boundary

This source set is prepared, not deployed. The September 5 continuation records a successful ordered 0058500598 business-corpus rehearsal and fresh Naver evidence. A full recovery point and owner-approved maintenance window remain required; the earlier 11-pair merge proposal is not an unconditional authorization to close those rows. 00589 now retains seven pairs and aborts on incomplete/closed/redirected targets, human curation, changed captured identity, or nonempty operational/user relations. Four ambiguous proposals and all court-to-parent Naver transfers were withdrawn. 00597 protects future alias writes; 00598 adds scoped source, category and pin repairs with recovery snapshots. Before changing the linked project, confirm that the remote still ends at 00584, review the full ordered dry run—including the interleaved 00590 and 00591 decompositions—and take a verified database recovery snapshot. The full-chain rehearsal must clear the six 00592 preflight classes: venue scalar counts outside 1–40; active rosters above 40; active rosters on a non-full_court kind; club-local counts outside 1–40; legacy correction counts outside 1–40; and malformed, empty, oversized, or out-of-range JSON count/roster correction proposals.

In one maintenance window, pause venue writers, deploy the backward-compatible guarded enrich-venues, ingest-venues, and corroborate-venues functions so new calls stop producing legacy evidence shapes, and then apply the migrations numerically. supabase db push applies individual migration files sequentially; the fourteen-file window is not one all-or-nothing transaction, so a failure can leave an earlier prefix applied. Confirm the remote head is 00598 and run read-only venue probes before resuming writers. Do not cherry-pick only the later quarantine/provenance migrations, and do not publish the client update until database application and a hosted venue smoke pass.

After application, recapture the hosted corpus and rerun the deterministic checker before reporting final counts. Before deployment, a source revert is sufficient. After database application, a Git revert is not a database rollback: use an owner-reviewed forward migration or restore the verified recovery point, understanding that a restore can discard intervening writes. The 00593 private restore path only restores one exact archived observation when its provider slot is empty and current attachment validation passes. It does not undo 00587 count clearing, 00588 adjudications, 00589 alias closures/observation moves, or later function and privilege replacements. Redeploy older edge code only after checking it against the applied database contract. No OTA rollback exists for this source closeout because no OTA was produced.

This document is an audit record, not proof that every missing field is knowable. Standard pgTAP corpus assertions are sentinels when production IDs are absent locally, so the four unknowns and shared-survivor merge were also replayed with disposable production-shaped fixtures. The safe claim is that the selected high-risk rows were adjudicated under the canonical source rule, systemic errors were converted into executable checks, historical attachment failures are reversibly removed from corroboration when their captured version still matches, and unresolved absence/conflict stays visible instead of being guessed. It is not a claim that all 3,492 rows are fully verified: the named legacy Place refetch cohort is explicit. After deployment, re-capture the hosted corpus before claiming a smaller exact queue.

Markdown remains the source of truth. Run yarn docs:check before handoff.