Compare commits
No commits in common. "45a74ab9706799f837ddbc0adc8d5930fb6a7bd5" and "b6a0134ba76137ba421a8cb16f7adf1bb6589e6c" have entirely different histories.
45a74ab970
...
b6a0134ba7
|
|
@ -1,14 +1,7 @@
|
|||
import os
|
||||
from pydantic import BaseModel
|
||||
from common.utils import get_env
|
||||
from integrations.llm.schemas.report import (
|
||||
ReportInput, ReportOutput,
|
||||
CriticalIssuesInput, CriticalIssuesOutput,
|
||||
YouTubeDiagnosisInput, YouTubeDiagnosisOutput,
|
||||
BrandConsistencyInput, BrandConsistencyOutput,
|
||||
TransformationInput, TransformationProposal,
|
||||
RoadmapInput, RoadmapOutput,
|
||||
)
|
||||
from integrations.llm.schemas.report import ReportInput, ReportOutput, YouTubeDiagnosisInput, YouTubeDiagnosisOutput
|
||||
from integrations.llm.schemas.plan import PlanInput, PlanOutput
|
||||
from integrations.llm.schemas.market import (
|
||||
MarketCompetitorsInput, MarketCompetitorsOutput,
|
||||
|
|
@ -94,31 +87,3 @@ 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,
|
||||
)
|
||||
|
||||
critical_issues_prompt = Prompt(
|
||||
file_name="critical_issues_prompt.txt",
|
||||
prompt_model="REPORT_MODEL",
|
||||
input_class=CriticalIssuesInput,
|
||||
output_class=CriticalIssuesOutput,
|
||||
)
|
||||
|
||||
transformation_prompt = Prompt(
|
||||
file_name="transformation_prompt.txt",
|
||||
prompt_model="REPORT_MODEL",
|
||||
input_class=TransformationInput,
|
||||
output_class=TransformationProposal,
|
||||
)
|
||||
|
||||
roadmap_prompt = Prompt(
|
||||
file_name="roadmap_prompt.txt",
|
||||
prompt_model="REPORT_MODEL",
|
||||
input_class=RoadmapInput,
|
||||
output_class=RoadmapOutput,
|
||||
)
|
||||
|
|
|
|||
|
|
@ -355,47 +355,3 @@ class YouTubeDiagnosisInput(BaseModel):
|
|||
|
||||
class YouTubeDiagnosisOutput(BaseModel):
|
||||
diagnosis: list[DiagnosisItem]
|
||||
|
||||
|
||||
# --- Diagnosis ---
|
||||
|
||||
class CriticalIssuesInput(BaseModel):
|
||||
clinic_name: str | None = None
|
||||
data: str | None = None
|
||||
|
||||
|
||||
class CriticalIssuesOutput(BaseModel):
|
||||
diagnosis: list[DiagnosisItem]
|
||||
|
||||
|
||||
# --- Roadmap ---
|
||||
|
||||
class RoadmapInput(BaseModel):
|
||||
clinic_name: str | None = None
|
||||
data: str | None = None
|
||||
|
||||
|
||||
class RoadmapOutput(BaseModel):
|
||||
roadmap: list[RoadmapMonth]
|
||||
|
||||
|
||||
# --- Transformation ---
|
||||
|
||||
class TransformationInput(BaseModel):
|
||||
clinic_name: str | None = None
|
||||
data: str | None = None
|
||||
|
||||
|
||||
# --- 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]
|
||||
|
|
|
|||
|
|
@ -1,18 +0,0 @@
|
|||
다음은 성형외과/피부과 {clinic_name} 의 채널별 브랜드 데이터입니다.
|
||||
|
||||
공식 홈페이지: {mainpage}
|
||||
인스타그램: {instagram}
|
||||
페이스북: {facebook}
|
||||
유튜브: {youtube}
|
||||
강남언니: {gangnam_unni}
|
||||
|
||||
위 채널들 간의 브랜드 불일치 항목을 분석해줘.
|
||||
비교 대상 필드 예시: 병원명(한글/영문), 전화번호, 주소, 로고, 슬로건, 소개 문구 등.
|
||||
|
||||
각 항목은 다음 JSON 형식의 배열로 출력해줘:
|
||||
- field: 불일치 필드명
|
||||
- values: 채널별 실제 값 목록 (channel, value, is_correct)
|
||||
- impact: 불일치가 브랜드에 미치는 영향
|
||||
- recommendation: 개선 권고사항
|
||||
|
||||
출처 번호([1], [2] 등)는 포함하지 마.
|
||||
|
|
@ -1,13 +0,0 @@
|
|||
다음은 성형외과/피부과 {clinic_name} 의 전 채널 수집 데이터입니다.
|
||||
|
||||
{data}
|
||||
|
||||
위 데이터를 바탕으로 이 병원의 마케팅 전반에 걸친 핵심 문제점과 개선사항을 진단해줘.
|
||||
각 항목은 category(진단 카테고리), detail(상세 설명), severity(critical/warning/info) 형식의 JSON 배열로 출력해줘.
|
||||
|
||||
현재 주요 진단 카테고리는 3개야.
|
||||
브랜드 아이덴티티 파편화
|
||||
콘텐츠 전략 부재
|
||||
플랫폼 간 유입 단절
|
||||
|
||||
출처 번호([1], [2] 등)는 포함하지 마.
|
||||
|
|
@ -1,14 +0,0 @@
|
|||
다음은 성형외과/피부과 {clinic_name} 의 전 채널 수집 데이터입니다.
|
||||
|
||||
{data}
|
||||
|
||||
위 데이터를 바탕으로 이 병원의 3개월 마케팅 실행 로드맵을 수립해줘.
|
||||
month 1, 2, 3 각각 하나씩, 총 3개 항목을 포함한 roadmap JSON 배열로 출력해줘.
|
||||
|
||||
각 항목은 아래 형식을 따라줘:
|
||||
- month: 월 번호 (1, 2, 3)
|
||||
- title: 해당 월의 핵심 테마 (예: "브랜드 정비")
|
||||
- subtitle: 한 줄 부제 (예: "기반 구축 — 로고·계정 통일")
|
||||
- tasks: 실행 과제 목록, 각 과제는 task(string)와 completed(false)로 구성
|
||||
|
||||
출처 번호([1], [2] 등)는 포함하지 마.
|
||||
|
|
@ -1,14 +0,0 @@
|
|||
다음은 성형외과/피부과 {clinic_name} 의 전 채널 수집 데이터입니다.
|
||||
|
||||
{data}
|
||||
|
||||
위 데이터를 바탕으로 이 병원의 마케팅 전환 전략을 수립해줘.
|
||||
아래 5개 항목을 포함한 JSON을 출력해줘.
|
||||
|
||||
1. brand_identity: 브랜드 아이덴티티 개선 항목 (area, as_is, to_be)
|
||||
2. content_strategy: 콘텐츠 전략 개선 항목 (area, as_is, to_be)
|
||||
3. platform_strategies: 플랫폼별 전략 (platform, icon, current_metric, target_metric, strategies[{strategy, detail}])
|
||||
4. website_improvements: 웹사이트 개선 항목 (area, as_is, to_be)
|
||||
5. new_channel_proposals: 신규 채널 제안 (channel, priority, rationale)
|
||||
|
||||
출처 번호([1], [2] 등)는 포함하지 마.
|
||||
|
|
@ -8,9 +8,10 @@ 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, brand_consistency_prompt, critical_issues_prompt, transformation_prompt, roadmap_prompt
|
||||
from integrations.llm.schemas.report import ReportOutput, ClinicSnapshot, YouTubeAudit, BrandConsistencyOutput, CriticalIssuesOutput, DiagnosisItem, TransformationProposal, RoadmapOutput, RoadmapMonth
|
||||
from integrations.llm.prompt import report_prompt, plan_prompt, youtube_diagnosis_prompt
|
||||
from integrations.llm.schemas.report import ReportOutput, ClinicSnapshot, YouTubeAudit
|
||||
from integrations.llm.schemas.plan import PlanOutput
|
||||
from models.status import AnalysisStatus
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
|
@ -208,39 +209,6 @@ async def _build_youtube_audit(youtube: dict) -> dict:
|
|||
return YouTubeAudit.model_validate(yt_patch).model_dump()
|
||||
|
||||
|
||||
async def _build_roadmap(analysis_run_id: str, raw: dict) -> list[dict]:
|
||||
result: RoadmapOutput = await LLMService(provider="perplexity").generate(
|
||||
roadmap_prompt,
|
||||
{
|
||||
"clinic_name": (raw.get("mainpage") or {}).get("clinicName"),
|
||||
"data": json.dumps(raw, ensure_ascii=False),
|
||||
},
|
||||
)
|
||||
return [RoadmapMonth.model_validate(item).model_dump() for item in result.roadmap]
|
||||
|
||||
|
||||
async def _build_transformation(analysis_run_id: str, raw: dict) -> dict:
|
||||
result: TransformationProposal = await LLMService(provider="perplexity").generate(
|
||||
transformation_prompt,
|
||||
{
|
||||
"clinic_name": (raw.get("mainpage") or {}).get("clinicName"),
|
||||
"data": json.dumps(raw, ensure_ascii=False),
|
||||
},
|
||||
)
|
||||
return result.model_dump()
|
||||
|
||||
|
||||
async def _build_critical_issues(analysis_run_id: str, raw: dict) -> list[dict]:
|
||||
result: CriticalIssuesOutput = await LLMService(provider="perplexity").generate(
|
||||
critical_issues_prompt,
|
||||
{
|
||||
"clinic_name": (raw.get("mainpage") or {}).get("clinicName"),
|
||||
"data": json.dumps(raw, ensure_ascii=False),
|
||||
},
|
||||
)
|
||||
return [DiagnosisItem.model_validate(item).model_dump() for item in result.diagnosis]
|
||||
|
||||
|
||||
async def _build_overrides(analysis_run_id: str) -> dict:
|
||||
raw = await select_run_raw_data(analysis_run_id)
|
||||
if not raw:
|
||||
|
|
@ -265,26 +233,15 @@ 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_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"]
|
||||
critical_issues = await _build_critical_issues(analysis_run_id, raw)
|
||||
transformation = await _build_transformation(analysis_run_id, raw)
|
||||
roadmap = await _build_roadmap(analysis_run_id, raw)
|
||||
|
||||
fb_patch: dict = {}
|
||||
if fb_pages:
|
||||
fb_patch["pages"] = [fb_pages]
|
||||
if brand_patch:
|
||||
fb_patch["brand_inconsistencies"] = brand_patch
|
||||
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"]
|
||||
|
||||
|
||||
overrides: dict = {}
|
||||
if snapshot:
|
||||
|
|
@ -292,15 +249,9 @@ async def _build_overrides(analysis_run_id: str) -> dict:
|
|||
if ig_patch:
|
||||
overrides["instagram_audit"] = {"accounts": [ig_patch]}
|
||||
if fb_patch:
|
||||
overrides["facebook_audit"] = fb_patch
|
||||
overrides["facebook_audit"] = {"pages": [fb_patch]}
|
||||
if yt_patch:
|
||||
overrides["youtube_audit"] = yt_patch
|
||||
if critical_issues:
|
||||
overrides["problem_diagnosis"] = critical_issues
|
||||
if transformation:
|
||||
overrides["transformation"] = transformation
|
||||
if roadmap:
|
||||
overrides["roadmap"] = roadmap
|
||||
return overrides
|
||||
|
||||
|
||||
|
|
@ -317,10 +268,8 @@ def _deep_merge(base: dict, overrides: dict) -> dict:
|
|||
return base
|
||||
|
||||
def _patch_report(result: ReportOutput, overrides: dict) -> ReportOutput:
|
||||
base = result.model_dump()
|
||||
for key in overrides:
|
||||
base.pop(key, None)
|
||||
return ReportOutput(**_deep_merge(base, overrides))
|
||||
merged = _deep_merge(result.model_dump(), overrides)
|
||||
return ReportOutput(**merged)
|
||||
|
||||
|
||||
_MOCK_DOMAINS = {"viewclinic.com"}
|
||||
|
|
@ -345,24 +294,6 @@ 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):
|
||||
|
|
@ -371,7 +302,6 @@ 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)
|
||||
|
|
|
|||
Loading…
Reference in New Issue