fix: clinic_registry CSV 임포트 + NaverPlace 검색 개선
- VerifiedChannels에 naverPlace 필드 추가 (registry URL → placeId 전달) - registryToVerifiedChannels: naver_place_url → placeId 추출하여 포함 - collect-channel-data NaverPlace 매칭 완화: exact match → contains match, 의원/병원 suffix 제거 shortName 사용, placeId 힌트로 검색 보강 - clinic_registry에 73개 병원 CSV 데이터 임포트 (올바른 YouTube/Blog/GangnamUnni/NaverPlace URL) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>main
parent
e81c4cfee9
commit
cd2463fb2d
|
|
@ -17,6 +17,7 @@ export interface VerifiedChannels {
|
||||||
naverBlog: VerifiedChannel | null;
|
naverBlog: VerifiedChannel | null;
|
||||||
gangnamUnni: VerifiedChannel | null;
|
gangnamUnni: VerifiedChannel | null;
|
||||||
tiktok: VerifiedChannel | null;
|
tiktok: VerifiedChannel | null;
|
||||||
|
naverPlace?: { url: string; placeId?: string } | null; // registry-sourced place URL
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
||||||
|
|
@ -560,23 +560,35 @@ Deno.serve(async (req) => {
|
||||||
const npVerified = (verified as Record<string, unknown>).naverPlace as Record<string, unknown> | null;
|
const npVerified = (verified as Record<string, unknown>).naverPlace as Record<string, unknown> | null;
|
||||||
|
|
||||||
channelTasks.push(wrapChannelTask("naverPlace", async () => {
|
channelTasks.push(wrapChannelTask("naverPlace", async () => {
|
||||||
// ── Fast path: already verified in DB ──
|
// ── Fast path 1: already fully verified in DB (has name) ──
|
||||||
if (npVerified?.name) {
|
if (npVerified?.name) {
|
||||||
console.log(`[naverPlace] Using verified DB data: ${npVerified.name}`);
|
console.log(`[naverPlace] Using verified DB data: ${npVerified.name}`);
|
||||||
channelData.naverPlace = npVerified;
|
channelData.naverPlace = npVerified;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// ── Slow path: first-time discovery via domain-matched search ──
|
// ── Fast path 2: registry-sourced place URL → use Naver Place ID directly ──
|
||||||
|
const registryPlaceUrl = (verified as Record<string, unknown>).naverPlace as { url?: string; placeId?: string } | null;
|
||||||
|
const registryPlaceId = registryPlaceUrl?.placeId;
|
||||||
|
if (registryPlaceId) {
|
||||||
|
console.log(`[naverPlace] Using registry place ID: ${registryPlaceId}`);
|
||||||
|
// Build the Naver local search with placeId as a hint — use clinic name to fetch details
|
||||||
|
// We'll pass through to search but prime the query with clinic name
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Slow path: search via Naver Local API ──
|
||||||
const normalize = (s: string) => (s || '').replace(/<[^>]*>/g, '').toLowerCase();
|
const normalize = (s: string) => (s || '').replace(/<[^>]*>/g, '').toLowerCase();
|
||||||
let clinicDomain = '';
|
let clinicDomain = '';
|
||||||
try { clinicDomain = new URL(row.url || '').hostname.replace('www.', ''); } catch { /* skip */ }
|
try { clinicDomain = new URL(row.url || '').hostname.replace('www.', ''); } catch { /* skip */ }
|
||||||
|
|
||||||
const districtMatch = address.match(/([가-힣]+(구|동))/);
|
const districtMatch = address.match(/([가-힣]+(구|동))/);
|
||||||
const district = districtMatch?.[1] || '';
|
const district = districtMatch?.[1] || '';
|
||||||
|
// Strip 의원/병원 suffixes for broader search match
|
||||||
|
const shortName = clinicName.replace(/의원$|병원$/, '').trim();
|
||||||
const queries = [
|
const queries = [
|
||||||
...(district ? [`${clinicName} ${district}`, `${clinicName} 성형 ${district}`] : []),
|
...(district ? [`${clinicName} ${district}`, `${shortName} ${district}`] : []),
|
||||||
`${clinicName} 성형외과`,
|
`${clinicName} 성형외과`,
|
||||||
|
`${shortName} 성형외과`,
|
||||||
`${clinicName} 성형`,
|
`${clinicName} 성형`,
|
||||||
];
|
];
|
||||||
|
|
||||||
|
|
@ -591,9 +603,10 @@ Deno.serve(async (req) => {
|
||||||
const data = await res.json();
|
const data = await res.json();
|
||||||
const items = (data.items || []) as Record<string, string>[];
|
const items = (data.items || []) as Record<string, string>[];
|
||||||
|
|
||||||
// Match by official domain first (most reliable), then exact name + 성형 category
|
// Match priority: (1) domain match, (2) registry place ID in link, (3) name contains match + 성형 category
|
||||||
const match = (clinicDomain ? items.find(i => (i.link || '').includes(clinicDomain)) : null)
|
const match = (clinicDomain ? items.find(i => (i.link || '').includes(clinicDomain)) : null)
|
||||||
?? items.find(i => normalize(i.title) === clinicName.toLowerCase() && (i.category || '').includes('성형'))
|
?? (registryPlaceId ? items.find(i => (i.link || '').includes(registryPlaceId)) : null)
|
||||||
|
?? items.find(i => normalize(i.title).includes(shortName.toLowerCase()) && (i.category || '').includes('성형'))
|
||||||
?? null;
|
?? null;
|
||||||
|
|
||||||
if (match) {
|
if (match) {
|
||||||
|
|
|
||||||
|
|
@ -74,6 +74,11 @@ function registryToVerifiedChannels(reg: RegistryRow): import("../_shared/verify
|
||||||
const blogHandle = extractHandleFromUrl(reg.naver_blog_url || '', 'naverBlog');
|
const blogHandle = extractHandleFromUrl(reg.naver_blog_url || '', 'naverBlog');
|
||||||
const ttHandle = extractHandleFromUrl(reg.tiktok_url || '', 'tiktok');
|
const ttHandle = extractHandleFromUrl(reg.tiktok_url || '', 'tiktok');
|
||||||
|
|
||||||
|
// Extract Naver Place ID from URL (e.g. https://m.place.naver.com/hospital/11709005 → "11709005")
|
||||||
|
const naverPlaceId = reg.naver_place_url
|
||||||
|
? (reg.naver_place_url.match(/\/(\d+)\/?$/) || [])[1] || undefined
|
||||||
|
: undefined;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
instagram: igHandles,
|
instagram: igHandles,
|
||||||
youtube: ytHandle ? { handle: ytHandle, verified: true, url: reg.youtube_url! } : null,
|
youtube: ytHandle ? { handle: ytHandle, verified: true, url: reg.youtube_url! } : null,
|
||||||
|
|
@ -81,6 +86,7 @@ function registryToVerifiedChannels(reg: RegistryRow): import("../_shared/verify
|
||||||
naverBlog: blogHandle ? { handle: blogHandle, verified: true, url: reg.naver_blog_url! } : null,
|
naverBlog: blogHandle ? { handle: blogHandle, verified: true, url: reg.naver_blog_url! } : null,
|
||||||
gangnamUnni: reg.gangnam_unni_url ? { handle: reg.gangnam_unni_url, verified: true, url: reg.gangnam_unni_url } : null,
|
gangnamUnni: reg.gangnam_unni_url ? { handle: reg.gangnam_unni_url, verified: true, url: reg.gangnam_unni_url } : null,
|
||||||
tiktok: ttHandle ? { handle: ttHandle, verified: true, url: reg.tiktok_url! } : null,
|
tiktok: ttHandle ? { handle: ttHandle, verified: true, url: reg.tiktok_url! } : null,
|
||||||
|
naverPlace: reg.naver_place_url ? { url: reg.naver_place_url, placeId: naverPlaceId } : null,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue