diff --git a/data/clinic-registry/INFINITH_Outbound_List.csv b/data/clinic-registry/INFINITH_Outbound_List.csv new file mode 100644 index 0000000..ae5b30f --- /dev/null +++ b/data/clinic-registry/INFINITH_Outbound_List.csv @@ -0,0 +1,92 @@ +순번,병원명,지역,지점/규모,공식 웹사이트,네이버 플레이스,네이버 리뷰 현황,강남언니,강남언니 비고,YouTube,Instagram (KR),네이버 블로그,활성 채널 수,컨택 담당자,컨택 상태,미팅 일정,BD 메모 +1,바노바기성형외과,강남,,https://www.banobagi.com,https://m.place.naver.com/hospital/21033469,리뷰 773개,https://www.gangnamunni.com/hospitals/23,,https://www.youtube.com/c/banobagips,https://www.instagram.com/banobagi_ps/,https://blog.naver.com/banobagips,6,,,, +2,뷰성형외과,강남,뷰성형외과 역삼센터(역삼),https://www.viewclinic.com,https://m.place.naver.com/hospital/11709005,리뷰 776개,https://www.gangnamunni.com/hospitals/189,,https://www.youtube.com/@ViewclinicKR,https://www.instagram.com/viewplastic/,https://blog.naver.com/viewclinicps,6,,,, +3,아이디병원,강남,아이디병원 별관(역삼),https://www.idhospital.com,https://m.place.naver.com/hospital/11548359,,https://www.gangnamunni.com/hospitals/257,,https://www.youtube.com/user/IDhospital,https://www.instagram.com/idhospital,https://blog.naver.com/idfacial,6,,,, +4,그랜드성형외과,강남,,https://www.grandsurgery.com,https://m.place.naver.com/hospital/12322994,,https://www.gangnamunni.com/hospitals/62,,https://www.youtube.com/channel/UCU2o_aHqsNFuqwtdzVM3xbQ,https://www.instagram.com/grand_korea/,https://blog.naver.com/grandprs,6,,,, +5,원진성형외과,강남,,https://www.k-wonjin.co.kr,https://m.place.naver.com/hospital/11887873,리뷰 9개,https://www.gangnamunni.com/hospitals/2500,,https://www.youtube.com/@wjwonjin,https://www.instagram.com/wonjin_official/,https://blog.naver.com/popokpop,7,,,, +6,마인드성형외과,강남,,https://www.mindprs.com,https://m.place.naver.com/hospital/1342923541,리뷰 8개,https://www.gangnamunni.com/hospitals/729,,https://www.youtube.com/channel/UCzM5tIgkC8Es10YmLI55R_w,https://www.instagram.com/mind.prs/,,4,,,, +7,브라운성형외과,강남,,https://www.braunps.co.kr,https://m.place.naver.com/hospital/13299185,리뷰 2451개,https://www.gangnamunni.com/hospitals/215,,https://www.youtube.com/@BraunPlasticSurgery,https://www.instagram.com/braunps_official/,,5,,,, +8,오메가성형외과,강남,,http://www.omegaps.co.kr,https://m.place.naver.com/hospital/12840364,리뷰 1711개,https://www.gangnamunni.com/hospitals/926,,https://www.youtube.com/channel/UC4C2BB4Dp9L_QyuyD22FL0A,https://www.instagram.com/omega_plastic_surgery/,,5,,,, +9,나나성형외과,강남,,https://www.nanaprs.com,https://m.place.naver.com/hospital/1518147116,리뷰 919개,https://www.gangnamunni.com/hospitals/938,,https://www.youtube.com/@Nanaprstv,https://www.instagram.com/nanaprs/,https://blog.naver.com/nanaprs1,6,,,, +10,노트성형외과,강남,,http://notebreast.com,https://m.place.naver.com/hospital/1572670484,리뷰 9203개,https://www.gangnamunni.com/hospitals/2186,,https://www.youtube.com/channel/UC2OIyb2serotqDyEzQYyCtA,https://www.instagram.com/note.prs/,https://blog.naver.com/noteprs01,6,,,, +11,디에이성형외과,강남,,https://daprs.com,https://m.place.naver.com/hospital/33084820,리뷰 1908개,https://www.gangnamunni.com/hospitals/250,,https://www.youtube.com/channel/UC0wlA-w5JIt0G0EeWQf65AQ,https://www.instagram.com/da_plastic_surgery,https://blog.naver.com/daprs,7,,,, +12,에이비성형외과,강남,,https://www.abps.co.kr,https://m.place.naver.com/hospital/1304260302,리뷰 23599개,https://www.gangnamunni.com/hospitals/3004,,https://www.youtube.com/@abplasticsurgery,https://www.instagram.com/ab_plasticsurgery_kr/,https://blog.naver.com/abps20,6,,,, +13,비아이오성형외과,강남,,http://biopskorea.com,https://m.place.naver.com/hospital/21428819,,,,https://www.youtube.com/channel/UC2WCPPw2onOI4UVqt7LIfKw,https://www.instagram.com/bio_dr.hongsungpyo,https://blog.naver.com/bioplastics20,5,,,, +14,윈성형외과,강남,,,,,,,,,,0,,,, +15,제이준성형외과,강남,,http://www.jjprs.com,https://m.place.naver.com/hospital/36294945,리뷰 873개,https://www.gangnamunni.com/hospitals/139,,https://www.youtube.com/channel/UC1p4msJnetDKeOW-B4nlwZA,https://www.instagram.com/jayjunps/,https://blog.naver.com/gusrlf00,6,,,, +16,마블성형외과,강남,마블성형외과 압구정(압구정),https://marbleps.com,https://m.place.naver.com/hospital/37206762,리뷰 15207개,https://www.gangnamunni.com/hospitals/141,,https://www.youtube.com/@marbleps,https://www.instagram.com/marble__ps/,https://blog.naver.com/marbleplastic,6,,,, +17,쥬얼리성형외과,강남,,https://www.jewelryps.kr,https://m.place.naver.com/hospital/13443226,리뷰 257개,https://www.gangnamunni.com/hospitals/55,,https://www.youtube.com/@JewelrypsKr1,https://www.instagram.com/jewelryps_kr,https://blog.naver.com/prsshin2,6,,,, +18,티에스성형외과,강남,,http://www.tsprs.com,https://m.place.naver.com/hospital/36905792,리뷰 397개,https://www.gangnamunni.com/hospitals/116,,https://www.youtube.com/channel/UCgnizu8p7lbCnfIs76O8J-A,https://www.instagram.com/tsprs_official/,https://blog.naver.com/tsprs,6,,,, +19,유노성형외과,강남,,https://www.yunoprs.com,https://m.place.naver.com/hospital/38486006,리뷰 9209개,https://www.gangnamunni.com/hospitals/248,,https://www.youtube.com/channel/UCrEe-LfBLXA1-bTGS4bWmtg,https://www.instagram.com/doctor.yuno/,https://blog.naver.com/yuno_blog,6,,,, +20,리젠성형외과,강남,,http://www.regen.co.kr,,,,,,,,0,,,, +21,리팅성형외과,강남,,https://liting.co.kr,https://m.place.naver.com/hospital/38673363,리뷰 5474개,https://www.gangnamunni.com/hospitals/331,,https://www.youtube.com/@liting_ps,https://www.instagram.com/liting_psps/,https://blog.naver.com/night140160,5,,,, +22,앤써성형외과,강남,,http://www.answer-ps.co.kr,https://m.place.naver.com/hospital/34138819,,https://www.gangnamunni.com/hospitals/1449,,https://www.youtube.com/@TV-gf8ms,https://www.instagram.com/answerps,https://blog.naver.com/answerps,6,,,, +23,더픽스성형외과,강남,,http://thefixps.com,https://m.place.naver.com/hospital/1310778318,,https://www.gangnamunni.com/hospitals/2196,,https://www.youtube.com/channel/UCXVNowmecu1tF-kCYhFE_QQ,https://www.instagram.com/thefixps/,https://blog.naver.com/donghwn,6,,,, +24,기린성형외과,강남,,https://girinps.com,https://m.place.naver.com/hospital/21705376,리뷰 152개,https://www.gangnamunni.com/hospitals/398,,https://www.youtube.com/@girin_official,https://www.instagram.com/girin_ps_official/,https://blog.naver.com/girinlife,6,,,, +25,페이스성형외과,강남,,http://www.face-plus.co.kr,https://m.place.naver.com/hospital/13347560,,https://www.gangnamunni.com/hospitals/1515,,https://www.youtube.com/@koreanplasticsurgery,https://www.instagram.com/faceplus_ps/,https://blog.naver.com/soonjung-49,5,,,, +26,압구정서울성형외과,압구정,,http://www.asps.co.kr,https://m.place.naver.com/hospital/11531189,리뷰 357개,https://www.gangnamunni.com/hospitals/213,,https://www.youtube.com/channel/UCqNtVCL2u5Xvx74ymboxDyg,https://www.instagram.com/asps_no.1/,https://blog.naver.com/asps0119,5,,,, +27,리젠메디컬그룹,압구정,,http://regenskin.co.kr,,,https://www.gangnamunni.com/hospitals/4154,윈느성형외과 리젠메디컬타워,,https://www.instagram.com/regenskin,https://blog.naver.com/shinygn1,4,,,, +28,코코성형외과,압구정,,http://www.kodoctor.co.kr,https://m.place.naver.com/hospital/1023834988,,https://www.gangnamunni.com/hospitals/623,,https://www.youtube.com/channel/UCc19LE6Elp0OTNo9gDizJMw,https://www.instagram.com/koko_ps_official/,https://blog.naver.com/koko_plastic_surgery,6,,,, +29,오브제성형외과,압구정,,http://objetps.com,,,https://www.gangnamunni.com/hospitals/2122,,https://www.youtube.com/channel/UC2QrFhj-S8oUrbfOp1NXKZA,https://www.instagram.com/objet_plastic_surgery/,https://blog.naver.com/objetps,4,,,, +30,리상성형외과,압구정,,https://theregenps.com,https://m.place.naver.com/hospital/1125605502,,https://www.gangnamunni.com/hospitals/6597,,https://www.youtube.com/@theregen_ps,https://www.instagram.com/theregen_ps,https://blog.naver.com/theps_kor,5,,,, +31,에이트성형외과,압구정,에이트성형외과 서초(서초),https://www.eightprs.com,https://m.place.naver.com/hospital/31986276,"방문자리뷰 586, 블로그리뷰 3120",https://www.gangnamunni.com/hospitals/166,,https://www.youtube.com/channel/UCpTmm84yJLgIBwBcBhjie4Q,https://www.instagram.com/eightps8/,https://blog.naver.com/eightplasticsurgery,6,,,, +32,오페라성형외과,압구정,,http://www.operasurgery.co.kr,https://m.place.naver.com/hospital/10827484,,https://www.gangnamunni.com/hospitals/108,,https://www.youtube.com/channel/UC2R_jfmmn0zE-dkButFS_ZA,https://www.instagram.com/opera_ps/,https://blog.naver.com/fasolt2,6,,,, +33,리엔장성형외과,압구정,,https://ps.lienjang.net,,,https://www.gangnamunni.com/hospitals/69,,https://www.youtube.com/channel/UCxyiPTH9xHqhPVfwyyosXMQ,https://www.instagram.com/lienjang_official/,,4,,,, +34,루호성형외과,압구정,,https://www.luho.kr,https://m.place.naver.com/hospital/21868487,"방문자리뷰 1577, 블로그리뷰 608",https://www.gangnamunni.com/hospitals/660,,https://www.youtube.com/channel/UCZE8dRSsc6CwdORbAhxV2Rw,https://www.instagram.com/luho_beauty/,,5,,,, +35,티아라성형외과,압구정,,,,,https://www.gangnamunni.com/hospitals/231,SC301의원으로 상호변경,,,,1,,,, +36,쏘울성형외과,압구정,,https://www.soulps.kr,,,https://www.gangnamunni.com/hospitals/4244,,,https://www.instagram.com/soul_plastic_surgery/,,2,,,, +37,에이탑성형외과,압구정,,https://atopps.com,https://m.place.naver.com/hospital/877412762,,https://www.gangnamunni.com/hospitals/300,,https://www.youtube.com/channel/UCPBCTxCX1hyRnXejA8QfFEQ,https://www.instagram.com/atop_instalog/,https://blog.naver.com/atopps_,6,,,, +38,유스성형외과,압구정,,,,,,,,,,0,,,, +39,유캔비성형외과,압구정,,https://www.ucanb.co.kr,,,https://www.gangnamunni.com/hospitals/563,,https://www.youtube.com/channel/UCegLA3CpLkDOe5h_DnhUpFA,https://www.instagram.com/ucanb_plastic_surgery/,https://blog.naver.com/ucanb2338,5,,,, +40,스타성형외과,압구정,,https://www.starclinic.co.kr,https://m.place.naver.com/hospital/2092418109,,,,https://www.youtube.com/channel/UCpIF_ffpk4R10_iw0q0tR7g,,,2,,,, +41,아이템성형외과,압구정,,https://www.ittemps.kr,,,,,https://www.youtube.com/channel/UCJolLz6ag4m2UfZh56HC2hQ,https://www.instagram.com/ittem_ps,https://blog.naver.com/ittem_ps,4,,,, +42,리코성형외과,압구정,,http://www.licoclinic.com,https://m.place.naver.com/hospital/20800108,,,,https://www.youtube.com/channel/UC3T_EiEpQr1xE16o69XCqjQ,https://www.instagram.com/licobest/,https://blog.naver.com/licops,5,,,, +43,플레저성형외과,압구정,,http://www.pleasureps.com,,,https://www.gangnamunni.com/hospitals/2991,,https://www.youtube.com/c/PSPS_korea,https://www.instagram.com/pleasure_ps/,https://blog.naver.com/2amsomething,4,,,, +44,비너스성형외과,압구정,,,,,,,,,,0,,,, +45,아이웰성형외과,압구정,,http://www.iwellps.com,,,https://www.gangnamunni.com/hospitals/58,,,https://www.instagram.com/iwellps/,,2,,,, +46,글로비성형외과,압구정,,https://glovips.com,https://m.place.naver.com/hospital/13359904,,https://www.gangnamunni.com/hospitals/54,,https://www.youtube.com/channel/UCH4Orbmc3cFqajWx1I6Xs2w,https://www.instagram.com/glovips/,https://blog.naver.com/glovips,6,,,, +47,엘르성형외과,압구정,,http://www.elleclinic.com,,,https://www.gangnamunni.com/hospitals/1181,,,https://www.instagram.com/elleps_korea/,,2,,,, +48,원픽성형외과,압구정,,https://onepeakps.com,,,https://www.gangnamunni.com/hospitals/3000,,,,,1,,,, +49,탑페이스성형외과,압구정,,https://www.topfaceps.com,https://m.place.naver.com/hospital/33283530,"방문자리뷰 312, 블로그리뷰 179",https://www.gangnamunni.com/hospitals/66,,https://www.youtube.com/channel/UCNfZc5aXZ5A1mqX5iIw91EA,https://www.instagram.com/topfaceps/,https://blog.naver.com/ina3599,5,,,, +50,세민성형외과,역삼,,http://www.semin100.co.kr,,,,,https://www.youtube.com/channel/UCKaNYEvRqME2h1lUSOYIYew,,https://blog.naver.com/semin100,2,,,, +51,에이치성형외과,역삼,,https://www.3dfit.co.kr,,,,,https://www.youtube.com/channel/UC6d8i9WwIkm161ueFyVU2PA,,https://blog.naver.com/ys100ps,3,,,, +52,리노보성형외과,역삼,,http://www.renovo.co.kr,https://m.place.naver.com/hospital/13023672,,https://www.gangnamunni.com/hospitals/4749,,,https://www.instagram.com/renovo.clinic.1/,,3,,,, +53,디엘성형외과,역삼,,https://www.dlprs.com,https://m.place.naver.com/hospital/1998543478,,https://www.gangnamunni.com/hospitals/5500,,https://www.youtube.com/@LINEKING_DL,https://www.instagram.com/dl_plastic_official/,,4,,,, +54,피알성형외과,역삼,,http://prprs.co.kr,https://m.place.naver.com/hospital/1655749880,,https://www.gangnamunni.com/hospitals/431,,https://www.youtube.com/@pr_ps,https://www.instagram.com/prprs_official/,,5,,,, +55,라프린성형외과,역삼,,http://laprinps.com,https://m.place.naver.com/hospital/33741139,,,,https://www.youtube.com/channel/UCfMt8TTT8kJklZORjf4nJ2w,https://www.instagram.com/laprin_kr/,https://blog.naver.com/ff8kds6t5cmxd,5,,,, +56,도도성형외과,역삼,,http://www.dodobeauty.com,,,,,,,https://blog.naver.com/mmscjh,1,,,, +57,엠제이성형외과,역삼,,https://www.mjskinclinic.com,,,,,https://www.youtube.com/channel/UCFjkFyYDu4HpLjc9axlh6YQ,,https://blog.naver.com/mjskinclinic,3,,,, +58,아우라성형외과,역삼,,http://psaura.com,https://m.place.naver.com/hospital/1806079685,,https://www.gangnamunni.com/hospitals/5870,,https://www.youtube.com/@auraps2024,https://www.instagram.com/auraps_official/,https://blog.naver.com/pristor,5,,,, +59,하이봄성형외과,역삼,,https://www.highvom.com,https://m.place.naver.com/hospital/1714219923,,https://www.gangnamunni.com/hospitals/2052,,https://www.youtube.com/channel/UCuuH9oWh9h29hVRmH2sHAuA,https://www.instagram.com/highvom/,https://blog.naver.com/hivom,6,,,, +60,에이원성형외과,역삼,,,,,,,,,,0,,,, +61,유앤아이성형외과,역삼,,https://www.uni114.co.kr,https://m.place.naver.com/hospital/60058049,,https://www.gangnamunni.com/hospitals/4459,,https://www.youtube.com/channel/UCHgZnNk3JIDxnoYcWSWtAJw,https://www.instagram.com/skinuni114/,,5,,,, +62,리본성형외과,역삼,,,,,https://www.gangnamunni.com/hospitals/339,,https://www.youtube.com/channel/UCyFdX9zxfu2e2JVTwPjki5A,https://www.instagram.com/rebornps_/,https://blog.naver.com/reborn1999,5,,,, +63,리메이성형외과,역삼,,,,,,,,,,0,,,, +64,라이크성형외과,역삼,,http://www.likeps.com,,,https://www.gangnamunni.com/hospitals/912,,https://www.youtube.com/c/LIKEPLASTICSURGERY,https://www.instagram.com/likeps_kr/,,3,,,, +65,케이플러스성형외과,역삼,,https://k-clinics.com,https://m.place.naver.com/hospital/36912372,,https://www.gangnamunni.com/hospitals/1265,,https://www.youtube.com/@k-plasticsurgery,https://www.instagram.com/k_plasticsurgery/,https://blog.naver.com/kclinics-osh,6,,,, +66,케이아트성형외과,역삼,,http://www.k-artps.com,,,,,https://www.youtube.com/channel/UCHs9LOYtauBIklXhPPZBAgA,https://www.instagram.com/kartps/,https://blog.naver.com/kartps,4,,,, +67,이룸성형외과,역삼,,http://www.seoulips.com,https://m.place.naver.com/hospital/1240083198,,https://www.gangnamunni.com/hospitals/839,,https://www.youtube.com/@seouliplasticsurgery,https://www.instagram.com/seoulips/,https://blog.naver.com/seoulips,7,,,, +68,서초서울성형외과,서초,,https://srprs.co.kr,https://m.place.naver.com/hospital/1692465198,,https://www.gangnamunni.com/hospitals/5554,,,https://www.instagram.com/saerops/,,3,,,, +69,서초연세성형외과,서초,,https://www.chaminst.com,,,https://www.gangnamunni.com/hospitals/4212,,https://www.youtube.com/channel/UCMxBfZivKz5jjhRiy0mzOXQ,https://www.instagram.com/chamin_ps,https://blog.naver.com/chaminst,4,,,, +70,리모성형외과,서초,,http://www.ksh-ps.com,https://m.place.naver.com/hospital/1865533181,,https://www.gangnamunni.com/hospitals/6680,,https://www.youtube.com/channel/UCCiH9OffJgZTMnkxatK8-5A,https://www.instagram.com/sh___ps/,https://blog.naver.com/shpsclinic,5,,,, +71,화이트성형외과,서초,,http://www.whiteclinic.com,,,,,,,,0,,,, +72,미호성형외과,서초,,https://mihops.co.kr,https://m.place.naver.com/hospital/344342362,,https://www.gangnamunni.com/hospitals/369,,https://www.youtube.com/channel/UC8kyWAnMM7f_xguyPxR_gtA,https://www.instagram.com/mihops_kr/,https://blog.naver.com/qalfuqbjtmnf,5,,,, +73,나우성형외과,서초,,,,,,,,,,0,,,, +74,와이즈성형외과,서초,,,,,https://www.gangnamunni.com/hospitals/2414,와이즈유의원,,https://www.instagram.com/wise_ps/,,2,,,, +75,케이스타성형외과,서초,,,,,,,,,,0,,,, +76,셀린성형외과,서초,,,,,,,,,,0,,,, +77,에비뉴성형외과,서초,,http://www.avenueps.com,,,https://www.gangnamunni.com/hospitals/6204,,https://www.youtube.com/channel/UCOTVAerYogSEia3L-ERkAFg,,,2,,,, +78,비온성형외과,서초,,http://www.bon-ps.com,https://m.place.naver.com/hospital/1492257313,,,,https://www.youtube.com/channel/UClCjGIfEb3b1N-Q5tCNSU5w,,https://blog.naver.com/miz2199,3,,,, +79,아크성형외과,서초,,http://arc-ps.com,https://m.place.naver.com/hospital/1638918034,,https://www.gangnamunni.com/hospitals/3569,,https://www.youtube.com/@-arcisart7955,https://www.instagram.com/arc_plastic_surgery/,,4,,,, +80,에버성형외과,서초,,http://www.everclinic.com,,,,,https://www.youtube.com/channel/UCwSusNRTM2B_mvdRd0yNVcw,https://www.instagram.com/everclinic0088/,https://blog.naver.com/drblue007,4,,,, +81,원스성형외과,서초,,,,,,,,,,0,,,, +82,메이성형외과,서초,,,,,,,,,,0,,,, +83,라인업성형외과,서초,,,,,,,,,,0,,,, +84,유앤미성형외과,서초,,https://www.knyounmeclinic.co.kr,https://m.place.naver.com/hospital/31068065,,https://www.gangnamunni.com/hospitals/1178,,https://www.youtube.com/channel/UCAROD-B1ZBinpyzql7o_IlA,https://www.instagram.com/knyounmeclinic/,,4,,,, +85,리더스성형외과,서초,,,,,,,,,,0,,,, +86,마노성형외과,서초,,https://manops.co.kr,,,https://www.gangnamunni.com/hospitals/3429,,https://www.youtube.com/@mano_ps,https://www.instagram.com/manops_official,https://blog.naver.com/tkhr4747,4,,,, +87,예롬성형외과,서초,,,,,https://www.gangnamunni.com/hospitals/450,,,,,1,,,, +88,에스엠성형외과,서초,,https://www.sm-ps.co.kr,https://m.place.naver.com/hospital/32876182,,https://www.gangnamunni.com/hospitals/413,,https://www.youtube.com/@smpsclinic,https://www.instagram.com/smps_plastic_surgery/,https://blog.naver.com/smps1004,6,,,, +89,리안성형외과,서초,,,,,,,,,,0,,,, +90,서울미작성형외과,서초,,https://mijakclinic.co.kr,,,,,https://www.youtube.com/channel/UCld-NgVeydtDRVTt6xQRbmA,https://www.instagram.com/mijak2444/,https://blog.naver.com/mijak3444,4,,,, +91,더원성형외과,서초,,https://www.theoneclinic.co.kr,,,https://www.gangnamunni.com/hospitals/5636,,,https://www.instagram.com/theone_plastic_surgery/,https://blog.naver.com/brs0714,4,,,, diff --git a/data/clinic-registry/clinic_registry_working.csv b/data/clinic-registry/clinic_registry_working.csv new file mode 100644 index 0000000..a8721dd --- /dev/null +++ b/data/clinic-registry/clinic_registry_working.csv @@ -0,0 +1,92 @@ +hospital_name,brand_group,district,branches,website_kr,website_en,youtube_url,youtube_note,instagram_kr_url,instagram_kr_note,instagram_en_url,instagram_en_note,facebook_url,facebook_note,tiktok_url,tiktok_note,gangnam_unni_url,gangnam_unni_note,naver_blog_url,naver_blog_note,naver_place_url,naver_place_reviews_note,google_maps_url,google_reviews_note +바노바기성형외과,프리미엄/하이타깃 후보,강남,,https://www.banobagi.com,,https://www.youtube.com/c/banobagips,,https://www.instagram.com/banobagi_ps/,,,,https://www.facebook.com/BanobagiPlasticSurgery,,,,https://www.gangnamunni.com/hospitals/23,,https://blog.naver.com/banobagips,,https://m.place.naver.com/hospital/21033469,리뷰 773개,, +뷰성형외과,프리미엄/하이타깃 후보,강남,뷰성형외과 역삼센터(역삼),https://www.viewclinic.com,,https://www.youtube.com/@ViewclinicKR,,https://www.instagram.com/viewplastic/,,,,https://www.facebook.com/viewps1/,,,,https://www.gangnamunni.com/hospitals/189,,https://blog.naver.com/viewclinicps,,https://m.place.naver.com/hospital/11709005,리뷰 776개,, +아이디병원,프리미엄/하이타깃 후보,강남,아이디병원 별관(역삼),https://www.idhospital.com,,https://www.youtube.com/user/IDhospital,,https://www.instagram.com/idhospital,,,,https://www.facebook.com/idhospital0050,,,,https://www.gangnamunni.com/hospitals/257,,https://blog.naver.com/idfacial,,https://m.place.naver.com/hospital/11548359,,, +그랜드성형외과,프리미엄/하이타깃 후보,강남,,https://www.grandsurgery.com,,https://www.youtube.com/channel/UCU2o_aHqsNFuqwtdzVM3xbQ,,https://www.instagram.com/grand_korea/,,,,https://www.facebook.com/grandps.korea,,,,https://www.gangnamunni.com/hospitals/62,,https://blog.naver.com/grandprs,,https://m.place.naver.com/hospital/12322994,,, +원진성형외과,프리미엄/하이타깃 후보,강남,,https://www.k-wonjin.co.kr,,https://www.youtube.com/@wjwonjin,,https://www.instagram.com/wonjin_official/,,,,https://www.facebook.com/KwonjinPS,,https://www.tiktok.com/@wonjin_official,,https://www.gangnamunni.com/hospitals/2500,,https://blog.naver.com/popokpop,,https://m.place.naver.com/hospital/11887873,리뷰 9개,, +마인드성형외과,프리미엄/하이타깃 후보,강남,,https://www.mindprs.com,,https://www.youtube.com/channel/UCzM5tIgkC8Es10YmLI55R_w,,https://www.instagram.com/mind.prs/,,,,,,,,https://www.gangnamunni.com/hospitals/729,,,,https://m.place.naver.com/hospital/1342923541,리뷰 8개,, +브라운성형외과,프리미엄/하이타깃 후보,강남,,https://www.braunps.co.kr,,https://www.youtube.com/@BraunPlasticSurgery,,https://www.instagram.com/braunps_official/,,,,https://www.facebook.com/braunps,,,,https://www.gangnamunni.com/hospitals/215,,,,https://m.place.naver.com/hospital/13299185,리뷰 2451개,, +오메가성형외과,프리미엄/하이타깃 후보,강남,,http://www.omegaps.co.kr,,https://www.youtube.com/channel/UC4C2BB4Dp9L_QyuyD22FL0A,,https://www.instagram.com/omega_plastic_surgery/,,,,https://www.facebook.com/오메가성형외과-1682577712065719/,,,,https://www.gangnamunni.com/hospitals/926,,,,https://m.place.naver.com/hospital/12840364,리뷰 1711개,, +나나성형외과,프리미엄/하이타깃 후보,강남,,https://www.nanaprs.com,,https://www.youtube.com/@Nanaprstv,,https://www.instagram.com/nanaprs/,,,,https://www.facebook.com/nanaprs2,,,,https://www.gangnamunni.com/hospitals/938,,https://blog.naver.com/nanaprs1,,https://m.place.naver.com/hospital/1518147116,리뷰 919개,, +노트성형외과,프리미엄/하이타깃 후보,강남,,http://notebreast.com,,https://www.youtube.com/channel/UC2OIyb2serotqDyEzQYyCtA,,https://www.instagram.com/note.prs/,,,,https://www.facebook.com/noteprs2/,,,,https://www.gangnamunni.com/hospitals/2186,,https://blog.naver.com/noteprs01,,https://m.place.naver.com/hospital/1572670484,리뷰 9203개,, +디에이성형외과,프리미엄/하이타깃 후보,강남,,https://daprs.com,,https://www.youtube.com/channel/UC0wlA-w5JIt0G0EeWQf65AQ,,https://www.instagram.com/da_plastic_surgery,,,,https://www.facebook.com/daprs/,,https://www.tiktok.com/@daprs,,https://www.gangnamunni.com/hospitals/250,,https://blog.naver.com/daprs,,https://m.place.naver.com/hospital/33084820,리뷰 1908개,, +에이비성형외과,프리미엄/하이타깃 후보,강남,,https://www.abps.co.kr,,https://www.youtube.com/@abplasticsurgery,,https://www.instagram.com/ab_plasticsurgery_kr/,,,,https://www.facebook.com/profile.php?id=100063560996454,,,,https://www.gangnamunni.com/hospitals/3004,,https://blog.naver.com/abps20,,https://m.place.naver.com/hospital/1304260302,리뷰 23599개,, +비아이오성형외과,프리미엄/하이타깃 후보,강남,,http://biopskorea.com,,https://www.youtube.com/channel/UC2WCPPw2onOI4UVqt7LIfKw,,https://www.instagram.com/bio_dr.hongsungpyo,,,,https://www.facebook.com/biopskorea,,,,,,https://blog.naver.com/bioplastics20,,https://m.place.naver.com/hospital/21428819,,, +윈성형외과,프리미엄/하이타깃 후보,강남,,,,,,,,,,,,,,,,,,,,, +제이준성형외과,프리미엄/하이타깃 후보,강남,,http://www.jjprs.com,,https://www.youtube.com/channel/UC1p4msJnetDKeOW-B4nlwZA,,https://www.instagram.com/jayjunps/,,,,https://www.facebook.com/jayjunps,,,,https://www.gangnamunni.com/hospitals/139,,https://blog.naver.com/gusrlf00,,https://m.place.naver.com/hospital/36294945,리뷰 873개,, +마블성형외과,프리미엄/하이타깃 후보,강남,마블성형외과 압구정(압구정),https://marbleps.com,,https://www.youtube.com/@marbleps,,https://www.instagram.com/marble__ps/,,,,https://www.facebook.com/marbleps/,,,,https://www.gangnamunni.com/hospitals/141,,https://blog.naver.com/marbleplastic,,https://m.place.naver.com/hospital/37206762,리뷰 15207개,, +쥬얼리성형외과,프리미엄/하이타깃 후보,강남,,https://www.jewelryps.kr,,https://www.youtube.com/@JewelrypsKr1,,https://www.instagram.com/jewelryps_kr,,,,https://www.facebook.com/juelyps,,,,https://www.gangnamunni.com/hospitals/55,,https://blog.naver.com/prsshin2,,https://m.place.naver.com/hospital/13443226,리뷰 257개,, +티에스성형외과,프리미엄/하이타깃 후보,강남,,http://www.tsprs.com,,https://www.youtube.com/channel/UCgnizu8p7lbCnfIs76O8J-A,,https://www.instagram.com/tsprs_official/,,,,https://www.facebook.com/tsprs,,,,https://www.gangnamunni.com/hospitals/116,,https://blog.naver.com/tsprs,,https://m.place.naver.com/hospital/36905792,리뷰 397개,, +유노성형외과,프리미엄/하이타깃 후보,강남,,https://www.yunoprs.com,,https://www.youtube.com/channel/UCrEe-LfBLXA1-bTGS4bWmtg,,https://www.instagram.com/doctor.yuno/,,,,https://www.facebook.com/doctor.yuno/,,,,https://www.gangnamunni.com/hospitals/248,,https://blog.naver.com/yuno_blog,,https://m.place.naver.com/hospital/38486006,리뷰 9209개,, +리젠성형외과,프리미엄/하이타깃 후보,강남,,http://www.regen.co.kr,,,,,,,,,,,,,,,,,,, +리팅성형외과,프리미엄/하이타깃 후보,강남,,https://liting.co.kr,,https://www.youtube.com/@liting_ps,,https://www.instagram.com/liting_psps/,,,,,,,,https://www.gangnamunni.com/hospitals/331,,https://blog.naver.com/night140160,,https://m.place.naver.com/hospital/38673363,리뷰 5474개,, +앤써성형외과,프리미엄/하이타깃 후보,강남,,http://www.answer-ps.co.kr,,https://www.youtube.com/@TV-gf8ms,,https://www.instagram.com/answerps,,,,https://www.facebook.com/answerps4881,,,,https://www.gangnamunni.com/hospitals/1449,,https://blog.naver.com/answerps,,https://m.place.naver.com/hospital/34138819,,, +더픽스성형외과,프리미엄/하이타깃 후보,강남,,http://thefixps.com,,https://www.youtube.com/channel/UCXVNowmecu1tF-kCYhFE_QQ,,https://www.instagram.com/thefixps/,,,,https://www.facebook.com/psdonghoon,,,,https://www.gangnamunni.com/hospitals/2196,,https://blog.naver.com/donghwn,,https://m.place.naver.com/hospital/1310778318,,, +기린성형외과,프리미엄/하이타깃 후보,강남,,https://girinps.com,,https://www.youtube.com/@girin_official,,https://www.instagram.com/girin_ps_official/,,,,,,https://www.tiktok.com/@girin_korea,,https://www.gangnamunni.com/hospitals/398,,https://blog.naver.com/girinlife,,https://m.place.naver.com/hospital/21705376,리뷰 152개,, +페이스성형외과,프리미엄/하이타깃 후보,강남,,http://www.face-plus.co.kr,,https://www.youtube.com/@koreanplasticsurgery,,https://www.instagram.com/faceplus_ps/,,,,,,,,https://www.gangnamunni.com/hospitals/1515,,https://blog.naver.com/soonjung-49,,https://m.place.naver.com/hospital/13347560,,, +압구정서울성형외과,프리미엄/하이타깃 후보,압구정,,http://www.asps.co.kr,,https://www.youtube.com/channel/UCqNtVCL2u5Xvx74ymboxDyg,,https://www.instagram.com/asps_no.1/,,https://www.instagram.com/asps_en/,,,,,,https://www.gangnamunni.com/hospitals/213,,https://blog.naver.com/asps0119,,https://m.place.naver.com/hospital/11531189,리뷰 357개,, +리젠메디컬그룹,프리미엄/하이타깃 후보,압구정,,http://regenskin.co.kr,,,,https://www.instagram.com/regenskin,,,,https://www.facebook.com/pages/리젠피부과/785877121503059,,,,https://www.gangnamunni.com/hospitals/4154,윈느성형외과 리젠메디컬타워,https://blog.naver.com/shinygn1,,,,, +코코성형외과,프리미엄/하이타깃 후보,압구정,,http://www.kodoctor.co.kr,,https://www.youtube.com/channel/UCc19LE6Elp0OTNo9gDizJMw,,https://www.instagram.com/koko_ps_official/,,,,https://www.facebook.com/kokodoctor/,,,,https://www.gangnamunni.com/hospitals/623,,https://blog.naver.com/koko_plastic_surgery,,https://m.place.naver.com/hospital/1023834988,,, +오브제성형외과,프리미엄/하이타깃 후보,압구정,,http://objetps.com,,https://www.youtube.com/channel/UC2QrFhj-S8oUrbfOp1NXKZA,,https://www.instagram.com/objet_plastic_surgery/,,,,,,,,https://www.gangnamunni.com/hospitals/2122,,https://blog.naver.com/objetps,,,,, +리상성형외과,프리미엄/하이타깃 후보,압구정,,https://theregenps.com,,https://www.youtube.com/@theregen_ps,,https://www.instagram.com/theregen_ps,,,,,,,,https://www.gangnamunni.com/hospitals/6597,,https://blog.naver.com/theps_kor,,https://m.place.naver.com/hospital/1125605502,,, +에이트성형외과,프리미엄/하이타깃 후보,압구정,에이트성형외과 서초(서초),https://www.eightprs.com,,https://www.youtube.com/channel/UCpTmm84yJLgIBwBcBhjie4Q,,https://www.instagram.com/eightps8/,,,,,,https://www.tiktok.com/@eighthospital.th,,https://www.gangnamunni.com/hospitals/166,,https://blog.naver.com/eightplasticsurgery,,https://m.place.naver.com/hospital/31986276,"방문자리뷰 586, 블로그리뷰 3120",, +오페라성형외과,프리미엄/하이타깃 후보,압구정,,http://www.operasurgery.co.kr,,https://www.youtube.com/channel/UC2R_jfmmn0zE-dkButFS_ZA,,https://www.instagram.com/opera_ps/,,,,https://www.facebook.com/operasurgery/,,,,https://www.gangnamunni.com/hospitals/108,,https://blog.naver.com/fasolt2,,https://m.place.naver.com/hospital/10827484,,, +리엔장성형외과,프리미엄/하이타깃 후보,압구정,,https://ps.lienjang.net,,https://www.youtube.com/channel/UCxyiPTH9xHqhPVfwyyosXMQ,,https://www.instagram.com/lienjang_official/,,,,,,https://www.tiktok.com/@lienjang_ps,,https://www.gangnamunni.com/hospitals/69,,,,,,, +루호성형외과,프리미엄/하이타깃 후보,압구정,,https://www.luho.kr,,https://www.youtube.com/channel/UCZE8dRSsc6CwdORbAhxV2Rw,,https://www.instagram.com/luho_beauty/,,,,,,https://www.tiktok.com/@luho_beauty_jp,,https://www.gangnamunni.com/hospitals/660,,,,https://m.place.naver.com/hospital/21868487,"방문자리뷰 1577, 블로그리뷰 608",, +티아라성형외과,프리미엄/하이타깃 후보,압구정,,,,,,,,,,,,,,https://www.gangnamunni.com/hospitals/231,SC301의원으로 상호변경,,,,,, +쏘울성형외과,프리미엄/하이타깃 후보,압구정,,https://www.soulps.kr,,,,https://www.instagram.com/soul_plastic_surgery/,,,,,,,,https://www.gangnamunni.com/hospitals/4244,,,,,,, +에이탑성형외과,프리미엄/하이타깃 후보,압구정,,https://atopps.com,,https://www.youtube.com/channel/UCPBCTxCX1hyRnXejA8QfFEQ,,https://www.instagram.com/atop_instalog/,,,,https://www.facebook.com/Dr.goodhand,,,,https://www.gangnamunni.com/hospitals/300,,https://blog.naver.com/atopps_,,https://m.place.naver.com/hospital/877412762,,, +유스성형외과,프리미엄/하이타깃 후보,압구정,,,,,,,,,,,,,,,,,,,,, +유캔비성형외과,프리미엄/하이타깃 후보,압구정,,https://www.ucanb.co.kr,,https://www.youtube.com/channel/UCegLA3CpLkDOe5h_DnhUpFA,,https://www.instagram.com/ucanb_plastic_surgery/,,,,https://www.facebook.com/ucanbps,,,,https://www.gangnamunni.com/hospitals/563,,https://blog.naver.com/ucanb2338,,,,, +스타성형외과,프리미엄/하이타깃 후보,압구정,,https://www.starclinic.co.kr,,https://www.youtube.com/channel/UCpIF_ffpk4R10_iw0q0tR7g,,,,,,,,,,,,,,https://m.place.naver.com/hospital/2092418109,,, +아이템성형외과,프리미엄/하이타깃 후보,압구정,,https://www.ittemps.kr,,https://www.youtube.com/channel/UCJolLz6ag4m2UfZh56HC2hQ,,https://www.instagram.com/ittem_ps,,,,https://www.facebook.com/잇템성형외과-101634361973942,,,,,,https://blog.naver.com/ittem_ps,,,,, +리코성형외과,프리미엄/하이타깃 후보,압구정,,http://www.licoclinic.com,,https://www.youtube.com/channel/UC3T_EiEpQr1xE16o69XCqjQ,,https://www.instagram.com/licobest/,,,,https://www.facebook.com/licotop,,,,,,https://blog.naver.com/licops,,https://m.place.naver.com/hospital/20800108,,, +플레저성형외과,프리미엄/하이타깃 후보,압구정,,http://www.pleasureps.com,,https://www.youtube.com/c/PSPS_korea,,https://www.instagram.com/pleasure_ps/,,,,,,,,https://www.gangnamunni.com/hospitals/2991,,https://blog.naver.com/2amsomething,,,,, +비너스성형외과,프리미엄/하이타깃 후보,압구정,,,,,,,,,,,,,,,,,,,,, +아이웰성형외과,프리미엄/하이타깃 후보,압구정,,http://www.iwellps.com,,,,https://www.instagram.com/iwellps/,,,,,,,,https://www.gangnamunni.com/hospitals/58,,,,,,, +글로비성형외과,프리미엄/하이타깃 후보,압구정,,https://glovips.com,,https://www.youtube.com/channel/UCH4Orbmc3cFqajWx1I6Xs2w,,https://www.instagram.com/glovips/,,,,https://www.facebook.com/glovips,,,,https://www.gangnamunni.com/hospitals/54,,https://blog.naver.com/glovips,,https://m.place.naver.com/hospital/13359904,,, +엘르성형외과,프리미엄/하이타깃 후보,압구정,,http://www.elleclinic.com,,,,https://www.instagram.com/elleps_korea/,,,,,,,,https://www.gangnamunni.com/hospitals/1181,,,,,,, +원픽성형외과,프리미엄/하이타깃 후보,압구정,,https://onepeakps.com,,,,,,,,,,,,https://www.gangnamunni.com/hospitals/3000,,,,,,, +탑페이스성형외과,프리미엄/하이타깃 후보,압구정,,https://www.topfaceps.com,,https://www.youtube.com/channel/UCNfZc5aXZ5A1mqX5iIw91EA,,https://www.instagram.com/topfaceps/,,,,,,,,https://www.gangnamunni.com/hospitals/66,,https://blog.naver.com/ina3599,,https://m.place.naver.com/hospital/33283530,"방문자리뷰 312, 블로그리뷰 179",, +세민성형외과,프리미엄/하이타깃 후보,역삼,,http://www.semin100.co.kr,,https://www.youtube.com/channel/UCKaNYEvRqME2h1lUSOYIYew,,,,,,,,,,,,https://blog.naver.com/semin100,,,,, +에이치성형외과,프리미엄/하이타깃 후보,역삼,,https://www.3dfit.co.kr,,https://www.youtube.com/channel/UC6d8i9WwIkm161ueFyVU2PA,,,,,,https://www.facebook.com/3DFIT100,,,,,,https://blog.naver.com/ys100ps,,,,, +리노보성형외과,프리미엄/하이타깃 후보,역삼,,http://www.renovo.co.kr,,,,https://www.instagram.com/renovo.clinic.1/,,,,,,,,https://www.gangnamunni.com/hospitals/4749,,,,https://m.place.naver.com/hospital/13023672,,, +디엘성형외과,프리미엄/하이타깃 후보,역삼,,https://www.dlprs.com,,https://www.youtube.com/@LINEKING_DL,,https://www.instagram.com/dl_plastic_official/,,,,,,,,https://www.gangnamunni.com/hospitals/5500,,,,https://m.place.naver.com/hospital/1998543478,,, +피알성형외과,프리미엄/하이타깃 후보,역삼,,http://prprs.co.kr,,https://www.youtube.com/@pr_ps,,https://www.instagram.com/prprs_official/,,,,https://www.facebook.com/PRPRS.official,,,,https://www.gangnamunni.com/hospitals/431,,,,https://m.place.naver.com/hospital/1655749880,,, +라프린성형외과,프리미엄/하이타깃 후보,역삼,,http://laprinps.com,,https://www.youtube.com/channel/UCfMt8TTT8kJklZORjf4nJ2w,,https://www.instagram.com/laprin_kr/,,,,https://www.facebook.com/laprinprincess/,,,,,,https://blog.naver.com/ff8kds6t5cmxd,,https://m.place.naver.com/hospital/33741139,,, +도도성형외과,프리미엄/하이타깃 후보,역삼,,http://www.dodobeauty.com,,,,,,,,,,,,,,https://blog.naver.com/mmscjh,,,,, +엠제이성형외과,프리미엄/하이타깃 후보,역삼,,https://www.mjskinclinic.com,,https://www.youtube.com/channel/UCFjkFyYDu4HpLjc9axlh6YQ,,,,,,https://www.facebook.com/people/MJ피부과/100063928095304/,,,,,,https://blog.naver.com/mjskinclinic,,,,, +아우라성형외과,프리미엄/하이타깃 후보,역삼,,http://psaura.com,,https://www.youtube.com/@auraps2024,,https://www.instagram.com/auraps_official/,,,,,,,,https://www.gangnamunni.com/hospitals/5870,,https://blog.naver.com/pristor,,https://m.place.naver.com/hospital/1806079685,,, +하이봄성형외과,프리미엄/하이타깃 후보,역삼,,https://www.highvom.com,,https://www.youtube.com/channel/UCuuH9oWh9h29hVRmH2sHAuA,,https://www.instagram.com/highvom/,,,,https://www.facebook.com/highvom1,,,,https://www.gangnamunni.com/hospitals/2052,,https://blog.naver.com/hivom,,https://m.place.naver.com/hospital/1714219923,,, +에이원성형외과,프리미엄/하이타깃 후보,역삼,,,,,,,,,,,,,,,,,,,,, +유앤아이성형외과,프리미엄/하이타깃 후보,역삼,,https://www.uni114.co.kr,,https://www.youtube.com/channel/UCHgZnNk3JIDxnoYcWSWtAJw,,https://www.instagram.com/skinuni114/,,,,https://www.facebook.com/uni114,,,,https://www.gangnamunni.com/hospitals/4459,,,,https://m.place.naver.com/hospital/60058049,,, +리본성형외과,프리미엄/하이타깃 후보,역삼,,,,https://www.youtube.com/channel/UCyFdX9zxfu2e2JVTwPjki5A,,https://www.instagram.com/rebornps_/,,,,https://www.facebook.com/reborn3355/,,,,https://www.gangnamunni.com/hospitals/339,,https://blog.naver.com/reborn1999,,,,, +리메이성형외과,프리미엄/하이타깃 후보,역삼,,,,,,,,,,,,,,,,,,,,, +라이크성형외과,프리미엄/하이타깃 후보,역삼,,http://www.likeps.com,,https://www.youtube.com/c/LIKEPLASTICSURGERY,,https://www.instagram.com/likeps_kr/,,,,,,,,https://www.gangnamunni.com/hospitals/912,,,,,,, +케이플러스성형외과,프리미엄/하이타깃 후보,역삼,,https://k-clinics.com,,https://www.youtube.com/@k-plasticsurgery,,https://www.instagram.com/k_plasticsurgery/,,,,https://www.facebook.com/kclinics,,,,https://www.gangnamunni.com/hospitals/1265,,https://blog.naver.com/kclinics-osh,,https://m.place.naver.com/hospital/36912372,,, +케이아트성형외과,프리미엄/하이타깃 후보,역삼,,http://www.k-artps.com,,https://www.youtube.com/channel/UCHs9LOYtauBIklXhPPZBAgA,,https://www.instagram.com/kartps/,,,,https://www.facebook.com/kartpsseoul,,,,,,https://blog.naver.com/kartps,,,,, +이룸성형외과,프리미엄/하이타깃 후보,역삼,,http://www.seoulips.com,,https://www.youtube.com/@seouliplasticsurgery,,https://www.instagram.com/seoulips/,,,,https://www.facebook.com/서울아이성형외과-105199207892670/,,https://www.tiktok.com/@seoulips_jp,,https://www.gangnamunni.com/hospitals/839,,https://blog.naver.com/seoulips,,https://m.place.naver.com/hospital/1240083198,,, +서초서울성형외과,프리미엄/하이타깃 후보,서초,,https://srprs.co.kr,,,,https://www.instagram.com/saerops/,,,,,,,,https://www.gangnamunni.com/hospitals/5554,,,,https://m.place.naver.com/hospital/1692465198,,, +서초연세성형외과,프리미엄/하이타깃 후보,서초,,https://www.chaminst.com,,https://www.youtube.com/channel/UCMxBfZivKz5jjhRiy0mzOXQ,,https://www.instagram.com/chamin_ps,,,,,,,,https://www.gangnamunni.com/hospitals/4212,,https://blog.naver.com/chaminst,,,,, +리모성형외과,프리미엄/하이타깃 후보,서초,,http://www.ksh-ps.com,,https://www.youtube.com/channel/UCCiH9OffJgZTMnkxatK8-5A,,https://www.instagram.com/sh___ps/,,,,,,,,https://www.gangnamunni.com/hospitals/6680,,https://blog.naver.com/shpsclinic,,https://m.place.naver.com/hospital/1865533181,,, +화이트성형외과,프리미엄/하이타깃 후보,서초,,http://www.whiteclinic.com,,,,,,,,,,,,,,,,,,, +미호성형외과,프리미엄/하이타깃 후보,서초,,https://mihops.co.kr,,https://www.youtube.com/channel/UC8kyWAnMM7f_xguyPxR_gtA,,https://www.instagram.com/mihops_kr/,,,,,,,,https://www.gangnamunni.com/hospitals/369,,https://blog.naver.com/qalfuqbjtmnf,,https://m.place.naver.com/hospital/344342362,,, +나우성형외과,프리미엄/하이타깃 후보,서초,,,,,,,,,,,,,,,,,,,,, +와이즈성형외과,프리미엄/하이타깃 후보,서초,,,,,,https://www.instagram.com/wise_ps/,,,,,,,,https://www.gangnamunni.com/hospitals/2414,와이즈유의원,,,,,, +케이스타성형외과,프리미엄/하이타깃 후보,서초,,,,,,,,,,,,,,,,,,,,, +셀린성형외과,프리미엄/하이타깃 후보,서초,,,,,,,,,,,,,,,,,,,,, +에비뉴성형외과,프리미엄/하이타깃 후보,서초,,http://www.avenueps.com,,https://www.youtube.com/channel/UCOTVAerYogSEia3L-ERkAFg,,,,,,,,,,https://www.gangnamunni.com/hospitals/6204,,,,,,, +비온성형외과,프리미엄/하이타깃 후보,서초,,http://www.bon-ps.com,,https://www.youtube.com/channel/UClCjGIfEb3b1N-Q5tCNSU5w,,,,,,,,,,,,https://blog.naver.com/miz2199,,https://m.place.naver.com/hospital/1492257313,,, +아크성형외과,프리미엄/하이타깃 후보,서초,,http://arc-ps.com,,https://www.youtube.com/@-arcisart7955,,https://www.instagram.com/arc_plastic_surgery/,,,,,,,,https://www.gangnamunni.com/hospitals/3569,,,,https://m.place.naver.com/hospital/1638918034,,, +에버성형외과,프리미엄/하이타깃 후보,서초,,http://www.everclinic.com,,https://www.youtube.com/channel/UCwSusNRTM2B_mvdRd0yNVcw,,https://www.instagram.com/everclinic0088/,,,,https://www.facebook.com/everplasticsurgery,,,,,,https://blog.naver.com/drblue007,,,,, +원스성형외과,프리미엄/하이타깃 후보,서초,,,,,,,,,,,,,,,,,,,,, +메이성형외과,프리미엄/하이타깃 후보,서초,,,,,,,,,,,,,,,,,,,,, +라인업성형외과,프리미엄/하이타깃 후보,서초,,,,,,,,,,,,,,,,,,,,, +유앤미성형외과,프리미엄/하이타깃 후보,서초,,https://www.knyounmeclinic.co.kr,,https://www.youtube.com/channel/UCAROD-B1ZBinpyzql7o_IlA,,https://www.instagram.com/knyounmeclinic/,,,,,,,,https://www.gangnamunni.com/hospitals/1178,,,,https://m.place.naver.com/hospital/31068065,,, +리더스성형외과,프리미엄/하이타깃 후보,서초,,,,,,,,,,,,,,,,,,,,, +마노성형외과,프리미엄/하이타깃 후보,서초,,https://manops.co.kr,,https://www.youtube.com/@mano_ps,,https://www.instagram.com/manops_official,,,,,,,,https://www.gangnamunni.com/hospitals/3429,,https://blog.naver.com/tkhr4747,,,,, +예롬성형외과,프리미엄/하이타깃 후보,서초,,,,,,,,,,,,,,https://www.gangnamunni.com/hospitals/450,,,,,,, +에스엠성형외과,프리미엄/하이타깃 후보,서초,,https://www.sm-ps.co.kr,,https://www.youtube.com/@smpsclinic,,https://www.instagram.com/smps_plastic_surgery/,,,,https://www.facebook.com/thammySM3707,,,,https://www.gangnamunni.com/hospitals/413,,https://blog.naver.com/smps1004,,https://m.place.naver.com/hospital/32876182,,, +리안성형외과,프리미엄/하이타깃 후보,서초,,,,,,,,,,,,,,,,,,,,, +서울미작성형외과,프리미엄/하이타깃 후보,서초,,https://mijakclinic.co.kr,,https://www.youtube.com/channel/UCld-NgVeydtDRVTt6xQRbmA,,https://www.instagram.com/mijak2444/,,,,https://www.facebook.com/mijaknose,,,,,,https://blog.naver.com/mijak3444,,,,, +더원성형외과,프리미엄/하이타깃 후보,서초,,https://www.theoneclinic.co.kr,,,,https://www.instagram.com/theone_plastic_surgery/,,,,https://www.facebook.com/pages/category/Hospital/더원성형외과-1539415639613021/,,,,https://www.gangnamunni.com/hospitals/5636,,https://blog.naver.com/brs0714,,,,, diff --git a/data/clinic-registry/extract_place_ids.py b/data/clinic-registry/extract_place_ids.py new file mode 100644 index 0000000..ec934e2 --- /dev/null +++ b/data/clinic-registry/extract_place_ids.py @@ -0,0 +1,37 @@ +#!/usr/bin/env python3 +"""Extract Naver Place IDs from links arrays""" +import re +import json +import sys + +def extract_place_id(links): + """Extract first valid Naver Place ID from list of URLs""" + place_ids = set() + for link in links: + # Pattern: place/DIGITS in map.naver.com URLs + # But NOT in search URLs or directions URLs with coordinates + if 'map.naver.com' in link: + matches = re.findall(r'place/(\d{7,12})', link) + for m in matches: + # Filter out coordinate-like numbers (14140xxx pattern) + if not m.startswith('1414'): + place_ids.add(m) + + if place_ids: + # Return the most common ID (first one found in entry/place URLs) + for link in links: + if 'entry/place/' in link: + match = re.search(r'entry/place/(\d{7,12})', link) + if match and not match.group(1).startswith('1414'): + return match.group(1) + # Fallback: return smallest ID (usually the main one) + return min(place_ids, key=len) + return None + +if __name__ == '__main__': + data = json.load(sys.stdin) + pid = extract_place_id(data) + if pid: + print(pid) + else: + print("NOT_FOUND") diff --git a/data/clinic-registry/premium_seoul_plastic_surgery_channels_100names.csv b/data/clinic-registry/premium_seoul_plastic_surgery_channels_100names.csv new file mode 100644 index 0000000..512fe18 --- /dev/null +++ b/data/clinic-registry/premium_seoul_plastic_surgery_channels_100names.csv @@ -0,0 +1,96 @@ +hospital_name,brand_group,district,website_kr,website_en,youtube_url,youtube_note,instagram_kr_url,instagram_kr_note,instagram_en_url,instagram_en_note,facebook_url,facebook_note,tiktok_url,tiktok_note,gangnam_unni_url,gangnam_unni_note,naver_blog_url,naver_blog_note,naver_place_url,naver_place_reviews_note,google_maps_url,google_reviews_note +바노바기성형외과,프리미엄/하이타깃 후보,강남,https://www.banobagi.com,,,,,,,,,,,,,,,,,,, +뷰성형외과,프리미엄/하이타깃 후보,강남,https://www.viewclinic.com,,,,,,,,,,,,,,,,,,, +아이디병원,프리미엄/하이타깃 후보,강남,https://www.idhospital.com,,,,,,,,,,,,,,,,,,, +그랜드성형외과,프리미엄/하이타깃 후보,강남,https://www.grandsurgery.com,,,,,,,,,,,,,,,,,,, +원진성형외과,프리미엄/하이타깃 후보,강남,https://www.k-wonjin.co.kr,,,,,,,,,,,,,,,,,,, +마인드성형외과,프리미엄/하이타깃 후보,강남,https://www.mindprs.com,,,,,,,,,,,,,,,,,,, +브라운성형외과,프리미엄/하이타깃 후보,강남,https://www.braunps.co.kr,,,,,,,,,,,,,,,,,,, +오메가성형외과,프리미엄/하이타깃 후보,강남,http://www.omegaps.co.kr,,,,,,,,,,,,,,,,,,, +나나성형외과,프리미엄/하이타깃 후보,강남,https://www.nanaprs.com,,,,,,,,,,,,,,,,,,, +노트성형외과,프리미엄/하이타깃 후보,강남,http://notebreast.com,,,,,,,,,,,,,,,,,,, +디에이성형외과,프리미엄/하이타깃 후보,강남,https://daprs.com,,,,,,,,,,,,,,,,,,, +에이비성형외과,프리미엄/하이타깃 후보,강남,https://www.abps.co.kr,,,,,,,,,,,,,,,,,,, +비아이오성형외과,프리미엄/하이타깃 후보,강남,http://biopskorea.com,,,,,,,,,,,,,,,,,,, +윈성형외과,프리미엄/하이타깃 후보,강남,https://www.k-wonjin.co.kr,,,,,,,,,,,,,,,,,,, +제이준성형외과,프리미엄/하이타깃 후보,강남,http://www.jjprs.com,,,,,,,,,,,,,,,,,,, +마블성형외과,프리미엄/하이타깃 후보,강남,https://marbleps.com,,,,,,,,,,,,,,,,,,, +쥬얼리성형외과,프리미엄/하이타깃 후보,강남,https://www.jewelryps.kr,,,,,,,,,,,,,,,,,,, +티에스성형외과,프리미엄/하이타깃 후보,강남,http://www.tsprs.com,,,,,,,,,,,,,,,,,,, +유노성형외과,프리미엄/하이타깃 후보,강남,https://www.yunoprs.com,,,,,,,,,,,,,,,,,,, +리젠성형외과,프리미엄/하이타깃 후보,강남,리젠 성형외과,,,,,,,,,,,,,,,,,,, +리팅성형외과,프리미엄/하이타깃 후보,강남,https://liting.co.kr,,,,,,,,,,,,,,,,,,, +앤써성형외과,프리미엄/하이타깃 후보,강남,http://www.answer-ps.co.kr,,,,,,,,,,,,,,,,,,, +더픽스성형외과,프리미엄/하이타깃 후보,강남,http://thefixps.com,,,,,,,,,,,,,,,,,,, +기린성형외과,프리미엄/하이타깃 후보,강남,https://girinps.com,,,,,,,,,,,,,,,,,,, +페이스성형외과,프리미엄/하이타깃 후보,강남,http://www.face-plus.co.kr,,,,,,,,,,,,,,,,,,, +압구정서울성형외과,프리미엄/하이타깃 후보,압구정,http://www.asps.co.kr,,,,,,,,,,,,,,,,,,, +리젠메디컬그룹,프리미엄/하이타깃 후보,압구정,http://regenskin.co.kr,,,,,,,,,,,,,,,,,,, +마블성형외과 압구정,프리미엄/하이타깃 후보,압구정,https://marbleps.com,,,,,,,,,,,,,,,,,,, +코코성형외과,프리미엄/하이타깃 후보,압구정,http://www.kodoctor.co.kr,,,,,,,,,,,,,,,,,,, +오브제성형외과,프리미엄/하이타깃 후보,압구정,http://objetps.com,,,,,,,,,,,,,,,,,,, +리상성형외과,프리미엄/하이타깃 후보,압구정,https://theregenps.com,,,,,,,,,,,,,,,,,,, +에이트성형외과,프리미엄/하이타깃 후보,압구정,https://www.eightprs.com,,,,,,,,,,,,,,,,,,, +오페라성형외과,프리미엄/하이타깃 후보,압구정,https://gangnam.lienjang.net,,,,,,,,,,,,,,,,,,, +리엔장성형외과,프리미엄/하이타깃 후보,압구정,https://www.soulps.kr,,,,,,,,,,,,,,,,,,, +루호성형외과,프리미엄/하이타깃 후보,압구정,https://m.atopps.com,,,,,,,,,,,,,,,,,,, +티아라성형외과,프리미엄/하이타깃 후보,압구정,https://www.ucanb.co.kr,,,,,,,,,,,,,,,,,,, +쏘울성형외과,프리미엄/하이타깃 후보,압구정,https://www.starclinic.co.kr,,,,,,,,,,,,,,,,,,, +에이탑성형외과,프리미엄/하이타깃 후보,압구정,http://www.licoclinic.com,,,,,,,,,,,,,,,,,,, +유스성형외과,프리미엄/하이타깃 후보,압구정,https://www.pspskorea.com,,,,,,,,,,,,,,,,,,, +유캔비성형외과,프리미엄/하이타깃 후보,압구정,http://www.iwellps.com,,,,,,,,,,,,,,,,,,, +스타성형외과,프리미엄/하이타깃 후보,압구정,https://glovips.com,,,,,,,,,,,,,,,,,,, +아이템성형외과,프리미엄/하이타깃 후보,압구정,http://www.elleclinic.com,,,,,,,,,,,,,,,,,,, +리코성형외과,프리미엄/하이타깃 후보,압구정,https://onepeakps.com,,,,,,,,,,,,,,,,,,, +플레저성형외과,프리미엄/하이타깃 후보,압구정,https://www.pspskorea.com,,,,,,,,,,,,,,,,,,, +비너스성형외과,프리미엄/하이타깃 후보,압구정,http://www.venusbreast.com,,,,,,,,,,,,,,,,,,, +아이웰성형외과,프리미엄/하이타깃 후보,압구정,http://www.iwellps.com,,,,,,,,,,,,,,,,,,, +글로비성형외과,프리미엄/하이타깃 후보,압구정,https://glovips.com,,,,,,,,,,,,,,,,,,, +엘르성형외과,프리미엄/하이타깃 후보,압구정,http://www.elleclinic.com,,,,,,,,,,,,,,,,,,, +원픽성형외과,프리미엄/하이타깃 후보,압구정,https://onepeakps.com,,,,,,,,,,,,,,,,,,, +탑페이스성형외과,프리미엄/하이타깃 후보,압구정,https://www.topfaceps.com/,,,,,,,,,,,,,,,,,,, +아이디병원 별관,프리미엄/하이타깃 후보,역삼,https://www.idhospital.com,,,,,,,,,,,,,,,,,,, +뷰성형외과 역삼센터,프리미엄/하이타깃 후보,역삼,https://www.viewclinic.com,,,,,,,,,,,,,,,,,,, +세민성형외과,프리미엄/하이타깃 후보,역삼,http://www.semin100.co.kr,,,,,,,,,,,,,,,,,,, +에이치성형외과,프리미엄/하이타깃 후보,역삼,https://www.3dfit.co.kr,,,,,,,,,,,,,,,,,,, +리노보성형외과,프리미엄/하이타깃 후보,역삼,http://www.renovo.co.kr,,,,,,,,,,,,,,,,,,, +디엘성형외과,프리미엄/하이타깃 후보,역삼,https://www.dlprs.com,,,,,,,,,,,,,,,,,,, +피알성형외과,프리미엄/하이타깃 후보,역삼,http://prprs.co.kr,,,,,,,,,,,,,,,,,,, +라프린성형외과,프리미엄/하이타깃 후보,역삼,http://laprinps.com,,,,,,,,,,,,,,,,,,, +도도성형외과,프리미엄/하이타깃 후보,역삼,http://www.dodobeauty.com,,,,,,,,,,,,,,,,,,, +엠제이성형외과,프리미엄/하이타깃 후보,역삼,https://www.mjskinclinic.com,,,,,,,,,,,,,,,,,,, +아우라성형외과,프리미엄/하이타깃 후보,역삼,http://psaura.com,,,,,,,,,,,,,,,,,,, +하이봄성형외과,프리미엄/하이타깃 후보,역삼,https://www.highvom.com,,,,,,,,,,,,,,,,,,, +에이원성형외과,프리미엄/하이타깃 후보,역삼,http://www.aone.achttps://www.uni114.co.kr,,,,,,,,,,,,,,,,,,, +유앤아이성형외과,프리미엄/하이타깃 후보,역삼,https://www.rebornps.com검색 중,,,,,,,,,,,,,,,,,,, +리본성형외과,프리미엄/하이타깃 후보,역삼,http://www.likeps.com,,,,,,,,,,,,,,,,,,, +리메이성형외과,프리미엄/하이타깃 후보,역삼,https://remayps.com,,,,,,,,,,,,,,,,,,, +라이크성형외과,프리미엄/하이타깃 후보,역삼,http://www.likeps.com,,,,,,,,,,,,,,,,,,, +케이플러스성형외과,프리미엄/하이타깃 후보,역삼,https://k-clinics.com,,,,,,,,,,,,,,,,,,, +케이아트성형외과,프리미엄/하이타깃 후보,역삼,http://www.k-artps.com,,,,,,,,,,,,,,,,,,, +이룸성형외과,프리미엄/하이타깃 후보,역삼,http://www.seoulips.com,,,,,,,,,,,,,,,,,,, +서초서울성형외과,프리미엄/하이타깃 후보,서초,https://srprs.co.kr,,,,,,,,,,,,,,,,,,, +서초연세성형외과,프리미엄/하이타깃 후보,서초,https://www.chaminst.com,,,,,,,,,,,,,,,,,,, +리모성형외과,프리미엄/하이타깃 후보,서초,http://www.ksh-ps.com,,,,,,,,,,,,,,,,,,, +화이트성형외과,프리미엄/하이타깃 후보,서초,http://www.whiteclinic.com,,,,,,,,,,,,,,,,,,, +미호성형외과,프리미엄/하이타깃 후보,서초,https://mihops.co.kr,,,,,,,,,,,,,,,,,,, +나나성형외과,프리미엄/하이타깃 후보,서초,https://www.nanaprs.com,,,,,,,,,,,,,,,,,,, +와이즈성형외과,프리미엄/하이타깃 후보,서초,,,,,,,,,,,,,,,,,,,, +케이스타성형외과,프리미엄/하이타깃 후보,서초,,,,,,,,,,,,,,,,,,,, +셀린성형외과,프리미엄/하이타깃 후보,서초,,,,,,,,,,,,,,,,,,,, +에비뉴성형외과,프리미엄/하이타깃 후보,서초,http://www.avenueps.com,,,,,,,,,,,,,,,,,,, +에이트성형외과 서초,프리미엄/하이타깃 후보,서초,https://www.eightprs.com,,,,,,,,,,,,,,,,,,, +비온성형외과,프리미엄/하이타깃 후보,서초,http://www.bon-ps.com,,,,,,,,,,,,,,,,,,, +아크성형외과,프리미엄/하이타깃 후보,서초,http://arc-ps.com,,,,,,,,,,,,,,,,,,, +에버성형외과,프리미엄/하이타깃 후보,서초,http://www.everclinic.com,,,,,,,,,,,,,,,,,,, +원스성형외과,프리미엄/하이타깃 후보,서초,,,,,,,,,,,,,,,,,,,, +메이성형외과,프리미엄/하이타깃 후보,서초,http://www.makeps.com,,,,,,,,,,,,,,,,,,, +라인업성형외과,프리미엄/하이타깃 후보,서초,,,,,,,,,,,,,,,,,,,, +유앤미성형외과,프리미엄/하이타깃 후보,서초,https://www.knyounmeclinic.co.kr,,,,,,,,,,,,,,,,,,, +리더스성형외과,프리미엄/하이타깃 후보,서초,,,,,,,,,,,,,,,,,,,, +마노성형외과,프리미엄/하이타깃 후보,서초,https://manops.co.kr,,,,,,,,,,,,,,,,,,, +예롬성형외과,프리미엄/하이타깃 후보,서초,http://www.yeromclinic.co.kr,,,,,,,,,,,,,,,,,,, +에스엠성형외과,프리미엄/하이타깃 후보,서초,https://www.sm-ps.co.kr,,,,,,,,,,,,,,,,,,, +리안성형외과,프리미엄/하이타깃 후보,서초,,,,,,,,,,,,,,,,,,,, +서울미작성형외과,프리미엄/하이타깃 후보,서초,https://mijakclinic.co.kr,,,,,,,,,,,,,,,,,,, +더원성형외과,프리미엄/하이타깃 후보,서초,https://www.theoneclinic.co.kr,,,,,,,,,,,,,,,,,,, \ No newline at end of file diff --git a/data/clinic-registry/update_csv.py b/data/clinic-registry/update_csv.py new file mode 100644 index 0000000..4e71266 --- /dev/null +++ b/data/clinic-registry/update_csv.py @@ -0,0 +1,114 @@ +#!/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") diff --git a/docs/AGENT_SYSTEM_PROMPTS.md b/docs/AGENT_SYSTEM_PROMPTS.md new file mode 100644 index 0000000..fe6f768 --- /dev/null +++ b/docs/AGENT_SYSTEM_PROMPTS.md @@ -0,0 +1,234 @@ +# INFINITH Agent System & Prompts + +각 에이전트의 역할, 시스템 프롬프트, 프로세스 정의. + +--- + +## Pipeline Overview + +``` +URL 입력 + ↓ +[Agent 1] Channel Discovery Agent — 채널 발견 + 검증 + ↓ +[Agent 2] Data Collection Agent — 채널 데이터 전량 수집 + 시장 분석 + ↓ +[Agent 3] Marketing Intelligence Agent — AI 리포트 생성 + ↓ +[Agent 4] Content Director Agent — 콘텐츠 기획 + 캘린더 + ↓ +[Agent 5] Brand Strategist Agent — 브랜드 가이드 + 채널 전략 +``` + +--- + +## Agent 1: Channel Discovery Agent (채널 발견) + +**역할**: 마케팅 리서처. 병원의 모든 온라인 채널을 찾아내는 전문가. + +**File**: `supabase/functions/discover-channels/index.ts` + +### Process (3단계) +1. **Stage A**: Firecrawl 웹사이트 스크래핑 (병원명 추출 + 소셜 링크 파싱) +2. **Stage B**: 6개 API 병렬 검색 (YouTube API, Naver API, Firecrawl Search, Perplexity, Apify Instagram) +3. **Stage C**: 5개 소스 병합 + 핸들 검증 + +### System Prompt (Perplexity — Online Presence 종합 분석) +``` +Role: Digital marketing analyst specializing in Korean medical clinics. +Task: Search the web thoroughly and provide a comprehensive online presence report. +Output: ONLY valid JSON, no explanation. + +User Prompt: +"{clinicName}" 병원의 Online Presence를 종합 분석해줘. + +아래 채널들을 모두 검색해서 찾아줘: +- 인스타그램 계정 (병원 공식, 원장 개인, 영문 계정 등 여러개 있을 수 있음) +- 유튜브 채널 (메인 채널, Q&A 채널 등) +- 페이스북 페이지 +- 틱톡 계정 +- 네이버 블로그 (공식 블로그) +- 카카오톡 채널 +- 강남언니 등록 여부 및 URL +- 바비톡 등록 여부 +- 네이버 플레이스 등록 여부 +``` + +### System Prompt (Perplexity — 병원명 추출 fallback) +``` +Role: None (simple extraction) +System: Respond with ONLY the clinic name in Korean, nothing else. +User: {url} 이 URL의 병원/클리닉 한국어 이름이 뭐야? +``` + +### Data Sources +| Source | API | 검색 방법 | +|--------|-----|----------| +| 웹사이트 HTML | Firecrawl scrape + map | URL 파싱으로 소셜 링크 추출 | +| YouTube | YouTube Data API v3 | `search?type=channel&q={clinicName}` | +| Naver Blog | Naver Search API | `blog.json?query={clinicName} 공식 블로그` | +| Naver Web | Naver Search API | `webkr.json?query={clinicName} 인스타그램 유튜브` | +| Instagram | Apify instagram-profile-scraper | 병원명 변형으로 직접 프로필 검색 | +| 종합 검색 | Firecrawl Search | `{clinicName} instagram youtube 공식` | +| 종합 분석 | Perplexity sonar | Online Presence 종합 분석 | + +--- + +## Agent 2: Data Collection Agent (데이터 수집) + +**역할**: 데이터 엔지니어. 검증된 채널에서 raw 데이터를 전량 수집. + +**File**: `supabase/functions/collect-channel-data/index.ts` + +### Process +9개 API 병렬 호출 (Promise.allSettled): +1. Instagram — Apify `instagram-profile-scraper` +2. YouTube — YouTube Data API v3 (채널 통계 + 인기 영상 10개) +3. Facebook — Apify `facebook-pages-scraper` +4. 강남언니 — Firecrawl JSON 추출 +5. Naver Blog — Naver Search API +6. Naver Place — Naver Local API +7. Google Maps — Apify `compass~crawler-google-places` +8-11. 시장 분석 — Perplexity (경쟁사, 키워드, 시장, 타겟 4개 병렬) + +### System Prompt (시장 분석 — 4개 공통) +``` +Role: Korean medical marketing analyst +System: Always respond in Korean. Provide data in valid JSON format. + +Queries: +1. 경쟁사: {address} 근처 {services} 전문 경쟁 병원 5곳 분석 +2. 키워드: {services} 관련 검색 키워드 트렌드 (네이버+구글 월간 검색량 20개) +3. 시장: {services[0]} 시장 트렌드 2025-2026 (규모, 성장률, 트렌드) +4. 타겟: {clinicName} 잠재 고객 분석 (연령, 성별, 채널, 의사결정) +``` + +### System Prompt (강남언니 추출) +``` +Firecrawl JSON extraction: +Extract: hospital name, overall rating (out of 10), total review count, +doctors with names/ratings/review counts/specialties, procedures offered, +address, certifications/badges +``` + +--- + +## Agent 3: Marketing Intelligence Agent (리포트 생성) + +**역할**: 마케팅 커뮤니케이션 전략가. 실제 수집 데이터를 기반으로 종합 리포트 작성. + +**File**: `supabase/functions/generate-report/index.ts` + +### System Prompt +``` +Role: Korean medical marketing analyst +Constraints: + - Respond ONLY with valid JSON, no markdown code blocks + - Use Korean for text fields + - 강남언니 rating is 10-point scale + - Use ONLY the provided real data — NEVER invent metrics + - If data is missing, write "데이터 없음" + +User Prompt Structure: +1. 병원 기본 정보 (scraped data) +2. 실제 채널 데이터 (collected from APIs — YouTube 구독자, Instagram 팔로워 등) +3. 시장 분석 데이터 (Perplexity 검색 결과) +4. 웹사이트 브랜딩 (Firecrawl 추출) +5. JSON 리포트 구조 (channelAnalysis, brandIdentity, kpiTargets, recommendations 등) +``` + +### Output Structure +```json +{ + "clinicInfo": {}, + "executiveSummary": "", + "overallScore": 0-100, + "channelAnalysis": { "naverBlog": {}, "instagram": {}, "youtube": {}, ... }, + "brandIdentity": [{ "area": "", "asIs": "", "toBe": "" }], + "kpiTargets": [{ "metric": "", "current": "", "target3Month": "", "target12Month": "" }], + "recommendations": [{ "priority": "", "category": "", "title": "", "description": "" }], + "competitors": [], + "keywords": {}, + "targetAudience": {}, + "marketTrends": [] +} +``` + +--- + +## Agent 4: Content Director Agent (콘텐츠 기획) + +**역할**: 콘텐츠 디렉터. 채널 전략과 브랜드 가이드를 기반으로 4주 콘텐츠 캘린더 기획. + +**File**: `src/lib/contentDirector.ts` + +### Process (결정론적 — AI 호출 없음) +1. 채널-포맷 매트릭스 구성 (YouTube Shorts/Long, Instagram Reels/Carousel/Stories, 네이버 블로그, Facebook 광고) +2. 주차별 테마 할당 (Week 1: 브랜드 정비, Week 2: 콘텐츠 엔진, Week 3: 소셜 증거, Week 4: 전환 최적화) +3. Pillar-Service 매트릭스로 토픽 생성 (전문성×서비스, 비포애프터×서비스, 후기×서비스, 트렌드×서비스) +4. 기존 YouTube 인기 영상 리퍼포징 배치 +5. 월간 콘텐츠 서머리 계산 + +### Input +```typescript +{ + channels: ChannelStrategyCard[]; // 활성 채널 목록 + pillars: ContentPillar[]; // 4개 콘텐츠 필라 + services: string[]; // 시술 목록 + youtubeVideos: TopVideo[]; // 리퍼포징 소스 + clinicName: string; +} +``` + +--- + +## Agent 5: Brand Strategist Agent (브랜드 전략) + +**역할**: 브랜드 전략가. 채널 분석 결과를 브랜드 가이드와 채널별 커뮤니케이션 전략으로 변환. + +**File**: `src/lib/transformPlan.ts` + +### Process (결정론적 — AI 호출 없음) +1. 채널 스코어 기반 전략 카드 생성 (P0/P1/P2 우선순위) +2. 브랜드 일관성 분석 (채널 간 이름/로고/연락처 비교) +3. 콘텐츠 필라 정의 (전문성·신뢰 / 비포·애프터 / 환자 후기 / 트렌드·교육) +4. 에셋 수집 및 리퍼포징 제안 + +--- + +## Agent 6: Image Creator Agent (이미지 생성) + +**역할**: 비주얼 디자이너. 마케팅 이미지 생성. + +**File**: `src/services/geminiImageGen.ts` + +### System Prompt +``` +Generate a premium medical marketing image for a plastic surgery clinic. +Theme: {pillarContext} // safety | expertise | results | care +Style: {channelHint} // youtube | instagram | naver_blog | tiktok | facebook +Color palette: soft purple (#7B2D8E), gold (#E8B931), warm white (#FAF8F5). +Premium, luxurious, trustworthy aesthetic. +No text or logos in the image. +Photorealistic, high quality, professional medical marketing. +``` + +--- + +## Known Issues & Improvement Plan + +### 검색 성능 +- [ ] Instagram 검색: Perplexity가 찾아도 verify에서 탈락 → unverified 핸들도 후보로 유지 +- [ ] Apify Instagram 검색 타임아웃 30초 → 60초로 증가 +- [ ] 강남언니 verify 성공률 개선 — Perplexity URL 힌트 활용도 높이기 + +### 프롬프트 품질 +- [ ] Few-shot example 추가 (성공 응답 예시 포함) +- [ ] Chain-of-thought 유도 (리포트 생성 시 분석 과정 단계별 진행) +- [ ] JSON 파싱 실패 시 재시도 (temperature 올려서 1회) +- [ ] Perplexity `response_format: json_object` 옵션 활용 + +### 데이터 품질 +- [ ] 주소 정보: Google Maps + Naver Place에서 수집한 주소를 최우선 사용 +- [ ] 개원 연도 파싱: "데이터 없음 (NaN년)" 방지 +- [ ] KPI 수치: enrichment 실제 데이터 우선, AI 추측 무시 diff --git a/docs/AI_PROMPTS_CATALOG.md b/docs/AI_PROMPTS_CATALOG.md new file mode 100644 index 0000000..615209a --- /dev/null +++ b/docs/AI_PROMPTS_CATALOG.md @@ -0,0 +1,428 @@ +# INFINITH AI Prompts Catalog (v2 — Updated 2026-04-04) + +현재 프로덕션에서 사용 중인 모든 AI 프롬프트. +Pipeline V2 아키텍처 기반 (discover → collect → generate). + +--- + +## Pipeline Overview + +``` +Phase 1: discover-channels + ├─ A. Firecrawl scrape+map (병원 정보 + 소셜 링크 추출) + ├─ B1. YouTube Data API (채널 직접 검색) + ├─ B2. Naver Search API (블로그 + 웹 검색) + ├─ B3. Firecrawl Search (소셜 URL 웹 검색) + ├─ B4. Perplexity sonar (Online Presence 통합 검색) + ├─ B4b. Perplexity sonar (강남언니 URL 검색) + ├─ B5. Apify Instagram (프로필 직접 검색) + └─ C. 핸들 검증 (HEAD 요청 + YouTube API) + +Phase 2: collect-channel-data + ├─ Instagram (Apify) + ├─ YouTube (YouTube Data API v3) + ├─ Facebook (Apify) + ├─ 강남언니 (Firecrawl JSON 추출) + ├─ Naver Blog + Place (Naver API) + ├─ Google Maps (Apify) + └─ 시장 분석 (Perplexity × 4 병렬) + +Phase 3: generate-report + └─ Perplexity sonar (실제 수집 데이터 기반 리포트 생성) +``` + +--- + +## Phase 1: discover-channels + +### P1-A. Firecrawl — 병원 정보 + 소셜 링크 추출 + +**File**: `supabase/functions/discover-channels/index.ts` (Stage A) +**API**: Firecrawl `v1/scrape` (JSON + links) +**Wait**: 5000ms + +**Extraction Prompt**: +``` +Extract: clinic name (Korean), clinic name (English), address, phone, +services offered, doctors with specialties, ALL social media links +(instagram handles/URLs, youtube channel URL/handle, naver blog URL, +facebook page URL, tiktok, kakao channel), business hours, slogan +``` + +**Schema**: clinicName, clinicNameEn, address, phone, businessHours, slogan, services[], doctors[], socialMedia{} + +**용도**: 병원 기본 정보 수집 + HTML에서 소셜 링크 직접 추출 + +--- + +### P1-A2. Firecrawl — 브랜딩 추출 + +**API**: Firecrawl `v1/scrape` (JSON) +**Wait**: 3000ms + +**Extraction Prompt**: +``` +Extract brand identity: primary/accent/background/text colors (hex), +heading/body fonts, logo URL, favicon URL, tagline +``` + +**Schema**: primaryColor, accentColor, backgroundColor, textColor, headingFont, bodyFont, logoUrl, faviconUrl, tagline + +--- + +### P1-B1. YouTube Data API — 채널 직접 검색 + +**File**: `supabase/functions/discover-channels/index.ts` (Stage B1) +**API**: YouTube Data API v3 `search?type=channel` +**Prompt**: 없음 (API 직접 호출) + +``` +GET https://www.googleapis.com/youtube/v3/search + ?part=snippet + &type=channel + &q={clinicName} + &maxResults=3 + &key={YOUTUBE_API_KEY} +``` + +**매칭 로직**: 검색 결과 채널명이 병원명을 포함하면 channelId 추출 + +--- + +### P1-B2a. Naver Search API — 블로그 검색 + +**File**: `supabase/functions/discover-channels/index.ts` (Stage B2a) +**API**: Naver Search `blog.json` + +``` +GET https://openapi.naver.com/v1/search/blog.json + ?query={clinicName} 공식 블로그 + &display=5 + &sort=sim +``` + +**추출**: `blog.naver.com/{blogId}` 패턴 매칭 + +--- + +### P1-B2b. Naver Search API — 웹 검색 (소셜 URL 발견) + +**API**: Naver Search `webkr.json` + +``` +GET https://openapi.naver.com/v1/search/webkr.json + ?query={clinicName} 인스타그램 유튜브 공식 + &display=10 +``` + +**추출**: 검색 결과 URL에서 instagram.com, youtube.com, facebook.com 패턴 매칭 + +--- + +### P1-B3. Firecrawl Search — 소셜 URL 웹 검색 + +**API**: Firecrawl `v1/search` + +```json +{ + "query": "{clinicName} 성형외과 instagram youtube 공식", + "limit": 10 +} +``` + +**추출**: 검색 결과 URL에서 소셜 핸들 패턴 매칭 (extractSocialLinks) + +--- + +### P1-B4. Perplexity — Online Presence 통합 검색 ⭐ (핵심 프롬프트) + +**File**: `supabase/functions/discover-channels/index.ts` (Stage B4) +**API**: Perplexity `sonar`, temp=0.1 +**목적**: 다른 API가 놓친 소셜 계정을 웹 검색으로 보충 발견 + +**System Message**: +``` +You are a social media researcher. Search the web and find social media accounts. Respond ONLY with valid JSON. +``` + +**User Message** (template): +``` +{clinicName} ({clinicNameEn}) 병원의 인스타그램, 유튜브, 페이스북, 틱톡, 네이버블로그 계정을 검색해서 찾아줘. 검색 결과에서 발견된 계정을 모두 알려줘. 인스타그램은 여러 계정이 있을 수 있어. + +{"instagram": ["handle1", "handle2"], "youtube": "channel URL or handle", "facebook": "page name or URL", "tiktok": "handle", "naverBlog": "blog ID"} +``` + +**핵심 학습 (프롬프트 엔지니어링)**: +- ❌ 실패 패턴: "공식 계정만 찾아줘" / "확인된 계정만" / "Never guess" → 전부 null 반환 +- ❌ 실패 패턴: sonar-pro + 장문 시스템 프롬프트 → 빈 결과 +- ❌ 실패 패턴: 3개로 분리된 쿼리 → 각각 빈 결과 +- ✅ 성공 패턴: 짧은 시스템 프롬프트 + 모든 채널 한 쿼리 + "검색해서 찾아줘" + 영문명 괄호 포함 +- ✅ 성공 패턴: `sonar` 모델 (sonar-pro보다 오히려 나음) +- ✅ 성공 패턴: 예시 JSON을 user message 끝에 포함 (output 형식 유도) + +**변수 구성**: +```typescript +const clinicNameEn = clinic.clinicNameEn || ''; +const searchName = clinicNameEn + ? `${resolvedName} (${clinicNameEn})` // "그랜드성형외과 (Grand Plastic Surgery)" + : resolvedName; // "그랜드성형외과" +``` + +--- + +### P1-B4b. Perplexity — 강남언니 URL 검색 + +**API**: Perplexity `sonar`, temp=0.1 + +**System Message**: +``` +You search for clinic listings on medical platforms. Respond ONLY with valid JSON. +``` + +**User Message**: +``` +{clinicName} 병원 강남언니 gangnamunni.com 페이지를 찾아줘. + +{"gangnamUnni": {"url": "https://gangnamunni.com/hospitals/...", "rating": 9.5, "reviews": 1000}} +``` + +--- + +### P1-B5. Apify — Instagram 프로필 직접 검색 + +**File**: `supabase/functions/discover-channels/index.ts` (Stage B5) +**API**: Apify `instagram-profile-scraper` +**Timeout**: 30초 per candidate + +**핸들 후보 생성 로직**: +```typescript +const baseName = clinicName.replace(/성형외과|병원|의원|클리닉|피부과/g, '').trim().toLowerCase(); +const baseNameEn = clinic.clinicNameEn.replace(/\s+/g, '').toLowerCase(); + +candidates = [ + baseNameEn, // "grandplasticsurgery" + `${baseNameEn}_official`, // "grandplasticsurgery_official" + `${baseNameEn}_ps`, // "grandplasticsurgery_ps" + `${baseNameEn}_clinic`, // "grandplasticsurgery_clinic" + domainBase, // "grandplasticsurgery" (from URL) + `${domainBase}_official`, // "grandplasticsurgery_official" +] +``` + +**유효성 조건**: `followersCount >= 50` → 후보로 채택 + +--- + +### P1-C. 병원명 추출 Fallback (Perplexity) + +**조건**: Firecrawl이 clinicName을 추출하지 못한 경우 +**API**: Perplexity `sonar`, temp=0.1 + +**System Message**: +``` +Respond with ONLY the clinic name in Korean, nothing else. +``` + +**User Message**: +``` +{url} 이 URL의 병원/클리닉 한국어 이름이 뭐야? +``` + +--- + +## Phase 2: collect-channel-data + +### P2-1. Firecrawl — 강남언니 페이지 데이터 추출 + +**File**: `supabase/functions/collect-channel-data/index.ts` +**API**: Firecrawl `v1/scrape` (JSON) +**Wait**: 5000ms + +**Extraction Prompt**: +``` +Extract: hospital name, overall rating (out of 10), total review count, +doctors with names/ratings/review counts/specialties, procedures offered, +address, certifications/badges +``` + +**Schema**: hospitalName, rating(number), totalReviews(number), doctors[], procedures[], address, badges[] + +--- + +### P2-2~5. Perplexity — 시장 분석 (4개 병렬) + +**API**: Perplexity `sonar`, temp=0.3 + +**공통 System Message**: +``` +You are a Korean medical marketing analyst. Always respond in Korean. Provide data in valid JSON format. +``` + +**쿼리 4개**: + +| ID | User Prompt | +|----|-------------| +| competitors | `{address} 근처 {services} 전문 성형외과/피부과 경쟁 병원 5곳을 분석해줘. 각 병원의 이름, 주요 시술, 온라인 평판, 마케팅 채널을 JSON 형식으로 제공해줘.` | +| keywords | `한국 {services} 관련 검색 키워드 트렌드. 네이버와 구글에서 월간 검색량이 높은 키워드 20개, 경쟁 강도, 추천 롱테일 키워드를 JSON 형식으로 제공해줘.` | +| market | `한국 {services[0]} 시장 트렌드 2025-2026. 시장 규모, 성장률, 주요 트렌드, 마케팅 채널별 효과를 JSON 형식으로 제공해줘.` | +| targetAudience | `{clinicName}의 잠재 고객 분석. 연령대별, 성별, 관심 시술, 정보 탐색 채널, 의사결정 요인을 JSON 형식으로 제공해줘.` | + +--- + +## Phase 3: generate-report + +### P3. Perplexity — 마케팅 리포트 생성 (V2: 실제 데이터 기반) + +**File**: `supabase/functions/generate-report/index.ts` +**API**: Perplexity `sonar`, temp=0.3 + +**System Message**: +``` +You are a Korean medical marketing analyst. Respond ONLY with valid JSON, no markdown code blocks. Use Korean for text fields. 강남언니 rating is 10-point scale. Use ONLY the provided real data — never invent metrics. +``` + +**User Message** (template 구조): +``` +당신은 프리미엄 의료 마케팅 전문 분석가입니다. 아래 **실제 수집된 데이터**를 기반으로 종합 마케팅 리포트를 생성해주세요. + +⚠️ 중요: 아래 데이터에 없는 수치는 절대 추측하지 마세요. 데이터가 없으면 "데이터 없음"으로 표시하세요. + +## 병원 기본 정보 +- 병원명: {clinic.clinicName} +- 주소: {clinic.address} +- 전화: {clinic.phone} +- 시술: {services.join(", ")} +- 의료진: {doctors JSON} +- 슬로건: {clinic.slogan} + +## 실제 채널 데이터 (수집 완료) +### Instagram @{handle} +- 팔로워: {followers}명, 게시물: {posts}개 +- 비즈니스 계정: O/X +- Bio: {bio} + +### YouTube {handle} +- 구독자: {subscribers}명, 영상: {totalVideos}개, 총 조회수: {totalViews} +- 인기 영상 TOP 5: [실제 제목+조회수] + +### 강남언니 {name} +- 평점: {rating}/10, 리뷰: {totalReviews}건 +- 등록 의사: [실제 이름+전문분야] + +### Google Maps {name} +- 평점: {rating}/5, 리뷰: {reviewCount}건 + +### 네이버 블로그: 검색결과 {totalResults}건 +### 네이버 플레이스: {name} ({category}) + +## 시장 분석 데이터 +{market analysis JSON} + +## 웹사이트 브랜딩 +{branding JSON} + +## 리포트 형식 (JSON 구조) +{ + "clinicInfo": { ... }, + "executiveSummary": "경영진 요약 (3-5문장)", + "overallScore": 0-100, + "channelAnalysis": { + "naverBlog": { score, status, posts, recommendation, diagnosis[] }, + "instagram": { score, status, followers, posts, recommendation, diagnosis[] }, + "youtube": { score, status, subscribers, recommendation, diagnosis[] }, + "naverPlace": { score, rating, reviews, recommendation }, + "gangnamUnni": { score, rating, ratingScale:10, reviews, status, recommendation }, + "website": { score, issues[], recommendation, trackingPixels[], snsLinksOnSite, mainCTA } + }, + "brandIdentity": [{ area, asIs, toBe }], + "kpiTargets": [{ metric, current, target3Month, target12Month }], + "recommendations": [{ priority, category, title, description, expectedImpact }], + "competitors": [], + "keywords": { primary: [], longTail: [] }, + "targetAudience": {}, + "marketTrends": [], + "newChannelProposals": [{ channel, priority, rationale }] +} +``` + +**핵심**: `channelSummary`는 `buildChannelSummary()` 함수가 `channel_data` DB 컬럼에서 실제 수집된 데이터를 요약 텍스트로 변환. AI는 이 텍스트에 포함된 수치만 사용. + +--- + +## 이미지 생성 (별도) + +### Gemini — 마케팅 이미지 생성 + +**File**: `src/services/geminiImageGen.ts` +**API**: Google Gemini `gemini-2.5-flash-image` + +**Prompt** (template): +``` +Generate a premium medical marketing image for a plastic surgery clinic. +Theme: {pillarContext} // safety | expertise | results | care +Style: {channelHint} // youtube | instagram | naver_blog | tiktok | facebook +Color palette: soft purple (#7B2D8E), gold (#E8B931), warm white (#FAF8F5). +Premium, luxurious, trustworthy aesthetic. +No text or logos in the image. +Photorealistic, high quality, professional medical marketing. +``` + +**Pillar Context**: +- safety: "hospital safety systems, clean surgical rooms, CCTV monitoring" +- expertise: "medical expertise, advanced surgical equipment, certifications" +- results: "natural beautiful results, before and after transformation" +- care: "patient-centered care, warm consultation, personalized treatment" + +--- + +## 프롬프트 엔지니어링 교훈 + +### 1. Perplexity sonar — 프롬프트 길이 vs 성능 +| 프롬프트 길이 | 결과 | +|-------------|------| +| 시스템 1줄 + 유저 3줄 | ✅ Instagram, YouTube 등 잘 찾음 | +| 시스템 50줄 + 유저 30줄 | ❌ 전부 null/빈 배열 | +| 시스템 5줄 + 유저 10줄 (분리 3회) | ❌ 각각 빈 결과 | + +### 2. 검색 키워드 패턴 +| 패턴 | 결과 | +|------|------| +| `"{병원명}" 성형외과 공식 인스타그램` | ❌ null (너무 제한적) | +| `{병원명} 병원의 인스타그램...검색해서 찾아줘` | ✅ 핸들 발견 | +| `{병원명} ({영문명}) 병원의 인스타그램...` | ✅✅ 국제 계정도 발견 | + +### 3. 모델 선택 +| 모델 | 용도 | 온도 | +|------|------|------| +| sonar | 채널 검색, 강남언니 검색 | 0.1 | +| sonar | 시장 분석, 리포트 생성 | 0.3 | +| sonar-pro | ❌ 테스트 결과 오히려 성능 저하 | - | + +### 4. JSON 응답 안정성 +- 항상 시스템에 "Respond ONLY with valid JSON" 포함 +- 유저 메시지 끝에 예시 JSON 구조를 포함하면 포맷 준수율 ↑ +- `text.match(/\{[\s\S]*\}/)` 로 JSON 추출 (설명 텍스트 제거) +- `text.match(/```(?:json)?\n?([\s\S]*?)```/)` 로 코드 블록 내 JSON 추출 + +### 5. Verify 전략 +- Instagram HEAD 요청은 불안정 → unverified도 후보로 유지 +- YouTube `channels?part=id` API가 가장 정확한 검증 +- Apify instagram-profile-scraper가 HEAD 요청보다 신뢰성 높음 +- `UC`로 시작하는 channel ID에 `@` 붙이면 검증 실패 + +--- + +## Summary + +| Phase | API | 프롬프트 수 | 주 용도 | +|-------|-----|-----------|---------| +| discover-channels | Perplexity sonar | 2~3개 | 소셜 채널 검색 + 강남언니 | +| discover-channels | Firecrawl | 2개 | 웹사이트 스크래핑 + 브랜딩 | +| discover-channels | YouTube API | 0 (직접 호출) | 채널 검색 | +| discover-channels | Naver API | 0 (직접 호출) | 블로그/웹 검색 | +| discover-channels | Apify | 0 (직접 호출) | Instagram 프로필 검색 | +| collect-channel-data | Perplexity sonar | 4개 | 시장/경쟁/키워드/타겟 분석 | +| collect-channel-data | Firecrawl | 1개 | 강남언니 데이터 추출 | +| generate-report | Perplexity sonar | 1개 | 종합 리포트 생성 | +| 이미지 생성 | Gemini | 1개 | 마케팅 이미지 | +| **합계** | | **~12개** | | diff --git a/docs/CHANNEL_CONTENT_STRATEGY_AUDIT.md b/docs/CHANNEL_CONTENT_STRATEGY_AUDIT.md new file mode 100644 index 0000000..536c275 --- /dev/null +++ b/docs/CHANNEL_CONTENT_STRATEGY_AUDIT.md @@ -0,0 +1,236 @@ +# Channel & Content Strategy Planning Agent — Audit Report + +> 작성일: 2026-04-06 +> 대상: INFINITH Content Director Engine + generate-content-plan Edge Function +> 관점: 하이티켓 메디컬 클리닉 콘텐츠 마케팅 디렉터 + +--- + +## 1. Executive Summary + +INFINITH의 콘텐츠 전략 수립 시스템은 **2-layer 아키텍처** (Deterministic Engine + AI Engine)로 구성되어 있다. 분석 파이프라인(discover → collect → generate-report)에서 수집한 실제 채널 데이터를 기반으로 전략을 생성하는 구조는 올바르나, **하이티켓 성형외과의 특수한 마케팅 요구사항**을 충분히 반영하지 못하는 critical gap이 다수 존재한다. + +### 전체 성숙도: 40/100 + +| 영역 | 점수 | 판정 | +|------|------|------| +| YouTube 전략 | 55/100 | 기본 구조 있으나 포맷 다양성 부족 | +| Instagram 전략 | 45/100 | 멀티계정·해시태그·CTA 전략 부재 | +| 강남언니 전략 | 10/100 | 캘린더에서 완전 누락 (Critical) | +| 네이버 블로그 전략 | 35/100 | 키워드 연결·포맷 다양성 부재 | +| Facebook 전략 | 25/100 | 오가닉/페이드 미분리, 최소 빈도 | +| TikTok 전략 | 5/100 | 캘린더에서 완전 누락 | +| 채널 간 크로스 전략 | 20/100 | 리퍼포징 기초만 존재 | +| 커뮤니케이션 전략 | 30/100 | 톤 단일화, 고객여정 미매핑 | + +--- + +## 2. 채널별 상세 감사 + +### 2-1. YouTube + +**현재 구현:** +- `YOUTUBE_SLOTS`: Shorts(주3, 월수금) + Long-form(주1, 목) = **2 포맷** +- 토픽: 4-Pillar × 시술 매트릭스로 자동 생성 +- 리퍼포징: 인기 영상 제목 기반 Shorts 토픽 재활용 + +**Gap 분석:** + +| # | Gap | 심각도 | 설명 | +|---|-----|--------|------| +| Y1 | 포맷 부족 | High | Shorts/Long 2가지만 존재. 라이브 Q&A, 의사 브이로그, 수술실 CCTV, Community Post 누락 | +| Y2 | 빈도 계산 로직 단순 | Medium | 채널 점수(0-100)만으로 결정. 구독자 수, 조회수 트렌드, 경쟁사 대비 위치 미반영 | +| Y3 | 리퍼포징 얕음 | Medium | 영상 제목만 추출. 타임스탬프, 하이라이트 구간, 댓글 기반 토픽 확장 없음 | +| Y4 | SEO 전략 없음 | High | YouTube 검색 키워드와 토픽 매칭이 없음. report.keywords 미활용 | +| Y5 | 톤 단일 | Medium | "전문적·친근한" 고정. Shorts(캐주얼+후킹) vs Long(교육적+권위) 차별화 필요 | + +**개선 계획:** +- [x] `YOUTUBE_SHORTS_SLOT`, `YOUTUBE_LONG_SLOT`, `YOUTUBE_LIVE_SLOT`, `YOUTUBE_COMMUNITY_SLOT` 4포맷 확장 +- [x] 톤 매트릭스: `shorts: "캐주얼·후킹"`, `long: "교육적·권위"`, `live: "친근·대화"` +- [x] report.keywords.primary를 토픽 생성 시 주입 + +### 2-2. Instagram + +**현재 구현:** +- `INSTAGRAM_SLOTS`: Reel(주3) + Carousel(주2) + Stories(주2) = **3 포맷** +- 멀티 계정 데이터는 수집하지만 전략 생성 시 구분하지 않음 + +**Gap 분석:** + +| # | Gap | 심각도 | 설명 | +|---|-----|--------|------| +| I1 | 멀티계정 전략 없음 | High | KR/EN 계정별 차별화된 콘텐츠 전략 부재 | +| I2 | 해시태그 전략 부재 | High | instagramAnalysis.topHashtags 수집하지만 전략에 미반영 | +| I3 | CTA 퍼널 없음 | Critical | DM→카카오톡→전화 단계별 전환 전략 부재 | +| I4 | Feed 포맷 부재 | Medium | 정적 피드 이미지(포트폴리오, 의사 소개) 포맷이 캘린더에 없음 | +| I5 | Stories 활용 약함 | Medium | 폴, Q&A 스티커, 카운트다운 등 인터랙티브 기능 활용 전략 없음 | + +**개선 계획:** +- [x] `INSTAGRAM_FEED_SLOT` 추가 (정적 포트폴리오 이미지) +- [x] 해시태그 전략을 `ChannelStrategyCard.formatGuidelines`에 주입 +- [x] CTA 전략을 주간 테마에 반영 (Week 4: 전환 최적화 → CTA 집중) + +### 2-3. 강남언니 + +**현재 구현:** +- `channelAnalysis.gangnamUnni`: score/rating/reviews 분석만 존재 +- **캘린더 슬롯 완전 누락** — `FormatSlot`에 포함되지 않음 +- 콘텐츠 전략 액션 아이템 없음 + +**Gap 분석:** + +| # | Gap | 심각도 | 설명 | +|---|-----|--------|------| +| G1 | 캘린더 완전 누락 | Critical | 성형외과 핵심 전환 채널이 전략에서 빠짐 | +| G2 | 리뷰 대응 전략 없음 | Critical | 부정 리뷰 대응, 긍정 리뷰 활용 프로토콜 없음 | +| G3 | 의사 프로필 최적화 없음 | High | 의사별 전문분야, 경력, 사진 관리 전략 없음 | +| G4 | 가격 전략 없음 | High | 시술별 가격 포지셔닝, 프로모션 전략 없음 | +| G5 | 배지/인증 관리 없음 | Medium | 강남언니 뱃지 획득 전략 없음 | + +**개선 계획:** +- [x] `GANGNAMUNNI_SLOTS` 추가: 리뷰관리(주2) + 프로필최적화(주1) +- [x] 리뷰 대응 전략을 Week 3(소셜 증거) 테마에 통합 +- [x] 강남언니 데이터 (doctors, rating, reviews)를 전략 생성에 활용 + +### 2-4. 네이버 블로그 + +**현재 구현:** +- `NAVER_SLOTS`: 블로그(주2, 화목) = **1 포맷** +- 토픽: 시술명 + 필러 조합 + +**Gap 분석:** + +| # | Gap | 심각도 | 설명 | +|---|-----|--------|------| +| N1 | 키워드 미연결 | Critical | report.keywords 존재하지만 블로그 토픽에 미활용 | +| N2 | 포맷 단일 | High | SEO글, 의사칼럼, FAQ시리즈, 후기정리, 비교분석 구분 없음 | +| N3 | 네이버 플레이스 연동 없음 | Medium | 블로그→플레이스 리뷰 유도 퍼널 부재 | +| N4 | 네이버 검색 광고 미고려 | Low | 유기적+유료 통합 전략 없음 | + +**개선 계획:** +- [x] `NAVER_SEO_SLOT` + `NAVER_COLUMN_SLOT` 2포맷으로 확장 +- [x] `buildTopicPool()`에 report.keywords 주입 + +### 2-5. Facebook + +**현재 구현:** +- `FACEBOOK_SLOTS`: 광고(주1, 토) = **1 포맷** + +**Gap 분석:** + +| # | Gap | 심각도 | 설명 | +|---|-----|--------|------| +| F1 | 오가닉/페이드 미분리 | High | 광고만 있고 오가닉 포스트 없음 | +| F2 | 캠페인 유형 미분화 | High | 리타겟팅, 리드젠, 브랜드 인지도 구분 없음 | +| F3 | 빈도 너무 적음 | Medium | 주 1회로는 A/B 테스트 불가 | + +**개선 계획:** +- [x] `FACEBOOK_ORGANIC_SLOT`(주1) + `FACEBOOK_AD_SLOT`(주2) 분리 + +### 2-6. TikTok + +**현재 구현:** +- 캘린더 슬롯 **완전 누락**. channelAnalysis에서 score만 표시 + +**개선 계획:** +- [x] `TIKTOK_SLOTS` 추가: 숏폼(주3, 월수금). YouTube Shorts와 크로스포스팅 전략 + +--- + +## 3. 크로스채널 전략 감사 + +### 3-1. 고객 여정 매핑 (현재: 없음 → 추가 필요) + +| 단계 | 채널 | 콘텐츠 유형 | 목표 | +|------|------|------------|------| +| **인지** (Awareness) | YouTube Shorts, TikTok, Instagram Reels | 후킹 숏폼, 트렌드 | 신규 유입 | +| **관심** (Interest) | YouTube Long, 네이버 블로그, Instagram Carousel | 교육 콘텐츠, SEO글 | 정보 탐색 | +| **고려** (Consideration) | 강남언니, 네이버 플레이스, Instagram Feed | 리뷰, 비포애프터, 의사 소개 | 비교 검토 | +| **전환** (Conversion) | Instagram DM, Facebook Ad, 카카오톡 | CTA, 상담 예약, 프로모션 | 상담 예약 | +| **충성** (Loyalty) | Instagram Stories, 카카오톡, YouTube Community | 후속 관리, VIP 혜택 | 재방문·추천 | + +### 3-2. 리퍼포징 매트릭스 (현재: YouTube→Shorts만 → 확장 필요) + +| 원본 | → 파생 1 | → 파생 2 | → 파생 3 | +|------|----------|----------|----------| +| YouTube Long | Shorts ×3 | 블로그 스크립트 | Carousel 4장 | +| 네이버 블로그 | Instagram Carousel | Facebook Post | TikTok 요약 | +| 환자 후기 영상 | Reel 하이라이트 | 강남언니 리뷰 참조 | Stories 추천 | +| 의사 칼럼 | LinkedIn Post | YouTube Community | 네이버 칼럼 | + +--- + +## 4. 커뮤니케이션 전략 감사 + +### 4-1. 채널별 톤 매트릭스 (현재: "전문적·친근한" 단일 → 차별화 필요) + +| 채널 | 톤 | 말투 예시 | 금지 표현 | +|------|-----|----------|----------| +| YouTube Long | 교육적 · 권위 | "오늘은 코성형의 3가지 접근법을 비교해보겠습니다" | 과장, 비교광고 | +| YouTube Shorts | 캐주얼 · 후킹 | "코성형 전에 이것만은 꼭 확인하세요!" | 의학적 보장 | +| Instagram Feed | 감성적 · 프리미엄 | "자연스러운 아름다움의 완성" | 저가 이미지 | +| Instagram Reel | 트렌디 · 공감 | "성형 고민 있으신 분? 이 영상 저장하세요" | 전문용어 과다 | +| Instagram Stories | 친근 · 일상 | "오늘 수술실에서 있었던 일 🏥" | 환자 정보 노출 | +| 네이버 블로그 | 정보성 · SEO | "{시술명} 비용, 회복기간, 부작용 총정리" | 감성적 표현 | +| 강남언니 | 전문 · 응대 | "소중한 후기 감사합니다. 추가 문의 사항..." | 방어적 태도 | +| Facebook | 타겟팅 · CTA | "지금 상담 예약하시면 3D 시뮬레이션 무료" | 스팸성 반복 | +| TikTok | 밈 · 교육 | "성형외과 의사가 알려주는 TMI" | 의료광고법 위반 | + +### 4-2. 위기 대응 커뮤니케이션 (현재: 없음) + +| 상황 | 대응 채널 | 프로토콜 | +|------|----------|----------| +| 부정 리뷰 (강남언니) | 강남언니 답변 | 24시간 내 전문 응대, 사과+해결방안 | +| SNS 부정 댓글 | Instagram/YouTube | 공개 응대 → DM 전환 → 사적 해결 | +| 의료 사고 루머 | 전 채널 | 공식 입장문 + 법적 대응 병행 | +| 가격 비교 공격 | 네이버 블로그 | 가치 중심 콘텐츠로 간접 대응 | + +--- + +## 5. 콘텐츠 필러 감사 + +### 현재 4-Pillar (하드코딩): +1. 전문성·신뢰 — 의료진, 수술 과정, 인증 +2. 비포·애프터 — 전후 비교, 결과 시각화 +3. 환자 후기 — 인터뷰, 리뷰 +4. 트렌드·교육 — Q&A, 비용 가이드 + +### 추가 필요 Pillar: +5. **안전·케어** — 수술 후 관리, 24시간 모니터링, 리커버리 프로그램 + - 하이티켓 클리닉의 핵심 차별점. 가격이 높은 이유를 정당화하는 콘텐츠 + - 예시: "수술 후 48시간 집중 케어 시스템", "전담 간호사 1:1 관리" + +--- + +## 6. 개선 사항 요약 + +### Critical (즉시 반영) +- [x] C1: 강남언니 캘린더 슬롯 추가 +- [x] C2: TikTok 캘린더 슬롯 추가 +- [x] C3: 채널별 톤 매트릭스 구현 +- [x] C4: 고객 여정 매핑 → 주간 테마 재설계 +- [x] C5: report.keywords → 토픽 생성 연결 + +### High (이번 스프린트) +- [x] H1: YouTube 4포맷 확장 +- [x] H2: Instagram Feed 슬롯 추가 +- [x] H3: 네이버 블로그 2포맷 확장 +- [x] H4: Facebook 오가닉/페이드 분리 +- [x] H5: 5th Pillar "안전·케어" 추가 + +### Medium (다음 스프린트) +- [ ] M1: 해시태그 전략 자동 생성 +- [ ] M2: 리퍼포징 매트릭스 자동화 +- [ ] M3: 위기 대응 프로토콜 UI +- [ ] M4: 시즌 전략 (성수기/비수기) 캘린더 반영 + +--- + +## 7. 영향 받는 파일 + +| 파일 | 변경 내용 | +|------|-----------| +| `src/lib/contentDirector.ts` | 강남언니/TikTok 슬롯, 5th Pillar, 키워드 연결, 톤 매트릭스 | +| `src/types/plan.ts` | ChannelStrategyCard에 tone 세분화, customerJourneyStage 추가 | +| `src/lib/transformPlan.ts` | 5th Pillar 추가, 톤 매트릭스 적용, 키워드 주입 | +| `supabase/functions/generate-content-plan/index.ts` | AI 프롬프트에 경쟁사·키워드·톤 매트릭스 주입 | diff --git a/docs/CONTENT_STRATEGY_PLAN.md b/docs/CONTENT_STRATEGY_PLAN.md new file mode 100644 index 0000000..2e7573f --- /dev/null +++ b/docs/CONTENT_STRATEGY_PLAN.md @@ -0,0 +1,180 @@ +# 콘텐츠 기획 & 전략 수립 기능 구현 계획 + +## Context + +현재 INFINITH 파이프라인은 3-phase (discover → collect → generate-report)까지 완성되어 있고, DB 스키마에는 `content_plans`, `performance_metrics`, `strategy_adjustments`, `content_performance` 테이블이 이미 설계되어 있다. 하지만: +- **콘텐츠 플랜 생성 Edge Function이 없다** — `content_plans` 테이블은 비어 있음 +- **`contentDirector.ts`는 완전 deterministic** — AI 호출 없이 하드코딩 템플릿으로 캘린더 생성 +- **PerformancePage는 mock 데이터** — 실제 DB 연동 없음 +- **전략 조정 루프가 없다** — 성과 → 전략 피드백 메커니즘 미구현 + +이 기능은 파이프라인 Phase 4로 자연스럽게 확장되며, 기존 데이터 흐름 위에 AI 전략 계층을 추가한다. + +--- + +## Phase 1: `generate-content-plan` Edge Function + 타입 확장 + +### 1-1. Edge Function 생성 +**새 파일:** `supabase/functions/generate-content-plan/index.ts` + +- [x] `generate-report/index.ts` 패턴 그대로 따름 (CORS, service role, Deno.serve) +- [x] Input 인터페이스: `{ reportId, clinicId?, runId? }` +- [x] `analysis_runs` (또는 `marketing_reports`)에서 channelAnalysis, kpiTargets, recommendations, services 읽기 +- [x] Perplexity sonar 호출 (temp=0.3, 짧은 프롬프트) + - System: `"You are a Korean medical marketing content strategist. Respond ONLY with valid JSON."` + - User: 채널 점수 요약 + 서비스 목록 + KPI 타겟 + 전략 생성 요청 +- [x] JSON 파싱 (기존 regex 패턴 재사용) +- [x] `content_plans` 테이블에 INSERT (`is_active=true`, 이전 플랜 비활성화) +- [x] AI 실패 시 fallback: deterministic `contentDirector.ts` 결과 사용 + +**AI 출력 스키마:** +```json +{ + "channelStrategies": [{ "channelId", "targetGoal", "contentTypes", "postingFrequency", "priority" }], + "contentPillars": [{ "title", "description", "relatedUSP", "exampleTopics" }], + "calendar": { "weeks": [{ "weekNumber", "label", "entries": [...] }], "monthlySummary": [...] }, + "postingSchedule": { "bestTimes", "rationale" } +} +``` + +### 1-2. CalendarEntry 타입 확장 +**수정:** `src/types/plan.ts` (line 107-113) + +- [x] `id?: string` — UUID (드래그&드롭, 개별 재생성용) +- [x] `description?: string` — AI 생성 가이드 +- [x] `pillar?: string` — 콘텐츠 필러 연결 +- [x] `status?: 'draft' | 'approved' | 'published'` +- [x] `isManualEdit?: boolean` — AI 덮어쓰기 방지 플래그 +- [x] `aiPromptSeed?: string` — 개별 재생성 컨텍스트 + +### 1-3. 데이터 레이어 +**수정:** `src/lib/supabase.ts` + +- [x] `generateContentPlan(reportId, clinicId?, runId?)` — Edge Function 호출 +- [x] `fetchActiveContentPlan(clinicId)` — content_plans 쿼리 +- [x] `updateCalendarEntry(planId, entryId, updates)` — JSONB 패치 + +**수정:** `src/hooks/useMarketingPlan.ts` + +- [x] 데이터 소스 우선순위: content_plans → navigation state → marketing_reports + +--- + +## Phase 2: Enhanced Content Calendar UI + +### 2-1. ContentCalendar 인터랙티브 업그레이드 +**수정:** `src/components/plan/ContentCalendar.tsx` + +- [x] `useState`로 로컬 편집 상태 관리 +- [x] 엔트리 클릭 → EditEntryModal 열기 +- [x] Status 뱃지 (gray=draft, purple=approved, green=published) +- [x] 엔트리 hover 시 AI 재생성 버튼 +- [x] Weekly/Monthly 뷰 토글 +- [x] 채널/콘텐츠 타입 필터 + +### 2-2. EditEntryModal 생성 +**새 파일:** `src/components/plan/EditEntryModal.tsx` + +- [x] Title, Description, Channel, Content Type, Day, Status 필드 +- [x] "AI 재생성" 버튼 (aiPromptSeed 기반) +- [x] Save / Cancel 버튼 +- [x] 기존 디자인 시스템 적용 (rounded-2xl, shadow, purple accent) + +--- + +## Phase 3: Strategy Adjustment Loop + +### 3-1. adjust-strategy Edge Function +**새 파일:** `supabase/functions/adjust-strategy/index.ts` + +- [x] Input: `{ clinicId }` +- [x] `channel_weekly_delta` 뷰로 최근 채널 변화량 조회 +- [x] 활성 `content_plans` 조회 +- [x] `analysis_runs.report`에서 kpiTargets 추출 +- [x] KPI 달성률 계산 +- [x] Perplexity sonar로 조정 추천 생성 +- [x] `performance_metrics` INSERT (channel_deltas, kpi_progress, strategy_suggestions) +- [x] `strategy_adjustments` INSERT (adjustment_type, before/after values) + +### 3-2. StrategyAdjustmentSection +**새 파일:** `src/components/plan/StrategyAdjustmentSection.tsx` + +- [x] KPI 진행률 프로그레스 바 +- [x] 전략 제안 카드 (수락/거절) +- [x] 조정 이력 타임라인 + +--- + +## Phase 4: 파이프라인 & 페이지 통합 + +### 4-1. AnalysisLoadingPage에 Phase 4 추가 +**수정:** `src/pages/AnalysisLoadingPage.tsx` + +- [x] `PHASE_STEPS`에 planning 단계 추가 +- [x] `runPipeline`에서 generate-report 후 `generateContentPlan()` 호출 + +### 4-2. MarketingPlanPage에 조정 섹션 추가 +**수정:** `src/pages/MarketingPlanPage.tsx` + +- [x] AssetCollection 다음에 `` 추가 + +### 4-3. PerformancePage 실제 데이터 연동 +**수정:** `src/pages/PerformancePage.tsx` + +- [x] Mock 데이터 → 실제 DB 쿼리로 교체 +- [x] "전략 조정 실행" 버튼 → `triggerStrategyAdjustment()` 호출 + +**새 파일:** `src/hooks/usePerformanceData.ts` + +- [x] `channel_snapshots`, `performance_metrics`, `content_performance` 통합 조회 + +### 4-4. Route 업데이트 +**수정:** `src/main.tsx` + +- [x] `/performance/:clinicId?` 형태로 변경 + +--- + +## 파일 변경 요약 + +### 새로 생성 (6) +| 파일 | 목적 | Phase | +|------|------|-------| +| `supabase/functions/generate-content-plan/index.ts` | AI 콘텐츠 전략 생성 | 1 | +| `supabase/functions/adjust-strategy/index.ts` | 성과 기반 전략 조정 | 3 | +| `src/components/plan/EditEntryModal.tsx` | 캘린더 엔트리 편집 | 2 | +| `src/components/plan/StrategyAdjustmentSection.tsx` | 전략 조정 UI | 3 | +| `src/hooks/useContentPlan.ts` | content_plans 데이터 훅 | 1 | +| `src/hooks/usePerformanceData.ts` | 성과 데이터 통합 훅 | 4 | + +### 수정 (7) +| 파일 | 변경 내용 | Phase | +|------|-----------|-------| +| `src/types/plan.ts` | CalendarEntry에 optional 필드 6개 추가 | 1 | +| `src/hooks/useMarketingPlan.ts` | content_plans 우선 소스 추가 | 1 | +| `src/lib/supabase.ts` | 새 API 함수 4개 추가 | 1 | +| `src/components/plan/ContentCalendar.tsx` | 인터랙티브 UI 업그레이드 | 2 | +| `src/pages/AnalysisLoadingPage.tsx` | Phase 4 파이프라인 추가 | 4 | +| `src/pages/MarketingPlanPage.tsx` | StrategyAdjustment 섹션 추가 | 4 | +| `src/pages/PerformancePage.tsx` | Mock → 실제 DB 데이터 | 4 | + +### 변경 없음 (Fallback 유지) +- `src/lib/contentDirector.ts` — deterministic fallback 엔진 +- `src/lib/transformPlan.ts` — fallback 변환 +- `supabase/functions/_shared/config.ts` — 재사용 + +--- + +## 검증 체크리스트 + +- [x] Edge Function 테스트: `curl`로 `generate-content-plan` 호출 → `content_plans` 테이블 적재 확인 +- [x] 프론트엔드 테스트: `npm run dev` → `/plan/:id` → content_plans 데이터 렌더링 확인 +- [x] 캘린더 인터랙션: 엔트리 클릭 → 편집 → 저장 → 새로고침 후 유지 확인 +- [x] 파이프라인 E2E: URL 입력 → 4단계 파이프라인 완료 → 플랜 페이지 자동 표시 +- [x] 타입 체크: `npm run lint` (tsc --noEmit) 통과 + +## 핵심 패턴 참조 파일 +- `supabase/functions/generate-report/index.ts` — Edge Function 패턴 +- `src/lib/contentDirector.ts` — 현재 deterministic 엔진 (fallback) +- `src/hooks/useMarketingPlan.ts` — 데이터 소스 우선순위 체인 +- `src/components/plan/ContentCalendar.tsx` — 현재 캘린더 UI diff --git a/docs/DB_SCHEMA_V3.md b/docs/DB_SCHEMA_V3.md new file mode 100644 index 0000000..ed96bec --- /dev/null +++ b/docs/DB_SCHEMA_V3.md @@ -0,0 +1,262 @@ +# INFINITH SaaS Database Schema V3 + +**작성일**: 2026-04-05 +**마이그레이션 파일**: `supabase/migrations/20260405_saas_schema_v3.sql` + +## 설계 원칙 + +1. **CLINIC-CENTRIC**: 병원 1개 = 1행. URL이 달라도 같은 병원이면 같은 행 +2. **TIME-SERIES**: 채널 메트릭은 INSERT-only 스냅샷 (시계열 쿼리) +3. **SEPARATION**: 원시 데이터 / 분석 리포트 / 콘텐츠 전략 분리 +4. **LOOP-READY**: 매 분석이 이전 데이터를 참조해 전략 자동 조정 +5. **MULTI-TENANT**: user_id 기반 접근 제어 (미래 auth) + +--- + +## ERD (Entity Relationship) + +``` +clinics (병원 마스터) + ├─ analysis_runs (분석 실행 히스토리) ← 매주 1행씩 쌓임 + │ ├─ channel_snapshots (채널별 시계열 메트릭) ← INSERT-only + │ ├─ screenshots (스크린샷 증거) + │ └─ performance_metrics (성과 메트릭) + │ └─ strategy_adjustments (전략 조정 근거) + ├─ channel_configs (사용자 수동 채널 연결) + ├─ content_plans (콘텐츠 기획 — 활성 1개) + │ └─ content_performance (개별 콘텐츠 성과) + └─ (marketing_reports) ← 레거시 호환 +``` + +--- + +## 테이블 상세 + +### 1. `clinics` — 병원 마스터 +| 컬럼 | 타입 | 설명 | +|------|------|------| +| id | UUID PK | | +| user_id | UUID | 소유자 (미래 auth) | +| url | TEXT UNIQUE | 대표 URL | +| name | TEXT | 한국어 병원명 | +| name_en | TEXT | 영문 병원명 | +| domain | TEXT | 도메인 | +| address, phone | TEXT | 기본 정보 | +| established_year | INT | 개원 연도 | +| services | TEXT[] | 시술 목록 | +| branding | JSONB | 컬러, 폰트, 로고, 태그라인 | +| social_handles | JSONB | 검증된 소셜 핸들 | +| verified_channels | JSONB | Phase 1 결과 캐시 | +| analysis_frequency | TEXT | 'manual' / 'daily' / 'weekly' / 'monthly' | +| last_analyzed_at | TIMESTAMPTZ | 마지막 분석 시간 | + +### 2. `analysis_runs` — 분석 실행 히스토리 +| 컬럼 | 타입 | 설명 | +|------|------|------| +| id | UUID PK | | +| clinic_id | UUID FK → clinics | | +| status | TEXT | pending/discovering/collecting/generating/complete/partial/error | +| scrape_data | JSONB | Firecrawl 원시 데이터 | +| raw_channel_data | JSONB | API 수집 원시 데이터 | +| analysis_data | JSONB | 시장 분석 | +| vision_analysis | JSONB | Vision 분석 결과 | +| report | JSONB | AI 리포트 | +| channel_errors | JSONB | 채널별 에러 기록 | +| trigger | TEXT | 'manual' / 'scheduled' / 'webhook' | + +### 3. `channel_snapshots` — 채널별 시계열 ⭐ 핵심 +| 컬럼 | 타입 | 설명 | +|------|------|------| +| id | UUID PK | | +| clinic_id | UUID FK → clinics | | +| run_id | UUID FK → analysis_runs | | +| channel | TEXT | youtube/instagram/facebook/gangnamUnni/... | +| handle | TEXT | @handle 또는 URL | +| followers | INT | 구독자/팔로워 | +| posts | INT | 게시물/영상 수 | +| total_views | BIGINT | 총 조회수 | +| rating | NUMERIC(3,1) | 평점 | +| reviews | INT | 리뷰 수 | +| health_score | INT | 0-100 | +| details | JSONB | 상세 (top videos, latest posts 등) | +| screenshot_url | TEXT | 채널 랜딩 스크린샷 | +| captured_at | TIMESTAMPTZ | 캡처 시간 | + +**핵심**: INSERT-only. 절대 UPDATE 하지 않음. `captured_at` 기준 시계열 쿼리. + +### 4. `screenshots` — 스크린샷 증거 +| 컬럼 | 타입 | 설명 | +|------|------|------| +| id | UUID PK | | +| clinic_id, run_id | UUID FK | | +| channel | TEXT | 어떤 채널 | +| page_type | TEXT | main/doctors/surgery/landing | +| url | TEXT | 이미지 URL (Supabase Storage) | +| source_url | TEXT | 원본 페이지 URL | +| vision_data | JSONB | Gemini Vision 추출 데이터 | + +### 5. `content_plans` — 콘텐츠 기획 +| 컬럼 | 타입 | 설명 | +|------|------|------| +| id | UUID PK | | +| clinic_id | UUID FK → clinics | | +| run_id | UUID FK → analysis_runs | 어떤 분석 기반 | +| brand_guide | JSONB | 브랜딩 가이드 | +| channel_strategies | JSONB | 채널별 전략 | +| content_strategy | JSONB | 필라, 타입, 워크플로우 | +| calendar | JSONB | 4주 캘린더 | +| is_active | BOOLEAN | 현재 활성 기획 | + +### 6. `channel_configs` — 사용자 수동 채널 연결 +| 컬럼 | 타입 | 설명 | +|------|------|------| +| id | UUID PK | | +| clinic_id | UUID FK | | +| channel | TEXT | 채널 종류 | +| handle | TEXT | @handle | +| is_verified | BOOLEAN | 사용자가 직접 확인 | +| access_token | TEXT | OAuth token (미래) | + +### 7. `performance_metrics` — 성과 메트릭 (루프 핵심) +| 컬럼 | 타입 | 설명 | +|------|------|------| +| id | UUID PK | | +| clinic_id, run_id | UUID FK | | +| prev_run_id | UUID FK | 이전 분석 (비교 기준) | +| channel_deltas | JSONB | 채널별 변화량 | +| kpi_progress | JSONB | KPI 달성률 | +| top_performing_content | JSONB | 성과 좋은 콘텐츠 | +| underperforming_channels | TEXT[] | 성과 미달 채널 | +| strategy_suggestions | JSONB | AI 전략 조정 제안 | + +### 8. `content_performance` — 개별 콘텐츠 성과 +| 컬럼 | 타입 | 설명 | +|------|------|------| +| id | UUID PK | | +| clinic_id | UUID FK | | +| plan_id | UUID FK | 어떤 기획의 콘텐츠 | +| channel, content_type | TEXT | 채널 + 유형 | +| views, likes, comments, shares | INT | 반응 지표 | +| engagement_rate | NUMERIC | 참여율 | +| performance_score | INT | 0-100 | + +### 9. `strategy_adjustments` — 전략 조정 히스토리 +| 컬럼 | 타입 | 설명 | +|------|------|------| +| id | UUID PK | | +| clinic_id, plan_id | UUID FK | | +| performance_id | UUID FK | 어떤 성과 분석 기반 | +| adjustment_type | TEXT | frequency_change/pillar_shift/channel_add/... | +| description | TEXT | "YouTube Shorts 주 3회 → 5회로 증가" | +| reason | TEXT | "Shorts 조회수가 Long-form 대비 300% 높음" | +| before_value, after_value | JSONB | 변경 전/후 값 | + +--- + +## Views (트렌드 쿼리용) + +### `channel_latest` +각 채널의 최신 스냅샷만 반환. `DISTINCT ON` + `ORDER BY captured_at DESC`. + +### `channel_weekly_delta` +현재 vs 7일 전 스냅샷을 `LATERAL JOIN`으로 비교. 팔로워 변화율(%) 자동 계산. + +--- + +## 성과 → 기획 루프 플로우 + +``` +Week N: + 1. analysis_run 생성 + 2. channel_snapshots INSERT (현재 메트릭) + 3. performance_metrics 생성 (이전 run과 비교) + → channel_deltas: {youtube: +2%, instagram: +7.1%} + → kpi_progress: [{metric: "YouTube 구독자", progress: 97.4%}] + → strategy_suggestions: ["Instagram Reels 빈도 증가 권장"] + 4. strategy_adjustments INSERT (조정 내역) + 5. content_plans UPDATE (조정 반영) + → Content Director가 strategy_suggestions를 기반으로 캘린더 자동 조정 + 6. report 생성 (이전 분석 대비 변화 포함) + +Week N+1: + → 2번부터 반복. channel_snapshots에 데이터가 쌓이면서 트렌드 정확도 ↑ +``` + +--- + +## 기존 호환성 + +| 기존 테이블 | 상태 | 이관 계획 | +|-----------|------|---------| +| `marketing_reports` | 유지 (deprecated) | 기존 API 호환 유지, 새 데이터는 새 테이블에만 저장 | +| `scrape_results` | 유지 | 캐시용 | + +--- + +## 구현 체크리스트 + +### Phase 1: DB 마이그레이션 (테이블 생성) + +- [x] Supabase Dashboard에서 `20260405_saas_schema_v3.sql` 실행 +- [x] 테이블 9개 생성 확인: clinics, analysis_runs, channel_snapshots, screenshots, content_plans, channel_configs, performance_metrics, content_performance, strategy_adjustments +- [x] View 2개 생성 확인: channel_latest, channel_weekly_delta +- [x] RLS 정책 적용 확인 +- [x] 인덱스 생성 확인 + +### Phase 2: Edge Function 전환 — discover-channels + +- [x] `discover-channels/index.ts`: `clinics` 테이블에 UPSERT (url 기준) +- [x] `discover-channels/index.ts`: `analysis_runs` 테이블에 INSERT (status: 'discovering') +- [x] `discover-channels/index.ts`: `clinic.verified_channels` 업데이트 +- [x] 기존 `marketing_reports`에도 병행 쓰기 유지 (호환성) +- [ ] 검증: 분석 실행 → clinics + analysis_runs 행 생성 확인 + +### Phase 3: Edge Function 전환 — collect-channel-data + +- [x] `collect-channel-data/index.ts`: `channel_snapshots`에 채널별 INSERT +- [x] `collect-channel-data/index.ts`: `screenshots`에 스크린샷 INSERT +- [x] `collect-channel-data/index.ts`: `analysis_runs.raw_channel_data` 업데이트 +- [x] `collect-channel-data/index.ts`: `analysis_runs.vision_analysis` 업데이트 +- [x] 기존 `marketing_reports.channel_data`에도 병행 쓰기 유지 +- [ ] 검증: channel_snapshots에 채널별 행 생성 확인 + +### Phase 4: Edge Function 전환 — generate-report + +- [x] `generate-report/index.ts`: `analysis_runs.report` 업데이트 +- [x] `generate-report/index.ts`: `analysis_runs.status = 'complete'` +- [x] `generate-report/index.ts`: `clinics.last_analyzed_at` 업데이트 +- [ ] `content_plans` 자동 생성 (transformPlan 로직 서버사이드 이동) +- [x] 기존 `marketing_reports.report`에도 병행 쓰기 유지 +- [ ] 검증: analysis_runs.status = 'complete' + report JSONB 생성 확인 + +### Phase 5: 성과 분석 루프 구현 + +- [ ] 반복 분석 시 `performance_metrics` 자동 생성 (이전 run 대비) +- [ ] `channel_weekly_delta` View 쿼리 테스트 +- [ ] `strategy_suggestions` 생성 로직 (Content Director 연동) +- [ ] `content_plans` 자동 업데이트 (전략 조정 반영) +- [ ] `strategy_adjustments` 히스토리 기록 +- [ ] 검증: 2회 연속 분석 → performance_metrics에 변화량 기록 확인 + +### Phase 6: Frontend 전환 + +- [ ] `useReport` 훅: analysis_runs + channel_snapshots에서 읽기 +- [ ] `useMarketingPlan` 훅: content_plans에서 읽기 +- [ ] ReportPage: channel_snapshots 시계열 데이터로 트렌드 차트 추가 +- [ ] KPIDashboard: performance_metrics의 달성률 표시 +- [ ] 검증: 기존 리포트 URL이 새 테이블에서도 정상 렌더링 + +### Phase 7: 스케줄링 + +- [ ] `clinics.analysis_frequency` 기반 자동 분석 트리거 +- [ ] Supabase Cron 또는 external scheduler 연동 +- [ ] `analysis_runs.trigger = 'scheduled'` 기록 +- [ ] 검증: 주간 자동 분석 실행 확인 + +### Phase 8: 기존 데이터 이관 + +- [ ] `marketing_reports` → `clinics` + `analysis_runs` 이관 스크립트 +- [ ] `marketing_reports.channel_data` → `channel_snapshots` 변환 +- [ ] `marketing_reports.report` → `analysis_runs.report` 복사 +- [ ] 이관 후 기존 리포트 URL 정상 작동 확인 +- [ ] `marketing_reports` deprecated 마킹 diff --git a/docs/PIPELINE_IMPROVEMENT_PLAN.md b/docs/PIPELINE_IMPROVEMENT_PLAN.md new file mode 100644 index 0000000..97b58a6 --- /dev/null +++ b/docs/PIPELINE_IMPROVEMENT_PLAN.md @@ -0,0 +1,291 @@ +# 검색/분석 파이프라인 종합 개선 계획 + +**작성일**: 2026-04-04 +**최종 수정**: 2026-04-07 (파이프라인 전수 감사 + P0 버그 수정) +**상태**: Sprint 0 ✅ 구현 완료 | Sprint 1 ✅ 완료 | Sprint 2 🔜 진행 중 + +--- + +## 2026-04-07 전수 감사 결과 (Full Pipeline Audit) + +### URL → DB → Channel Discovery → Firecrawl → Report 흐름 검증 + +전체 4단계 파이프라인을 코드 수준으로 감사한 결과: + +### ✅ 이미 구현 완료된 것들 +| 항목 | 파일 | 상태 | +|------|------|------| +| Registry-first domain lookup | `discover-channels/index.ts` | ✅ 완전 구현 | +| Vision Analysis (screenshot + Gemini) | `_shared/visionAnalysis.ts`, `collect-channel-data` | ✅ 완전 구현 | +| `wrapChannelTask` 에러 격리 | `_shared/retry.ts` | ✅ Promise.all 안전 | +| `fetchWithRetry` + 지수 백오프 | `_shared/retry.ts` | ✅ Firecrawl/Apify에 적용 | +| channel_errors 추적 + unconditional DB save | `collect-channel-data/index.ts` | ✅ 구현 | +| 강남언니 Firecrawl JSON scrape | `collect-channel-data/index.ts` | ✅ 구현 (rating 버그 수정됨) | +| Vision data → report 강제 주입 harness | `generate-report/index.ts` | ✅ 구현 | +| Founding year 3단계 fallback chain | collect → generate 양쪽 | ✅ 구현 | +| V3 dual-write (clinics + analysis_runs) | discover + collect + generate | ✅ 구현 | + +### 🔧 이번 세션에서 수정한 P0 버그 +| 버그 | 위치 | 수정 내용 | +|------|------|----------| +| 강남언니 rating 0-10 스케일 오변환 | `collect-channel-data:323` | `rating ≤ 5 → ×2` 로직 제거. Firecrawl 프롬프트가 이미 0-10 지시 → 직접 신뢰 | +| Perplexity 단일 fetch (재시도 없음) | `generate-report:115` | `fetchWithRetry(maxRetries:2, backoffMs:[5000,15000], timeoutMs:90s)` 로 교체 | + +### 🚧 남은 Gap (우선순위순) +| 우선순위 | Gap | 세부 내용 | +|---------|-----|---------| +| P1 | Health score 미계산 | `channel_snapshots.health_score` 컬럼은 있지만 항상 NULL. 수집된 followers/rating/reviews 기반으로 계산 필요 | +| P1 | 네이버 블로그 공식 컨텐츠 미수집 | 현재: Naver Search API로 3rd-party 언급 수집. 필요: 등록된 공식 블로그 URL을 Firecrawl로 직접 스크랩 | +| P1 | Firecrawl 스크린샷 URL 만료 | GCS URL 7일 후 만료 → Supabase Storage로 아카이빙 필요 | +| P2 | Delta/트렌드 비교 없음 | `channel_snapshots`에 시계열 데이터 있지만 이전 run 대비 delta 계산 미구현 | +| P2 | V3 dual-write silent error | 에러 발생 시 console만 출력, `analysis_runs.error_message`에 기록 안 됨 | +| P3 | 강남언니 rating > 5 엣지케이스 | 수정 후에도 Firecrawl이 0-5 반환 시 그대로 저장. 추후 `rawRating`과 비교하는 정규화 로직 고려 | + +--- + +## Context + +현재 파이프라인의 3-phase 아키텍처(discover → collect → generate)는 구조적으로는 괜찮으나, **실행 신뢰성**과 **정보 수집 범위**에 심각한 문제가 있음. + +### 핵심 문제 +- **Vision 분석 부재**: 텍스트만 수집 → 이미지 속 정보(개원 연도, 의료진 사진, 인증 마크, 시술 전후) 100% 누락. 전체 정보의 약 40%를 놓침 +- **Silent failure 16건**: 모든 catch 블록이 에러를 삼킴 +- **데이터 품질 8건**: 잘못된 URL, 동명이인 병원, 평점 스케일 혼동 +- **에러 복구 0건**: 재시도 로직 없음, 새로고침 시 데이터 유실 +- **API 불안정 6건**: 타임아웃, 쿼터 소진, 인증 실패 미감지 + +**목표**: Vision 분석 추가 + 검색 정확도 + 에러 회복력 + UX 투명성 대폭 개선 + +--- + +## 진행 상태 체크리스트 + +### Sprint 0: Vision Analysis 추가 ✅ 완료 + +성형외과 홈페이지의 핵심 정보가 이미지로 제공됨 (배너, 의료진 사진, 인증 마크 등). +Firecrawl screenshot + Gemini Vision으로 이미지 속 정보를 추출. + +- [x] **WP-V1**. 멀티페이지 스크린샷 캡처 + 저장 (45min) + - 파일: `collect-channel-data/index.ts`에 추가 (Phase 2 — 검증된 URL들이 있는 시점) + - 캡처 대상 (6+ 페이지): + 1. **병원 메인 페이지** — 배너, 소셜 아이콘, 카카오톡 버튼 + 2. **의료진 페이지** — siteMap에서 `/doctor`, `/team`, `/staff` URL 자동 탐지 + 3. **시술 안내 페이지** — `/surgery`, `/service`, `/procedure` 탐지 + 4. **YouTube 채널 랜딩** — `youtube.com/@{handle}` (구독자 수, 고정 영상) + 5. **Instagram 프로필** — `instagram.com/{handle}` (팔로워 수, 피드 미리보기) + 6. **강남언니 페이지** — verified URL (평점, 리뷰 수, 의료진) + - API: Firecrawl `formats: ["screenshot"]` + `screenshotOptions: { fullPage: false, quality: 80, viewport: { width: 1280, height: 800 } }` + - 저장: Supabase Storage `screenshots/{reportId}/{channel}.png` → signed URL + - DB: `channel_data.screenshots[]`에 `ScreenshotEvidence` 형태로 저장 + - 프론트엔드: 이미 구현된 `ScreenshotProvider` → `EvidenceGallery` → `EvidenceLightbox` 에 바로 연결 + +- [x] **WP-V2**. Gemini Vision 분석 (1h) + - 파일: 새 `_shared/visionAnalysis.ts` + - API: Google Gemini `gemini-2.0-flash` (GEMINI_API_KEY 이미 .env에 있음) + - 각 스크린샷별 분석 프롬프트: + - **메인 페이지**: 개원 연도, 소셜 아이콘, 카카오톡 버튼, 브랜드 컬러, 슬로건, 인증 마크 + - **의료진 페이지**: 의사 이름 + 전문 분야 + 약력 (이미지 내 텍스트 OCR) + - **시술 페이지**: 시술 카테고리, 가격 정보, 전후 사진 유무 + - **YouTube 랜딩**: 구독자 수, 영상 수, 최근 업로드 제목, 고정 영상 + - **Instagram 프로필**: 팔로워 수, 게시물 수, 최근 피드 미리보기, 바이오 + - **강남언니**: 평점(/10), 리뷰 수, 의료진 수, 시술 종류 + - 응답: 페이지별 JSON → `channel_data.visionAnalysis`에 저장 + +- [x] **WP-V3**. Vision 데이터 → 리포트 + 증거 통합 (45min) + - 파일: `generate-report/index.ts`, `transformReport.ts` + - 변경: + - 스크린샷 → `report.screenshots[]` (ScreenshotEvidence 형태) + - 프론트엔드의 기존 EvidenceGallery/Lightbox에 바로 표시 + - Vision 추출 의료진 → `clinicSnapshot.doctors` 보강 (이미지에서 읽은 정보) + - Vision 추출 개원 연도 → `clinicSnapshot.established` 보강 + - Vision 추출 YouTube/Instagram 수치 → KPI에 cross-reference + - 각 채널 진단 항목에 `evidenceIds` 연결 → 해당 채널 스크린샷이 증거로 표시 + - 검증: + - 그랜드성형외과 분석 → "SINCE 2004" 개원 연도 추출 확인 + - YouTube 분석 섹션 → 채널 랜딩 스크린샷 썸네일 표시 확인 + - Instagram 분석 섹션 → 프로필 스크린샷 표시 확인 + - 스크린샷 클릭 → 라이트박스 모달에서 풀사이즈 보기 확인 + +### Vision Analysis 프롬프트 설계 + +``` +System: You are a medical clinic website visual analyst. Extract structured +information from website screenshots. Respond ONLY with valid JSON. + +User: Analyze this Korean plastic surgery clinic homepage screenshot. +Extract: +1. Founding year or operation duration (e.g., "SINCE 2004", "21년 무사고") +2. Doctor names and specialties shown in profile photos +3. Certification badges/marks (JCI, 보건복지부, medical tourism) +4. Main service categories from navigation menu +5. Social media icons/buttons visible (Instagram, YouTube, Blog, KakaoTalk, etc.) +6. Floating consultation buttons (KakaoTalk, LINE, WhatsApp) +7. Brand colors (primary, accent) from visual elements +8. Any promotional text or slogans in banners + +{ + "foundingYear": "2004", + "operationYears": 21, + "doctors": [{"name": "김OO", "specialty": "안면윤곽", "position": "대표원장"}], + "certifications": ["JCI", "보건복지부 인증"], + "serviceCategories": ["눈성형", "코성형", "가슴성형", "안면윤곽"], + "socialIcons": [{"platform": "instagram", "visible": true}, ...], + "floatingButtons": ["kakaotalk", "line"], + "brandColors": {"primary": "#C4A882", "accent": "#FF1493"}, + "slogans": ["끊임없이 의료성형 뷰티 트렌드를 연구하는 그랜드 의료진"] +} +``` + +### Vision Analysis에 필요한 API/리소스 + +| API | 용도 | 비용 | +|-----|------|------| +| Firecrawl `formats: ["screenshot"]` | 페이지 스크린샷 캡처 | 기존 요금에 포함 | +| Gemini `gemini-2.0-flash-exp` | 이미지 분석 (Vision) | ~$0.002/이미지 | +| Gemini `GEMINI_API_KEY` | 이미 설정됨 (.env) | ✅ 사용 가능 | + +### Vision으로 수집 가능한 추가 정보 (현재 누락) + +| 정보 | 현재 수집 | Vision 추가 후 | +|------|---------|-------------| +| 개원 연도 | ❌ AI 추측 (자주 틀림) | ✅ 배너에서 직접 읽기 | +| 의료진 수/이름 | ⚠️ 강남언니에서만 | ✅ 홈페이지 프로필에서 추출 | +| 인증 마크 | ❌ 미수집 | ✅ JCI, 보건복지부 등 인식 | +| 시술 카테고리 | ⚠️ Firecrawl JSON | ✅ 네비게이션 메뉴에서 확인 | +| 카카오톡 상담 버튼 | ❌ JS 렌더링이라 못 잡음 | ✅ 플로팅 버튼 감지 | +| 브랜드 컬러 | ⚠️ CSS 추출 (부정확) | ✅ 실제 비주얼에서 추출 | +| 슬로건/태그라인 | ⚠️ 이미지 내 텍스트 누락 | ✅ 배너 텍스트 OCR | + +--- + +### Sprint 1: 데이터 품질 Quick Wins (~2.5h) ✅ 완료 + +- [x] **WP-1**. YouTube Channel ID 정규식 수정 (20min) +- [x] **WP-2**. Naver Place 동명이인 방지 (30min) +- [x] **WP-3**. Google Maps URL 수정 (20min) +- [x] **WP-4**. Naver Blog 공식 블로그 분리 (30min) +- [x] **WP-5**. 강남언니 평점 정규화 (30min) +- [x] **WP-6**. Perplexity 모델 상수화 (20min) +- [x] **WP-7**. Apify 타임아웃 증가 (10min) + +**추가 완료:** +- [x] 소셜 버튼 직접 추출 (Firecrawl actions + JS 렌더링 후 href 추출) + +--- + +### Sprint 2: 에러 가시성 ✅ 완료 + +- [x] **WP-8**. 채널 수집 에러 추적 ⭐ 핵심 (1.5h) + - 파일: `collect-channel-data/index.ts` + - DB: `ALTER TABLE marketing_reports ADD COLUMN IF NOT EXISTS channel_errors JSONB DEFAULT '{}';` + - 변경: + - [ ] HTTP 상태 코드 체크 (429, 403, 500) + - [ ] `Promise.allSettled` 결과 순회 → `channelErrors` 기록 + - [ ] 부분 성공이어도 항상 DB 저장 (unconditional save) + - [ ] status: `"collected"` vs `"partial"` vs `"collection_failed"` + - [ ] 응답: `{ channelData, channelErrors, partialFailure }` + - 검증: API 토큰 무효화 시 에러가 기록되는지 확인 + +- [ ] **WP-9**. Instagram/Facebook 검증 개선 (45min) + - 파일: `_shared/verifyHandles.ts` + - 변경: + - [ ] `verified` 타입: `boolean | "unverifiable"` + - [ ] Instagram: 로그인 리다이렉트 감지 + - [ ] Facebook: HEAD → GET, 실패 시 `"unverifiable"` + - [ ] `collect-channel-data`에서 `"unverifiable"` 포함 + - 검증: Facebook 핸들이 "unverifiable"로 표시되고 수집은 시도됨 + +--- + +### Sprint 3: 에러 회복 🔜 일부 완료 (~5.25h) + +- [x] **WP-10**. API 재시도 유틸리티 (2h) + - 파일: 새 `_shared/retry.ts` + - 변경: `fetchWithRetry()` — 지수 백오프, 429 존중, AbortController 타임아웃 + - 검증: 429 응답 시 자동 재시도 후 성공 + +- [x] **WP-11**. 부분 실패 복구 (1h) + - 파일: `collect-channel-data/index.ts` + - 변경: `Promise.allSettled` 직후 무조건 중간 DB 저장 + - 검증: 일부 채널 실패해도 나머지 데이터 보존 + +- [ ] **WP-12**. 파이프라인 이어하기 (1.5h) + - 파일: `AnalysisLoadingPage.tsx`, `supabase.ts` + - 변경: + - [ ] `sessionStorage`에 reportId 저장 + - [ ] URL에 reportId 포함 + - [ ] DB status 폴링 + - [ ] status별 이어하기 로직 + - 검증: 분석 중 새로고침 → 이어서 진행 + +- [ ] **WP-13**. Enrichment 재시도 버튼 (45min) + - 파일: `useEnrichment.ts`, `EmptyState.tsx` + - 변경: `retry()` 함수 + "다시 시도" 버튼 (최대 2회) + - 검증: Enrichment 실패 후 버튼 클릭 → 재시도 성공 + +--- + +### Sprint 4: UX 마무리 (~50min) + +- [ ] **WP-14**. EmptyState 상태별 UI (20min) + - 파일: `EmptyState.tsx` + - 변경: `loading | error | not_found` 상태별 다른 UI + - 검증: 각 상태에 맞는 아이콘/메시지/버튼 표시 + +- [ ] **WP-15**. Firecrawl Rate Limiting (30min) + - 파일: `_shared/retry.ts`에 추가 + - 변경: 도메인별 500ms 간격 강제 + - 검증: Firecrawl 429 에러 발생 안 함 + +--- + +## 핵심 파일 목록 + +| 파일 | Sprint | 설명 | +|------|--------|------| +| `supabase/functions/discover-channels/index.ts` | 0, 1 | 채널 발견 + Vision | +| `supabase/functions/collect-channel-data/index.ts` | 0, 1, 2, 3 | 데이터 수집 | +| `supabase/functions/_shared/visionAnalysis.ts` (신규) | 0 | Vision 분석 유틸 | +| `supabase/functions/_shared/config.ts` | 1 | 공유 설정 | +| `supabase/functions/_shared/verifyHandles.ts` | 1, 2 | 핸들 검증 | +| `supabase/functions/_shared/retry.ts` (신규) | 3 | 재시도 유틸 | +| `supabase/functions/enrich-channels/index.ts` | 1 | 레거시 enrichment | +| `supabase/functions/generate-report/index.ts` | 0 | 리포트에 Vision 데이터 반영 | +| `supabase/migrations/20260404_channel_errors.sql` (신규) | 2 | DB 마이그레이션 | +| `src/pages/AnalysisLoadingPage.tsx` | 3 | 로딩 페이지 | +| `src/hooks/useEnrichment.ts` | 3 | Enrichment 훅 | +| `src/components/report/ui/EmptyState.tsx` | 4 | 빈 상태 UI | +| `src/lib/supabase.ts` | 3 | API 클라이언트 | + +## 총 예상 소요 시간 + +| Sprint | 시간 | 배포 범위 | 상태 | +|--------|------|---------|------| +| Sprint 0: Vision Analysis | ~2h | Edge Functions + Gemini | 🔜 다음 | +| Sprint 1: Quick Wins | ~2.5h | Edge Functions × 5 | ✅ 완료 | +| Sprint 2: 에러 가시성 | ~2.25h | DB + Edge Functions | 대기 | +| Sprint 3: 에러 회복 | ~5.25h | Edge Functions + Frontend + Vercel | 대기 | +| Sprint 4: UX 마무리 | ~50min | Frontend + Vercel | 대기 | +| **합계** | **~13h** | | | + +--- + +## Vision Analysis 아키텍처 + +``` +discover-channels (Stage A) + ├─ A1. Firecrawl scrape (JSON + links) ← 기존 + ├─ A2. Firecrawl map ← 기존 + ├─ A3. Firecrawl branding ← 기존 + ├─ A4. Firecrawl social buttons (JS actions) ← 방금 추가 + └─ A5. Firecrawl screenshot + Gemini Vision ← Sprint 0 신규 + ├─ 메인 페이지 스크린샷 캡처 + ├─ Gemini Vision 분석 (개원 연도, 의료진, 인증, 소셜 아이콘) + └─ 결과를 clinic 정보 + socialHandles에 병합 + +collect-channel-data + └─ Vision 데이터를 channel_data.visionAnalysis에 저장 + +generate-report + └─ Vision 데이터를 리포트 프롬프트에 포함 (실제 데이터 기반) +``` diff --git a/docs/REGISTRY_FUNCTIONAL_SPECS.md b/docs/REGISTRY_FUNCTIONAL_SPECS.md new file mode 100644 index 0000000..b6c4195 --- /dev/null +++ b/docs/REGISTRY_FUNCTIONAL_SPECS.md @@ -0,0 +1,284 @@ +# Clinic Registry — Functional Specifications + +**Document Type**: Functional Specifications +**Version**: 1.0 +**Status**: Design Approved — Implementation Hold +**Date**: 2026-04-05 +**Author**: Claude Code, directed by Haewon Kam + +--- + +## 1. 목적 (Purpose) + +잠재 고객사 성형외과의 **사전 검증된 채널 마스터 DB**를 구축하여, INFINITH 분석 파이프라인의 discovery 단계를 **확률적 검색(probabilistic search)**에서 **결정적 조회(deterministic lookup)**로 전환한다. + +## 2. Scope + +### In-Scope +- 국내 성형외과 **잠재 고객사 100곳 (MVP)** → 500곳 확장 +- 병원 마스터 정보 (이름, 도메인, 웹사이트, 개원연도, 위치) +- 사전 검증된 채널 핸들 (YouTube/Instagram/Facebook/Naver Blog/Naver Place/강남언니/TikTok) +- Human-in-the-loop 방식 초기 구축 (Perplexity Comet 활용) +- `discover-channels` Edge Function의 Registry-only 전환 +- 미등록 도메인에 대한 에러 UX + +### Out-of-Scope +- 미등록 병원 자동 fallback discovery (명시적으로 제외) +- 병원 자가 등록 포털 (추후 Sprint) +- 경쟁사 자동 업데이트 추적 +- 실시간 채널 메트릭 수집 (별도 `collect-channel-data` Function의 책임) + +## 3. 사용자 스토리 (User Stories) + +### US-1: 등록된 고객사 분석 +**As a** INFINITH 운영자 +**I want** 등록된 병원의 URL로 분석을 요청하면 +**So that** 10초 이내에 검증된 채널 기반의 정확한 분석 결과를 받는다 + +**Acceptance Criteria**: +- URL의 도메인이 `clinic_registry.domain`과 매칭되면 discovery 단계를 건너뛴다 +- 분석 리포트에 "✓ Registry-verified" 배지가 표시된다 +- 개원연도, 위치 정보가 자동 포함된다 + +### US-2: 미등록 병원 차단 +**As a** INFINITH 운영자 +**I want** 등록되지 않은 병원 URL 입력 시 명확한 에러를 받고 +**So that** 검색 오류가 있는 부정확한 분석을 방지한다 + +**Acceptance Criteria**: +- 미등록 도메인 입력 시 `404 CLINIC_NOT_REGISTERED` 응답 +- 사용자에게 "지원하지 않는 병원" 안내 + "등록 요청" CTA 표시 +- 에러는 analytics에 기록되어 향후 등록 우선순위에 반영 + +### US-3: Registry 데이터 큐레이션 +**As a** INFINITH 운영자 +**I want** Perplexity Comet으로 병원 정보를 수집하고 CSV로 import하여 +**So that** 100곳의 검증된 마스터 데이터를 30–60분 내에 구축한다 + +**Acceptance Criteria**: +- 제공된 CSV 템플릿에 맞는 데이터를 입력할 수 있다 +- import 스크립트가 데이터 검증 (필수 필드, 중복 domain 체크) 후 DB 적재 +- Import 결과 요약: 성공/실패/경고 카운트 + +## 4. 데이터 모델 (Data Model) + +### 4.1 Table: `clinic_registry` + +```sql +CREATE TABLE clinic_registry ( + id uuid PRIMARY KEY DEFAULT gen_random_uuid(), + -- 식별 정보 + name text NOT NULL, + name_aliases text[] DEFAULT '{}', + domain text UNIQUE NOT NULL, + website_url text NOT NULL, + -- 병원 메타 + founded_year int, + location_gu text, + category text DEFAULT '성형외과', + -- 채널 핸들 (사전 검증) + youtube_handle text, + youtube_channel_id text, + instagram_handle text, + facebook_handle text, + facebook_url text, + naver_blog_handle text, + naver_place_id text, + gangnam_unni_url text, + tiktok_handle text, + -- 큐레이션 메타 + verified_by text NOT NULL CHECK (verified_by IN ('manual','llm','scrape')), + verified_at timestamptz DEFAULT now(), + last_checked_at timestamptz DEFAULT now(), + is_active boolean DEFAULT true, + notes text +); + +CREATE UNIQUE INDEX idx_clinic_registry_domain ON clinic_registry (domain) WHERE is_active = true; +CREATE INDEX idx_clinic_registry_aliases ON clinic_registry USING gin (name_aliases); +CREATE INDEX idx_clinic_registry_last_checked ON clinic_registry (last_checked_at); +``` + +### 4.2 Field Dictionary + +| Field | Type | Required | Notes | +|-------|------|----------|-------| +| `name` | text | ✓ | 공식 병원명 (e.g., "아이디병원") | +| `name_aliases` | text[] | | 별칭/영문명 (e.g., `{"아이디성형외과","ID Hospital"}`) | +| `domain` | text | ✓ | `www.` 제거된 도메인. **Lookup key**. | +| `website_url` | text | ✓ | 전체 URL (redirect 대응) | +| `founded_year` | int | | 개원연도 (4-digit) | +| `location_gu` | text | | "강남구", "서초구" 등 | +| `youtube_channel_id` | text | | `UC...` 형식 (24자) | +| `naver_place_id` | text | | Naver Place 고유 ID (동명이인 방지) | +| `verified_by` | enum | ✓ | `manual` / `llm` / `scrape` | +| `is_active` | boolean | | false면 lookup에서 제외 | + +## 5. 기능 요구사항 (Functional Requirements) + +### FR-1: Registry Lookup +- **Input**: `url` (string, full URL) +- **Process**: + 1. `new URL(url).hostname` 추출 + 2. `^www\.` prefix 제거 + 3. `clinic_registry` 테이블에서 `domain = {hostname} AND is_active = true` 조회 +- **Output (match)**: `{ clinicName, verifiedChannels, source: 'registry', foundedYear, location }` +- **Output (miss)**: `{ error: 'CLINIC_NOT_REGISTERED', domain, status: 404 }` +- **Latency**: <100ms (single indexed query) + +### FR-2: VerifiedChannels 매핑 +`clinic_registry` 레코드를 기존 `VerifiedChannels` 타입으로 변환: + +```typescript +function mapRegistryToVerifiedChannels(r: ClinicRegistry): VerifiedChannels { + return { + youtube: r.youtube_channel_id ? { + handle: r.youtube_handle, channelId: r.youtube_channel_id, + verified: true, url: `https://youtube.com/${r.youtube_handle}` + } : null, + instagram: r.instagram_handle ? [{ + handle: r.instagram_handle, verified: true, + url: `https://instagram.com/${r.instagram_handle}` + }] : [], + facebook: r.facebook_handle ? { + handle: r.facebook_handle, verified: true, + url: r.facebook_url ?? `https://facebook.com/${r.facebook_handle}` + } : null, + naverBlog: r.naver_blog_handle ? { + handle: r.naver_blog_handle, verified: true, + url: `https://blog.naver.com/${r.naver_blog_handle}` + } : null, + gangnamUnni: r.gangnam_unni_url ? { handle: '', url: r.gangnam_unni_url, verified: true } : null, + tiktok: r.tiktok_handle ? { + handle: r.tiktok_handle, verified: true, + url: `https://tiktok.com/@${r.tiktok_handle}` + } : null, + }; +} +``` + +### FR-3: CSV Import +- **Input**: CSV 파일 (컬럼: FR-1 field dictionary 참조) +- **Validation**: + - `name`, `domain`, `website_url`, `verified_by` 필수 + - `domain` 중복 검사 (upsert on conflict) + - `youtube_channel_id` 형식 체크 (`^UC[a-zA-Z0-9_-]{22}$`) + - `founded_year` 범위 체크 (1950–현재) +- **Output**: `{ inserted, updated, skipped, errors: [] }` + +### FR-4: 미등록 병원 UX +- `AnalysisLoadingPage.tsx`에서 `CLINIC_NOT_REGISTERED` 응답 감지 +- 표시: 전용 페이지 (로딩 스피너 대신) + - 헤드라인: "현재 지원하지 않는 병원입니다" + - 본문: "{domain}은(는) 분석 대상 병원 목록에 포함되지 않습니다." + - CTA: "병원 등록 요청하기" 버튼 → Slack 알림 또는 간단한 폼 + +### FR-5: Registry-verified 배지 +- `ClinicSnapshot.tsx`에서 `source === 'registry'`일 때 + - 녹색 "✓ 검증된 병원" 배지 + - 개원연도 + 위치 표시 (`founded_year` + `location_gu`) + +## 6. 비기능 요구사항 (Non-Functional Requirements) + +| 항목 | 목표 | 근거 | +|------|------|------| +| Registry lookup 레이턴시 | <100ms | 인덱스된 단일 쿼리 | +| Registry 정확도 | 100% | Human-in-the-loop 큐레이션 | +| Registry 커버리지 (MVP) | 100곳 | 잠재 고객사 TOP 100 | +| CSV import 처리량 | 500행/30초 | 1회성 bulk insert | +| 재검증 주기 | 30일 | `last_checked_at` 기반 알림 | + +## 7. API 계약 (API Contract) + +### POST `/functions/v1/discover-channels` (변경됨) + +**Request**: +```json +{ "url": "https://www.idhospital.com", "clinicName": "아이디병원" } +``` + +**Response 200 (registry hit)**: +```json +{ + "clinicName": "아이디병원", + "verifiedChannels": { "youtube": {...}, "instagram": [...], ... }, + "source": "registry", + "foundedYear": 2005, + "location": "강남구" +} +``` + +**Response 404 (registry miss)**: +```json +{ + "error": "CLINIC_NOT_REGISTERED", + "message": "현재 지원하지 않는 병원입니다.", + "domain": "unknown-clinic.com" +} +``` + +**Breaking Changes**: +- 기존 5개 외부 API 호출 전부 제거 +- `source` 필드 추가 (항상 `"registry"` 또는 에러) +- 미등록 도메인은 404 반환 (이전에는 부정확한 결과라도 200 반환) + +## 8. 마이그레이션 전략 (Migration Strategy) + +### Phase 0: 문서화 (완료) +- Plan / Functional Specs / Retrospective ✓ + +### Phase 1: Schema & Template (30min) +1. `supabase/migrations/20260405_clinic_registry.sql` 작성/적용 +2. `data/clinic-registry-template.csv` 템플릿 배포 + +### Phase 2: Human-in-the-loop 수집 (사용자 직접, 30–60min) +1. 사용자가 Perplexity Comet으로 100곳 정보 수집 +2. CSV 템플릿에 기입 +3. 수동 QA (명백한 오류 제거) + +### Phase 3: Import & Integration (1.5h) +1. `scripts/import-registry.ts` 작성 + 실행 +2. `discover-channels/index.ts` Registry-only 전환 +3. `AnalysisLoadingPage.tsx` 에러 UX +4. `ClinicSnapshot.tsx` verified 배지 + +### Phase 4: 검증 (30min) +1. 등록 도메인 테스트 (아이디병원) → registry hit + 정확한 채널 +2. 미등록 도메인 테스트 → 404 + 등록 요청 UX +3. Edge Function 배포 + Vercel 배포 + +## 9. 검증 시나리오 (Test Scenarios) + +| # | 시나리오 | Expected | +|---|----------|----------| +| T1 | 등록된 `idhospital.com`으로 분석 | `source: registry`, verified 배지, <100ms discovery | +| T2 | 등록된 `www.banobagi.com`으로 분석 (`www.` 처리) | Registry hit | +| T3 | 미등록 `random-clinic.com`으로 분석 | 404 + 등록 요청 UX | +| T4 | `is_active=false`인 병원 분석 | 404 (폐업 병원 차단) | +| T5 | 잘못된 CSV (중복 domain) import | Error 반환, insert 차단 | +| T6 | Registry 레코드의 Instagram 핸들 수정 후 재분석 | 수정된 핸들로 즉시 분석 | + +## 10. 의존성 & 리스크 (Dependencies & Risks) + +### Dependencies +- Supabase PostgreSQL (기존 인프라) +- Perplexity Comet (사용자가 수집에 사용) +- 기존 `collect-channel-data` Function (변경 없음) + +### Risks +| Risk | Mitigation | +|------|-----------| +| 병원 폐업/리브랜딩 추적 실패 | `last_checked_at` 30일 기반 알림, `is_active` 플래그 | +| 채널 핸들 변경 (병원이 Instagram ID 교체) | 수동 업데이트 + 분석 시 404 응답 패턴 모니터링 | +| 고객사 확장 시 curation 병목 | Admin UI 추후 개발 (Sprint 6) | +| 미등록 URL 입력이 많아 UX 저하 | 등록 요청 CTA + 운영자 알림으로 scope 확장 | + +## 11. Open Questions + +1. 미등록 병원의 "등록 요청" CTA는 어떤 채널로 알림을 보낼지? (Slack / email / DB) +2. Registry 수정 권한 관리는 어떻게? (현재는 직접 SQL, 추후 Admin UI) +3. 경쟁사(non-고객사)를 비교 분석용으로 registry에 추가할 것인지? + +--- + +*This document specifies the Registry-first architecture that replaces probabilistic channel discovery with deterministic lookup over a curated customer scope.* diff --git a/reference/INFINITH Prototype Screenshot/Asset Collection.png b/reference/INFINITH Prototype Screenshot/Asset Collection.png new file mode 100644 index 0000000..ba78eff Binary files /dev/null and b/reference/INFINITH Prototype Screenshot/Asset Collection.png differ diff --git a/reference/INFINITH Prototype Screenshot/Brand Guide.png b/reference/INFINITH Prototype Screenshot/Brand Guide.png new file mode 100644 index 0000000..ad63af8 Binary files /dev/null and b/reference/INFINITH Prototype Screenshot/Brand Guide.png differ diff --git a/reference/INFINITH Prototype Screenshot/Channel Strategy 02.png b/reference/INFINITH Prototype Screenshot/Channel Strategy 02.png new file mode 100644 index 0000000..b447f5b Binary files /dev/null and b/reference/INFINITH Prototype Screenshot/Channel Strategy 02.png differ diff --git a/reference/INFINITH Prototype Screenshot/Channel Strategy01.png b/reference/INFINITH Prototype Screenshot/Channel Strategy01.png new file mode 100644 index 0000000..8241147 Binary files /dev/null and b/reference/INFINITH Prototype Screenshot/Channel Strategy01.png differ diff --git a/reference/INFINITH Prototype Screenshot/Content Strategy.png b/reference/INFINITH Prototype Screenshot/Content Strategy.png new file mode 100644 index 0000000..4632277 Binary files /dev/null and b/reference/INFINITH Prototype Screenshot/Content Strategy.png differ diff --git a/reference/INFINITH Prototype Screenshot/KPI.png b/reference/INFINITH Prototype Screenshot/KPI.png new file mode 100644 index 0000000..92b9f40 Binary files /dev/null and b/reference/INFINITH Prototype Screenshot/KPI.png differ diff --git a/reference/INFINITH Prototype Screenshot/Repurposing Proposal.png b/reference/INFINITH Prototype Screenshot/Repurposing Proposal.png new file mode 100644 index 0000000..4a5d314 Binary files /dev/null and b/reference/INFINITH Prototype Screenshot/Repurposing Proposal.png differ diff --git a/reference/INFINITH Prototype Screenshot/Typography Logo Rules.png b/reference/INFINITH Prototype Screenshot/Typography Logo Rules.png new file mode 100644 index 0000000..a7f2c26 Binary files /dev/null and b/reference/INFINITH Prototype Screenshot/Typography Logo Rules.png differ diff --git a/reference/INFINITH Prototype Screenshot/content calendar.png b/reference/INFINITH Prototype Screenshot/content calendar.png new file mode 100644 index 0000000..b2a274c Binary files /dev/null and b/reference/INFINITH Prototype Screenshot/content calendar.png differ diff --git a/reference/INFINITH Prototype Screenshot/facebook anal.png b/reference/INFINITH Prototype Screenshot/facebook anal.png new file mode 100644 index 0000000..3c52406 Binary files /dev/null and b/reference/INFINITH Prototype Screenshot/facebook anal.png differ diff --git a/reference/INFINITH Prototype Screenshot/instagram anal01.png b/reference/INFINITH Prototype Screenshot/instagram anal01.png new file mode 100644 index 0000000..ead50c6 Binary files /dev/null and b/reference/INFINITH Prototype Screenshot/instagram anal01.png differ diff --git a/reference/INFINITH Prototype Screenshot/youtube anal.png b/reference/INFINITH Prototype Screenshot/youtube anal.png new file mode 100644 index 0000000..54da9b8 Binary files /dev/null and b/reference/INFINITH Prototype Screenshot/youtube anal.png differ diff --git a/reference/INFINITH Prototype Screenshot/youtube anal02.png b/reference/INFINITH Prototype Screenshot/youtube anal02.png new file mode 100644 index 0000000..b468aeb Binary files /dev/null and b/reference/INFINITH Prototype Screenshot/youtube anal02.png differ diff --git a/reference/INFINITH Prototype Screenshot/기타채널 분석.png b/reference/INFINITH Prototype Screenshot/기타채널 분석.png new file mode 100644 index 0000000..e447649 Binary files /dev/null and b/reference/INFINITH Prototype Screenshot/기타채널 분석.png differ diff --git a/reference/INFINITH Prototype Screenshot/로드맵 제안.png b/reference/INFINITH Prototype Screenshot/로드맵 제안.png new file mode 100644 index 0000000..be82df4 Binary files /dev/null and b/reference/INFINITH Prototype Screenshot/로드맵 제안.png differ diff --git a/reference/INFINITH Prototype Screenshot/핵심문제진.png b/reference/INFINITH Prototype Screenshot/핵심문제진.png new file mode 100644 index 0000000..824c09f Binary files /dev/null and b/reference/INFINITH Prototype Screenshot/핵심문제진.png differ diff --git a/reference/INFINITH_Marketing_Report_바노바기성형외과.pdf b/reference/INFINITH_Marketing_Report_바노바기성형외과.pdf new file mode 100644 index 0000000..641bb9a Binary files /dev/null and b/reference/INFINITH_Marketing_Report_바노바기성형외과.pdf differ diff --git a/scripts/import-registry.ts b/scripts/import-registry.ts new file mode 100644 index 0000000..2882073 --- /dev/null +++ b/scripts/import-registry.ts @@ -0,0 +1,154 @@ +/** + * Import clinic_registry_working.csv into clinic_registry table. + * + * Usage: + * npx tsx scripts/import-registry.ts + * + * Requires env vars: + * SUPABASE_URL, SUPABASE_SERVICE_ROLE_KEY + * + * Or use .env.local file. + */ + +import { createClient } from "@supabase/supabase-js"; +import { readFileSync } from "fs"; +import { config } from "dotenv"; + +config({ path: ".env.local" }); + +const SUPABASE_URL = process.env.SUPABASE_URL || process.env.VITE_SUPABASE_URL; +const SUPABASE_KEY = process.env.SUPABASE_SERVICE_ROLE_KEY; + +if (!SUPABASE_URL || !SUPABASE_KEY) { + console.error("Missing SUPABASE_URL or SUPABASE_SERVICE_ROLE_KEY"); + process.exit(1); +} + +const supabase = createClient(SUPABASE_URL, SUPABASE_KEY); + +// CSV column indices (0-based) +const COL = { + name: 0, + brand_group: 1, + district: 2, + branches: 3, + website_kr: 4, + website_en: 5, + youtube_url: 6, + // 7: youtube_note + instagram_kr_url: 8, + // 9: instagram_kr_note + instagram_en_url: 10, + // 11: instagram_en_note + facebook_url: 12, + // 13: facebook_note + tiktok_url: 14, + // 15: tiktok_note + gangnam_unni_url: 16, + // 17: gangnam_unni_note + naver_blog_url: 18, + // 19: naver_blog_note + naver_place_url: 20, + // 21: naver_place_reviews_note + google_maps_url: 22, + // 23: google_reviews_note +} as const; + +function extractDomain(url: string): string { + try { + return new URL(url).hostname.replace(/^www\./, ""); + } catch { + // Handle URLs without protocol + const clean = url.replace(/^https?:\/\//, "").replace(/^www\./, ""); + return clean.split("/")[0]; + } +} + +function parseCSVLine(line: string): string[] { + // Simple CSV parser (no quoted fields with commas in this CSV) + return line.split(","); +} + +async function main() { + const csv = readFileSync("data/clinic-registry/clinic_registry_working.csv", "utf8"); + const lines = csv.split("\n").filter((l) => l.trim()); + const rows = lines.slice(1); // skip header + + console.log(`Parsing ${rows.length} clinics from CSV...`); + + const records: Record[] = []; + const skipped: string[] = []; + + for (const line of rows) { + const cols = parseCSVLine(line); + const name = cols[COL.name]?.trim(); + const website = cols[COL.website_kr]?.trim(); + + if (!name || !website) { + skipped.push(name || "(unnamed)"); + continue; + } + + const domain = extractDomain(website); + if (!domain) { + skipped.push(name); + continue; + } + + records.push({ + name, + name_aliases: [], // Can be enriched later + domain, + website_url: website, + brand_group: cols[COL.brand_group]?.trim() || null, + district: cols[COL.district]?.trim() || null, + branches: cols[COL.branches]?.trim() || null, + website_en: cols[COL.website_en]?.trim() || null, + youtube_url: cols[COL.youtube_url]?.trim() || null, + instagram_url: cols[COL.instagram_kr_url]?.trim() || null, + instagram_en_url: cols[COL.instagram_en_url]?.trim() || null, + facebook_url: cols[COL.facebook_url]?.trim() || null, + tiktok_url: cols[COL.tiktok_url]?.trim() || null, + gangnam_unni_url: cols[COL.gangnam_unni_url]?.trim() || null, + naver_blog_url: cols[COL.naver_blog_url]?.trim() || null, + naver_place_url: cols[COL.naver_place_url]?.trim() || null, + google_maps_url: cols[COL.google_maps_url]?.trim() || null, + verified_by: "scrape", + is_active: true, + }); + } + + console.log(`Prepared ${records.length} records (skipped ${skipped.length}: ${skipped.join(", ")})`); + + // Upsert in batches of 20 + const BATCH_SIZE = 20; + let inserted = 0; + let updated = 0; + let errors = 0; + + for (let i = 0; i < records.length; i += BATCH_SIZE) { + const batch = records.slice(i, i + BATCH_SIZE); + const { data, error } = await supabase + .from("clinic_registry") + .upsert(batch, { onConflict: "domain" }) + .select("id, domain"); + + if (error) { + console.error(`Batch ${i / BATCH_SIZE + 1} error:`, error.message); + errors += batch.length; + } else { + inserted += data?.length || 0; + console.log(`Batch ${i / BATCH_SIZE + 1}: ${data?.length} rows upserted`); + } + } + + console.log(`\nDone! Inserted/updated: ${inserted}, Errors: ${errors}`); + + // Verify + const { count } = await supabase + .from("clinic_registry") + .select("*", { count: "exact", head: true }); + console.log(`Total rows in clinic_registry: ${count}`); +} + +main().catch(console.error); diff --git a/src/components/plan/ContentCalendar.tsx b/src/components/plan/ContentCalendar.tsx index 83eee86..675469d 100644 --- a/src/components/plan/ContentCalendar.tsx +++ b/src/components/plan/ContentCalendar.tsx @@ -1,3 +1,4 @@ +import { useState, useCallback } from 'react'; import { motion } from 'motion/react'; import { VideoFilled, @@ -6,10 +7,13 @@ import { MegaphoneFilled, } from '../icons/FilledIcons'; import { SectionWrapper } from '../report/ui/SectionWrapper'; +import EditEntryModal from './EditEntryModal'; import type { CalendarData, ContentCategory, CalendarEntry } from '../../types/plan'; interface ContentCalendarProps { data: CalendarData; + planId?: string; + onEntryUpdate?: (entryId: string, updates: Partial) => void; } const contentTypeColors: Record = { @@ -44,9 +48,118 @@ const channelEmojiMap: Record = { video: '▷', }; +const statusDotColors: Record = { + draft: 'bg-slate-300', + approved: 'bg-purple-400', + published: 'bg-green-400', +}; + const dayHeaders = ['월', '화', '수', '목', '금', '토', '일']; -export default function ContentCalendar({ data }: ContentCalendarProps) { +export default function ContentCalendar({ data, planId, onEntryUpdate }: ContentCalendarProps) { + const [weeks, setWeeks] = useState(data.weeks); + const [editingEntry, setEditingEntry] = useState(null); + const [filterType, setFilterType] = useState(null); + const [viewMode, setViewMode] = useState<'weekly' | 'monthly'>('weekly'); + + const handleEntryClick = useCallback((entry: CalendarEntry) => { + setEditingEntry(entry); + }, []); + + const handleSave = useCallback((updated: CalendarEntry) => { + setWeeks((prev) => + prev.map((week) => ({ + ...week, + entries: week.entries.map((e) => + (e.id && e.id === updated.id) ? updated : e + ), + })) + ); + if (onEntryUpdate && updated.id) { + onEntryUpdate(updated.id, updated); + } + setEditingEntry(null); + }, [onEntryUpdate]); + + const handleClose = useCallback(() => { + setEditingEntry(null); + }, []); + + const toggleFilter = (type: ContentCategory) => { + setFilterType((prev) => (prev === type ? null : type)); + }; + + // Monthly view: flatten all weeks into one 7-day grid + const renderMonthlyView = () => { + const allEntries = weeks.flatMap((w) => w.entries); + const filtered = filterType + ? allEntries.filter((e) => e.contentType === filterType) + : allEntries; + + const dayCells: CalendarEntry[][] = Array.from({ length: 7 }, () => []); + for (const entry of filtered) { + if (entry.dayOfWeek >= 0 && entry.dayOfWeek <= 6) { + dayCells[entry.dayOfWeek].push(entry); + } + } + + return ( +
+

