Venue Trust Pipeline — Implementation Plan
Status: Archived Last reviewed: 2026-09-06
Historical August plan, superseded by venue data canon. The owner retired the public 미확인/확인됨 tiers and source-count penalties on 2026-09-06. 인증됨 now requires verified ownership. One reliable source may establish venue identity; normal listing remains inclusive while missing legacy evidence is reviewed. The original plan below is retained as history, not current implementation guidance.
RATIFIED by owner 2026-08-07: 3 trust tiers · nightly 03:15 KST cron · the cron also drains the gov-only backlog (50/night, club_pick rows first). Goal: (1) fully-automated corroboration so pick-ingested venues (migration 00452) promote into the curated cohort with zero manual steps; (2) trust level clearly indicated on every venue surface.
Grounding: two research passes 2026-08-07 (enrichment/cron infra + trust-display surfaces); all file:line facts verified.
Phase 1 — Automated corroboration (server-only, no OTA)
1a. New edge fn corroborate-venues (weather-cron pattern A — the only precedented route for external-API calls, since Postgres never fetches external hosts directly in this repo):
- Auth:
x-cron-secretfail-closed on entry (verbatimfetch-weather/index.ts:789-796pattern). - Cohort (owner-ratified): 50/run —
club_pickrows below 2 families FIRST (user-facing latency matters most), then fill the remainder from the gov-only single-source backlog ordered by an anti-join on existingkakaoobservations (the--naver-gapsprecedent) so the whole directory converges over nightly runs without operator involvement. - Per venue: Kakao Local keyword search (name +
x/y+ radius ≈ 300 m — same matcher logic asenrich-venues.mjs:103-125), match by name similarity + proximity. - On match: write via existing
record_venue_observationsRPC (providerkakao, phone/category/coords payload). The 00362 trigger flipssource_count1→2 automatically — the curated-gate crossing needs no further step. Thenresolve_venue_facts([ids])to materialize facts +field_confidence(feeds the detail screen's trust ring). - No match: leave at count 1 (venue stays club-visible only; picker still finds it via Naver supplement). Log the miss.
1b. Secret: KAKAO_REST_API_KEY exists only in local .env today → npx supabase secrets set KAKAO_REST_API_KEY=... (owner-held key). Naver server creds already exist (NAVER_API_HUB_CLIENT_ID/SECRET) if a Naver re-check is ever added.
1c. Migration 00453 — schedule: pg_cron + net.http_post, idempotent reschedule guard (00444 pattern), daily 03:15 KST:
SELECT cron.schedule('corroborate-venues', '15 18 * * *', -- 03:15 KST
$$SELECT net.http_post(
url := get_app_config('supabase_url') || '/functions/v1/corroborate-venues',
headers := jsonb_build_object('x-cron-secret', get_app_config('cron_secret'), ...),
body := '{}'::jsonb, timeout_milliseconds := 150000);$$);1d. pgTAP: club_pick venue + kakao observation → source_count = 2 and passes the corroborated predicate.
Phase 2 — Trust indication (client, wireframe-first, OTA)
2a. Canonical tier derivation (domain, pure): deriveVenueTrustTier(verificationStatus, sourceCount):
| Tier | Predicate | Label (ko) | Badge variant |
|---|---|---|---|
verified | official | host_verified | 인증됨 | success |
corroborated | count ≥ 2 AND not unverified | 확인됨 | info |
unconfirmed | everything else (incl. fresh club_pick until the nightly run) | 미확인 | warning |
Recommendation: 3 visible tiers, reusing the existing register (인증됨/미확인 already live on DirectoryVenueCard; 확인됨 is new). A 4th "신규/단일 출처" tier for fresh picks is possible but corroboration runs nightly, so the window is <24 h — not worth a vocabulary slot. Naver-supplement rows keep the N badge (provenance, not trust).
2b. Canonical badge: VenueTrustBadge wrapper in status-badges.tsx + VENUE_TRUST_VARIANT map in status-variant-maps.ts (bounded-variant lint applies). Sibling of ConfidenceTierBadge.
2c. Data plumbing: SearchedVenue carries none of the trust fields today — add verification_status, source_count to VENUE_SEARCH_COLUMNS in the search-venues fn + port type + mapping (Naver rows: no tier, N badge only).
2d. Surfaces (single badge component everywhere):
VenueRow: new optionaltrust?: { verificationStatus: string; sourceCount: number } | nullprop renderingVenueTrustBadgebeside the name (DirectoryVenueCard's inline-badge pattern — NOTtrailing, which owns the chevron/✕).- Create-club picker results + picked rows.
DirectoryVenueCard: replace the local binary isVerified/isUnconfirmed logic with the canonical helper + badge (one implementation).- Club-scoped
VenueDetailScreen+ directory detail header: same badge; the detail screen's existing TrustRing/ConfidenceBars/SourceChips stay as the deep view.
2e. Wireframe first: add trust-badge states to the create-club picker frames + courts directory wireframe (kit primitives WFTrustRing/WFConfidenceBars/WFSourceChips exist; add a WFTrustBadge chip) → owner iterates → then build.
Phase 3 — Docs & enforcement
- canon/status-and-recruitment.md:
VenueTrustBadgejoins the canonical-wrapper list; canon/clubs.md provenance section links the tier table. - i18n: new
courts.trustTier.*keys (ko/en), matching the 미확인/인증됨 register. - Unit test for
deriveVenueTrustTier; rebuild-log entry; memory update.
Open questions (owner)
- Tier vocabulary — 3-tier (인증됨/확인됨/미확인, recommended) or add a 4th "신규 등록" tier for <24 h picks?
- Cadence — nightly 03:15 KST (recommended) vs hourly? Nightly means a picked court can stay 미확인 up to a day; hourly costs ~24× the Kakao calls (still tiny).
- Scope — corroborate-venues stays club_pick-only (recommended); the gov-only 미확인 backlog remains the operator batch script.
Build order & effort
| Step | Scope | Ship |
|---|---|---|
| 1 | corroborate-venues fn + secret + migration 00453 + pgTAP | deploy + db push (no OTA) |
| 2 | wireframe trust states → owner review | cockpit deploy |
| 3 | tier helper + badge + fn projection + surfaces | OTA |
| 4 | docs/canon/i18n/tests | commit |
Phases 1 and 2 are independent — can run as parallel agents.