Skip to content

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-secret fail-closed on entry (verbatim fetch-weather/index.ts:789-796 pattern).
  • Cohort (owner-ratified): 50/run — club_pick rows 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 existing kakao observations (the --naver-gaps precedent) 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 as enrich-venues.mjs:103-125), match by name similarity + proximity.
  • On match: write via existing record_venue_observations RPC (provider kakao, phone/category/coords payload). The 00362 trigger flips source_count 1→2 automatically — the curated-gate crossing needs no further step. Then resolve_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:

sql
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):

TierPredicateLabel (ko)Badge variant
verifiedofficial | host_verified인증됨success
corroboratedcount ≥ 2 AND not unverified확인됨info
unconfirmedeverything 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 optional trust?: { verificationStatus: string; sourceCount: number } | null prop rendering VenueTrustBadge beside the name (DirectoryVenueCard's inline-badge pattern — NOT trailing, 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: VenueTrustBadge joins 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)

  1. Tier vocabulary — 3-tier (인증됨/확인됨/미확인, recommended) or add a 4th "신규 등록" tier for <24 h picks?
  2. 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).
  3. Scope — corroborate-venues stays club_pick-only (recommended); the gov-only 미확인 backlog remains the operator batch script.

Build order & effort

StepScopeShip
1corroborate-venues fn + secret + migration 00453 + pgTAPdeploy + db push (no OTA)
2wireframe trust states → owner reviewcockpit deploy
3tier helper + badge + fn projection + surfacesOTA
4docs/canon/i18n/testscommit

Phases 1 and 2 are independent — can run as parallel agents.

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