[{"data":1,"prerenderedAt":5041},["ShallowReactive",2],{"doc:\u002Fdeveloper-guide":3},{"id":4,"title":5,"body":6,"description":668,"extension":5034,"meta":5035,"navigation":5036,"path":5037,"seo":5038,"stem":5039,"__hash__":5040},"docs\u002FDEVELOPER-GUIDE.md","Malgn Helper 개발자 가이드 (전체 통합본)",{"type":7,"value":8,"toc":4950},"minimark",[9,13,53,56,60,185,187,191,205,208,240,247,361,376,381,399,401,405,408,412,505,509,603,607,653,655,659,669,674,699,701,705,708,898,908,913,935,937,941,948,952,960,965,991,999,1078,1086,1188,1197,1211,1216,1229,1235,1256,1261,1272,1281,1323,1331,1371,1379,1410,1418,1449,1457,1468,1476,1496,1504,1521,1530,1549,1554,1591,1598,1628,1633,1677,1679,1683,1692,1697,1705,1710,1716,1721,1755,1760,1843,1848,1879,1902,1904,1908,1921,1930,1942,2041,2046,2095,2106,2151,2159,2164,2179,2281,2296,2301,2362,2374,2390,2392,2396,2405,2442,2444,2448,2456,2529,2531,2535,2542,2567,2574,2585,2795,2800,2847,2885,2892,2899,2960,2966,2985,2987,2991,2995,3028,3032,3061,3065,3068,3104,3108,3127,3131,3168,3172,3202,3204,3208,3219,3223,3230,3263,3270,3276,3290,3296,3303,3309,3315,3341,3347,3350,3407,3451,3457,3460,3468,3475,3481,3488,3536,3542,3549,3554,3586,3591,3661,3666,3739,3751,3755,3758,3778,3780,3784,3788,3846,3850,3876,3883,3914,3918,3938,3945,3958,3960,3964,3968,3995,4001,4056,4068,4072,4102,4115,4121,4149,4154,4160,4209,4211,4215,4219,4229,4261,4317,4322,4347,4351,4444,4448,4453,4482,4486,4581,4583,4587,4668,4670,4674,4679,4683,4724,4728,4764,4768,4796,4798,4802,4805,4929,4931,4946],[10,11,5],"h1",{"id":12},"malgn-helper-개발자-가이드-전체-통합본",[14,15,16,24],"blockquote",{},[17,18,19,23],"p",{},[20,21,22],"strong",{},"이 문서 하나로 Malgn Helper 프로젝트 전체를 이해하는 것","이 목표입니다.\n신입·초급 개발자가 사전 지식 없이 읽어도 따라올 수 있도록, 용어부터 차근차근 설명하고\n실제 코드·설정·절차를 최대한 구체적으로 적었습니다.",[25,26,27,31,43],"ul",{},[28,29,30],"li",{},"작성 기준일: 2026-06-09",[28,32,33,34,38,39,42],{},"정본 위치: ",[35,36,37],"code",{},"malgn-helper-mng\u002Fdocs\u002FDEVELOPER-GUIDE.md"," (이 관리 허브 앱의 ",[35,40,41],{},"\u002Fdocs","에서도 볼 수 있음)",[28,44,45,48,49,52],{},[20,46,47],{},"\"설계(계획)\"와 \"현재 구현됨\"을 구분","해서 표기합니다. 문서에 적혀 있어도 아직 코드로 없는 것은 ",[35,50,51],{},"🔜 계획","으로 명시합니다.",[54,55],"hr",{},[57,58,59],"h2",{"id":59},"목차",[61,62,63,70,76,82,88,131,137,143,149,155,161,167,173,179],"ol",{},[28,64,65],{},[66,67,69],"a",{"href":68},"#1-%ED%95%9C%EB%88%88%EC%97%90-%EB%B3%B4%EB%8A%94-%ED%81%B0-%EA%B7%B8%EB%A6%BC","한눈에 보는 큰 그림",[28,71,72],{},[66,73,75],{"href":74},"#2-%EB%A8%BC%EC%A0%80-%EC%95%8C%EC%95%84%EC%95%BC-%ED%95%A0-%EC%9A%A9%EC%96%B4-%EC%82%AC%EC%A0%84","먼저 알아야 할 용어 사전",[28,77,78],{},[66,79,81],{"href":80},"#3-%EC%A0%84%EC%B2%B4-%EC%95%84%ED%82%A4%ED%85%8D%EC%B2%98%EC%99%80-%EB%8D%B0%EC%9D%B4%ED%84%B0-%ED%9D%90%EB%A6%84","전체 아키텍처와 데이터 흐름",[28,83,84],{},[66,85,87],{"href":86},"#4-%EC%9D%B8%ED%94%84%EB%9D%BC-%EA%B5%AC%EC%84%B1%EC%9A%94%EC%86%8C-%EC%89%BD%EA%B2%8C-%EC%9D%B4%ED%95%B4%ED%95%98%EA%B8%B0","인프라 구성요소 (Cloudflare\u002FAWS) 쉽게 이해하기",[28,89,90,94],{},[66,91,93],{"href":92},"#5-%EB%A0%88%ED%8F%AC%EC%A7%80%ED%86%A0%EB%A6%AC%EB%B3%84-%EC%83%81%EC%84%B8","레포지토리별 상세",[25,95,96,103,110,117,124],{},[28,97,98,99],{},"5.1 ",[66,100,102],{"href":101},"#51-malgn-helper-api--%ED%95%B5%EC%8B%AC-%EB%B0%B1%EC%97%94%EB%93%9C","malgn-helper-api (핵심 백엔드)",[28,104,105,106],{},"5.2 ",[66,107,109],{"href":108},"#52-malgn-helper-pms--pms-%EC%95%A0%EB%93%9C%EC%98%A8","malgn-helper-pms (PMS 애드온)",[28,111,112,113],{},"5.3 ",[66,114,116],{"href":115},"#53-malgn-helper-admin--%EA%B4%80%EB%A6%AC%EC%9E%90%EB%8B%A8","malgn-helper-admin (관리자단)",[28,118,119,120],{},"5.4 ",[66,121,123],{"href":122},"#54-malgn-helper--%EC%82%AC%EC%9A%A9%EC%9E%90-%EC%B1%97%EB%B4%87","malgn-helper (사용자 챗봇)",[28,125,126,127],{},"5.5 ",[66,128,130],{"href":129},"#55-malgn-helper-mng--%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8-%EA%B4%80%EB%A6%AC-%ED%97%88%EB%B8%8C","malgn-helper-mng (프로젝트 관리 허브)",[28,132,133],{},[66,134,136],{"href":135},"#6-%EB%8D%B0%EC%9D%B4%ED%84%B0-%EB%AA%A8%EB%8D%B8-db-%ED%85%8C%EC%9D%B4%EB%B8%94","데이터 모델 (DB 테이블)",[28,138,139],{},[66,140,142],{"href":141},"#7-llm%ED%94%84%EB%A1%AC%ED%94%84%ED%8A%B8-%EC%84%A4%EA%B3%84","LLM·프롬프트 설계",[28,144,145],{},[66,146,148],{"href":147},"#8-%EA%B2%80%EC%83%89-%ED%8C%8C%EC%9D%B4%ED%94%84%EB%9D%BC%EC%9D%B8","검색 파이프라인",[28,150,151],{},[66,152,154],{"href":153},"#9-%EC%9D%B8%EC%A6%9D%EB%B3%B4%EC%95%88","인증·보안",[28,156,157],{},[66,158,160],{"href":159},"#10-%EB%A1%9C%EC%BB%AC-%EA%B0%9C%EB%B0%9C-%ED%99%98%EA%B2%BD-%EC%85%8B%EC%97%85-%EC%A0%88%EC%B0%A8","로컬 개발 환경 셋업 (절차)",[28,162,163],{},[66,164,166],{"href":165},"#11-%EB%B0%B0%ED%8F%AC-%EC%A0%88%EC%B0%A8","배포 절차",[28,168,169],{},[66,170,172],{"href":171},"#12-%EC%9A%B4%EC%98%81-%EC%BB%A8%EB%B2%A4%EC%85%98","운영 컨벤션",[28,174,175],{},[66,176,178],{"href":177},"#13-%ED%98%84%EC%9E%AC-%EA%B5%AC%ED%98%84-%ED%98%84%ED%99%A9-vs-%EB%82%A8%EC%9D%80-%EC%9E%91%EC%97%85","현재 구현 현황 vs 남은 작업",[28,180,181],{},[66,182,184],{"href":183},"#14-%EC%9E%90%EC%A3%BC-%EB%A7%8C%EB%82%98%EB%8A%94-%ED%95%A8%EC%A0%95-%ED%8A%B8%EB%9F%AC%EB%B8%94%EC%8A%88%ED%8C%85","자주 만나는 함정 (트러블슈팅)",[54,186],{},[57,188,190],{"id":189},"_1-한눈에-보는-큰-그림","1. 한눈에 보는 큰 그림",[17,192,193,196,197,200,201,204],{},[20,194,195],{},"Malgn Helper","는 \"NotebookLM 수준의 사내 솔루션 전문 ",[20,198,199],{},"고객상담 AI 챗봇","\"을 만드는 프로젝트입니다.\n자사 솔루션(LMS 등) 사용법·안내를 자동화해서 ",[20,202,203],{},"상담 비용을 줄이고 24시간 응답 채널","을 확보하는 것이 목적입니다.",[17,206,207],{},"핵심 원칙 5가지 (이게 모든 코드의 판단 기준):",[61,209,210,216,222,228,234],{},[28,211,212,215],{},[20,213,214],{},"근거 기반 답변 + 출처 인용"," — 업로드된 자료에 근거해서만 답하고, 어디서 나온 답인지 출처를 단다.",[28,217,218,221],{},[20,219,220],{},"정확성·일관성 우선"," — 같은 질문엔 늘 같은 답. 잘못된 안내를 내느니 안 내는 게 낫다.",[28,223,224,227],{},[20,225,226],{},"표준 답변 우선"," — 회사가 검증한 \"표준 답변\"이 있으면 그걸 먼저 쓴다.",[28,229,230,233],{},[20,231,232],{},"\"모르면 모른다\""," — 추측 금지. 애매하면 사람(상담사)에게 넘긴다(에스컬레이션).",[28,235,236,239],{},[20,237,238],{},"한국어 중심"," — 콘텐츠 대부분이 한국어다.",[17,241,242,243,246],{},"이 목적을 이루기 위해 ",[20,244,245],{},"5개의 독립 레포","로 나눠서 개발하고 있습니다.",[248,249,250,269],"table",{},[251,252,253],"thead",{},[254,255,256,260,263,266],"tr",{},[257,258,259],"th",{},"레포",[257,261,262],{},"역할",[257,264,265],{},"한 줄 설명",[257,267,268],{},"종류",[270,271,272,291,309,326,343],"tbody",{},[254,273,274,280,285,288],{},[275,276,277],"td",{},[35,278,279],{},"malgn-helper-api",[275,281,282],{},[20,283,284],{},"백엔드(두뇌)",[275,286,287],{},"검색·LLM·DB 접근을 전부 담당하는 API 서버",[275,289,290],{},"Hono \u002F Cloudflare Workers",[254,292,293,298,303,306],{},[275,294,295],{},[35,296,297],{},"malgn-helper-pms",[275,299,300],{},[20,301,302],{},"PMS 애드온",[275,304,305],{},"기존 사내 PMS(맑은프로젝트게시판) 안에 끼워 상담사를 돕는 화면",[275,307,308],{},"Nuxt 3 \u002F Cloudflare Pages",[254,310,311,316,321,324],{},[275,312,313],{},[35,314,315],{},"malgn-helper-admin",[275,317,318],{},[20,319,320],{},"관리자단",[275,322,323],{},"자료 업로드·표준답변 관리·로그\u002F비용 검토 화면",[275,325,308],{},[254,327,328,333,338,341],{},[275,329,330],{},[35,331,332],{},"malgn-helper",[275,334,335],{},[20,336,337],{},"사용자 챗봇",[275,339,340],{},"고객이 직접 대화하는 챗봇 화면 (Phase 2 본격화)",[275,342,308],{},[254,344,345,350,355,358],{},[275,346,347],{},[35,348,349],{},"malgn-helper-mng",[275,351,352],{},[20,353,354],{},"관리 허브",[275,356,357],{},"이 프로젝트 자체의 진척·일정·문서를 조망 (제품 아님)",[275,359,360],{},"Nuxt 3 \u002F Cloudflare Pages + D1",[14,362,363],{},[17,364,365,368,369,372,373,375],{},[20,366,367],{},"중요한 구조 원칙",": 프론트엔드 3종(pms\u002Fadmin\u002Fhelper)은 ",[20,370,371],{},"DB나 LLM에 직접 접근하지 않습니다.","\n모든 데이터·AI 호출은 반드시 ",[35,374,279],{},"를 거칩니다. 이렇게 해야 인증·권한·로깅·캐싱을 한 곳에서 통제할 수 있습니다.",[377,378,380],"h3",{"id":379},"프로젝트는-2단계로-나뉜다","프로젝트는 2단계로 나뉜다",[25,382,383,393],{},[28,384,385,388,389,392],{},[20,386,387],{},"Phase 1 — CS 관리자 + AI 추천 답변"," (현재 진행 중): 상담사가 고객 문의를 받으면 AI가 답변을 추천하고, 상담사가 ",[20,390,391],{},"채택\u002F수정\u002F거절","한다. 그 피드백이 자산으로 쌓인다.",[28,394,395,398],{},[20,396,397],{},"Phase 2 — 고객용 상담 챗봇"," (예정): 고객이 직접 챗봇과 대화한다. \"모르면 모른다\" 원칙으로 안전하게 상담사에게 넘긴다.",[54,400],{},[57,402,404],{"id":403},"_2-먼저-알아야-할-용어-사전","2. 먼저 알아야 할 용어 사전",[17,406,407],{},"초급 개발자가 이 프로젝트에서 자주 마주치는 단어들입니다. 모르는 단어가 나오면 여기로 돌아오세요.",[377,409,411],{"id":410},"ai-검색-관련","AI \u002F 검색 관련",[25,413,414,424,430,436,446,456,473,479,485,495],{},[28,415,416,419,420,423],{},[20,417,418],{},"LLM (Large Language Model, 대규모 언어 모델)",": ChatGPT 같은 \"글을 이해하고 생성하는 AI\". 여기서는 OpenAI의 ",[35,421,422],{},"gpt-4.1-mini"," 모델을 주로 쓴다.",[28,425,426,429],{},[20,427,428],{},"RAG (Retrieval-Augmented Generation, 검색 증강 생성)",": LLM이 그냥 머릿속(학습한 지식)으로 답하면 틀린 말(환각)을 한다. 그래서 **먼저 회사 자료에서 관련 문서를 \"검색(Retrieval)\"해 찾아오고, 그 문서를 근거로 LLM이 답을 \"생성(Generation)\"**하게 한다. 이 프로젝트의 핵심 방식.",[28,431,432,435],{},[20,433,434],{},"임베딩 (Embedding)",": 문장을 의미가 담긴 **숫자 배열(벡터)**로 바꾼 것. 예: \"환불 어떻게 해요?\"를 1536개의 숫자로 변환. 의미가 비슷한 문장은 벡터도 가깝다 → \"비슷한 뜻 찾기\"가 가능해진다.",[28,437,438,441,442,445],{},[20,439,440],{},"벡터 검색 \u002F k-NN",": 임베딩(벡터)끼리의 거리를 재서 ",[20,443,444],{},"의미가 가까운 문서","를 찾는 검색. k-NN = \"가장 가까운 k개 이웃\". 단어가 안 겹쳐도 뜻이 비슷하면 찾는다.",[28,447,448,451,452,455],{},[20,449,450],{},"BM25",": 전통적인 ",[20,453,454],{},"키워드(단어) 일치 기반"," 검색 점수 방식. 단어가 정확히 겹칠수록 점수가 높다.",[28,457,458,461,462,465,466,468,469,472],{},[20,459,460],{},"하이브리드 검색",": 벡터 검색(의미) + BM25(키워드)를 ",[20,463,464],{},"함께 써서"," 둘의 장점을 합친 검색. (※ 이 프로젝트에서는 ",[35,467,51],{}," 단계. 현재는 단순 키워드 검색만 구현됨 — ",[66,470,471],{"href":147},"8장"," 참고.)",[28,474,475,478],{},[20,476,477],{},"표준 답변 (Standard Answer)",": 회사가 미리 만들어 검증한 모범 답안. 같은 질문이 오면 AI가 새로 짜내기 전에 이걸 먼저 쓴다.",[28,480,481,484],{},[20,482,483],{},"에스컬레이션 (Escalation)",": AI가 자신 없을 때 \"사람(상담사)에게 넘기기\".",[28,486,487,490,491,494],{},[20,488,489],{},"환각 (Hallucination)",": LLM이 그럴듯하지만 ",[20,492,493],{},"사실이 아닌 답","을 지어내는 것. 이 프로젝트가 가장 막고 싶어하는 현상.",[28,496,497,500,501,504],{},[20,498,499],{},"Vision",": LLM이 ",[20,502,503],{},"이미지를 보고 이해","하는 기능. 게시글에 붙은 캡처 이미지를 분석해 \"이건 무슨 화면이다\"라고 설명을 단다.",[377,506,508],{"id":507},"인프라-관련","인프라 관련",[25,510,511,517,523,529,543,553,563,576,582,588,597],{},[28,512,513,516],{},[20,514,515],{},"Cloudflare",": 전 세계에 서버(엣지)를 둔 클라우드 회사. 이 프로젝트의 프론트·백엔드가 전부 여기서 돈다.",[28,518,519,522],{},[20,520,521],{},"Cloudflare Pages",": 정적\u002FSSR 웹사이트를 배포하는 서비스. 프론트엔드 4종이 여기에 올라간다.",[28,524,525,528],{},[20,526,527],{},"Cloudflare Workers",": 서버 코드를 전 세계 엣지에서 실행하는 서비스. 백엔드 API가 여기서 돈다.",[28,530,531,534,535,538,539,542],{},[20,532,533],{},"D1",": Cloudflare가 제공하는 ",[20,536,537],{},"SQLite 기반 DB",". (관리 허브 ",[35,540,541],{},"mng","만 사용)",[28,544,545,548,549,552],{},[20,546,547],{},"R2",": Cloudflare의 ",[20,550,551],{},"파일 저장소","(AWS S3 같은 것). 업로드한 원본 파일·이미지를 여기에 둔다.",[28,554,555,558,559,562],{},[20,556,557],{},"Hyperdrive",": Cloudflare Workers에서 ",[20,560,561],{},"외부 MySQL DB에 빠르게 연결","하게 해주는 가속·풀링 장치. (커넥션을 매번 새로 여는 비용을 줄여줌)",[28,564,565,534,568,571,572,575],{},[20,566,567],{},"AI Gateway",[20,569,570],{},"LLM 호출 중계소",". 우리가 OpenAI를 직접 부르지 않고 이 게이트웨이를 거치면 ",[20,573,574],{},"캐싱·로깅·rate limit(호출 제한)"," 을 한 곳에서 받을 수 있다.",[28,577,578,581],{},[20,579,580],{},"BYOK (Bring Your Own Key)",": \"내 OpenAI 키를 직접 쓴다\"는 뜻. AI Gateway를 거치되 비용은 우리 OpenAI 계정으로 청구된다.",[28,583,584,587],{},[20,585,586],{},"Aurora MySQL",": AWS의 관리형 MySQL. 기존 PMS 운영 데이터가 들어있다.",[28,589,590,593,594,596],{},[20,591,592],{},"OpenSearch",": AWS의 검색 엔진(Elasticsearch 계열). 벡터+키워드 하이브리드 검색용. (",[35,595,51],{},")",[28,598,599,602],{},[20,600,601],{},"SSR \u002F 프리렌더",": SSR = 요청이 올 때마다 서버가 HTML을 만들어 줌. 프리렌더 = 빌드할 때 미리 HTML을 구워둠.",[377,604,606],{"id":605},"도메인cs-관련","도메인\u002FCS 관련",[25,608,609,615,625,631,637,643],{},[28,610,611,614],{},[20,612,613],{},"PMS",": 사내 \"맑은프로젝트게시판\". 프로젝트별로 고객 문의(게시글)와 답변(댓글)이 쌓이는 기존 시스템.",[28,616,617,620,621,624],{},[20,618,619],{},"FRT (First Response Time)",": 고객 문의가 올라온 뒤 ",[20,622,623],{},"첫 응답까지 걸린 시간",". CS 품질 핵심 지표.",[28,626,627,630],{},[20,628,629],{},"FCR (First Contact Resolution)",": 한 번의 응답으로 문제가 해결됐는지.",[28,632,633,636],{},[20,634,635],{},"브리핑 카드",": 한 프로젝트(고객사)의 최근 상황을 한 장으로 요약한 카드(담당자·문의 추이·미응답·핫토픽 등).",[28,638,639,642],{},[20,640,641],{},"Q&A 평가 카드",": 하나의 문의-답변 쌍을 5개 기준(축)으로 채점한 카드.",[28,644,645,648,649,652],{},[20,646,647],{},"soft delete",": 데이터를 진짜로 지우지 않고 ",[35,650,651],{},"status = -1","로 표시만 해서 \"지운 것처럼\" 다루는 방식. 복구·감사에 유리.",[54,654],{},[57,656,658],{"id":657},"_3-전체-아키텍처와-데이터-흐름","3. 전체 아키텍처와 데이터 흐름",[660,661,666],"pre",{"className":662,"code":664,"language":665},[663],"language-text","   고객 브라우저        상담사 (PMS 화면)        관리자 브라우저\n        │                    │                       │\n        ▼                    ▼                       ▼\n  malgn-helper        malgn-helper-pms        malgn-helper-admin\n  (사용자 챗봇)        (PMS 임베드 애드온)        (관리자단)\n   Nuxt\u002FPages           Nuxt\u002FPages               Nuxt\u002FPages\n        │                    │                       │\n        └────────────────────┼───────────────────────┘\n                             │  (모든 호출은 HTTPS API 한 곳으로)\n                             ▼\n                     malgn-helper-api\n                   (Hono on Cloudflare Workers)\n                             │\n        ┌────────────┬───────┴────────┬───────────────┐\n        ▼            ▼                ▼               ▼\n   Hyperdrive       R2          AI Gateway      OpenSearch\n        │       (원본 파일)    (malgn-helper2)   (k-NN+BM25)\n        ▼                          │              🔜 계획\n   Aurora MySQL                    ▼\n   (PMS 운영 DB +              OpenAI API\n    hp_* 헬퍼 테이블)        (gpt-4.1-mini)\n\n  ※ 별도로, 프로젝트 진척 관리용 malgn-helper-mng 는\n    Cloudflare D1(SQLite)만 사용하고 위 흐름과 독립적이다.\n","text",[35,667,664],{"__ignoreMap":668},"",[17,670,671],{},[20,672,673],{},"읽는 법:",[25,675,676,679,693,696],{},[28,677,678],{},"사용자\u002F상담사\u002F관리자는 각자의 프론트엔드 화면을 본다.",[28,680,681,682,685,686,688,689,692],{},"프론트엔드는 데이터가 필요하면 ",[20,683,684],{},"무조건"," ",[35,687,279],{},"(하나의 URL: ",[35,690,691],{},"https:\u002F\u002Fmalgn-helper-api.malgnsoft.workers.dev",")에 HTTP 요청을 보낸다.",[28,694,695],{},"API 서버가 상황에 따라 MySQL을 읽거나, R2에서 파일을 가져오거나, AI Gateway를 통해 OpenAI를 부른다.",[28,697,698],{},"프론트엔드는 절대 MySQL·OpenAI를 직접 만지지 않는다.",[54,700],{},[57,702,704],{"id":703},"_4-인프라-구성요소-쉽게-이해하기","4. 인프라 구성요소 쉽게 이해하기",[17,706,707],{},"이 프로젝트가 실제로 쓰는 인프라를 하나씩 \"왜 쓰는지\" 중심으로 설명합니다.",[248,709,710,725],{},[251,711,712],{},[254,713,714,717,719,722],{},[257,715,716],{},"구성요소",[257,718,268],{},[257,720,721],{},"이 프로젝트에서의 역할",[257,723,724],{},"식별자\u002F설정",[270,726,727,745,766,788,807,825,843,862,880],{},[254,728,729,733,736,739],{},[275,730,731],{},[20,732,521],{},[275,734,735],{},"정적\u002FSSR 호스팅",[275,737,738],{},"프론트엔드 4종 배포 (helper\u002Fadmin\u002Fpms\u002Fmng)",[275,740,741,742],{},"빌드 출력 ",[35,743,744],{},"dist\u002F",[254,746,747,751,754,760],{},[275,748,749],{},[20,750,527],{},[275,752,753],{},"서버리스 실행",[275,755,756,757,759],{},"백엔드 API(",[35,758,279],{},") 실행",[275,761,762,763],{},"account_id ",[35,764,765],{},"d2b8c5524b7259214fa302f1fecb4ad6",[254,767,768,773,776,782],{},[275,769,770],{},[20,771,772],{},"Cloudflare D1",[275,774,775],{},"SQLite DB",[275,777,778,779,781],{},"관리 허브(",[35,780,541],{},")의 진척 데이터만",[275,783,784,785],{},"DB명 ",[35,786,787],{},"malgn-helper-project",[254,789,790,795,798,801],{},[275,791,792],{},[20,793,794],{},"Cloudflare R2",[275,796,797],{},"객체 저장소",[275,799,800],{},"업로드 원본 파일·이미지",[275,802,803,804],{},"버킷 ",[35,805,806],{},"malgn-helper-files",[254,808,809,813,816,819],{},[275,810,811],{},[20,812,557],{},[275,814,815],{},"DB 연결 가속",[275,817,818],{},"Workers ↔ Aurora MySQL 연결",[275,820,821,822],{},"id ",[35,823,824],{},"aea36082dfd84125a0da2063d5b8e5e0",[254,826,827,831,834,837],{},[275,828,829],{},[20,830,567],{},[275,832,833],{},"LLM 중계",[275,835,836],{},"OpenAI 호출 캐싱\u002F로깅\u002F제한",[275,838,839,840],{},"게이트웨이명 ",[35,841,842],{},"malgn-helper2",[254,844,845,849,852,859],{},[275,846,847],{},[20,848,586],{},[275,850,851],{},"관계형 DB",[275,853,854,855,858],{},"PMS 운영 데이터 + ",[35,856,857],{},"hp_*"," 헬퍼 테이블",[275,860,861],{},"Hyperdrive 경유",[254,863,864,869,872,875],{},[275,865,866],{},[20,867,868],{},"OpenAI API",[275,870,871],{},"LLM 제공자",[275,873,874],{},"실제 AI 답변 생성",[275,876,877,879],{},[35,878,422],{}," (BYOK)",[254,881,882,887,890,895],{},[275,883,884],{},[20,885,886],{},"AWS OpenSearch",[275,888,889],{},"검색 엔진",[275,891,892,893,596],{},"하이브리드 검색 (",[35,894,51],{},[275,896,897],{},"미구축",[17,899,900,903,904,907],{},[20,901,902],{},"Smart Placement",": API Workers는 ",[35,905,906],{},"placement.mode = \"smart\"","로 설정돼 있다. 이건 Cloudflare가 \"백엔드(OpenAI·MySQL)와 가까운 위치\"에서 Worker를 실행하도록 자동 배치하는 기능이다. 한국 엣지에서 OpenAI를 직접 부를 때 생긴 지역 차단·지연 문제를 우회하는 데도 도움이 됐다.",[17,909,910],{},[20,911,912],{},"인덱싱(자료를 검색 가능하게 만드는 작업) 전략:",[25,914,915,925],{},[28,916,917,920,921,924],{},[20,918,919],{},"MVP(현재 계획)",": 자료 업로드 시 ",[20,922,923],{},"동기 처리"," (텍스트 추출 → 잘게 나누기(청킹) → 임베딩 → 검색엔진에 저장).",[28,926,927,930,931,934],{},[20,928,929],{},"2단계",": 동영상·대용량이 늘면 ",[20,932,933],{},"Cloudflare Queues + Indexer Worker","로 비동기 파이프라인 전환.",[54,936],{},[57,938,940],{"id":939},"_5-레포지토리별-상세","5. 레포지토리별 상세",[17,942,943,944,947],{},"각 레포의 로컬 경로는 ",[35,945,946],{},"~\u002FProjects\u002F\u003C레포명>"," 입니다.",[377,949,951],{"id":950},"_51-malgn-helper-api-핵심-백엔드","5.1 malgn-helper-api — 핵심 백엔드",[14,953,954],{},[17,955,956,959],{},[20,957,958],{},"이게 시스템의 두뇌입니다."," 검색·LLM·DB·인증을 전부 여기서 처리합니다. 가장 중요하므로 가장 자세히 설명합니다.",[17,961,962],{},[20,963,964],{},"기술 스택",[25,966,967,978,984],{},[28,968,969,970,973,974,977],{},"프레임워크: ",[20,971,972],{},"Hono"," (",[35,975,976],{},"hono@^4.6.0",") — Cloudflare Workers에서 도는 초경량 웹 프레임워크",[28,979,980,981],{},"DB 드라이버: ",[35,982,983],{},"mysql2@3.22.4",[28,985,986,987,990],{},"런타임: Cloudflare Workers (",[35,988,989],{},"nodejs_compat"," 플래그)",[17,992,993],{},[20,994,995,996,596],{},"프로젝트 구조 (",[35,997,998],{},"src\u002F",[248,1000,1001,1010],{},[251,1002,1003],{},[254,1004,1005,1008],{},[257,1006,1007],{},"파일",[257,1009,262],{},[270,1011,1012,1025,1043,1053,1068],{},[254,1013,1014,1019],{},[275,1015,1016],{},[35,1017,1018],{},"src\u002Findex.ts",[275,1020,1021,1024],{},[20,1022,1023],{},"메인 파일(약 2,800줄)",". 모든 라우트·DB 연결·LLM 호출·인증이 여기 모여 있다.",[254,1026,1027,1032],{},[275,1028,1029],{},[35,1030,1031],{},"src\u002Fllm.ts",[275,1033,1034,1035,1038,1039,1042],{},"OpenAI\u002FAI Gateway 호출 추상화 (",[35,1036,1037],{},"callOpenAiJson",", ",[35,1040,1041],{},"callWorkersAi","). Vision·JSON 강제·타임아웃 처리.",[254,1044,1045,1050],{},[275,1046,1047],{},[35,1048,1049],{},"src\u002Fclassify.ts",[275,1051,1052],{},"사용자 분류 규칙(직원\u002F협력사\u002F고객).",[254,1054,1055,1060],{},[275,1056,1057],{},[35,1058,1059],{},"src\u002Fbusiness-hours.ts",[275,1061,1062,1063,1067],{},"영업시간 기준 FRT 계산(월",[1064,1065,1066],"del",{},"금 09:00","17:00 KST, 공휴일 제외).",[254,1069,1070,1075],{},[275,1071,1072],{},[35,1073,1074],{},"src\u002Fopenapi.ts",[275,1076,1077],{},"OpenAPI 3.1 스펙 + Scalar UI 문서 페이지 정의.",[17,1079,1080],{},[20,1081,1082,1083,596],{},"설정 파일 (",[35,1084,1085],{},"wrangler.jsonc",[660,1087,1091],{"className":1088,"code":1089,"language":1090,"meta":668,"style":668},"language-jsonc shiki shiki-themes github-light github-dark","{\n  \"account_id\": \"d2b8c5524b7259214fa302f1fecb4ad6\",\n  \"compatibility_date\": \"2026-05-27\",\n  \"compatibility_flags\": [\"nodejs_compat\"],\n  \"observability\": { \"enabled\": true },\n  \"placement\": { \"mode\": \"smart\" },\n  \"r2_buckets\":  [{ \"binding\": \"R2\",         \"bucket_name\": \"malgn-helper-files\" }],\n  \"hyperdrive\":  [{ \"binding\": \"HYPERDRIVE\", \"id\": \"aea36082dfd84125a0da2063d5b8e5e0\" }],\n  \"ai\":          { \"binding\": \"AI\" },         \u002F\u002F Workers AI (fallback 용)\n  \"vars\": {\n    \"AI_GATEWAY_URL\":    \"https:\u002F\u002Fgateway.ai.cloudflare.com\u002Fv1\u002Fd2b8c5524b7259214fa302f1fecb4ad6\u002Fmalgn-helper2\u002Fcompat\",\n    \"LLM_MODEL_DEFAULT\": \"openai\u002Fgpt-4.1-mini\",\n    \"LLM_MODEL_PREMIUM\": \"openai\u002Fgpt-4.1-mini\"\n  }\n}\n","jsonc",[35,1092,1093,1101,1107,1113,1119,1125,1131,1137,1143,1152,1158,1164,1170,1176,1182],{"__ignoreMap":668},[1094,1095,1098],"span",{"class":1096,"line":1097},"line",1,[1094,1099,1100],{},"{\n",[1094,1102,1104],{"class":1096,"line":1103},2,[1094,1105,1106],{},"  \"account_id\": \"d2b8c5524b7259214fa302f1fecb4ad6\",\n",[1094,1108,1110],{"class":1096,"line":1109},3,[1094,1111,1112],{},"  \"compatibility_date\": \"2026-05-27\",\n",[1094,1114,1116],{"class":1096,"line":1115},4,[1094,1117,1118],{},"  \"compatibility_flags\": [\"nodejs_compat\"],\n",[1094,1120,1122],{"class":1096,"line":1121},5,[1094,1123,1124],{},"  \"observability\": { \"enabled\": true },\n",[1094,1126,1128],{"class":1096,"line":1127},6,[1094,1129,1130],{},"  \"placement\": { \"mode\": \"smart\" },\n",[1094,1132,1134],{"class":1096,"line":1133},7,[1094,1135,1136],{},"  \"r2_buckets\":  [{ \"binding\": \"R2\",         \"bucket_name\": \"malgn-helper-files\" }],\n",[1094,1138,1140],{"class":1096,"line":1139},8,[1094,1141,1142],{},"  \"hyperdrive\":  [{ \"binding\": \"HYPERDRIVE\", \"id\": \"aea36082dfd84125a0da2063d5b8e5e0\" }],\n",[1094,1144,1146,1149],{"class":1096,"line":1145},9,[1094,1147,1148],{},"  \"ai\":          { \"binding\": \"AI\" },",[1094,1150,1151],{},"         \u002F\u002F Workers AI (fallback 용)\n",[1094,1153,1155],{"class":1096,"line":1154},10,[1094,1156,1157],{},"  \"vars\": {\n",[1094,1159,1161],{"class":1096,"line":1160},11,[1094,1162,1163],{},"    \"AI_GATEWAY_URL\":    \"https:\u002F\u002Fgateway.ai.cloudflare.com\u002Fv1\u002Fd2b8c5524b7259214fa302f1fecb4ad6\u002Fmalgn-helper2\u002Fcompat\",\n",[1094,1165,1167],{"class":1096,"line":1166},12,[1094,1168,1169],{},"    \"LLM_MODEL_DEFAULT\": \"openai\u002Fgpt-4.1-mini\",\n",[1094,1171,1173],{"class":1096,"line":1172},13,[1094,1174,1175],{},"    \"LLM_MODEL_PREMIUM\": \"openai\u002Fgpt-4.1-mini\"\n",[1094,1177,1179],{"class":1096,"line":1178},14,[1094,1180,1181],{},"  }\n",[1094,1183,1185],{"class":1096,"line":1184},15,[1094,1186,1187],{},"}\n",[17,1189,1190],{},[20,1191,1192,1193,1196],{},"시크릿(코드·설정파일에 절대 넣지 않고 ",[35,1194,1195],{},"wrangler secret put","으로 등록):",[25,1198,1199,1205],{},[28,1200,1201,1204],{},[35,1202,1203],{},"OPENAI_API_KEY"," — OpenAI 키 (AI Gateway 경유 호출용)",[28,1206,1207,1210],{},[35,1208,1209],{},"JWT_SECRET"," — 관리자 로그인 JWT 서명 키",[17,1212,1213],{},[20,1214,1215],{},"HTTP 엔드포인트 (현재 구현된 것, 약 45개)",[14,1217,1218],{},[17,1219,1220,1221,1224,1225,1228],{},"전체 대화형 문서는 배포된 서버의 ",[35,1222,1223],{},"\u002Fdoc","(Scalar UI)에서 볼 수 있고, 스펙 JSON은 ",[35,1226,1227],{},"\u002Fdoc\u002Fopenapi.json","이다.",[17,1230,1231],{},[1232,1233,1234],"em",{},"헬스체크·문서",[25,1236,1237,1247],{},[28,1238,1239,1242,1243,1246],{},[35,1240,1241],{},"GET \u002F"," · ",[35,1244,1245],{},"GET \u002Fhealthz"," — 살아있는지 확인",[28,1248,1249,1242,1252,1255],{},[35,1250,1251],{},"GET \u002Fdoc",[35,1253,1254],{},"GET \u002Fdoc\u002Fopenapi.json"," — API 문서",[17,1257,1258],{},[1232,1259,1260],{},"WBS (R2에 JSON 저장)",[25,1262,1263],{},[28,1264,1265,1242,1268,1271],{},[35,1266,1267],{},"GET \u002Fwbs",[35,1269,1270],{},"PUT \u002Fwbs"," — 간단한 WBS JSON 읽기\u002F쓰기",[17,1273,1274],{},[1232,1275,1276,1277,1280],{},"PMS 연동 (",[35,1278,1279],{},"\u002Fpms\u002F*",", Hyperdrive→MySQL 조회)",[25,1282,1283,1289,1295,1301,1314],{},[28,1284,1285,1288],{},[35,1286,1287],{},"GET \u002Fpms\u002Fprojects"," — 프로젝트 목록(검색·필터·페이지네이션)",[28,1290,1291,1294],{},[35,1292,1293],{},"GET \u002Fpms\u002Fprojects\u002F:id"," — 프로젝트 한 건 메타",[28,1296,1297,1300],{},[35,1298,1299],{},"GET \u002Fpms\u002Fgroups"," — 프로젝트 그룹 목록",[28,1302,1303,1306,1307,1310,1311,596],{},[35,1304,1305],{},"GET \u002Fpms\u002Fprojects\u002F:id\u002Fposts"," — 프로젝트의 게시글 목록(필터: ",[35,1308,1309],{},"unanswered","\u002F",[35,1312,1313],{},"customer",[28,1315,1316,1319,1320,596],{},[35,1317,1318],{},"GET \u002Fpms\u002Fposts\u002F:id"," — 게시글 1건 + 댓글 흐름 (",[20,1321,1322],{},"비공개 댓글 본문은 마스킹",[17,1324,1325],{},[1232,1326,1327,1328,596],{},"브리핑 카드 (",[35,1329,1330],{},"hp_briefing",[25,1332,1333,1339,1353,1359,1365],{},[28,1334,1335,1338],{},[35,1336,1337],{},"GET  \u002Fpms\u002Fprojects\u002F:id\u002Fbriefing"," — 즉석 DB 집계(저장 안 함)",[28,1340,1341,1344,1345,1348,1349,1352],{},[35,1342,1343],{},"POST \u002Fpms\u002Fprojects\u002F:id\u002Fbriefing\u002Fgenerate"," — LLM로 브리핑 생성 (24h 캐시, ",[35,1346,1347],{},"?force=1"," 강제 재생성, ",[35,1350,1351],{},"?nollm=1"," LLM 스킵)",[28,1354,1355,1358],{},[35,1356,1357],{},"GET  \u002Fpms\u002Fprojects\u002F:id\u002Fbriefings"," — 저장된 브리핑 히스토리",[28,1360,1361,1364],{},[35,1362,1363],{},"GET  \u002Fpms\u002Fbriefings\u002F:id"," — 브리핑 1건",[28,1366,1367,1370],{},[35,1368,1369],{},"DELETE \u002Fpms\u002Fbriefings\u002F:id"," — soft delete",[17,1372,1373],{},[1232,1374,1375,1376,596],{},"Q&A 평가 (",[35,1377,1378],{},"hp_qa_eval",[25,1380,1381,1387,1393,1404],{},[28,1382,1383,1386],{},[35,1384,1385],{},"POST \u002Fpms\u002Fposts\u002F:id\u002Feval\u002Fgenerate"," — 문의-답변 5축 평가 또는 (답변 없으면) 추천답변 생성. Vision·캐시 지원",[28,1388,1389,1392],{},[35,1390,1391],{},"POST \u002Fpms\u002Fposts\u002F:id\u002Fannounce-eval\u002Fgenerate"," — 직원 \"안내글\" 3축 평가 + 3개 변형",[28,1394,1395,1242,1398,1242,1401],{},[35,1396,1397],{},"GET  \u002Fpms\u002Fposts\u002F:id\u002Fevals",[35,1399,1400],{},"GET \u002Fpms\u002Fevals\u002F:id",[35,1402,1403],{},"DELETE \u002Fpms\u002Fevals\u002F:id",[28,1405,1406,1409],{},[35,1407,1408],{},"POST \u002Fpms\u002Fprojects\u002F:id\u002Fstandard-answer-suggestions"," — 평가 결과에서 표준답변 자동 생성",[17,1411,1412],{},[1232,1413,1414,1415,596],{},"표준 답변 (",[35,1416,1417],{},"hp_standard_answer",[25,1419,1420,1426,1435,1443],{},[28,1421,1422,1425],{},[35,1423,1424],{},"POST \u002Fstandard-answers"," — 등록(label·question·answer 필수)",[28,1427,1428,1242,1431,1434],{},[35,1429,1430],{},"GET \u002Fstandard-answers",[35,1432,1433],{},"GET \u002Fstandard-answers\u002F:id"," — 목록\u002F단건 (projectId 필터: 해당 프로젝트 + 전사 공통(NULL) 포함)",[28,1436,1437,1242,1440],{},[35,1438,1439],{},"PATCH \u002Fstandard-answers\u002F:id",[35,1441,1442],{},"DELETE \u002Fstandard-answers\u002F:id",[28,1444,1445,1448],{},[35,1446,1447],{},"POST \u002Fstandard-answers\u002F:id\u002Fuse"," — 사용 카운트 증가",[17,1450,1451],{},[1232,1452,1453,1454,596],{},"이미지 자산 (",[35,1455,1456],{},"hp_image_asset",[25,1458,1459],{},[28,1460,1461,1242,1464,1467],{},[35,1462,1463],{},"GET \u002Fimage-assets",[35,1465,1466],{},"GET \u002Fimage-assets\u002F:id"," — Vision 분석된 이미지 메타 조회",[17,1469,1470],{},[1232,1471,1472,1473,596],{},"관리자 대시보드 (",[35,1474,1475],{},"\u002Fadmin\u002F*",[25,1477,1478,1484,1490],{},[28,1479,1480,1483],{},[35,1481,1482],{},"GET \u002Fadmin\u002Fkpi"," — 홈 KPI 집계",[28,1485,1486,1489],{},[35,1487,1488],{},"GET \u002Fadmin\u002Fcost"," — LLM 비용·지연·실패 집계(모델별\u002F엔티티별\u002F일별)",[28,1491,1492,1495],{},[35,1493,1494],{},"GET \u002Fadmin\u002Fevals"," — Q&A 평가 목록(정렬·점수 필터)",[17,1497,1498],{},[1232,1499,1500,1501,596],{},"인증 (",[35,1502,1503],{},"\u002Fauth\u002F*",[25,1505,1506],{},[28,1507,1508,1242,1511,1242,1514,1517,1518,596],{},[35,1509,1510],{},"POST \u002Fauth\u002Flogin",[35,1512,1513],{},"POST \u002Fauth\u002Flogout",[35,1515,1516],{},"GET \u002Fauth\u002Fme"," — JWT 쿠키 기반 (자세한 건 ",[66,1519,1520],{"href":153},"9장",[17,1522,1523],{},[1232,1524,1525,1526,1529],{},"임시 DB 탐색 (",[35,1527,1528],{},"\u002Fdb\u002F*",", 안정화 후 제거 예정)",[25,1531,1532],{},[28,1533,1534,1242,1537,1242,1540,1242,1543,1242,1546],{},[35,1535,1536],{},"GET \u002Fdb\u002Fping",[35,1538,1539],{},"\u002Fdb\u002Fwhoami",[35,1541,1542],{},"\u002Fdb\u002Ftables",[35,1544,1545],{},"\u002Fdb\u002Fcolumns\u002F:table",[35,1547,1548],{},"\u002Fdb\u002Fsample\u002F:table",[17,1550,1551],{},[20,1552,1553],{},"DB 접근 방식",[25,1555,1556,1582],{},[28,1557,1558,1561,1562,1565,1566,1573,1574,1577,1578,1581],{},[35,1559,1560],{},"withConn(c, fn)"," 헬퍼가 Hyperdrive 바인딩에서 접속 정보를 꺼내 ",[35,1563,1564],{},"mysql2","로 커넥션을 연다. ",[20,1567,1568,1569,1572],{},"타임존은 ",[35,1570,1571],{},"+09:00","(KST)로 고정"," — PMS의 ",[35,1575,1576],{},"reg_date","가 KST 기준 문자열(",[35,1579,1580],{},"YYYYMMDDHHMMSS",", varchar(14))이기 때문.",[28,1583,1584,1586,1587,1590],{},[35,1585,1576],{},"(문자열) ↔ ISO8601 변환은 ",[35,1588,1589],{},"toIso()"," 사용.",[17,1592,1593],{},[20,1594,1595,1596,596],{},"LLM 호출 방식 (",[35,1597,1031],{},[25,1599,1600,1614,1621],{},[28,1601,1602,1605,1606,1609,1610,1613],{},[35,1603,1604],{},"callOpenAiJson(env, opts)"," — AI Gateway URL로 OpenAI를 부르고 ",[20,1607,1608],{},"JSON만"," 받도록 강제(",[35,1611,1612],{},"response_format: json_object","). 이미지가 있으면 Vision 처리. 기본 타임아웃 25초.",[28,1615,1616,1617,1620],{},"모델: 기본\u002F프리미엄 모두 현재 ",[35,1618,1619],{},"openai\u002Fgpt-4.1-mini",". Vision이 필요한 호출은 이미지를 첨부해 보낸다.",[28,1622,1623,1624,1627],{},"모든 호출 결과(토큰·지연·비용·캐시 적중·에러)는 ",[35,1625,1626],{},"hp_llm_log","에 기록된다.",[17,1629,1630],{},[20,1631,1632],{},"package.json 스크립트",[660,1634,1638],{"className":1635,"code":1636,"language":1637,"meta":668,"style":668},"language-bash shiki shiki-themes github-light github-dark","pnpm dev        # wrangler dev  (로컬, 보통 :8787)\npnpm run deploy # wrangler deploy  (Workers 배포)\npnpm typecheck  # tsc --noEmit\n","bash",[35,1639,1640,1654,1667],{"__ignoreMap":668},[1094,1641,1642,1646,1650],{"class":1096,"line":1097},[1094,1643,1645],{"class":1644},"sScJk","pnpm",[1094,1647,1649],{"class":1648},"sZZnC"," dev",[1094,1651,1653],{"class":1652},"sJ8bj","        # wrangler dev  (로컬, 보통 :8787)\n",[1094,1655,1656,1658,1661,1664],{"class":1096,"line":1103},[1094,1657,1645],{"class":1644},[1094,1659,1660],{"class":1648}," run",[1094,1662,1663],{"class":1648}," deploy",[1094,1665,1666],{"class":1652}," # wrangler deploy  (Workers 배포)\n",[1094,1668,1669,1671,1674],{"class":1096,"line":1109},[1094,1670,1645],{"class":1644},[1094,1672,1673],{"class":1648}," typecheck",[1094,1675,1676],{"class":1652},"  # tsc --noEmit\n",[54,1678],{},[377,1680,1682],{"id":1681},"_52-malgn-helper-pms-pms-애드온","5.2 malgn-helper-pms — PMS 애드온",[14,1684,1685],{},[17,1686,1687,1688,1691],{},"기존 사내 PMS 화면 ",[20,1689,1690],{},"안에 iframe\u002F팝업으로 끼워 넣어"," 상담사를 돕는 Nuxt 앱.\n상담사가 게시글을 보다가 \"AI 분석\" 버튼을 누르면 이 앱의 카드가 모달로 뜬다.",[17,1693,1694,1696],{},[20,1695,964],{},": Nuxt 3 + @nuxt\u002Fui v3 + Tailwind CSS v4. 다크모드는 비활성(라이트 고정). 한글 폰트 Pretendard.",[17,1698,1699,1702,1703],{},[20,1700,1701],{},"API 기본 URL"," (코드 상수): ",[35,1704,691],{},[17,1706,1707],{},[20,1708,1709],{},"주요 디렉터리",[660,1711,1714],{"className":1712,"code":1713,"language":665},[663],"pages\u002F\n  projects\u002F[id]\u002Findex.vue   # 브리핑 카드 워크플로 (임베드 진입점)\n  projects\u002F[id]\u002Fposts.vue   # 프로젝트 게시글 목록\n  posts\u002F[id]\u002Findex.vue      # 게시글 상세 + \"AI 문의 답변 분석\" 모달\n  posts\u002F[id]\u002Feval.vue       # Q&A 평가 카드 전용 페이지 (외부 임베드용)\n  admin\u002Fevals.vue           # 평가 목록(정렬·필터)\n  index.vue                 # 데모 + 임베드 스니펫 예시\ncomponents\u002F\n  BriefingCard.vue          # 고객사 브리핑 카드(모달)\n  QaEvalCard.vue            # Q&A 평가 카드(모달) — 로딩\u002F에러\u002F결과 상태 전환\n  qa\u002FQaAxisCard.vue         # 5축 중 한 축 카드\n  qa\u002FQaScoreSummary.vue     # 5축 종합 점수표\ncomposables\u002F\n  useBriefingHistory.ts     # 브리핑 CRUD(서버 저장)\n  useBriefingClipboard.ts   # 브리핑 평문 복사\n  useQaEvalClipboard.ts     # 평가 평문 복사\nutils\u002Fpms-html.ts           # fixPmsHtml(): 본문 이미지 상대경로 → 절대경로 보정\ntypes\u002F                      # briefing.ts, qa-eval.ts (데이터 타입 정의)\n",[35,1715,1713],{"__ignoreMap":668},[17,1717,1718],{},[20,1719,1720],{},"4개 핵심 카드 컴포넌트",[25,1722,1723,1729,1739,1749],{},[28,1724,1725,1728],{},[20,1726,1727],{},"BriefingCard",": 프로젝트 1개의 요약. 담당자(고객\u002F협력사\u002F맑소), 최근 30일 응대 통계(건수·평균 FRT·미응답·긴급), 핫 카테고리, 알림, FAQ, 이미 안내한 정책 등을 접이식 섹션으로 보여준다. 배지(★우수\u002F⚠주의\u002F✕위험).",[28,1730,1731,1734,1735,1738],{},[20,1732,1733],{},"QaEvalCard",": 문의-답변 1쌍의 평가. 메타(문의\u002F응답 시각·FRT·공개여부), 본문, ",[20,1736,1737],{},"추천 문의 답변(D축 템플릿)",", 5축 평가(QaAxisCard ×5), 종합 점수(QaScoreSummary).",[28,1740,1741,1744,1745,1748],{},[20,1742,1743],{},"QaAxisCard",": A",[1064,1746,1747],{},"E 한 축을 ★1","5 또는 ⚠(주의)로 표시 + 측정 항목 표 + 코멘트.",[28,1750,1751,1754],{},[20,1752,1753],{},"QaScoreSummary",": 5축 막대그래프 + 평균 ★.",[17,1756,1757],{},[20,1758,1759],{},"PMS 임베드 메커니즘 (이 앱의 핵심 트릭)",[25,1761,1762,1786,1804,1832],{},[28,1763,1764,1767,1768],{},[20,1765,1766],{},"진입 URL",":\n",[25,1769,1770,1780],{},[28,1771,1772,1773,1776,1777,596],{},"브리핑: ",[35,1774,1775],{},"\u002Fprojects\u002F{projectId}?modal=open"," (예: ",[35,1778,1779],{},"...\u002Fprojects\u002F1162?modal=open",[28,1781,1782,1783],{},"평가: ",[35,1784,1785],{},"\u002Fposts\u002F{postId}\u002Feval",[28,1787,1788,1791,1792,1795,1796,1799,1800,1803],{},[20,1789,1790],{},"임베드 감지",": ",[35,1793,1794],{},"?embed=1"," 또는 ",[35,1797,1798],{},"?modal=open","이면 ",[35,1801,1802],{},"isEmbedded = true"," → 내부 네비게이션\u002Fbreadcrumb을 숨겨 깔끔한 모달처럼 보이게 한다.",[28,1805,1806,1809,1810],{},[20,1807,1808],{},"닫기 신호 (postMessage)",": 모달이 닫히면 부모 창(PMS)에 메시지를 쏜다.\n",[25,1811,1812,1817,1822],{},[28,1813,1772,1814],{},[35,1815,1816],{},"{ type: \"malgn-helper:briefing:close\" }",[28,1818,1782,1819],{},[35,1820,1821],{},"{ type: \"malgn-helper:qa-eval:close\" }",[28,1823,1824,1827,1828,1831],{},[35,1825,1826],{},"window.open()"," 팝업으로 띄운 경우엔 ",[35,1829,1830],{},"window.close()","도 시도.",[28,1833,1834,1835,1838,1839,1842],{},"PMS 쪽은 ",[35,1836,1837],{},"window.addEventListener('message', ...)","로 이 신호를 받아 오버레이를 닫는다. (실제 삽입용 iframe\u002F팝업 스니펫이 ",[35,1840,1841],{},"pages\u002Findex.vue","에 복붙용으로 들어있다.)",[17,1844,1845],{},[20,1846,1847],{},"상담사 작업 흐름 (예: Q&A 평가)",[61,1849,1850,1857,1863,1866,1876],{},[28,1851,1852,1853,1856],{},"상담사가 PMS 게시글에서 \"AI 문의 답변 분석\" 클릭 → ",[35,1854,1855],{},"posts\u002F[id]"," 모달 오픈",[28,1858,1859,1860,1862],{},"즉시 ",[35,1861,1385],{}," 호출 → 로딩 표시",[28,1864,1865],{},"결과가 오면 QaEvalCard가 5축 평가 + 추천 답변을 렌더",[28,1867,1868,1869,1872,1873,1875],{},"마음에 드는 추천 답변에서 ",[20,1870,1871],{},"\"표준답변으로 저장\""," 클릭 → ",[35,1874,1424],{}," (label·question·answer·projectId·sourcePostId·sourceAxis='D')",[28,1877,1878],{},"답변 없는 문의(inquiry-only)면 5축 평가는 생략하고 추천 답변 6종만 제시",[17,1880,1881,1791,1884,1887,1888,1038,1890,1893,1894,1897,1898,1901],{},[20,1882,1883],{},"배포",[35,1885,1886],{},"wrangler.toml","(name ",[35,1889,297],{},[35,1891,1892],{},"pages_build_output_dir = \"dist\"","), ",[35,1895,1896],{},"\"deploy\": \"nuxt build && wrangler pages deploy\"",". URL ",[35,1899,1900],{},"https:\u002F\u002Fmalgn-helper-pms.pages.dev",".",[54,1903],{},[377,1905,1907],{"id":1906},"_53-malgn-helper-admin-관리자단","5.3 malgn-helper-admin — 관리자단",[14,1909,1910],{},[17,1911,1912,1913,1916,1917,1920],{},"자료 업로드·표준답변 관리·로그\u002F비용 검토를 위한 관리자 화면. ",[20,1914,1915],{},"기획(IA)은 17개 메뉴로 완성","돼 있고, ",[20,1918,1919],{},"우선순위 4개 화면 + 로그인만 실제 동작",", 나머지는 stub(빈 골격)이다.",[17,1922,1923,1925,1926,1929],{},[20,1924,964],{},": Nuxt 3.13 + @nuxt\u002Fui v3 + Tailwind v4 + ",[35,1927,1928],{},"lucide-vue-next"," 아이콘. 폰트 Pretendard(한글)+DM Sans(숫자).",[17,1931,1932],{},[20,1933,1934,1935,1938,1939,596],{},"메뉴 구조 (5그룹 × 17메뉴, ",[35,1936,1937],{},"composables\u002Fuse-admin-menu.ts","의 ",[35,1940,1941],{},"ADMIN_MENU",[61,1943,1944,1968,1989,2006,2027],{},[28,1945,1946,1949,1950,1952,1953,1956,1957,1960,1961,1964,1965],{},[20,1947,1948],{},"운영 보드",": 홈 ",[35,1951,1310],{}," · 미커버 질문 ",[35,1954,1955],{},"\u002Funcovered"," · 에스컬레이션 ",[35,1958,1959],{},"\u002Fescalations"," · 챗봇 로그 ",[35,1962,1963],{},"\u002Fchat-logs"," · Q&A 평가 ",[35,1966,1967],{},"\u002Fqa-evals",[28,1969,1970,1973,1974,1977,1978,1981,1982,1985,1986],{},[20,1971,1972],{},"지식 자산",": 표준답변 ",[35,1975,1976],{},"\u002Fstandard-answers"," · 자료 ",[35,1979,1980],{},"\u002Fmaterials"," · 이미지 카탈로그 ",[35,1983,1984],{},"\u002Fimages"," · 토픽·서비스 ",[35,1987,1988],{},"\u002Fcatalog",[28,1990,1991,1994,1995,1998,1999,2002,2003],{},[20,1992,1993],{},"분석·비용",": LLM 비용 ",[35,1996,1997],{},"\u002Fcost"," · 응답 품질 ",[35,2000,2001],{},"\u002Fanalytics\u002Fquality"," · 사용량 ",[35,2004,2005],{},"\u002Fanalytics\u002Fusage",[28,2007,2008,2011,2012,2015,2016,2019,2020,2023,2024],{},[20,2009,2010],{},"설정",": AI ",[35,2013,2014],{},"\u002Fsettings\u002Fai"," · 안전가드 ",[35,2017,2018],{},"\u002Fsettings\u002Fsafety"," · 캐싱 ",[35,2021,2022],{},"\u002Fsettings\u002Fcache"," · 외부연동 ",[35,2025,2026],{},"\u002Fsettings\u002Fintegrations",[28,2028,2029,2032,2033,2036,2037,2040],{},[20,2030,2031],{},"시스템",": 계정 ",[35,2034,2035],{},"\u002Faccounts"," · 감사 로그 ",[35,2038,2039],{},"\u002Faudit-logs"," · API 문서(외부 링크)",[17,2042,2043],{},[20,2044,2045],{},"구현 현황",[25,2047,2048,2085],{},[28,2049,2050,2051,1791,2054,1038,2057,2060,2061,1893,2063,2066,2067,1893,2070,2066,2073,2075,2076,2079,2080,2066,2083,596],{},"🟢 ",[20,2052,2053],{},"실제 동작 (5)",[35,2055,2056],{},"login.vue",[35,2058,2059],{},"index.vue","(홈 KPI → ",[35,2062,1482],{},[35,2064,2065],{},"cost.vue","(",[35,2068,2069],{},"GET \u002Fadmin\u002Fcost?days=",[35,2071,2072],{},"qa-evals.vue",[35,2074,1494],{}," + 행 클릭 시 PMS ",[35,2077,2078],{},"\u002Fposts\u002F:id\u002Feval","를 iframe 모달로 임베드), ",[35,2081,2082],{},"images.vue",[35,2084,1463],{},[28,2086,2087,2088,1791,2091,2094],{},"🔴 ",[20,2089,2090],{},"stub (나머지 ~12)",[35,2092,2093],{},"AdminPagePlaceholder"," 컴포넌트로 \"준비 중\" 표시만.",[17,2096,2097,973,2100,1038,2103,596],{},[20,2098,2099],{},"인증 흐름",[35,2101,2102],{},"composables\u002Fuse-auth.ts",[35,2104,2105],{},"middleware\u002Fauth.global.ts",[61,2107,2108,2119,2126,2135],{},[28,2109,2110,2113,2114,973,2116,596],{},[35,2111,2112],{},"\u002Flogin","에서 loginId+password 입력 → ",[35,2115,1510],{},[35,2117,2118],{},"credentials: \"include\"",[28,2120,2121,2122,2125],{},"서버가 ",[35,2123,2124],{},"helper_session"," 쿠키(8시간) 발급",[28,2127,2128,2129,2131,2132],{},"모든 라우트 진입 시 전역 미들웨어가 ",[35,2130,1516],{},"로 검증 → 실패하면 ",[35,2133,2134],{},"\u002Flogin?redirect=...",[28,2136,2137,685,2140,1791,2143,2146,2147,2150],{},[20,2138,2139],{},"역할 매핑",[35,2141,2142],{},"roleOf(level)",[35,2144,2145],{},"level >= 9"," → admin, ",[35,2148,2149],{},">= 5"," → developer, 그 외 → agent. 메뉴는 역할로 필터링.",[25,2152,2153],{},[28,2154,2155,2156,2158],{},"Cloudflare Access는 아직 미사용(",[35,2157,51],{},").",[17,2160,2161],{},[20,2162,2163],{},"권한 매트릭스 (역할별 가능 작업, 설계)",[17,2165,2166,2167,2170,2171,2174,2175,2178],{},"역할은 로그인 시 ",[35,2168,2169],{},"tb_user.level","로 정해진다(",[35,2172,2173],{},"level>=9"," admin \u002F ",[35,2176,2177],{},">=5"," developer \u002F 그 외 agent). 메뉴 노출과 동작 권한이 역할로 갈린다.",[248,2180,2181,2198],{},[251,2182,2183],{},[254,2184,2185,2188,2192,2195],{},[257,2186,2187],{},"작업",[257,2189,2191],{"align":2190},"center","관리자(admin)",[257,2193,2194],{"align":2190},"개발자(developer)",[257,2196,2197],{"align":2190},"상담사(agent)",[270,2199,2200,2213,2225,2236,2248,2259,2270],{},[254,2201,2202,2205,2208,2210],{},[275,2203,2204],{},"자료 업로드",[275,2206,2207],{"align":2190},"✅",[275,2209,2207],{"align":2190},[275,2211,2212],{"align":2190},"❌",[254,2214,2215,2218,2220,2222],{},[275,2216,2217],{},"표준답변 등록",[275,2219,2207],{"align":2190},[275,2221,2207],{"align":2190},[275,2223,2224],{"align":2190},"✅(제안만)",[254,2226,2227,2230,2232,2234],{},[275,2228,2229],{},"표준답변 승인",[275,2231,2207],{"align":2190},[275,2233,2207],{"align":2190},[275,2235,2212],{"align":2190},[254,2237,2238,2241,2243,2245],{},[275,2239,2240],{},"이미지 편집",[275,2242,2207],{"align":2190},[275,2244,2207],{"align":2190},[275,2246,2247],{"align":2190},"❌(조회만)",[254,2249,2250,2253,2255,2257],{},[275,2251,2252],{},"챗봇 로그 조회",[275,2254,2207],{"align":2190},[275,2256,2207],{"align":2190},[275,2258,2207],{"align":2190},[254,2260,2261,2264,2266,2268],{},[275,2262,2263],{},"설정 변경",[275,2265,2207],{"align":2190},[275,2267,2207],{"align":2190},[275,2269,2212],{"align":2190},[254,2271,2272,2275,2277,2279],{},[275,2273,2274],{},"계정 관리",[275,2276,2207],{"align":2190},[275,2278,2212],{"align":2190},[275,2280,2212],{"align":2190},[14,2282,2283],{},[17,2284,2285,2286,2289,2290,2293,2294,1901],{},"백엔드에서도 같은 원칙을 미들웨어로 강제하는 설계다(예: ",[35,2287,2288],{},"requireRole(...)","로 ",[35,2291,2292],{},"@malgnsoft.com"," + 역할 검사 후 403). ",[35,2295,51],{},[17,2297,2298],{},[20,2299,2300],{},"구현 우선순위 (주차별 계획, 설계)",[25,2302,2303,2312,2326,2334,2352],{},[28,2304,2305,2308,2309],{},[20,2306,2307],{},"Week 1 — 골격+인증",": 레이아웃·로그인·JWT 검증·신규 테이블 DDL ← ",[1232,2310,2311],{},"대체로 완료",[28,2313,2314,1791,2317,2319,2320,2322,2323],{},[20,2315,2316],{},"Week 2 — 표준답변+이미지",[35,2318,1976],{},"(목록·상세·승인), ",[35,2321,1984],{},"(그리드·검색·태그·숨김) ← ",[1232,2324,2325],{},"images만 완료",[28,2327,2328,1791,2331,2333],{},[20,2329,2330],{},"Week 3 — 자료 업로드+인덱싱",[35,2332,1980],{},"(파일\u002FURL 업로드·재인덱싱·삭제) + R2 + OpenSearch 색인(동기 MVP)",[28,2335,2336,1791,2339,2342,2343,2345,2346,2348,2349],{},[20,2337,2338],{},"Week 4 — 설정+모니터링",[35,2340,2341],{},"\u002Fsettings\u002F*"," + ",[35,2344,1967],{},"·",[35,2347,1997],{}," ← ",[1232,2350,2351],{},"qa-evals·cost 완료, settings stub",[28,2353,2354,1791,2357,2359,2360],{},[20,2355,2356],{},"Week 5 — 계정·감사 + Phase 2 준비",[35,2358,2035],{}," CRUD·초대, ",[35,2361,2039],{},[17,2363,2364,1791,2366,1887,2368,2370,2371,1901],{},[20,2365,1883],{},[35,2367,1886],{},[35,2369,315],{},"), URL ",[35,2372,2373],{},"https:\u002F\u002Fmalgn-helper-admin.pages.dev",[14,2375,2376],{},[17,2377,2378,2379,2382,2383,2066,2386,2389],{},"⚠️ ",[20,2380,2381],{},"주의",": admin은 API 기본 URL을 ",[20,2384,2385],{},"각 페이지\u002F컴포저블에 하드코딩",[35,2387,2388],{},"const API_BASE = \"https:\u002F\u002F...\"",")한다. 환경변수가 아니라서, URL이 바뀌면 여러 파일을 같이 고쳐야 한다.",[54,2391],{},[377,2393,2395],{"id":2394},"_54-malgn-helper-사용자-챗봇","5.4 malgn-helper — 사용자 챗봇",[14,2397,2398],{},[17,2399,2400,2401,2404],{},"고객이 직접 대화하는 챗봇 프론트엔드. ",[20,2402,2403],{},"현재는 스켈레톤(준비 중)"," 상태. Phase 2에서 본격 개발된다.",[25,2406,2407,2427,2439],{},[28,2408,2409,2410,2413,2414,2345,2417,2345,2420,2423,2424],{},"현재 ",[35,2411,2412],{},"app.vue","에 \"사용자 챗봇 프론트엔드 (준비 중)\" 텍스트만 있고, ",[35,2415,2416],{},"pages\u002F",[35,2418,2419],{},"components\u002F",[35,2421,2422],{},"composables\u002F","·UI 라이브러리·인증·API 연동이 ",[20,2425,2426],{},"모두 아직 없다.",[28,2428,2429,2430,2432,2433,1038,2435,2438],{},"배포 골격(",[35,2431,1886],{}," name ",[35,2434,332],{},[35,2436,2437],{},"nuxt.config.ts",")은 준비됨 → 화면만 채우면 바로 배포 가능.",[28,2440,2441],{},"향후: 채팅 UI, 세션 관리, \"모르면 모른다\" 기반 에스컬레이션, 비공개 자료 노출 금지 등이 추가될 예정.",[54,2443],{},[377,2445,2447],{"id":2446},"_55-malgn-helper-mng-프로젝트-관리-허브","5.5 malgn-helper-mng — 프로젝트 관리 허브",[14,2449,2450],{},[17,2451,2452,2455],{},[20,2453,2454],{},"제품이 아니라",", 이 프로젝트 자체의 진척·일정·문서를 한곳에서 보는 관리 허브. (지금 이 가이드 문서가 들어있는 레포)",[25,2457,2458,2478,2493,2508,2515,2523],{},[28,2459,2460,2461,2463,2464,2467,2468,2471,2472,2474,2475],{},"화면 5개: 대시보드 ",[35,2462,1310],{},", 현황판 ",[35,2465,2466],{},"\u002Fboard",", WBS 간트 ",[35,2469,2470],{},"\u002Fwbs",", 문서 ",[35,2473,41],{},", 작업 이력 ",[35,2476,2477],{},"\u002Fhistory",[28,2479,2480,2481,2066,2483,2485,2486,2492],{},"데이터 정본 2종: 진척·작업·간트는 ",[20,2482,772],{},[35,2484,787],{},"), 문서·이력은 ",[20,2487,2488,2491],{},[35,2489,2490],{},"docs\u002F"," 마크다운","(@nuxt\u002Fcontent, 빌드 타임 프리렌더)",[28,2494,2495,2496,2345,2498,2345,2500,2502,2503,2345,2505,2507],{},"렌더링: ",[35,2497,1310],{},[35,2499,2466],{},[35,2501,2470],{},"는 SSR(D1 런타임 조회), ",[35,2504,41],{},[35,2506,2477],{},"는 프리렌더",[28,2509,2510,2511,2514],{},"D1이 없으면(로컬 개발) 시드(",[35,2512,2513],{},"server\u002Futils\u002FboardSeed.ts",")로 자동 폴백",[28,2516,2517,2518,2522],{},"더 자세한 설계는 같은 레포의 ",[66,2519,2521],{"href":2520},"PROJECT_MANAGEMENT_BLUEPRINT","PROJECT_MANAGEMENT_BLUEPRINT.md"," 참고",[28,2524,2525,2526],{},"배포 URL ",[35,2527,2528],{},"https:\u002F\u002Fmalgn-helper-mng.pages.dev",[54,2530],{},[57,2532,2534],{"id":2533},"_6-데이터-모델-db-테이블","6. 데이터 모델 (DB 테이블)",[17,2536,2537,2538,2541],{},"DB는 ",[20,2539,2540],{},"두 종류","입니다.",[61,2543,2544,2559],{},[28,2545,2546,973,2548,2551,2552,2555,2556,2558],{},[20,2547,586],{},[35,2549,2550],{},"pms"," 데이터베이스): 기존 PMS 운영 테이블(",[35,2553,2554],{},"tb_*",") + 헬퍼 전용 테이블(",[35,2557,857],{},"). 백엔드가 Hyperdrive로 접근.",[28,2560,2561,2563,2564,2566],{},[20,2562,772],{},": 관리 허브(",[35,2565,541],{},")의 진척 데이터만. (위와 무관, 별도)",[377,2568,2570,2571,2573],{"id":2569},"_61-헬퍼-전용-테이블-hp_-mysql","6.1 헬퍼 전용 테이블 (",[35,2572,857],{},", MySQL)",[17,2575,2576,2577,2580,2581,2584],{},"모두 ",[35,2578,2579],{},"hp_"," 접두사 + soft delete(",[35,2582,2583],{},"status TINYINT",": 1=활성, -1=삭제) 규칙을 따른다.",[248,2586,2587,2600],{},[251,2588,2589],{},[254,2590,2591,2594,2597],{},[257,2592,2593],{},"테이블",[257,2595,2596],{},"용도",[257,2598,2599],{},"핵심 컬럼",[270,2601,2602,2646,2676,2714,2757],{},[254,2603,2604,2608,2611],{},[275,2605,2606],{},[35,2607,1330],{},[275,2609,2610],{},"프로젝트 브리핑 카드 캐시",[275,2612,2613,1038,2616,1038,2619,2622,2623,1038,2626,2629,2630,1038,2633,1038,2636,1038,2639,2642,2643],{},[35,2614,2615],{},"project_id",[35,2617,2618],{},"generated_at",[35,2620,2621],{},"generator","(db_only\u002Fllm\u002Fhybrid), ",[35,2624,2625],{},"llm_model",[35,2627,2628],{},"llm_input_hash","(CHAR 64), ",[35,2631,2632],{},"prompt_tokens",[35,2634,2635],{},"completion_tokens",[35,2637,2638],{},"latency_ms",[35,2640,2641],{},"briefing_json","(LONGTEXT), ",[35,2644,2645],{},"status",[254,2647,2648,2652,2655],{},[275,2649,2650],{},[35,2651,1378],{},[275,2653,2654],{},"게시글 Q&A\u002F안내글 평가",[275,2656,2657,1038,2660,1038,2662,2642,2665,2668,2669,2672,2673,2675],{},[35,2658,2659],{},"post_id",[35,2661,2615],{},[35,2663,2664],{},"eval_json",[35,2666,2667],{},"overall_score","(DECIMAL 3,2), ",[35,2670,2671],{},"overall_verdict","(VARCHAR 100), ",[35,2674,2628],{},", 토큰·지연",[254,2677,2678,2682,2685],{},[275,2679,2680],{},[35,2681,1417],{},[275,2683,2684],{},"표준 답변 카탈로그",[275,2686,2687,1038,2690,1038,2693,1038,2696,2698,2699,1038,2702,2705,2706,1038,2709,1038,2712],{},[35,2688,2689],{},"label",[35,2691,2692],{},"question",[35,2694,2695],{},"answer",[35,2697,2615],{},"(NULL=전사공통), ",[35,2700,2701],{},"source_post_id",[35,2703,2704],{},"source_axis","(A~E), ",[35,2707,2708],{},"usage_count",[35,2710,2711],{},"last_used_at",[35,2713,2645],{},[254,2715,2716,2720,2723],{},[275,2717,2718],{},[35,2719,1626],{},[275,2721,2722],{},"모든 LLM 호출 감사",[275,2724,2725,1038,2728,2731,2732,1038,2735,1038,2738,1038,2740,1038,2742,1038,2744,2747,2748,1038,2751,1038,2754],{},[35,2726,2727],{},"route",[35,2729,2730],{},"entity_type","(briefing\u002Fqa_eval\u002F…), ",[35,2733,2734],{},"entity_id",[35,2736,2737],{},"model",[35,2739,2632],{},[35,2741,2635],{},[35,2743,2638],{},[35,2745,2746],{},"cost_usd","(DECIMAL 10,6), ",[35,2749,2750],{},"cache_hit",[35,2752,2753],{},"error",[35,2755,2756],{},"request_at",[254,2758,2759,2763,2766],{},[275,2760,2761],{},[35,2762,1456],{},[275,2764,2765],{},"Vision 분석 이미지 메타",[275,2767,2768,2771,2772,1893,2775,1038,2778,1038,2781,1038,2784,1038,2787,2790,2791,1038,2793],{},[35,2769,2770],{},"src_path","(VARCHAR 500, ",[20,2773,2774],{},"UNIQUE",[35,2776,2777],{},"title",[35,2779,2780],{},"description",[35,2782,2783],{},"first_seen_post_id",[35,2785,2786],{},"first_seen_project_id",[35,2788,2789],{},"source","(inquiry\u002Freply), ",[35,2792,2625],{},[35,2794,2708],{},[17,2796,2797],{},[20,2798,2799],{},"인덱스\u002F스키마 핵심 결정(겪은 함정 포함)",[25,2801,2802,2815,2825,2837],{},[28,2803,2804,1310,2806,1791,2808,1038,2811,2814],{},[35,2805,1330],{},[35,2807,1378],{},[35,2809,2810],{},"(project_id, status, generated_at DESC)",[35,2812,2813],{},"(llm_input_hash)"," 인덱스 → 최신 N건 조회 + 캐시 lookup용",[28,2816,2817,2820,2821,2824],{},[35,2818,2819],{},"hp_qa_eval.overall_verdict","는 처음 VARCHAR(20)이었다가 ",[20,2822,2823],{},"VARCHAR(100)으로 확장","(평가 문구가 길어서)",[28,2826,2827,2829,2830,2342,2833,2836],{},[35,2828,1417],{},": InnoDB ",[35,2831,2832],{},"FULLTEXT(question, answer)",[35,2834,2835],{},"(status, usage_count DESC)","(인기순)",[28,2838,2839,2842,2843,2846],{},[35,2840,2841],{},"hp_image_asset.src_path","는 utf8mb4에서 ",[20,2844,2845],{},"UNIQUE prefix를 191자","로 잡아야 한다(인덱스 바이트 한계). 한 이미지는 한 번만 분석하고 재사용하는 캐시 키.",[17,2848,2849,2854,2855,1310,2858,2861,2862,2865,2866,2869,2870,2873,2874,1310,2877,2880,2881,2884],{},[20,2850,2851,2853],{},[35,2852,51],{},"인 신규 테이블"," (관리자단 자료 업로드·표준답변 고도화용): ",[35,2856,2857],{},"hp_material",[35,2859,2860],{},"hp_material_chunk","(자료·청크), ",[35,2863,2864],{},"hp_topic","(토픽), ",[35,2867,2868],{},"hp_service","(솔루션 카탈로그), ",[35,2871,2872],{},"hp_standard_answer_image","(표준답변↔이미지), ",[35,2875,2876],{},"hp_account",[35,2878,2879],{},"hp_audit_log","(계정·감사), ",[35,2882,2883],{},"hp_setting","(설정).",[377,2886,2888,2889,2891],{"id":2887},"_62-레거시-pms-운영-테이블-tb_-읽기-전용으로-활용","6.2 레거시 PMS 운영 테이블 (",[35,2890,2554],{},", 읽기 전용으로 활용)",[17,2893,2894,2895,2898],{},"백엔드는 PMS 운영 테이블을 ",[20,2896,2897],{},"읽기만"," 한다. 주로:",[25,2900,2901,2923,2934,2947],{},[28,2902,2903,2906,2907,2910,2911,2914,2915,2918,2919,2922],{},[35,2904,2905],{},"tb_project","(프로젝트), ",[35,2908,2909],{},"tb_project_group","(그룹), ",[35,2912,2913],{},"tb_post","(게시글\u002F문의), ",[35,2916,2917],{},"tb_post_comment","(댓글\u002F응답), ",[35,2920,2921],{},"tb_user","(사용자)",[28,2924,2925,2927,2928,685,2931,2933],{},[35,2926,1576],{},"는 ",[35,2929,2930],{},"varchar(14)",[35,2932,1580],{}," 형식 → 문자열 비교로 기간 필터.",[28,2935,2936,2939,2940,2942,2943,2946],{},[20,2937,2938],{},"레거시 인벤토리",": 약 1,358건의 Q&A 후보, 200+ 프로젝트, 27개 ",[35,2941,2554],{}," 테이블. 비공개 댓글(",[35,2944,2945],{},"private_yn='Y'",")이 약 9.3%, 첨부파일 다수(HWP 5천여 건 포함).",[28,2948,2949,2952,2953,1038,2956,2959],{},[20,2950,2951],{},"부하 대책으로 추가한 인덱스",": 예) ",[35,2954,2955],{},"tb_post(project_id, status, reg_date)",[35,2957,2958],{},"tb_post_comment(post_id, status, reg_date)"," 등 → 캐시 미스 시 쿼리 시간 대폭 단축.",[377,2961,2963,2964,596],{"id":2962},"_63-관리-허브-d1-mng","6.3 관리 허브 D1 (",[35,2965,541],{},[17,2967,2968,2970,2971,1038,2974,1038,2977,2980,2981,2984],{},[35,2969,541],{}," 레포는 위와 완전히 별개로 Cloudflare D1을 쓴다: ",[35,2972,2973],{},"board_meta",[35,2975,2976],{},"stage",[35,2978,2979],{},"task","(현황판), ",[35,2982,2983],{},"wbs_item","(간트). 자세한 건 mng 레포의 블루프린트 참고.",[54,2986],{},[57,2988,2990],{"id":2989},"_7-llm프롬프트-설계","7. LLM·프롬프트 설계",[377,2992,2994],{"id":2993},"_71-모델과-호출-경로","7.1 모델과 호출 경로",[25,2996,2997,3006,3022],{},[28,2998,2999,3002,3003,3005],{},[20,3000,3001],{},"모델",": 현재 기본·프리미엄 모두 ",[35,3004,1619],{},". 이미지 분석(Vision)이 필요한 호출은 이미지를 첨부해서 보낸다.",[28,3007,3008,3011,3012,3017,3018,3021],{},[20,3009,3010],{},"경로",": 코드 → ",[20,3013,3014,3015,596],{},"Cloudflare AI Gateway(",[35,3016,842],{}," → OpenAI. 게이트웨이를 거치는 이유는 ",[20,3019,3020],{},"캐싱·로깅·rate limit","을 한 곳에서 받기 위해서. (BYOK: 비용은 우리 OpenAI 키로 청구)",[28,3023,3024,3027],{},[20,3025,3026],{},"모든 LLM 호출은 반드시 게이트웨이를 통과","시킨다 — 직접 OpenAI를 부르는 코드는 금지.",[377,3029,3031],{"id":3030},"_72-캐싱-비용속도-핵심","7.2 캐싱 (비용·속도 핵심)",[25,3033,3034,3043,3050,3058],{},[28,3035,3036,3037,3042],{},"입력을 ",[20,3038,3039,3040,596],{},"SHA-256 해시(",[35,3041,2628],{}," 로 만들어 저장한다.",[28,3044,3045,3046,3049],{},"같은 입력이 ",[20,3047,3048],{},"24시간 이내","에 또 오면 저장된 결과를 재사용(새 LLM 호출 안 함).",[28,3051,3052,3054,3055,3057],{},[35,3053,1347],{},"로 캐시 무시, ",[35,3056,1351],{},"로 LLM 자체를 건너뛸 수 있다.",[28,3059,3060],{},"시스템 프롬프트·표준답변 카탈로그처럼 고정된 컨텍스트는 프롬프트 캐싱에 잘 태운다.",[377,3062,3064],{"id":3063},"_73-qa-5축-평가-ae","7.3 Q&A 5축 평가 (A~E)",[17,3066,3067],{},"하나의 문의-답변을 5개 축으로 ★1~5 채점한다.",[25,3069,3070,3076,3082,3088,3098],{},[28,3071,3072,3075],{},[20,3073,3074],{},"A. 답변 정확성·완결성"," — 직접 답했는가, 근거·대안·구체 안내·출처 인용이 있는가",[28,3077,3078,3081],{},[20,3079,3080],{},"B. 응대 시간·턴 효율"," — FRT(첫 직원 응답 시간), FCR(1회 해결), 재문의 여부, 긴급 SLA",[28,3083,3084,3087],{},[20,3085,3086],{},"C. 톤·친절도"," — 인삿말\u002F사과·공감\u002F능동 안내\u002F격식 일관성",[28,3089,3090,3093,3094,3097],{},[20,3091,3092],{},"D. 표준답변화 가능성"," — 반복 가능·정책 의존·시간 무관·",[20,3095,3096],{},"표준답변 템플릿 초안 생성","(이 축의 템플릿이 \"표준답변으로 저장\" 대상)",[28,3099,3100,3103],{},[20,3101,3102],{},"E. 챗봇 자동화 적합성 + 가시성"," — 자동응답 가능성, 공개\u002F비공개, 거절성 답변, PII·보안 위험",[377,3105,3107],{"id":3106},"_74-브리핑-카드","7.4 브리핑 카드",[25,3109,3110,3121],{},[28,3111,3112,3113,3116,3117,3120],{},"한 프로젝트의 최근 30일(일부 지표는 180일\u002F전체)을 요약. LLM은 ",[20,3114,3115],{},"핫토픽 군집화","와 ",[20,3118,3119],{},"extras(긴급 건수·FAQ·정책·페르소나)"," 를 만든다.",[28,3122,3123,3126],{},[20,3124,3125],{},"상태 라벨(휴면\u002F원활\u002F주의\u002F경고\u002F긴급)은 LLM이 멋대로 정하지 않고",", 미응답·부하 임계값 같은 규칙으로 결정한다(일관성 확보).",[377,3128,3130],{"id":3129},"_75-안전-원칙코드프롬프트에-반영","7.5 안전 원칙(코드\u002F프롬프트에 반영)",[25,3132,3133,3139,3150,3159],{},[28,3134,3135,3138],{},[20,3136,3137],{},"표준답변 우선",": 답을 새로 생성하기 전에 표준답변을 먼저 찾고, 있으면 그것을 컨텍스트로 우선 사용.",[28,3140,3141,3143,3144],{},[20,3142,232],{},": 근거 자료가 없으면 추측하지 말고 모른다고 하거나 에스컬레이션(신뢰도 낮으면 사람 연결). ",[1232,3145,3146,3147,3149],{},"(에스컬레이션 자동 분기 로직 자체는 ",[35,3148,51],{},"\u002F부분 구현)",[28,3151,3152,3155,3156,3158],{},[20,3153,3154],{},"출처 인용",": 답변에 근거 출처를 달도록 유도. 표준답변 사용 시 ",[35,3157,2708],{}," 증가·출처 추적.",[28,3160,3161,3164,3165],{},[20,3162,3163],{},"비공개 댓글 보호",": Phase 1 상담사 보조에는 활용하되, ",[20,3166,3167],{},"Phase 2 챗봇에는 비공개 본문·출처를 절대 노출하지 않는다.",[377,3169,3171],{"id":3170},"_76-vision이미지-캡션","7.6 Vision(이미지 캡션)",[25,3173,3174,3189],{},[28,3175,3176,3177,3180,3181,3184,3185,3188],{},"게시글\u002F댓글 본문의 ",[35,3178,3179],{},"\u003Cimg src=\"\u002Fdata\u002F...\">"," 같은 상대경로를 ",[20,3182,3183],{},"절대 URL로 변환","(예: ",[35,3186,3187],{},"https:\u002F\u002Fppm.malgn.co.kr\u002Fdata\u002F...",")해서 LLM에 넘긴다.",[28,3190,3191,3192,3194,3195,3197,3198,3201],{},"분석 결과(title·description)는 ",[35,3193,1456],{},"에 저장하고, ",[35,3196,2770],{}," UNIQUE로 ",[20,3199,3200],{},"한 번만 분석 후 재사용","(비용 절감).",[54,3203],{},[57,3205,3207],{"id":3206},"_8-검색-파이프라인","8. 검색 파이프라인",[14,3209,3210],{},[17,3211,3212,3215,3216,3218],{},[20,3213,3214],{},"이 장은 \"설계(목표)\"와 \"현재 구현\"의 차이가 가장 크다."," RAG의 심장부라서 가장 자세히 설명하지만,\n대부분은 아직 ",[35,3217,51],{},"이라는 점을 먼저 기억하자. 지금 코드에 있는 건 8.1뿐이다.",[377,3220,3222],{"id":3221},"_81-현재-구현됨-like-키워드-검색","8.1 현재 구현됨 — LIKE 키워드 검색",[17,3224,3225,3226,3229],{},"지금 백엔드에 실제로 있는 검색은 MySQL ",[35,3227,3228],{},"LIKE","(부분일치) 한 가지다. 벡터도, OpenSearch도 아직 없다.",[25,3231,3232,3245,3254],{},[28,3233,3234,3235,1310,3237,1310,3239,3241,3242],{},"표준답변 검색: ",[35,3236,2692],{},[35,3238,2695],{},[35,3240,2689],{}," 컬럼 ",[35,3243,3244],{},"LIKE '%검색어%'",[28,3246,3247,3248,1310,3250,685,3252],{},"이미지 검색: ",[35,3249,2777],{},[35,3251,2780],{},[35,3253,3228],{},[28,3255,3256,3257,3260,3261],{},"게시글 검색: ",[35,3258,3259],{},"subject","\u002F작성자 ",[35,3262,3228],{},[17,3264,3265,3266,3269],{},"한계: 한국어는 단어가 정확히 안 겹치면 못 찾는다(\"환불\"↔\"반환\"). 그래서 ",[20,3267,3268],{},"의미 기반 검색(RAG)이 필요","하고, 아래 8.2~가 그 설계다.",[377,3271,3273,3274],{"id":3272},"_82-rag-전체-흐름-설계-계획","8.2 RAG 전체 흐름 (설계) ",[35,3275,51],{},[17,3277,3278,3279,3282,3283,3286,3287,1901],{},"RAG는 ",[20,3280,3281],{},"두 갈래의 작업","으로 이뤄진다. ① 미리 자료를 검색 가능하게 만들어 두는 ",[20,3284,3285],{},"인덱싱(쓰기 시점)",", ② 질문이 올 때 찾아서 답하는 ",[20,3288,3289],{},"질의(읽기 시점)",[660,3291,3294],{"className":3292,"code":3293,"language":665},[663],"[인덱싱 — 자료가 추가될 때 1회]\n 자료(파일\u002FURL\u002F기존 Q&A)\n   → 텍스트 추출(extract)\n   → 청킹(chunking, 긴 글을 의미 단위로 분할)\n   → 임베딩 생성(각 청크 → 1536차원 벡터)\n   → OpenSearch 색인 저장 (원문 + 벡터)\n\n[질의 — 사용자\u002F상담사가 물어볼 때마다]\n 질문\n   → (A) 표준답변 우선 매칭   ── 있으면 그걸 최우선 컨텍스트로\n   → (B) 질문을 임베딩 → OpenSearch 하이브리드 검색(k-NN + BM25)\n   → 상위 N개 청크를 근거로 모음(+표준답변)\n   → LLM 프롬프트에 \"이 근거들로만, 출처를 달아 답하라\" 로 주입\n   → 답변 생성 + 출처 인용\n   → 근거가 부족\u002F신뢰도 낮으면 \"모른다\" 또는 에스컬레이션\n",[35,3295,3293],{"__ignoreMap":668},[17,3297,3298,3299,3302],{},"핵심은 ",[20,3300,3301],{},"LLM이 자기 지식으로 답하지 않고, 우리가 찾아준 근거 안에서만 답하게"," 만드는 것이다. 이래야 환각을 막고 출처를 달 수 있다.",[377,3304,3306,3307],{"id":3305},"_83-청킹chunking-전략-계획","8.3 청킹(chunking) 전략 ",[35,3308,51],{},[17,3310,3311,3312],{},"긴 문서를 통째로 임베딩하면 검색 정확도가 떨어진다(한 벡터에 너무 많은 의미가 섞임). 그래서 ",[20,3313,3314],{},"의미 단위로 잘게 나눈다.",[25,3316,3317,3320,3327],{},[28,3318,3319],{},"한 청크는 대략 한 문단~몇 문단 수준(겹침(overlap)을 약간 둬 문맥 끊김 방지).",[28,3321,3322,3323,3326],{},"스레드형 게시글(문의+여러 댓글)은 ",[20,3324,3325],{},"하나의 문서로 압축","하되, 시간순 + 화자 라벨(문의자\u002F직원)을 유지.",[28,3328,3329,3330,3332,3333,3336,3337,3340],{},"청크는 ",[35,3331,2860],{},"(설계) 또는 OpenSearch ",[35,3334,3335],{},"chunks"," 인덱스에 저장하고, 원본 문서 메타(",[35,3338,3339],{},"documents",")와 연결한다.",[377,3342,3344,3345],{"id":3343},"_84-opensearch-인덱스-설계-계획","8.4 OpenSearch 인덱스 설계 ",[35,3346,51],{},[17,3348,3349],{},"검색 엔진(AWS OpenSearch)에는 보통 3개 인덱스를 둔다.",[248,3351,3352,3365],{},[251,3353,3354],{},[254,3355,3356,3359,3362],{},[257,3357,3358],{},"인덱스",[257,3360,3361],{},"담는 것",[257,3363,3364],{},"검색 방식",[270,3366,3367,3379,3394],{},[254,3368,3369,3373,3376],{},[275,3370,3371],{},[35,3372,3339],{},[275,3374,3375],{},"원본 문서 메타(제목·출처·프로젝트·가시성 등)",[275,3377,3378],{},"필터\u002F집계",[254,3380,3381,3385,3391],{},[275,3382,3383],{},[35,3384,3335],{},[275,3386,3387,3388],{},"청크 본문 + ",[20,3389,3390],{},"임베딩 벡터(1536d)",[275,3392,3393],{},"k-NN(벡터) + BM25(본문)",[254,3395,3396,3401,3404],{},[275,3397,3398],{},[35,3399,3400],{},"standard_answers",[275,3402,3403],{},"표준답변 질문\u002F답변",[275,3405,3406],{},"우선 매칭",[25,3408,3409,3415],{},[28,3410,3411,3414],{},[20,3412,3413],{},"k-NN 필드",": 1536차원(임베딩 모델 출력 차원) 벡터. 코사인 유사도로 가까운 청크를 찾는다.",[28,3416,3417,3420,3421,3424,3425,3428,3429,3432,3433],{},[20,3418,3419],{},"가시성 필드","가 중요: 청크마다 ",[35,3422,3423],{},"body_public","(공개 댓글만) \u002F ",[35,3426,3427],{},"body_internal","(비공개 포함)을 구분 색인하고, ",[35,3430,3431],{},"has_internal_only_answer","(공개엔 답이 없고 비공개에만 있음 → 에스컬레이션 후보) 플래그를 둔다.\n",[25,3434,3435,3443],{},[28,3436,3437,1791,3440,3442],{},[20,3438,3439],{},"Phase 1 상담사용",[35,3441,3427],{},"까지 검색 가능.",[28,3444,3445,1791,3448,3450],{},[20,3446,3447],{},"Phase 2 챗봇용",[35,3449,3423],{},"만 검색 — 비공개 본문·출처를 절대 노출하지 않기 위해.",[377,3452,3454,3455],{"id":3453},"_85-하이브리드-점수-결합-계획","8.5 하이브리드 점수 결합 ",[35,3456,51],{},[17,3458,3459],{},"벡터 검색(의미)과 BM25(키워드)는 각각 약점이 있다.",[25,3461,3462,3465],{},[28,3463,3464],{},"벡터만: 정확한 고유명사·코드·버전번호를 놓칠 수 있음.",[28,3466,3467],{},"BM25만: 표현이 다르면(동의어) 못 찾음.",[17,3469,3470,3471,3474],{},"그래서 ",[20,3472,3473],{},"두 점수를 합쳐(하이브리드)"," 랭킹한다. 보통 각 검색의 상위 결과를 가져와 정규화 후 가중합하거나, RRF(Reciprocal Rank Fusion) 같은 순위 결합을 쓴다. 구체 가중치·결합식은 인덱싱 착수 시 튜닝 대상.",[377,3476,3478,3479],{"id":3477},"_86-표준답변-우선-매칭-플로우-계획","8.6 표준답변 우선 매칭 플로우 ",[35,3480,51],{},[17,3482,3483,3484,3487],{},"검색보다 ",[20,3485,3486],{},"표준답변이 항상 우선","이다(원칙 3). 흐름:",[61,3489,3490,3501,3515,3525,3528],{},[28,3491,3492,3493,3496,3497,3500],{},"질문에서 LLM이 ",[35,3494,3495],{},"scope","(common=전사 공통 \u002F service=특정 솔루션)와 ",[35,3498,3499],{},"topic","을 추론.",[28,3502,3503,3506,3507,3510,3511,3514],{},[35,3504,3505],{},"scope=service","면 사용자의 ",[35,3508,3509],{},"service_tag","로 표준답변을 필터, ",[35,3512,3513],{},"common","이면 서비스 무관.",[28,3516,3517,3518,3521,3522,3524],{},"후보가 여럿이면 ",[35,3519,3520],{},"tags"," 매치 수 + ",[35,3523,2708],{},"(많이 쓰인 것) 우선.",[28,3526,3527],{},"선택된 표준답변 N개를 LLM 컨텍스트 맨 앞에 붙여 \"검증된 답이 있으니 이를 우선 반영하라\"고 지시.",[28,3529,3530,3531,2289,3533,3535],{},"표준답변이 실제로 채택되면 ",[35,3532,1447],{},[35,3534,2708],{}," 증가(인기·신뢰 신호 축적).",[377,3537,3539,3540],{"id":3538},"_87-레거시-데이터-적재ingestion-계획","8.7 레거시 데이터 적재(ingestion) ",[35,3541,51],{},[17,3543,3544,3545,3548],{},"기존 PMS에는 약 ",[20,3546,3547],{},"1,358건의 Q&A 후보","가 있다(맑은소프트 사이트 1,146 + 내부 212, 200+ 프로젝트 분포). 이걸 검색 자산으로 끌어오는 게 초기 지식 확보의 핵심이다.",[17,3550,3551],{},[20,3552,3553],{},"후보 추출 필터(예)",[660,3555,3559],{"className":3556,"code":3557,"language":3558,"meta":668,"style":668},"language-sql shiki shiki-themes github-light github-dark","WHERE p.status = 1\n  AND p.comm_cnt >= 1                       -- 답변(댓글)이 최소 1개\n  AND p.is_task=0 AND p.is_schedule=0 AND p.is_poll=0 AND p.is_notice=0  -- 업무\u002F일정\u002F투표\u002F공지 제외\n  AND p.subject NOT LIKE '테스트%'\n  AND CHAR_LENGTH(p.content) >= 20          -- 너무 짧은 글 제외\n","sql",[35,3560,3561,3566,3571,3576,3581],{"__ignoreMap":668},[1094,3562,3563],{"class":1096,"line":1097},[1094,3564,3565],{},"WHERE p.status = 1\n",[1094,3567,3568],{"class":1096,"line":1103},[1094,3569,3570],{},"  AND p.comm_cnt >= 1                       -- 답변(댓글)이 최소 1개\n",[1094,3572,3573],{"class":1096,"line":1109},[1094,3574,3575],{},"  AND p.is_task=0 AND p.is_schedule=0 AND p.is_poll=0 AND p.is_notice=0  -- 업무\u002F일정\u002F투표\u002F공지 제외\n",[1094,3577,3578],{"class":1096,"line":1115},[1094,3579,3580],{},"  AND p.subject NOT LIKE '테스트%'\n",[1094,3582,3583],{"class":1096,"line":1121},[1094,3584,3585],{},"  AND CHAR_LENGTH(p.content) >= 20          -- 너무 짧은 글 제외\n",[17,3587,3588],{},[20,3589,3590],{},"데이터 품질 이슈와 처리",[248,3592,3593,3603],{},[251,3594,3595],{},[254,3596,3597,3600],{},[257,3598,3599],{},"이슈",[257,3601,3602],{},"처리",[270,3604,3605,3613,3621,3632,3640,3653],{},[254,3606,3607,3610],{},[275,3608,3609],{},"본문이 HTML",[275,3611,3612],{},"태그 제거·엔티티 디코드로 평문화",[254,3614,3615,3618],{},[275,3616,3617],{},"이미지만 있는 글",[275,3619,3620],{},"Stage A(텍스트만) → B(OCR) → C(Vision LLM) 단계적",[254,3622,3623,3629],{},[275,3624,3625,3628],{},[35,3626,3627],{},"\u002Fdata\u002F..."," 내부 경로",[275,3630,3631],{},"R2 서명 URL 또는 프록시로 치환",[254,3633,3634,3637],{},[275,3635,3636],{},"스레드형(댓글 여러 개)",[275,3638,3639],{},"1개 문서로 압축(시간순 + 화자 라벨)",[254,3641,3642,3648],{},[275,3643,3644,3645,3647],{},"비공개 댓글(",[35,3646,2945],{},", 약 9.3%)",[275,3649,3650,3652],{},[35,3651,3427],{},"로만 활용, 챗봇(공개)엔 미노출",[254,3654,3655,3658],{},[275,3656,3657],{},"첨부파일(다수)",[275,3659,3660],{},"종류별 단계 처리(아래)",[17,3662,3663],{},[20,3664,3665],{},"첨부파일 처리 단계",[248,3667,3668,3683],{},[251,3669,3670],{},[254,3671,3672,3674,3677,3680],{},[257,3673,268],{},[257,3675,3676],{},"Phase 1 MVP",[257,3678,3679],{},"Phase 1 후반",[257,3681,3682],{},"Phase 2",[270,3684,3685,3699,3713,3726],{},[254,3686,3687,3690,3693,3696],{},[275,3688,3689],{},"텍스트 문서(PDF\u002FDOCX\u002FHWP\u002FXLSX)",[275,3691,3692],{},"텍스트 추출→본문 부착",[275,3694,3695],{},"청킹·구조 개선",[275,3697,3698],{},"—",[254,3700,3701,3704,3707,3710],{},[275,3702,3703],{},"이미지",[275,3705,3706],{},"메타만(Stage A)",[275,3708,3709],{},"OCR(Stage B)",[275,3711,3712],{},"Vision LLM(Stage C)",[254,3714,3715,3718,3721,3724],{},[275,3716,3717],{},"슬라이드(PPTX)",[275,3719,3720],{},"텍스트 추출",[275,3722,3723],{},"슬라이드 단위 청킹",[275,3725,3698],{},[254,3727,3728,3731,3734,3736],{},[275,3729,3730],{},"동영상",[275,3732,3733],{},"메타만",[275,3735,3698],{},[275,3737,3738],{},"Queue + 트랜스크립트(Whisper)",[14,3740,3741],{},[17,3742,3743,3746,3747,3750],{},[20,3744,3745],{},"한국어 함정",": HWP(한컴) 파일이 약 5,094건. 표준 추출기로는 안 되고 ",[35,3748,3749],{},"hwp.js"," 같은 별도 추출기가 필요하다.",[377,3752,3754],{"id":3753},"_88-미정-결정-tbd","8.8 미정 결정 (TBD)",[17,3756,3757],{},"인덱싱 착수 직전에 정해야 할 것들:",[25,3759,3760,3766,3772],{},[28,3761,3762,3765],{},[20,3763,3764],{},"임베딩 모델",": OpenAI vs 오픈모델 vs 한국어 특화 — 한국어 검색 품질·비용으로 결정(차원수가 인덱스 설계에 직결).",[28,3767,3768,3771],{},[20,3769,3770],{},"인덱싱 동기\u002F비동기 전환 시점",": MVP는 동기, 동영상·대용량 도입 시 Cloudflare Queues + Indexer Worker로.",[28,3773,3774,3777],{},[20,3775,3776],{},"하이브리드 가중치\u002F결합식","(8.5).",[54,3779],{},[57,3781,3783],{"id":3782},"_9-인증보안","9. 인증·보안",[377,3785,3787],{"id":3786},"_91-백엔드-로그인-jwt-쿠키","9.1 백엔드 로그인 (JWT + 쿠키)",[25,3789,3790,3812,3832,3840],{},[28,3791,3792,3794,3795,3798,3799,3802,3803,3806,3807,1795,3809,2158],{},[35,3793,1510],{}," : ",[35,3796,3797],{},"loginId","+",[35,3800,3801],{},"password"," 검증. ",[20,3804,3805],{},"직원만 통과","(이메일 ",[35,3808,2292],{},[35,3810,3811],{},"tb_user.company='맑은소프트'",[28,3813,3814,3815,3820,3821,3824,3825,1038,3828,3831],{},"성공 시 ",[20,3816,3817,3818],{},"httpOnly 쿠키 ",[35,3819,2124],{}," 발급. TTL ",[20,3822,3823],{},"8시간",". ",[35,3826,3827],{},"secure",[35,3829,3830],{},"sameSite=None","(크로스 사이트 허용 — 프론트와 API 도메인이 다르므로).",[28,3833,3834,3836,3837,3839],{},[35,3835,1516],{},"로 현재 세션 확인(만료 시 401 + 쿠키 삭제), ",[35,3838,1513],{},"으로 쿠키 제거.",[28,3841,3842,3843,3845],{},"프론트엔드는 모든 요청에 ",[35,3844,2118],{},"를 붙여 쿠키를 보낸다.",[377,3847,3849],{"id":3848},"_92-cors","9.2 CORS",[25,3851,3852,3867],{},[28,3853,3854,3855,1038,3858,1038,3861,1310,3864,1901],{},"허용 origin: ",[35,3856,3857],{},"*.pages.dev",[35,3859,3860],{},"*.malgnsoft.com",[35,3862,3863],{},"localhost",[35,3865,3866],{},"127.0.0.1",[28,3868,3869,3872,3873,1901],{},[35,3870,3871],{},"credentials: true","(쿠키 허용), 메서드 ",[35,3874,3875],{},"GET\u002FPUT\u002FPOST\u002FPATCH\u002FDELETE\u002FOPTIONS",[377,3877,3879,3880,596],{"id":3878},"_93-사용자-분류-classifyts","9.3 사용자 분류 (",[35,3881,3882],{},"classify.ts",[25,3884,3885,3895,3901,3907],{},[28,3886,3887,1791,3890,1795,3892],{},[20,3888,3889],{},"직원",[35,3891,2292],{},[35,3893,3894],{},"company='맑은소프트'",[28,3896,3897,3900],{},[20,3898,3899],{},"협력사",": 화이트리스트(플로즈\u002F옐로우윈\u002F온케어\u002F송한나 등)",[28,3902,3903,3906],{},[20,3904,3905],{},"고객",": 그 외",[28,3908,3909,3910,3913],{},"⚠️ 이름·작성 패턴 같은 걸로 ",[20,3911,3912],{},"추정하지 말 것"," — 명시 규칙만 사용.",[377,3915,3917],{"id":3916},"_94-비공개-댓글","9.4 비공개 댓글",[25,3919,3920,3930],{},[28,3921,3922,3923,3925,3926,3929],{},"API 응답에서 ",[35,3924,2945],{}," 댓글의 ",[20,3927,3928],{},"본문은 마스킹","(null) 처리.",[28,3931,3932,3933,3935,3936,2158],{},"LLM 입력에서도 정책에 따라 분리(Phase 1 상담사용 ",[35,3934,3427],{}," vs Phase 2 챗봇용 ",[35,3937,3423],{},[377,3939,3941,3942,3944],{"id":3940},"_95-계획-cloudflare-access","9.5 ",[35,3943,51],{}," — Cloudflare Access",[25,3946,3947],{},[28,3948,3949,3950,1038,3952,3954,3955,3957],{},"관리자\u002F탐색 라우트(",[35,3951,1475],{},[35,3953,1528],{},")를 Cloudflare Access(Zero Trust)로 보호하고 ",[35,3956,2292],{}," 화이트리스트 + 메일 OTP\u002FSSO를 거는 설계가 있으나 아직 도입 전. 현재는 위 JWT 쿠키 방식이 실제 인증.",[54,3959],{},[57,3961,3963],{"id":3962},"_10-로컬-개발-환경-셋업-절차","10. 로컬 개발 환경 셋업 (절차)",[377,3965,3967],{"id":3966},"_101-공통-준비물","10.1 공통 준비물",[25,3969,3970,3988],{},[28,3971,3972,3975,3976,2342,3978,3981,3982,1795,3985,1901],{},[20,3973,3974],{},"Node.js","(LTS) + ",[20,3977,1645],{},[20,3979,3980],{},"wrangler","(Cloudflare CLI). ",[35,3983,3984],{},"npm i -g wrangler",[35,3986,3987],{},"pnpm dlx wrangler",[28,3989,3990,3991,3994],{},"처음이면 ",[35,3992,3993],{},"wrangler login","으로 Cloudflare 계정 인증.",[377,3996,3998,3999],{"id":3997},"_102-백엔드-malgn-helper-api","10.2 백엔드 ",[35,4000,279],{},[660,4002,4004],{"className":1635,"code":4003,"language":1637,"meta":668,"style":668},"cd ~\u002FProjects\u002Fmalgn-helper-api\npnpm install\n# 시크릿은 .dev.vars 파일에 두거나(로컬), 배포본은 wrangler secret으로 관리\n#   .dev.vars 예시:\n#   OPENAI_API_KEY=sk-...\n#   JWT_SECRET=...\npnpm dev            # wrangler dev → http:\u002F\u002F127.0.0.1:8787\n# 문서 확인: http:\u002F\u002F127.0.0.1:8787\u002Fdoc\n",[35,4005,4006,4015,4022,4027,4032,4037,4042,4051],{"__ignoreMap":668},[1094,4007,4008,4012],{"class":1096,"line":1097},[1094,4009,4011],{"class":4010},"sj4cs","cd",[1094,4013,4014],{"class":1648}," ~\u002FProjects\u002Fmalgn-helper-api\n",[1094,4016,4017,4019],{"class":1096,"line":1103},[1094,4018,1645],{"class":1644},[1094,4020,4021],{"class":1648}," install\n",[1094,4023,4024],{"class":1096,"line":1109},[1094,4025,4026],{"class":1652},"# 시크릿은 .dev.vars 파일에 두거나(로컬), 배포본은 wrangler secret으로 관리\n",[1094,4028,4029],{"class":1096,"line":1115},[1094,4030,4031],{"class":1652},"#   .dev.vars 예시:\n",[1094,4033,4034],{"class":1096,"line":1121},[1094,4035,4036],{"class":1652},"#   OPENAI_API_KEY=sk-...\n",[1094,4038,4039],{"class":1096,"line":1127},[1094,4040,4041],{"class":1652},"#   JWT_SECRET=...\n",[1094,4043,4044,4046,4048],{"class":1096,"line":1133},[1094,4045,1645],{"class":1644},[1094,4047,1649],{"class":1648},[1094,4049,4050],{"class":1652},"            # wrangler dev → http:\u002F\u002F127.0.0.1:8787\n",[1094,4052,4053],{"class":1096,"line":1139},[1094,4054,4055],{"class":1652},"# 문서 확인: http:\u002F\u002F127.0.0.1:8787\u002Fdoc\n",[25,4057,4058],{},[28,4059,4060,4061,1038,4064,4067],{},"로컬에서 MySQL(Hyperdrive)·R2에 붙으려면 적절한 바인딩\u002F접속정보가 필요하다. 빠른 확인은 ",[35,4062,4063],{},"\u002Fhealthz",[35,4065,4066],{},"\u002Fdb\u002Fping","부터.",[377,4069,4071],{"id":4070},"_103-프론트엔드-pms-admin","10.3 프론트엔드 (pms \u002F admin)",[660,4073,4075],{"className":1635,"code":4074,"language":1637,"meta":668,"style":668},"cd ~\u002FProjects\u002Fmalgn-helper-pms      # 또는 -admin\npnpm install\npnpm dev                            # http:\u002F\u002Flocalhost:3000\n",[35,4076,4077,4087,4093],{"__ignoreMap":668},[1094,4078,4079,4081,4084],{"class":1096,"line":1097},[1094,4080,4011],{"class":4010},[1094,4082,4083],{"class":1648}," ~\u002FProjects\u002Fmalgn-helper-pms",[1094,4085,4086],{"class":1652},"      # 또는 -admin\n",[1094,4088,4089,4091],{"class":1096,"line":1103},[1094,4090,1645],{"class":1644},[1094,4092,4021],{"class":1648},[1094,4094,4095,4097,4099],{"class":1096,"line":1109},[1094,4096,1645],{"class":1644},[1094,4098,1649],{"class":1648},[1094,4100,4101],{"class":1652},"                            # http:\u002F\u002Flocalhost:3000\n",[25,4103,4104],{},[28,4105,4106,4107,4110,4111,4114],{},"이 둘은 API 기본 URL이 ",[20,4108,4109],{},"운영 Workers 주소로 하드코딩","돼 있다. 로컬 API를 바라보게 하려면 코드의 ",[35,4112,4113],{},"API_BASE"," 상수를 임시로 바꾼다(커밋하지 말 것).",[377,4116,4118,4119],{"id":4117},"_104-사용자-챗봇-malgn-helper","10.4 사용자 챗봇 ",[35,4120,332],{},[660,4122,4124],{"className":1635,"code":4123,"language":1637,"meta":668,"style":668},"cd ~\u002FProjects\u002Fmalgn-helper\npnpm install && pnpm dev\n",[35,4125,4126,4133],{"__ignoreMap":668},[1094,4127,4128,4130],{"class":1096,"line":1097},[1094,4129,4011],{"class":4010},[1094,4131,4132],{"class":1648}," ~\u002FProjects\u002Fmalgn-helper\n",[1094,4134,4135,4137,4140,4144,4146],{"class":1096,"line":1103},[1094,4136,1645],{"class":1644},[1094,4138,4139],{"class":1648}," install",[1094,4141,4143],{"class":4142},"sVt8B"," && ",[1094,4145,1645],{"class":1644},[1094,4147,4148],{"class":1648}," dev\n",[25,4150,4151],{},[28,4152,4153],{},"아직 스켈레톤이라 화면은 \"준비 중\" 텍스트만 나온다.",[377,4155,4157,4158],{"id":4156},"_105-관리-허브-malgn-helper-mng","10.5 관리 허브 ",[35,4159,349],{},[660,4161,4163],{"className":1635,"code":4162,"language":1637,"meta":668,"style":668},"cd ~\u002FProjects\u002Fmalgn-helper-mng\npnpm install\npnpm dev                            # http:\u002F\u002Flocalhost:3000 (D1 없으면 시드 폴백)\n# D1 영속 편집까지 보려면:\npnpm db:apply && pnpm db:seed       # 원격 D1에 스키마+시드 적용\n",[35,4164,4165,4172,4178,4187,4192],{"__ignoreMap":668},[1094,4166,4167,4169],{"class":1096,"line":1097},[1094,4168,4011],{"class":4010},[1094,4170,4171],{"class":1648}," ~\u002FProjects\u002Fmalgn-helper-mng\n",[1094,4173,4174,4176],{"class":1096,"line":1103},[1094,4175,1645],{"class":1644},[1094,4177,4021],{"class":1648},[1094,4179,4180,4182,4184],{"class":1096,"line":1109},[1094,4181,1645],{"class":1644},[1094,4183,1649],{"class":1648},[1094,4185,4186],{"class":1652},"                            # http:\u002F\u002Flocalhost:3000 (D1 없으면 시드 폴백)\n",[1094,4188,4189],{"class":1096,"line":1115},[1094,4190,4191],{"class":1652},"# D1 영속 편집까지 보려면:\n",[1094,4193,4194,4196,4199,4201,4203,4206],{"class":1096,"line":1121},[1094,4195,1645],{"class":1644},[1094,4197,4198],{"class":1648}," db:apply",[1094,4200,4143],{"class":4142},[1094,4202,1645],{"class":1644},[1094,4204,4205],{"class":1648}," db:seed",[1094,4207,4208],{"class":1652},"       # 원격 D1에 스키마+시드 적용\n",[54,4210],{},[57,4212,4214],{"id":4213},"_11-배포-절차","11. 배포 절차",[377,4216,4218],{"id":4217},"_111-권장-워크스페이스-일괄-스크립트","11.1 권장: 워크스페이스 일괄 스크립트",[17,4220,4221,4222,4224,4225,4228],{},"모든 레포는 ",[35,4223,332],{},"(워크스페이스 루트)의 ",[35,4226,4227],{},"scripts\u002Fdeploy.sh","로 배포한다.",[660,4230,4232],{"className":1635,"code":4231,"language":1637,"meta":668,"style":668},"cd ~\u002FProjects\u002Fmalgn-helper\n.\u002Fscripts\u002Fdeploy.sh \u003Crepo-name> \"\u003Ccommit message>\"\n",[35,4233,4234,4240],{"__ignoreMap":668},[1094,4235,4236,4238],{"class":1096,"line":1097},[1094,4237,4011],{"class":4010},[1094,4239,4132],{"class":1648},[1094,4241,4242,4245,4249,4252,4255,4258],{"class":1096,"line":1103},[1094,4243,4244],{"class":1644},".\u002Fscripts\u002Fdeploy.sh",[1094,4246,4248],{"class":4247},"szBVR"," \u003C",[1094,4250,4251],{"class":1648},"repo-nam",[1094,4253,4254],{"class":4142},"e",[1094,4256,4257],{"class":4247},">",[1094,4259,4260],{"class":1648}," \"\u003Ccommit message>\"\n",[25,4262,4263,4279],{},[28,4264,4265,1791,4268,4270,4271,4270,4273,4270,4275,4270,4277],{},[35,4266,4267],{},"\u003Crepo-name>",[35,4269,332],{}," | ",[35,4272,315],{},[35,4274,279],{},[35,4276,297],{},[35,4278,349],{},[28,4280,4281,4282],{},"스크립트가 순서대로 수행:\n",[61,4283,4284,4291,4296,4308],{},[28,4285,4286,4287,4290],{},"해당 레포에서 ",[35,4288,4289],{},"git add -A && git commit"," (변경 없으면 skip)",[28,4292,4293],{},[35,4294,4295],{},"git push",[28,4297,4298,4301,4302,4304,4305],{},[35,4299,4300],{},"pnpm run deploy"," — ",[35,4303,1886],{}," 유무로 ",[20,4306,4307],{},"Pages\u002FWorkers 자동 분기",[28,4309,4310,1938,4313,4316],{},[35,4311,4312],{},"docs\u002Fhistory\u002Fhistory.{yyyyMMdd}.md",[35,4314,4315],{},"## 배포"," 섹션에 항목 append",[17,4318,4319],{},[20,4320,4321],{},"예시",[660,4323,4325],{"className":1635,"code":4324,"language":1637,"meta":668,"style":668},".\u002Fscripts\u002Fdeploy.sh malgn-helper-api \"feat: 챗 엔드포인트 추가\"\n.\u002Fscripts\u002Fdeploy.sh malgn-helper-mng \"chore: 진척 갱신\"\n",[35,4326,4327,4337],{"__ignoreMap":668},[1094,4328,4329,4331,4334],{"class":1096,"line":1097},[1094,4330,4244],{"class":1644},[1094,4332,4333],{"class":1648}," malgn-helper-api",[1094,4335,4336],{"class":1648}," \"feat: 챗 엔드포인트 추가\"\n",[1094,4338,4339,4341,4344],{"class":1096,"line":1103},[1094,4340,4244],{"class":1644},[1094,4342,4343],{"class":1648}," malgn-helper-mng",[1094,4345,4346],{"class":1648}," \"chore: 진척 갱신\"\n",[377,4348,4350],{"id":4349},"_112-배포-규칙-중요","11.2 배포 규칙 (중요)",[25,4352,4353,4359,4378,4422,4429,4438,4441],{},[28,4354,4355,4358],{},[20,4356,4357],{},"변경된 레포만"," 배포. 전체 일괄 배포 금지.",[28,4360,4361,4301,4366,4369,4370,4373,4374,4377],{},[20,4362,4363,4365],{},[35,4364,4300],{},"로 호출",[35,4367,4368],{},"deploy","는 pnpm 예약어라 ",[35,4371,4372],{},"pnpm deploy","는 충돌. 항상 ",[35,4375,4376],{},"run"," 명시.",[28,4379,4380,1767,4383],{},[20,4381,4382],{},"account_id 주입 방식 차이",[25,4384,4385,4397],{},[28,4386,4387,2066,4390,4392,4393,4396],{},[20,4388,4389],{},"Workers",[35,4391,1085],{},")는 ",[35,4394,4395],{},"account_id"," 필드를 지원 → 파일에 명시(api).",[28,4398,4399,2066,4402,4392,4404,4406,4407,4410,4411,4414,4415,4418,4419,1901],{},[20,4400,4401],{},"Pages",[35,4403,1886],{},[35,4405,4395],{}," 필드 ",[20,4408,4409],{},"미지원"," → ",[35,4412,4413],{},"deploy.sh","가 ",[35,4416,4417],{},"CLOUDFLARE_ACCOUNT_ID"," 환경변수로 주입(helper\u002Fadmin\u002Fpms\u002Fmng). 수동 배포 시엔 ",[35,4420,4421],{},"CLOUDFLARE_ACCOUNT_ID=... pnpm run deploy",[28,4423,4424,4425,973,4427,2158],{},"Nuxt Pages 빌드 출력 디렉터리는 ",[35,4426,744],{},[35,4428,1892],{},[28,4430,4431,4301,4434,4437],{},[20,4432,4433],{},"Secret 변경은 별도",[35,4435,4436],{},"wrangler secret put \u003CKEY>"," 후 배포.",[28,4439,4440],{},"배포 실패 시 사유를 history에 함께 기록.",[28,4442,4443],{},"같은 날 추가 배포는 같은 history 파일에 항목만 누적(덮어쓰기 금지).",[377,4445,4447],{"id":4446},"_113-mng-전용-주의-d1","11.3 mng 전용 주의 (D1)",[17,4449,4450,4452],{},[35,4451,541],{},"는 D1 스키마\u002F시드를 바꿨다면 배포 전에 따로 적용해야 한다(배포 스크립트엔 미포함).",[660,4454,4456],{"className":1635,"code":4455,"language":1637,"meta":668,"style":668},"cd ~\u002FProjects\u002Fmalgn-helper-mng\npnpm db:apply    # wrangler d1 migrations apply malgn-helper-project --remote\npnpm db:seed     # server\u002Fdb\u002Fseed.sql 적용\n",[35,4457,4458,4464,4473],{"__ignoreMap":668},[1094,4459,4460,4462],{"class":1096,"line":1097},[1094,4461,4011],{"class":4010},[1094,4463,4171],{"class":1648},[1094,4465,4466,4468,4470],{"class":1096,"line":1103},[1094,4467,1645],{"class":1644},[1094,4469,4198],{"class":1648},[1094,4471,4472],{"class":1652},"    # wrangler d1 migrations apply malgn-helper-project --remote\n",[1094,4474,4475,4477,4479],{"class":1096,"line":1109},[1094,4476,1645],{"class":1644},[1094,4478,4205],{"class":1648},[1094,4480,4481],{"class":1652},"     # server\u002Fdb\u002Fseed.sql 적용\n",[377,4483,4485],{"id":4484},"_114-수동-배포-스크립트-미사용-시","11.4 수동 배포 (스크립트 미사용 시)",[660,4487,4489],{"className":1635,"code":4488,"language":1637,"meta":668,"style":668},"cd ~\u002FProjects\u002F\u003Crepo>\ngit add . && git commit -m \"\u003Cmessage>\"\ngit push\n# Pages면:\nCLOUDFLARE_ACCOUNT_ID=d2b8c5524b7259214fa302f1fecb4ad6 pnpm run deploy\n# Workers(api)면:\npnpm run deploy\n# 그 뒤 docs\u002Fhistory\u002Fhistory.{yyyyMMdd}.md에 배포 항목 직접 추가\n",[35,4490,4491,4510,4534,4541,4546,4563,4568,4576],{"__ignoreMap":668},[1094,4492,4493,4495,4498,4501,4504,4507],{"class":1096,"line":1097},[1094,4494,4011],{"class":4010},[1094,4496,4497],{"class":1648}," ~\u002FProjects\u002F",[1094,4499,4500],{"class":4247},"\u003C",[1094,4502,4503],{"class":1648},"rep",[1094,4505,4506],{"class":4142},"o",[1094,4508,4509],{"class":4247},">\n",[1094,4511,4512,4515,4518,4521,4523,4525,4528,4531],{"class":1096,"line":1103},[1094,4513,4514],{"class":1644},"git",[1094,4516,4517],{"class":1648}," add",[1094,4519,4520],{"class":1648}," .",[1094,4522,4143],{"class":4142},[1094,4524,4514],{"class":1644},[1094,4526,4527],{"class":1648}," commit",[1094,4529,4530],{"class":4010}," -m",[1094,4532,4533],{"class":1648}," \"\u003Cmessage>\"\n",[1094,4535,4536,4538],{"class":1096,"line":1109},[1094,4537,4514],{"class":1644},[1094,4539,4540],{"class":1648}," push\n",[1094,4542,4543],{"class":1096,"line":1115},[1094,4544,4545],{"class":1652},"# Pages면:\n",[1094,4547,4548,4550,4553,4555,4558,4560],{"class":1096,"line":1121},[1094,4549,4417],{"class":4142},[1094,4551,4552],{"class":4247},"=",[1094,4554,765],{"class":1648},[1094,4556,4557],{"class":1644}," pnpm",[1094,4559,1660],{"class":1648},[1094,4561,4562],{"class":1648}," deploy\n",[1094,4564,4565],{"class":1096,"line":1127},[1094,4566,4567],{"class":1652},"# Workers(api)면:\n",[1094,4569,4570,4572,4574],{"class":1096,"line":1133},[1094,4571,1645],{"class":1644},[1094,4573,1660],{"class":1648},[1094,4575,4562],{"class":1648},[1094,4577,4578],{"class":1096,"line":1139},[1094,4579,4580],{"class":1652},"# 그 뒤 docs\u002Fhistory\u002Fhistory.{yyyyMMdd}.md에 배포 항목 직접 추가\n",[54,4582],{},[57,4584,4586],{"id":4585},"_12-운영-컨벤션","12. 운영 컨벤션",[25,4588,4589,4598,4608,4616,4631,4649],{},[28,4590,4591,1791,4594,4597],{},[20,4592,4593],{},"작업 이력",[35,4595,4596],{},"malgn-helper\u002Fdocs\u002Fhistory\u002Fhistory.yyyyMMdd.md"," — 하루 한 파일에 누적. 배포·오류·결정사항 기록.",[28,4599,4600,4603,4604,4607],{},[20,4601,4602],{},"Git",": 단일 ",[35,4605,4606],{},"main"," 브랜치. 커밋·푸시는 보통 요청 시. 무관한 파일을 끌어들이지 않기.",[28,4609,4610,4612,4613,4615],{},[20,4611,647],{},": 데이터는 물리 삭제하지 않고 ",[35,4614,651],{},"로 표시.",[28,4617,4618,4621,4622,4624,4625,4627,4628,4630],{},[20,4619,4620],{},"LLM 비용 관측",": 모든 호출이 ",[35,4623,1626],{},"에 쌓이고, ",[35,4626,1488],{},"(admin ",[35,4629,2065],{},")에서 모델별\u002F일별로 본다.",[28,4632,4633,4636,4637,4640,4641,1938,4643,2927,4645,4648],{},[20,4634,4635],{},"문서 정본 동기화",": 코드·구조가 바뀌면 관련 ",[35,4638,4639],{},"docs\u002F*.md","를 현행화. 특히 ",[35,4642,541],{},[35,4644,2490],{},[35,4646,4647],{},"malgn-helper\u002Fdocs\u002F","(워크스페이스 정본)을 반영.",[28,4650,4651,1791,4654,4656,4657,4660,4661,4663,4664,4667],{},[20,4652,4653],{},"mng 시드 3종 동기화",[35,4655,541],{},"에서 진척 데이터를 바꿀 땐 ",[35,4658,4659],{},"server\u002Fdb\u002Fseed.sql","(D1 정본) + ",[35,4662,2513],{},"(폴백) + ",[35,4665,4666],{},"app\u002Futils\u002FwbsData.ts","(간트)를 함께 갱신.",[54,4669],{},[57,4671,4673],{"id":4672},"_13-현재-구현-현황-vs-남은-작업","13. 현재 구현 현황 vs 남은 작업",[14,4675,4676],{},[17,4677,4678],{},"2026-06-09 기준 스냅샷. 가장 자주 묻는 \"어디까지 됐나\"를 정리.",[377,4680,4682],{"id":4681},"동작-중-운영베타","✅ 동작 중 (운영\u002F베타)",[25,4684,4685,4694,4704,4709,4718],{},[28,4686,4687,4690,4691,4693],{},[20,4688,4689],{},"API",": Hono Workers 운영. PMS 연동·브리핑·Q&A 평가·표준답변 CRUD·이미지 Vision 캡션·관리자 KPI\u002Fcost·인증(JWT 8h)·LLM 게이트웨이(",[35,4692,842],{},", gpt-4.1-mini) 안정화.",[28,4695,4696,4698,4699,1310,4701,4703],{},[20,4697,302],{},": 브리핑\u002FQ&A 평가 카드 + 임베드(",[35,4700,1794],{},[35,4702,1798],{},") + 실 API 연동 + 표준답변 저장 + inquiry-only 모드. PMS에서 일상적으로 사용.",[28,4705,4706,4708],{},[20,4707,320],{},": 골격 + 5그룹×17메뉴 IA + 로그인 + 우선순위 4화면(홈 KPI\u002Fcost\u002Fqa-evals\u002Fimages).",[28,4710,4711,4714,4715,4717],{},[20,4712,4713],{},"DB",": Aurora MySQL에 ",[35,4716,857],{}," 5테이블 적용 + 레거시 인덱스 보강.",[28,4719,4720,4723],{},[20,4721,4722],{},"관리 허브(mng)",": 대시보드\u002F현황판\u002FWBS\u002F문서\u002F이력 운영, D1 + 시드 폴백.",[377,4725,4727],{"id":4726},"남은-작업-요약","🔜 남은 작업 (요약)",[25,4729,4730,4736,4741,4749,4755],{},[28,4731,4732,4735],{},[20,4733,4734],{},"검색\u002FRAG 본체",": OpenSearch 하이브리드 인덱싱·검색, 임베딩 모델 선정, 자료 업로드→인덱싱 파이프라인.",[28,4737,4738,4740],{},[20,4739,320],{},": 자료 업로드(R2)·표준답변 관리·AI 시연 등 stub 12종 실데이터화.",[28,4742,4743,4748],{},[20,4744,4745,4746,596],{},"사용자 챗봇(",[35,4747,332],{},": 채팅 UI 처음부터. (Phase 2)",[28,4750,4751,4754],{},[20,4752,4753],{},"에스컬레이션 자동 분기·안전 가드","·단위\u002F통합\u002F최종 테스트·개발자\u002F상담사 교육·서비스 연동(SSO\u002FCS).",[28,4756,4757,4760,4761,4763],{},[20,4758,4759],{},"Cloudflare Access"," 도입, 임시 ",[35,4762,1528],{}," 엔드포인트 제거.",[377,4765,4767],{"id":4766},"단계별-목표로드맵-요약","단계별 목표(로드맵 요약)",[25,4769,4770,4785],{},[28,4771,4772,4775,4776,4779,4780],{},[20,4773,4774],{},"Phase 1 완료 기준",": 상담사가 관리자\u002FPMS에서 문의를 받아 AI 추천을 ",[20,4777,4778],{},"채택\u002F수정","해 응답 가능 + 자료·표준답변·피드백 누적.\n",[25,4781,4782],{},[28,4783,4784],{},"KPI: 추천 채택률 ≥60%(무수정 ≥30%), 인용 정확도 ≥95%, 잘못된 안내 0건, 응답 p95 ≤8초.",[28,4786,4787,4790,4791],{},[20,4788,4789],{},"Phase 2 완료 기준",": 고객이 챗봇과 직접 대화, 자동 해결 또는 안전 에스컬레이션.\n",[25,4792,4793],{},[28,4794,4795],{},"KPI: 자동 해결률 ≥50%, 에스컬레이션 정확 ≥90%, 만족도 ≥80%, 환각 0건.",[54,4797],{},[57,4799,4801],{"id":4800},"_14-자주-만나는-함정-트러블슈팅","14. 자주 만나는 함정 (트러블슈팅)",[17,4803,4804],{},"겪었거나 겪기 쉬운 문제들:",[61,4806,4807,4816,4836,4848,4860,4866,4880,4890,4905,4921],{},[28,4808,4809,4812,4813,4815],{},[20,4810,4811],{},"OpenAI 지역 차단\u002F지연"," — 한국 엣지에서 OpenAI 직접 호출이 막히거나 느릴 수 있다. → Workers ",[35,4814,906],{}," + AI Gateway 경유로 우회.",[28,4817,4818,4821,4822,685,4824,4827,4828,4831,4832,4835],{},[20,4819,4820],{},"이미지가 LLM\u002F화면에서 안 보임"," — 본문 이미지가 ",[35,4823,3627],{},[20,4825,4826],{},"상대경로","라서. → ",[35,4829,4830],{},"fixPmsHtml()","(pms) \u002F 서버단 절대화로 ",[35,4833,4834],{},"https:\u002F\u002Fppm.malgn.co.kr\u002F...","로 변환.",[28,4837,4838,4301,4841,4843,4844,4847],{},[20,4839,4840],{},"utf8mb4 UNIQUE 인덱스 길이 초과",[35,4842,2841],{}," 등에서 인덱스 바이트 한계. → UNIQUE prefix를 ",[20,4845,4846],{},"191자","로.",[28,4849,4850,4301,4853,4855,4856,4859],{},[20,4851,4852],{},"평가 문구 잘림",[35,4854,2671],{},"가 VARCHAR(20)이라 잘림. → ",[20,4857,4858],{},"VARCHAR(100)"," 으로 확장.",[28,4861,4862,4865],{},[20,4863,4864],{},"Hyperdrive stale read"," — 캐시된 오래된 값이 읽힘. → 캐시 TTL 인지하고, 즉시성이 필요한 경로는 주의.",[28,4867,4868,4871,4872,4875,4876,4879],{},[20,4869,4870],{},"CORS\u002F쿠키 안 됨"," — 프론트·API 도메인이 다르므로 ",[35,4873,4874],{},"credentials: include"," + 서버 ",[35,4877,4878],{},"sameSite=None; secure"," 둘 다 필요.",[28,4881,4882,4887,4888,1901],{},[20,4883,4884,4886],{},[35,4885,4372],{},"가 이상하게 동작"," — pnpm 예약어 충돌. → 반드시 ",[35,4889,4300],{},[28,4891,4892,4895,4896,4898,4899,4901,4902,4904],{},[20,4893,4894],{},"Pages 배포에서 account_id 에러"," — Pages ",[35,4897,1886],{},"은 ",[35,4900,4395],{}," 미지원. → ",[35,4903,4417],{}," 환경변수로 주입.",[28,4906,4907,4910,4911,1310,4914,1310,4917,4920],{},[20,4908,4909],{},"mng 데이터가 화면마다 다름"," — 진척 시드 3파일(",[35,4912,4913],{},"seed.sql",[35,4915,4916],{},"boardSeed.ts",[35,4918,4919],{},"wbsData.ts",") 중 일부만 고쳐서 생기는 불일치. → 항상 셋을 함께 갱신.",[28,4922,4923,4301,4926,4928],{},[20,4924,4925],{},"admin에서 API URL 바꿨는데 일부만 적용",[35,4927,4113],{},"가 여러 파일에 하드코딩. → 전체 검색해서 같이 수정(또는 추후 환경변수화).",[54,4930],{},[14,4932,4933],{},[17,4934,4935,4938,4939,4941,4942,4945],{},[20,4936,4937],{},"이 문서를 고치는 사람에게",": 코드가 바뀌면 여기 \"설계 vs 구현\" 표기(",[35,4940,51],{},")와 ",[66,4943,4944],{"href":177},"13장"," 스냅샷을 같이 갱신해 주세요. 특히 검색(8장)·관리자단(5.3)·사용자 챗봇(5.4)은 진척이 빠른 영역입니다.",[4947,4948,4949],"style",{},"html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html pre.shiki code .sScJk, html code.shiki .sScJk{--shiki-default:#6F42C1;--shiki-dark:#B392F0}html pre.shiki code .sZZnC, html code.shiki .sZZnC{--shiki-default:#032F62;--shiki-dark:#9ECBFF}html pre.shiki code .sJ8bj, html code.shiki .sJ8bj{--shiki-default:#6A737D;--shiki-dark:#6A737D}html pre.shiki code .sj4cs, html code.shiki .sj4cs{--shiki-default:#005CC5;--shiki-dark:#79B8FF}html pre.shiki code .sVt8B, html code.shiki .sVt8B{--shiki-default:#24292E;--shiki-dark:#E1E4E8}html pre.shiki code .szBVR, html code.shiki .szBVR{--shiki-default:#D73A49;--shiki-dark:#F97583}",{"title":668,"searchDepth":1109,"depth":1109,"links":4951},[4952,4953,4956,4961,4962,4963,4970,4978,4986,5002,5011,5021,5027,5028,5033],{"id":59,"depth":1103,"text":59},{"id":189,"depth":1103,"text":190,"children":4954},[4955],{"id":379,"depth":1109,"text":380},{"id":403,"depth":1103,"text":404,"children":4957},[4958,4959,4960],{"id":410,"depth":1109,"text":411},{"id":507,"depth":1109,"text":508},{"id":605,"depth":1109,"text":606},{"id":657,"depth":1103,"text":658},{"id":703,"depth":1103,"text":704},{"id":939,"depth":1103,"text":940,"children":4964},[4965,4966,4967,4968,4969],{"id":950,"depth":1109,"text":951},{"id":1681,"depth":1109,"text":1682},{"id":1906,"depth":1109,"text":1907},{"id":2394,"depth":1109,"text":2395},{"id":2446,"depth":1109,"text":2447},{"id":2533,"depth":1103,"text":2534,"children":4971},[4972,4974,4976],{"id":2569,"depth":1109,"text":4973},"6.1 헬퍼 전용 테이블 (hp_*, MySQL)",{"id":2887,"depth":1109,"text":4975},"6.2 레거시 PMS 운영 테이블 (tb_*, 읽기 전용으로 활용)",{"id":2962,"depth":1109,"text":4977},"6.3 관리 허브 D1 (mng)",{"id":2989,"depth":1103,"text":2990,"children":4979},[4980,4981,4982,4983,4984,4985],{"id":2993,"depth":1109,"text":2994},{"id":3030,"depth":1109,"text":3031},{"id":3063,"depth":1109,"text":3064},{"id":3106,"depth":1109,"text":3107},{"id":3129,"depth":1109,"text":3130},{"id":3170,"depth":1109,"text":3171},{"id":3206,"depth":1103,"text":3207,"children":4987},[4988,4989,4991,4993,4995,4997,4999,5001],{"id":3221,"depth":1109,"text":3222},{"id":3272,"depth":1109,"text":4990},"8.2 RAG 전체 흐름 (설계) 🔜 계획",{"id":3305,"depth":1109,"text":4992},"8.3 청킹(chunking) 전략 🔜 계획",{"id":3343,"depth":1109,"text":4994},"8.4 OpenSearch 인덱스 설계 🔜 계획",{"id":3453,"depth":1109,"text":4996},"8.5 하이브리드 점수 결합 🔜 계획",{"id":3477,"depth":1109,"text":4998},"8.6 표준답변 우선 매칭 플로우 🔜 계획",{"id":3538,"depth":1109,"text":5000},"8.7 레거시 데이터 적재(ingestion) 🔜 계획",{"id":3753,"depth":1109,"text":3754},{"id":3782,"depth":1103,"text":3783,"children":5003},[5004,5005,5006,5008,5009],{"id":3786,"depth":1109,"text":3787},{"id":3848,"depth":1109,"text":3849},{"id":3878,"depth":1109,"text":5007},"9.3 사용자 분류 (classify.ts)",{"id":3916,"depth":1109,"text":3917},{"id":3940,"depth":1109,"text":5010},"9.5 🔜 계획 — Cloudflare Access",{"id":3962,"depth":1103,"text":3963,"children":5012},[5013,5014,5016,5017,5019],{"id":3966,"depth":1109,"text":3967},{"id":3997,"depth":1109,"text":5015},"10.2 백엔드 malgn-helper-api",{"id":4070,"depth":1109,"text":4071},{"id":4117,"depth":1109,"text":5018},"10.4 사용자 챗봇 malgn-helper",{"id":4156,"depth":1109,"text":5020},"10.5 관리 허브 malgn-helper-mng",{"id":4213,"depth":1103,"text":4214,"children":5022},[5023,5024,5025,5026],{"id":4217,"depth":1109,"text":4218},{"id":4349,"depth":1109,"text":4350},{"id":4446,"depth":1109,"text":4447},{"id":4484,"depth":1109,"text":4485},{"id":4585,"depth":1103,"text":4586},{"id":4672,"depth":1103,"text":4673,"children":5029},[5030,5031,5032],{"id":4681,"depth":1109,"text":4682},{"id":4726,"depth":1109,"text":4727},{"id":4766,"depth":1109,"text":4767},{"id":4800,"depth":1103,"text":4801},"md",{},true,"\u002Fdeveloper-guide",{"title":5,"description":668},"DEVELOPER-GUIDE","YahOaFGjnjjDK5U3Mr0N7cOQoZPhglTBeHq6yCyjOxA",1783932938619]