add brand consistency generation

db-migration
jaehwang 2026-06-02 11:29:31 +09:00
parent b6a0134ba7
commit 35e5e98524
4 changed files with 83 additions and 15 deletions

View File

@ -1,7 +1,7 @@
import os
from pydantic import BaseModel
from common.utils import get_env
from integrations.llm.schemas.report import ReportInput, ReportOutput, YouTubeDiagnosisInput, YouTubeDiagnosisOutput
from integrations.llm.schemas.report import ReportInput, ReportOutput, YouTubeDiagnosisInput, YouTubeDiagnosisOutput, BrandConsistencyInput, BrandConsistencyOutput
from integrations.llm.schemas.plan import PlanInput, PlanOutput
from integrations.llm.schemas.market import (
MarketCompetitorsInput, MarketCompetitorsOutput,
@ -87,3 +87,10 @@ youtube_diagnosis_prompt = Prompt(
input_class=YouTubeDiagnosisInput,
output_class=YouTubeDiagnosisOutput,
)
brand_consistency_prompt = Prompt(
file_name="brand_consistency_prompt.txt",
prompt_model="REPORT_MODEL",
input_class=BrandConsistencyInput,
output_class=BrandConsistencyOutput,
)

View File

@ -355,3 +355,18 @@ class YouTubeDiagnosisInput(BaseModel):
class YouTubeDiagnosisOutput(BaseModel):
diagnosis: list[DiagnosisItem]
# --- BrandConsistency ---
class BrandConsistencyInput(BaseModel):
clinic_name: str | None = None
mainpage: str | None = None
instagram: str | None = None
facebook: str | None = None
youtube: str | None = None
gangnam_unni: str | None = None
class BrandConsistencyOutput(BaseModel):
brand_inconsistencies: list[BrandInconsistency]

View File

@ -0,0 +1,18 @@
다음은 성형외과/피부과 {clinic_name} 의 채널별 브랜드 데이터입니다.
공식 홈페이지: {mainpage}
인스타그램: {instagram}
페이스북: {facebook}
유튜브: {youtube}
강남언니: {gangnam_unni}
위 채널들 간의 브랜드 불일치 항목을 분석해줘.
비교 대상 필드 예시: 병원명(한글/영문), 전화번호, 주소, 로고, 슬로건, 소개 문구 등.
각 항목은 다음 JSON 형식의 배열로 출력해줘:
- field: 불일치 필드명
- values: 채널별 실제 값 목록 (channel, value, is_correct)
- impact: 불일치가 브랜드에 미치는 영향
- recommendation: 개선 권고사항
출처 번호([1], [2] 등)는 포함하지 마.

View File

@ -8,10 +8,9 @@ from common.db.run import select_run, update_run_report, update_run_plan
from common.db.source import select_run_raw_data, select_run_mainpage_url
from common.db.market import select_market
from integrations.llm.llm_service import LLMService
from integrations.llm.prompt import report_prompt, plan_prompt, youtube_diagnosis_prompt
from integrations.llm.schemas.report import ReportOutput, ClinicSnapshot, YouTubeAudit
from integrations.llm.prompt import report_prompt, plan_prompt, youtube_diagnosis_prompt, brand_consistency_prompt
from integrations.llm.schemas.report import ReportOutput, ClinicSnapshot, YouTubeAudit, BrandConsistencyOutput
from integrations.llm.schemas.plan import PlanOutput
from models.status import AnalysisStatus
logger = logging.getLogger(__name__)
@ -233,15 +232,23 @@ async def _build_overrides(analysis_run_id: str) -> dict:
if instagram.get("username"): ig_patch["profile_link"] = f"https://www.instagram.com/{instagram['username']}/"
# ── facebook ──────────────────────────────────────────────────────────────
fb_patch: dict = {}
if facebook.get("pageUrl"): fb_patch["url"] = facebook["pageUrl"]
if facebook.get("pageUrl"): fb_patch["link"] = facebook["pageUrl"]
if facebook.get("pageName"): fb_patch["page_name"] = facebook["pageName"]
if facebook.get("followers"): fb_patch["followers"] = facebook["followers"]
if facebook.get("intro"): fb_patch["bio"] = facebook["intro"]
if facebook.get("categories"): fb_patch["category"] = ", ".join(facebook["categories"])
if facebook.get("website"): fb_patch["linked_domain"] = facebook["website"]
fb_pages: dict = {}
if facebook.get("pageUrl"): fb_pages["url"] = facebook["pageUrl"]
if facebook.get("pageUrl"): fb_pages["link"] = facebook["pageUrl"]
if facebook.get("pageName"): fb_pages["page_name"] = facebook["pageName"]
if facebook.get("followers"): fb_pages["followers"] = facebook["followers"]
if facebook.get("intro"): fb_pages["bio"] = facebook["intro"]
if facebook.get("categories"): fb_pages["category"] = ", ".join(facebook["categories"])
if facebook.get("website"): fb_pages["linked_domain"] = facebook["website"]
brand = await generate_brand_consistency(analysis_run_id)
brand_patch = brand.model_dump()["brand_inconsistencies"]
fb_patch: dict = {}
if fb_pages:
fb_patch["pages"] = [fb_pages]
if brand_patch:
fb_patch["brand_inconsistencies"] = brand_patch
overrides: dict = {}
if snapshot:
@ -249,7 +256,7 @@ async def _build_overrides(analysis_run_id: str) -> dict:
if ig_patch:
overrides["instagram_audit"] = {"accounts": [ig_patch]}
if fb_patch:
overrides["facebook_audit"] = {"pages": [fb_patch]}
overrides["facebook_audit"] = fb_patch
if yt_patch:
overrides["youtube_audit"] = yt_patch
return overrides
@ -268,8 +275,10 @@ def _deep_merge(base: dict, overrides: dict) -> dict:
return base
def _patch_report(result: ReportOutput, overrides: dict) -> ReportOutput:
merged = _deep_merge(result.model_dump(), overrides)
return ReportOutput(**merged)
base = result.model_dump()
for key in overrides:
base.pop(key, None)
return ReportOutput(**_deep_merge(base, overrides))
_MOCK_DOMAINS = {"viewclinic.com"}
@ -294,6 +303,24 @@ def _load_mock_plan() -> PlanOutput:
return PlanOutput(**json.load(f))
async def generate_brand_consistency(analysis_run_id: str) -> BrandConsistencyOutput:
raw = await select_run_raw_data(analysis_run_id)
def _json(v) -> str | None:
return json.dumps(v, ensure_ascii=False) if v else None
mainpage = raw.get("mainpage") or {}
input_data = {
"clinic_name": mainpage.get("clinicName"),
"mainpage": _json(mainpage),
"instagram": _json(raw.get("instagram")),
"facebook": _json(raw.get("facebook")),
"youtube": _json(raw.get("youtube")),
"gangnam_unni": _json(raw.get("gangnam_unni")),
}
return await LLMService(provider="perplexity").generate(brand_consistency_prompt, input_data)
async def run_report_task(analysis_run_id: str) -> None:
logger.info("[report] start run=%s", analysis_run_id)
if await _is_mock(analysis_run_id):
@ -302,6 +329,7 @@ async def run_report_task(analysis_run_id: str) -> None:
result.youtube_audit.linked_urls = []
else:
result = await generate_report(analysis_run_id)
result = _patch_report(result, await _build_overrides(analysis_run_id))
await update_run_report(analysis_run_id, result.model_dump())
logger.info("[report] done run=%s", analysis_run_id)