월간 종합

+
+ {dayHeaders.map((day) => ( +
+ {day} +
+ ))} + {dayCells.map((entries, dayIdx) => ( +
0 + ? 'bg-slate-50/50 border border-slate-100' + : 'border border-dashed border-slate-200/60' + }`} + > + {entries.map((entry, entryIdx) => renderEntry(entry, entryIdx))} +
+ ))} +
+
+ ); + }; + + const renderEntry = (entry: CalendarEntry, entryIdx: number) => { + const colors = contentTypeColors[entry.contentType]; + const Icon = contentTypeIcons[entry.contentType]; + const channelSymbol = channelEmojiMap[entry.channelIcon] || '·'; + const statusDot = statusDotColors[entry.status || 'draft']; + + return ( +
handleEntryClick(entry)} + > +
+ + + {channelSymbol} + + +
+

+ {entry.title} +

+ {entry.description && ( +

+ {entry.description} +

+ )} +
+ ); + }; + return ( + {/* Toolbar: View Toggle + Filters */} +
+ {/* View toggle */} +
+ + +
+ + {/* Status legend */} +
+ 초안 + 승인 + 게시됨 +
+
+ {/* Monthly Summary */}
{data.monthlySummary.map((item) => { const colors = contentTypeColors[item.type]; + const isActive = filterType === item.type; return ( toggleFilter(item.type)} + data-no-print={undefined} >
- {/* Weekly Calendar Grid */} - {data.weeks.map((week, weekIdx) => { - const dayCells: CalendarEntry[][] = Array.from({ length: 7 }, () => []); - for (const entry of week.entries) { - const dayIndex = entry.dayOfWeek; - if (dayIndex >= 0 && dayIndex <= 6) { - dayCells[dayIndex].push(entry); + {/* Calendar Content */} + {viewMode === 'monthly' ? ( + renderMonthlyView() + ) : ( + /* Weekly Calendar Grid */ + weeks.map((week, weekIdx) => { + const dayCells: CalendarEntry[][] = Array.from({ length: 7 }, () => []); + for (const entry of week.entries) { + if (filterType && entry.contentType !== filterType) continue; + const dayIndex = entry.dayOfWeek; + if (dayIndex >= 0 && dayIndex <= 6) { + dayCells[dayIndex].push(entry); + } } - } - return ( - - {/* Week header with theme */} -

{week.label}

+ return ( + +

{week.label}

-
- {/* Day headers */} - {dayHeaders.map((day) => ( -
- {day} -
- ))} +
+ {dayHeaders.map((day) => ( +
+ {day} +
+ ))} - {/* Day cells */} - {dayCells.map((entries, dayIdx) => ( -
0 - ? 'bg-slate-50/50 border border-slate-100' - : 'border border-dashed border-slate-200/60' - }`} - > - {entries.map((entry, entryIdx) => { - const colors = contentTypeColors[entry.contentType]; - const Icon = contentTypeIcons[entry.contentType]; - const channelSymbol = channelEmojiMap[entry.channelIcon] || '·'; - return ( -
-
- - {channelSymbol} - - -
-

- {entry.title} -

-
- ); - })} -
- ))} -
- - ); - })} + {dayCells.map((entries, dayIdx) => ( +
0 + ? 'bg-slate-50/50 border border-slate-100' + : 'border border-dashed border-slate-200/60' + }`} + > + {entries.map((entry, entryIdx) => renderEntry(entry, entryIdx))} +
+ ))} +
+
+ ); + }) + )} - {/* Color Legend */} + {/* Color Legend (clickable filter) */}
{(Object.keys(contentTypeColors) as ContentCategory[]).map((type) => { const colors = contentTypeColors[type]; + const isActive = filterType === type; return ( - toggleFilter(type)} + className={`${colors.bg} ${colors.text} border ${colors.border} rounded-full px-3 py-1 text-xs font-medium ${colors.shadow} transition-all ${ + isActive ? 'ring-2 ring-purple-300' : '' + } ${filterType && !isActive ? 'opacity-40' : ''}`} > {contentTypeLabels[type]} - + ); })} + {filterType && ( + + )}
+ + {/* Edit Modal */} + {editingEntry && ( + + )} ); } diff --git a/src/components/plan/EditEntryModal.tsx b/src/components/plan/EditEntryModal.tsx new file mode 100644 index 0000000..453bd07 --- /dev/null +++ b/src/components/plan/EditEntryModal.tsx @@ -0,0 +1,235 @@ +import { useState, useEffect } from 'react'; +import { motion, AnimatePresence } from 'motion/react'; +import type { CalendarEntry, ContentCategory } from '../../types/plan'; + +interface EditEntryModalProps { + entry: CalendarEntry | null; + onSave: (updated: CalendarEntry) => void; + onClose: () => void; + onRegenerate?: (entry: CalendarEntry) => void; +} + +const CHANNELS = [ + { id: 'YouTube', icon: 'youtube' }, + { id: 'Instagram', icon: 'instagram' }, + { id: '네이버 블로그', icon: 'blog' }, + { id: 'Facebook', icon: 'facebook' }, + { id: 'TikTok', icon: 'video' }, +]; + +const CONTENT_TYPES: { value: ContentCategory; label: string }[] = [ + { value: 'video', label: '영상' }, + { value: 'blog', label: '블로그' }, + { value: 'social', label: '소셜' }, + { value: 'ad', label: '광고' }, +]; + +const DAYS = ['월', '화', '수', '목', '금', '토', '일']; + +const STATUS_OPTIONS: { value: CalendarEntry['status']; label: string; color: string }[] = [ + { value: 'draft', label: '초안', color: 'bg-slate-200 text-slate-600' }, + { value: 'approved', label: '승인', color: 'bg-purple-100 text-purple-700' }, + { value: 'published', label: '게시됨', color: 'bg-green-100 text-green-700' }, +]; + +export default function EditEntryModal({ entry, onSave, onClose, onRegenerate }: EditEntryModalProps) { + const [title, setTitle] = useState(''); + const [description, setDescription] = useState(''); + const [channel, setChannel] = useState('YouTube'); + const [channelIcon, setChannelIcon] = useState('youtube'); + const [contentType, setContentType] = useState('video'); + const [dayOfWeek, setDayOfWeek] = useState(0); + const [status, setStatus] = useState('draft'); + + useEffect(() => { + if (!entry) return; + setTitle(entry.title || ''); + setDescription(entry.description || ''); + setChannel(entry.channel || 'YouTube'); + setChannelIcon(entry.channelIcon || 'youtube'); + setContentType(entry.contentType || 'video'); + setDayOfWeek(entry.dayOfWeek); + setStatus(entry.status || 'draft'); + }, [entry]); + + if (!entry) return null; + + const handleChannelChange = (channelId: string) => { + const ch = CHANNELS.find((c) => c.id === channelId); + if (ch) { + setChannel(ch.id); + setChannelIcon(ch.icon); + } + }; + + const handleSave = () => { + onSave({ + ...entry, + title, + description, + channel, + channelIcon, + contentType, + dayOfWeek, + status, + isManualEdit: true, + }); + }; + + return ( + +
+ {/* Backdrop */} + + + {/* Modal */} + + {/* Header */} +
+

콘텐츠 편집

+ +
+ + {/* Body */} +
+ {/* Title */} +
+ + setTitle(e.target.value)} + className="w-full px-3 py-2 border border-slate-200 rounded-xl text-sm focus:ring-2 focus:ring-purple-200 focus:border-purple-400 outline-none transition-all" + placeholder="콘텐츠 제목을 입력하세요" + /> +
+ + {/* Description */} +
+ +