o2o-infinith-demo/supabase/functions/analyze-market/index.ts

114 lines
4.0 KiB
TypeScript

import "@supabase/functions-js/edge-runtime.d.ts";
const corsHeaders = {
"Access-Control-Allow-Origin": "*",
"Access-Control-Allow-Headers":
"authorization, x-client-info, apikey, content-type",
};
interface AnalyzeRequest {
clinicName: string;
services: string[];
address: string;
scrapeData?: Record<string, unknown>;
}
Deno.serve(async (req) => {
if (req.method === "OPTIONS") {
return new Response("ok", { headers: corsHeaders });
}
try {
const { clinicName, services, address } =
(await req.json()) as AnalyzeRequest;
const PERPLEXITY_API_KEY = Deno.env.get("PERPLEXITY_API_KEY");
if (!PERPLEXITY_API_KEY) {
throw new Error("PERPLEXITY_API_KEY not configured");
}
// Run multiple Perplexity queries in parallel
const queries = [
{
id: "competitors",
prompt: `${address} 근처 ${services.slice(0, 3).join(", ")} 전문 성형외과/피부과 경쟁 병원 5곳을 분석해줘. 각 병원의 이름, 주요 시술, 온라인 평판, 마케팅 채널(블로그, 인스타그램, 유튜브)을 포함해줘. JSON 형식으로 응답해줘.`,
},
{
id: "keywords",
prompt: `한국 ${services.slice(0, 3).join(", ")} 관련 검색 키워드 트렌드를 분석해줘. 네이버와 구글에서 월간 검색량이 높은 키워드 20개, 경쟁 강도, 추천 롱테일 키워드를 JSON 형식으로 제공해줘.`,
},
{
id: "market",
prompt: `한국 ${services[0] || "성형외과"} 시장 트렌드 2025-2026을 분석해줘. 시장 규모, 성장률, 주요 트렌드(비수술 시술 증가, AI 마케팅, 외국인 환자 유치 등), 마케팅 채널별 효과를 JSON 형식으로 제공해줘.`,
},
{
id: "targetAudience",
prompt: `${clinicName || services[0] + " 병원"}의 잠재 고객을 분석해줘. 연령대별, 성별, 관심 시술, 정보 탐색 채널(강남언니, 바비톡, 네이버, 인스타), 의사결정 요인을 JSON 형식으로 제공해줘.`,
},
];
const perplexityResults = await Promise.allSettled(
queries.map(async (q) => {
const response = await fetch(
"https://api.perplexity.ai/chat/completions",
{
method: "POST",
headers: {
"Content-Type": "application/json",
Authorization: `Bearer ${PERPLEXITY_API_KEY}`,
},
body: JSON.stringify({
model: "sonar",
messages: [
{
role: "system",
content:
"You are a Korean medical marketing analyst. Always respond in Korean. Provide data in valid JSON format when requested.",
},
{ role: "user", content: q.prompt },
],
temperature: 0.3,
}),
}
);
const data = await response.json();
return {
id: q.id,
content: data.choices?.[0]?.message?.content || "",
citations: data.citations || [],
};
})
);
const analysis: Record<string, unknown> = {};
for (const result of perplexityResults) {
if (result.status === "fulfilled") {
const { id, content, citations } = result.value;
let parsed = content;
const jsonMatch = content.match(/```json\n?([\s\S]*?)```/);
if (jsonMatch) {
try {
parsed = JSON.parse(jsonMatch[1]);
} catch {
// Keep as string if JSON parse fails
}
}
analysis[id] = { data: parsed, citations };
}
}
return new Response(
JSON.stringify({
success: true,
data: { clinicName, services, address, analysis, analyzedAt: new Date().toISOString() },
}),
{ headers: { ...corsHeaders, "Content-Type": "application/json" } }
);
} catch (error) {
return new Response(
JSON.stringify({ success: false, error: error.message }),
{ status: 500, headers: { ...corsHeaders, "Content-Type": "application/json" } }
);
}
});