o2o-infinith-demo/data/clinic-registry/update_csv.py
Haewon Kam d5f7f24e0a feat: clinic registry DB + pipeline audit P0 fixes
## Clinic Registry
- data/clinic-registry/clinic_registry_working.csv — 91개 병원 채널 마스터 DB
- data/clinic-registry/INFINITH_Outbound_List.csv — BD팀 아웃바운드 리스트 (17컬럼)
- data/clinic-registry/update_csv.py — 안전 CSV 업데이트 스크립트 (빈 필드만 채움)
- data/clinic-registry/extract_place_ids.py — 네이버 플레이스 ID 추출기
- scripts/import-registry.ts — CSV → Supabase clinic_registry 테이블 임포트
- supabase/migrations/20260406_clinic_registry.sql — clinic_registry 테이블 스키마

## Pipeline P0 Bug Fixes (전수 감사 후)
- fix(collect-channel-data): 강남언니 rating 0-10 스케일 오변환 제거
  - 기존: rating ≤ 5이면 ×2 → 4.8/10을 9.6/10으로 잘못 변환
  - 수정: Firecrawl 프롬프트가 이미 0-10 지시 → rawValue 직접 신뢰
- fix(generate-report): Perplexity 단일 fetch → fetchWithRetry 교체
  - maxRetries:2, backoffMs:[5000,15000], timeoutMs:90s
  - 기존: 타임아웃/429 시 리포트 생성 전체 실패
  - 수정: 자동 재시도로 일시적 API 오류 극복

## Docs
- docs/PIPELINE_IMPROVEMENT_PLAN.md — Sprint 0/1/2 완료 표시 + 전수 감사 결과 추가
- docs/REGISTRY_FUNCTIONAL_SPECS.md, DB_SCHEMA_V3.md 외 기획 문서 다수 추가

## New Components & Features
- supabase/functions/generate-content-plan, adjust-strategy — 콘텐츠 플랜/전략 조정
- src/components/plan/EditEntryModal, StrategyAdjustmentSection — 플랜 편집 UI
- supabase/functions/_shared/dataQuality, foundingYearExtractor, urlClassifier — 데이터 품질 유틸

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-07 09:33:25 +09:00

115 lines
4.0 KiB
Python

#!/usr/bin/env python3
"""CSV 업데이트 도우미 - 빈 필드만 안전하게 채움"""
import csv
import sys
import json
import os
CSV_PATH = os.path.join(os.path.dirname(__file__), 'clinic_registry_working.csv')
# Column indices
COLS = {
'hospital_name': 0, 'brand_group': 1, 'district': 2, 'branches': 3,
'website_kr': 4, 'website_en': 5,
'youtube_url': 6, 'youtube_note': 7,
'instagram_kr_url': 8, 'instagram_kr_note': 9,
'instagram_en_url': 10, 'instagram_en_note': 11,
'facebook_url': 12, 'facebook_note': 13,
'tiktok_url': 14, 'tiktok_note': 15,
'gangnam_unni_url': 16, 'gangnam_unni_note': 17,
'naver_blog_url': 18, 'naver_blog_note': 19,
'naver_place_url': 20, 'naver_place_reviews_note': 21,
'google_maps_url': 22, 'google_reviews_note': 23,
}
def load_csv():
with open(CSV_PATH, 'r', encoding='utf-8') as f:
reader = csv.reader(f)
header = next(reader)
rows = list(reader)
return header, rows
def save_csv(header, rows):
with open(CSV_PATH, 'w', encoding='utf-8', newline='') as f:
writer = csv.writer(f)
writer.writerow(header)
writer.writerows(rows)
def ensure_row_length(row, min_len=24):
while len(row) < min_len:
row.append('')
return row
def update_hospital(rows, hospital_name, updates: dict):
"""
updates: {'youtube_url': 'https://...', 'instagram_kr_url': 'https://...', ...}
Only fills EMPTY fields. Never overwrites existing data.
Returns True if hospital found.
"""
for row in rows:
row = ensure_row_length(row)
if row[0].strip() == hospital_name.strip():
changed = []
for col_name, value in updates.items():
if col_name not in COLS:
print(f" ⚠️ Unknown column: {col_name}")
continue
idx = COLS[col_name]
if row[idx].strip() == '' and value.strip() != '':
row[idx] = value.strip()
changed.append(f"{col_name}={value.strip()[:50]}")
elif row[idx].strip() != '':
pass # Skip - already has data
if changed:
print(f"{hospital_name}: {', '.join(changed)}")
else:
print(f" ⏭️ {hospital_name}: no empty fields to fill")
return True
print(f"{hospital_name}: NOT FOUND in CSV")
return False
def batch_update(updates_list):
"""
updates_list: [{'hospital_name': '...', 'youtube_url': '...', ...}, ...]
"""
header, rows = load_csv()
count = 0
for item in updates_list:
name = item.pop('hospital_name', None)
if name:
if update_hospital(rows, name, item):
count += 1
save_csv(header, rows)
print(f"\n📊 Updated {count} hospitals. CSV saved.")
def print_coverage():
header, rows = load_csv()
cols_to_check = {
'website_kr': 4, 'youtube': 6, 'instagram_kr': 8,
'facebook': 12, 'tiktok': 14, 'gangnam_unni': 16,
'naver_blog': 18, 'naver_place': 20, 'google_maps': 22
}
total = len(rows)
print(f"\n{'Channel':15s} {'Filled':>6s}/{total} {'%':>5s}")
print("-" * 35)
for name, idx in cols_to_check.items():
filled = sum(1 for r in rows if len(r) > idx and r[idx].strip())
pct = filled * 100 // total if total > 0 else 0
bar = '' * (pct // 5) + '' * (20 - pct // 5)
print(f'{name:15s} {filled:3d}/{total} {pct:3d}% {bar}')
if __name__ == '__main__':
if len(sys.argv) > 1 and sys.argv[1] == 'coverage':
print_coverage()
elif len(sys.argv) > 1 and sys.argv[1] == 'update':
# Read JSON from stdin
data = json.load(sys.stdin)
if isinstance(data, list):
batch_update(data)
elif isinstance(data, dict):
batch_update([data])
else:
print("Usage:")
print(" python update_csv.py coverage")
print(" echo '[{...}]' | python update_csv.py update")