Imported from wonsik99/shinhan-bonvoy-points-checker (
AGENTS.md). Install upstream withnpx skills add wonsik99/shinhan-bonvoy-points-checker. Copyright stays with the author.
This is NOT the Next.js you know
This version has breaking changes โ APIs, conventions, and file structure may all differ from your training data. Read the relevant guide in node_modules/next/dist/docs/ before writing any code. Heed deprecation notices.
Bonvoy L4/L5 Checker โ Agent Handoff
์ ํ ๋ฉ๋ฆฌ์ดํธ ๋ณธ๋ณด์ด ์นด๋ ์ฌ์ฉ์๊ฐ ํฌ์ธํธ ์ ๋ฆฝ ์์ธ๋ด์ญ ์์ ์ ์ฌ๋ฆฌ๋ฉด, ๋ฉ๋ฆฌ์ดํธ ๊ณ์ด ํธํ ๊ฒฐ์ ๊ฐ ์ ์ ํน๋ณ์ ๋ฆฝ(๊ตญ๋ด L4 / ํด์ธ L5)๋๋์ง ๊ฒ์ฌํด์ฃผ๋ ์น ๋๊ตฌ.
- Live: https://shinhan-bonvoy-points-checker.vercel.app (Vercel, ์์ ์ ์ โ ์๋ฒ ํจ์ ์์)
- Repo: github.com/wonsik99/shinhan-bonvoy-points-checker
- ๋ก์ปฌ์์ ์์
์ด๋ ฅยท๋น๊ณต๊ฐ ์ปจํ
์คํธ๊ฐ ํ์ํ๋ฉด
docs/_local/HANDOFF.md๋ฅผ ์ฝ์ ๊ฒ (gitignore๋ ๋ก์ปฌ ์ ์ฉ ๋ฌธ์).
์ ๋ ๊ท์น (์ฌ์ฉ์ ์ง์)
- LLM ํธ์ถ ๊ธ์ง. ํ์ ยท๊ณ์ฐ์ ์ ๋ถ ๊ฒฐ์ ์ ๊ท์น ๊ธฐ๋ฐ. (LLM์ ๋จผ ๋ฏธ๋์ ์ค๋ช ๋ณด์กฐ๋ก๋ง ๊ฒํ )
- ์
๋ก๋ ํ์ผ์ ์ ๋ ์๋ฒ๋ก ๋ณด๋ด์ง ์๋๋ค. ํ์ฑ์ 100% ๋ธ๋ผ์ฐ์ ์์. CSP
connect-src๊ฐ ์ด๋ฅผ ๊ฐ์ ํ๋ฉฐ, ์ด ๋ณด์ ์คํ ๋ฆฌ๋ฅผ ๊นจ๋ ๋ณ๊ฒฝ ๊ธ์ง. docs/_local/์ ์ ๋ ์ปค๋ฐ ๊ธ์ง (gitignore๋จ โ ์คํ ์๋ณธ, ํ ์คํธ ํ์ผ, ๋น๊ณต๊ฐ ๋ฌธ์ ์์น).- ์ปค๋ฐยทpush ์ ์ ๋ฐ๋์ ์ฌ์ฉ์์๊ฒ ํ๊ตญ์ด๋ก ํ๋ฝ์ ๋ฐ์ ๊ฒ.
- ์ปค๋ฐ ๋ฉ์์ง์ AI co-author ํธ๋ ์ผ๋ฌ๋ฅผ ๋ฃ์ง ๋ง ๊ฒ (์ฌ์ฉ์๊ฐ ๋ช ์์ ์ผ๋ก ๊ธ์งํจ).
- ๋จ์ ํํ ๊ธ์ง: "๋๋ฝ ์์ฌ"ยท"ํ์ธ ํ์"๋ง ์ฌ์ฉ, "๋๋ฝ ํ์ "ยท"๋ณด์ฅ" ๊ธ์ง. ์ด ์ฑ์ ์นด๋์ฌ ๋ฌธ์๋ฅผ ๋๋ ์ฐธ๊ณ ์๋ฃ๋ค.
- ์ฌ์ฉ์์๋ ํ๊ตญ์ด๋ก ์ํตํ๋ค.
๋๋ฉ์ธ ์ง์ (์ฌ์ฉ์=์นด๋ ์์ ์๊ฐ ํ์ธํด์ค ์ฌ์ค)
์ ๋ฆฝ ๋ฑ๊ธ ์ฝ๋ (ํฌ์ธํธ์ข
๋ฅ์์ธ ์ปฌ๋ผ, 1,000์๋น ๋ฉ๋ฆฌ์ดํธ ๋ณธ๋ณด์ด ํฌ์ธํธ, round(๊ธ์ก/1000รN)):
| ์ฝ๋ | ์๋ฏธ | ์ ๋ฆฝ |
|---|---|---|
| L1 | ๊ธฐ๋ณธ ์ ๋ฆฝ | 1P |
| L2 | ํด์ธ ๋งค์ถ ์ ๋ฆฝ | 3P |
| L3 | ํน๋ณ ์ ๋ฆฝ ์ ์ข (ํญ๊ณต/ํ์/์นดํ ๋ฑ) | 3P |
| L4 | ๊ตญ๋ด ๋ฉ๋ฆฌ์ดํธ ๊ฒฐ์ | 5P |
| L5 | ํด์ธ ๋ฉ๋ฆฌ์ดํธ ๊ฒฐ์ | 5P |
- ๊ธฐ์ค ์นด๋๋ ๋ฉ๋ฆฌ์ดํธ ๋ณธ๋ณด์ดโข ๋ ๋ฒ ์คํธ ์ ํ์นด๋ (
rules/cardProfiles.ts์activeCardProfile). - ๋ ํด๋์ ์นด๋๋ ๋ฉ๋ฆฌ์ดํธ ์ ๋ฆฝ์ด 4P/1,000์์ผ๋ก ๋ค๋ฆ โ ์์ง ๋ฏธ์ง์. ํด๋์ ๋ช ์ธ์์ ๋ฑ๊ธ ์ฝ๋๊ฐ ํ์ธ๋๋ฉด ํ๋กํ ์ถ๊ฐ + ์นด๋ ์ ํ UI๋ก ํ์ฅํ ๊ฒ.
์ค์ ์ ํ ์์ ๋ ์ด์์ (์ค๋ฌผ ํ์ผ๋ก ๊ฒ์ฆ๋จ):
- ํค๋ 2ํ + ๊ฑฐ๋ 1๊ฑด๋น 2ํ ์ธํฐ๋ฆฌ๋ธ ๊ตฌ์กฐ.
lib/parseShinhanExcel.ts์extractRecords๊ฐ ์ฒ๋ฆฌ (ํ๋ซ ๊ตฌ์กฐ, ์ ๋ชฉํ ์๋ ๋ณํ๋ ์ง์). - ํด์ธ ๊ฒฐ์ ๋
๊ฐ๋งน์ ๋ช์ด "VISAํด์ธ์ฌ์ฉ์ผ์๋ถ"(์ฑ๋๋ช )์ด๊ณ **์ค์ ํธํ ๋ช ์ํด์ธ๊ฐ๋งน์ ๋ช**์ ์์ โ ํด์ธ๊ฐ๋งน์ ๋ช ์ฐ์ . - ์ค์ ํฌ์ธํธ ์ปฌ๋ผ์
ํฌ์ธํธ์ ๋ฆฝ๊ธ์ก, ๋ ์ง๋ Excel serial ์ซ์. - ์ด ์์ ์ ์ฑ/ํํ์ด์ง์์ ๋ชป ๋ฐ๊ณ ๊ณ ๊ฐ์ผํฐ(1544-7000) ์ ํ๋ก๋ง ๋ฐ๊ธ๋จ.
์ํคํ ์ฒ
app/page.tsx ์ํ ๋ณด์ (baseResults + feedbackById), ํ์๊ฐ useMemo
lib/parseShinhanExcel File โ SheetJS โ extractRecords(์ธํฐ๋ฆฌ๋ธ/ํ๋ซ ๊ฐ์ง) โ ์ง๋จ(ShinhanParseError)
lib/normalizeTransaction ์ปฌ๋ผ ๋ณ์นญ ๋งคํ, ๊ธ์ก/๋ ์ง/๋ฑ๊ธ/์ทจ์ ์ ๊ทํ, ์นด๋๋ฒํธ ๋ง์คํน
lib/classifyMerchant ํ๊ธ ๋ธ๋๋ โ ํธํ
alias DB(์ธ๋ฑ์ค) โ ์๋ฌธ ๋ธ๋๋ โ ๊ตญ๋ด known/์ด์์ฌ โ ํธํ
์ ์ฌ ํค์๋
lib/analyzeTransactions ์ํ ํ์ (ok_l5/missing_suspected/needs_review/not_marriott/canceled), ํผ๋๋ฐฑ ์ ์ฉ, ์์ฝ
lib/inquiryMessage ์นด๋์ฌ ๋ฌธ์ ๋ฌธ๊ตฌ ์์ฑ
lib/feedback (์ ํ) ์ต๋ช
ํผ๋๋ฐฑ + ํ์ฑ ์คํจ ์ํด๋ฆญ ์ ๋ณด โ ๊ธฐ๋ณธ์ Google Sheets(Apps Script), env ์์ผ๋ฉด no-op
rules/marriott*.ts ๋ธ๋๋ ํค์๋ยทํธํ
alias DB(149๊ฐ ๊ตญ๊ฐยท์ง์ญ 10,287๊ฐ ์ด์ ํ์ธ seed)ยทํ๋ณด/์ด์์ฌ ๋ฃฐ / rules/cardProfiles.ts ์นด๋ ํ๋กํ
rules/marriottPropertyOverrides.ts ์ฌ๋์ด ๊ฒ์ฆํ ๊ฐ๋งน์ alias(๊ณต์ seed์ ๋ถ๋ฆฌ)
data/marriott/ ์์ IDโ๊ณต์ code ๋ฉํ๋ฐ์ดํฐ, ๋ฑ๋ก ๋ณด๋ฅยท์์ค ๋๋ฝ ์์ธ, ์น์ธ snapshot
scripts/marriott/ Marriott/Ritz HWS XML+Bvlgari ๊ณต์ code ์์งยท๋น๊ตยท๋ณด๊ณ ์ ์์ฑ(์ฝ๊ธฐ ์ ์ฉ)
components/ GuidedProgress(3๋จ๊ณ ๋ ์ผยท๋ชจ๋ฐ์ผ ์งํ๋ฐ), FileUpload(์ ๋ณด UI ํฌํจ), SummaryCards, 3๊ฐ ํ
์ด๋ธ, InquiryMessage, InquirySend(์ ํ/1:1๋ฌธ์/OS๊ณต์ ์ฑ๋ ์ฐ๊ฒฐ โ ๋คํธ์ํฌ ์ ์ก ์์), Disclaimer
app/globals.css ๋์์ธ ์ ํ ํฐ(@theme: ember/ink/muted/hairline ๋ฑ) โ ์์ ๋ฐ๋์ ํ ํฐ ํด๋์ค(text-ember ๋ฑ)๋ก ์ฌ์ฉ, hex ํ๋์ฝ๋ฉ ๊ธ์ง(OG ์ด๋ฏธ์ง ์ ์ธ)
google-apps-script/Code.gs ๊ตฌ๊ธ ์ํธ ์์ง๊ธฐ(doPost) + ๋ฐฐํฌ ์๋ด
next.config.ts CSP ํค๋ (connect-src 'self' + Apps Script ์์ง๊ธฐ ๋๋ฉ์ธ ์๋ ์ถ๊ฐ)
๋ถ์ ๊ท์น ์์ : ์ทจ์โcanceled / L4ยทL5๋ ๊ฐ๋งน์ ๋ช
DB ์ธ์ ์ฌ๋ถ์ ๊ด๊ณ์์ด ok_l5 / ๊ทธ ์ธ ๋น๋ฉ๋ฆฌ์ดํธโnot_marriott / ํ์ (certainยทhigh)+์ ์ ๋๋ฝ ํจํด(๊ตญ๋ด L1โL4, ํด์ธ L2โL5)+์์์ฐจ์ดโmissing_suspected / ๊ทธ ์ธโneeds_review. effectiveIncluded = missing_suspected(์ ์ธ ์ ํ ๊ฒ) + ์ฌ์ฉ์๊ฐ โ
ํฌํจํ needs_review.
ํผ๋๋ฐฑ ์ ์ก ๋ชจ๋ธ: โ
/โ/๋ชจ๋ฅด๊ฒ ์ ๋ฒํผ์ ๋ก์ปฌ ์ํ(ํฉ๊ณยท๋ฌธ์ ๋ฌธ๊ตฌ)๋ง ๋ฐ๊พผ๋ค. DB ๋ฏธ๋ฑ๋ก L4/L5 ๊ฐ๋งน์ ๋ช
์ ์
๋ก๋ ๋ถ์ ์ ๋ก์ปฌ ์ ๋ณด ๋ชฉ๋ก์ ์๋ ํฌํจ๋๋ฉฐ ์ฌ์ฉ์๊ฐ ์ ์ธํ ์ ์๋ค. ์ค์ ์ํธ ์ ์ก์ ์ ๋ ๋ถ์ยท์ ํ ์์ ์ ํ์ง ์๊ณ , ์ฌ์ฉ์๊ฐ ๋ง์ง๋ง ๋จ๊ณ์์ ๋ช
์์ ์ผ๋ก "์๋น์ค ๊ฐ์ ์ ๋ณด ๋ณด๋ด๊ธฐ" ๋ฒํผ์ ๋๋ฅผ ๋ ๋ชฉ๋ก์ ์ต์ข
๋ด์ฉ๋ง ํ ๋ฒ์ submitJudgments๋ก ๋ณด๋ธ๋ค. ํ์ฑ ์คํจ ์ ๋ณด๋ ๋ณ๋ ๋ฒํผ(๋ช
์์ ). ๋ ๋ค collector env ์์ผ๋ฉด no-op/๋ณต์ฌ fallback.
False-negative ๊ตฌ์ : ์ฑ์ด ์์ ๋ชป ์ก์(not_marriott) ๊ฑฐ๋๋ฅผ ์ฌ์ฉ์๊ฐ ์ ์ฒด ๊ฑฐ๋ ํ์์ "๐จ ๋ฉ๋ฆฌ์ดํธ๋ก ํ์"ํ๋ฉด, applyFeedback์ด ์์ ํฌ์ธํธ๋ฅผ ๊ณ์ฐํด userDesignatedMarriott๋ก ๋งํนํ๊ณ ํฉ๊ณยท๋ฌธ์ ๋ฌธ๊ตฌ์ ๋ฐ์(status๋ not_marriott ์ ์ง โ ์ ์ฒด ๊ฑฐ๋ ํ์์ ํ ๊ธ). ์ํธ์ detected_status="user_designated"๋ก ์ ์ก๋ผ ๊ท์น ํ๋ณด ์ค ์ต์ฐ์ ์ ํธ๊ฐ ๋จ.
๊ฒ์ฆ
npm test # vitest (์ ๋ถ ํต๊ณผ ์ํ ์ ์งํ ๊ฒ)
npm run test:coverage # ํต์ฌ ๋ก์ง ์ปค๋ฒ๋ฆฌ์ง + ํํ์ ๊ฒ์ฆ
npm run test:e2e # Playwright ํฉ์ฑ XLSX ์
๋ก๋ยทํผ๋๋ฐฑยท๋ฐ์ํ ํ๋ฆ
npm run lint && npm run build
npm run fixture # docs/_local/sample.xlsx ์์ฑ (์ค์ ๋ ์ด์์ ๋ชจ์ฌ, gitignore)
npm run hotels:check # ๊ณต์ property-code ์์ค ๋ณ๊ฒฝ ํ์ธ(์๋ ์์ /merge ์์)
fixture ๊ธฐ๋๊ฐ: 16๊ฑด / Marriott 13 / ์ ์ 2 / ๋๋ฝ ์์ฌ 9 / ํ์ธ ํ์ 1(ํด์ธ L1) / ์์ ์ถ๊ฐ 6,005P (SAMMAEBONG์ ๋ฉ๋ฆฌ์ดํธ๋ก ํ์ ์ +1,200P). ์ค๋ฌผ ํ์ผ ๊ฒ์ฆ ๋ฐฉ๋ฒ์ docs/_local/HANDOFF.md ์ฐธ๊ณ . UI ๋ณ๊ฒฝ ์ Playwright๋ก ์
๋ก๋โ์์ฝ ์์นโํผ๋๋ฐฑ ๋ฒํผโ๋ฌธ์ ๋ฌธ๊ตฌ๊น์ง ์ค์ ๋ก ํ์ธํ ๊ฒ.
๋ฐฐํฌ
vercel --prod(ํ๋ก์ ํธ ๋งํฌยท์ธ์ฆ ์๋ฃ ์ํ). ๋ฐฐํฌ ์ ๋ฐ๋์ ํ ์คํธ+๋น๋+์ฌ์ฉ์ ์น์ธ.- ์์ง์ ์ผ๋ ค๋ฉด Google Sheets ๊ธฐ๋ฐ์ผ๋ก
google-apps-script/Code.gs๋ฅผ ๊ตฌ๊ธ ์ํธ์ ๋ถ์ฌ ์น ์ฑ ๋ฐฐํฌ โ ๊ทธ URL์ Vercel์NEXT_PUBLIC_APPS_SCRIPT_URL๋ก ์ค์ โ ์ฌ๋ฐฐํฌ. CSP connect-src๋ next.config.ts๊ฐ Google ๋๋ฉ์ธ์ ์๋ ์ถ๊ฐ.
๋ก๋๋งต (์ฌ์ฉ์ ํ์ธ๋ ๋ฐฉํฅ)
- ์์ง ํ์ฑํ โ Google Sheets(Apps Script) ๊ธฐ๋ฐ. ํผ๋๋ฐฑ ์์ง(๊ฐ๋งน์ DB๊ฐ ์ฅ๊ธฐ ์์ฐ) + ํ์ฑ ์คํจ ์ํด๋ฆญ ์ ๋ณด. ์ผ์ง๋ฉด ํ์ด์ง ํ๋จ ์์ง ๊ณ ์ง๊ฐ ์๋ ํ์๋จ. (์๋ฃ)
- ๋ ํด๋์ ์นด๋ ์ง์ โ cardProfiles์ ํ๋กํ ์ถ๊ฐ + ์นด๋ ์ ํ UI.
- ์ ์ธ๊ณ Marriott ํธํ
alias DB ํ์ฅ. ํ์ฌ ๊ตฌ์กฐ๋
rules/marriottProperties/์ ์ ์ธ๊ณ 149๊ฐ ๊ตญ๊ฐยท์ง์ญ 10,287๊ฐ ์ด์ ํ์ธ ํธํ ์ ํ์ฑ seed๋ก ๋ฃ๊ณ , ๊ณต์๋ช /์งง์ ์๋ฌธ๋ช /ํ๊ธ๋ช ์ ๊ฒฐ์ ์ ๋ฃฐ๋ก ๋์กฐํ๋ค. ์ด์ ์ ์ด๊ฑฐ๋ ๊ณต์ ํ์ด์ง์์ ์์ฝ์ด ์์ง ์ ๊ณต๋์ง ์๋ ํธํ ์data/marriott/property-review-holds.json์ ๋์ด ์๋ ๋๋ฝ ์์ฌ ๋ฐ ์ ๊ท ๋ฑ๋ก ํ๋ณด์์ ์ ์ธํ๋ค. ๊ณต์ ํ์ด์ง๋ก ์ด์์ ํ์ธํ์ง๋ง ํ์ฌ ์์งํ๋ XML ๋ฌถ์์ ์๋ ํธํ ์data/marriott/property-source-exceptions.json์ ๊ทผ๊ฑฐ์ ๊ฒํ ์ผ์ ๊ธฐ๋กํ๋ค. ๋ฏธ๊ตญ ์ฃผ ๋จ์ sitemap์us.ts์ ์ค๋ณต๋๋ฏ๋ก ์ ์ธํ๊ณ , Antarctica sitemap์ ํ ์คํธ ํธํ ๋ฐ์ดํฐ์ ๊ณต์ ์ ํด ์ข ๋ฃ ํญ๋ชฉ์ ์ ์ธํ๋ค. - ํผ๋๋ฐฑ/์ ๋ณด ์ด๋๋ฏผ ๋ฆฌ๋ทฐ ํ์ด์ง (v2). ์ฌ์ฉ์ ํผ๋๋ฐฑ์ ์ ๋ ์๋์ผ๋ก ๊ท์น์ด ๋์ง ์์: ์ง๊ณ โ ํ๋ณด โ ์๋ ๊ฒํ โ ๊ท์น.
- ์กฐ๊ฑด๋ถ ํ์ด๋ธ๋ฆฌ๋ ๋ฐฑ์๋ (v2.x) โ ๋น๊ณต๊ฐ๋ก ์ ์งํ ๊ฐ์น๊ฐ ์๋ ์๋ ๊ฒ์ฆ alias๊ฐ ์ถฉ๋ถํ ์์ธ ๊ฒฝ์ฐ์๋ง, ํ์ผ ๋ก์ปฌ ์ฒ๋ฆฌ๋ฅผ ์ ์งํ๋ฉด์ ๋น๊ณต๊ฐ alias ์กฐํ API๋ฅผ ์ถ๊ฐํ๋ ๋ฐฉ์์ ์ฌ๊ฒํ ํ๋ค. ์์ธ ๊ณํ์ ์๋ ์ฐธ๊ณ .
v2.0 โ ๊ฐ๋งน์ DB ํ๋ ์ด์ ํฌํผ (LLM, ๋ฏธ์ฐฉ์)
์ฌ์ฉ์ ํ์ธ(2026-07-06): ๋ก๋๋งต์๋ง ์ก์๋๊ณ ์ง๊ธ์ ์ ๋ ๊ตฌํํ์ง ์์.
์ํธ์ ์์ธ ์ ๋ณด(ํนํ user_designated ๊ฐ๋งน์ : ์ฑ์ด ๋ชป ์ก์๋๋ฐ ์ฌ์ฉ์๊ฐ ๋ฉ๋ฆฌ์ดํธ๋ผ๊ณ ํ์ํ ๊ฒ)๋ฅผ ์ด์์ ์ ์ฉ ๋๊ตฌ์์ LLM์ผ๋ก ๊ท์น ํ๋ณด๋ฅผ ์ถ๋ฆฌ๋ ๊ฒ. ๋ฐ๋์ ์ง์ผ์ผ ํ ์ ์ฝ:
- ์ฌ์ฉ์ ์ฑ ๊ฒฝ๋ก์ ์ ๋ LLM ์์ โ ํ์ ยท๊ณ์ฐ์ ์์ํ ๊ฒฐ์ ์ ๊ท์น. ์ด๊ฑด ์ด ์ฑ์ ํ๋ผ์ด๋ฒ์ยท์ ๋ขฐ ์คํ ๋ฆฌ(ํ์ผ์ด ๋ธ๋ผ์ฐ์ ๋ฐ์ผ๋ก ์ ๋๊ฐ, CSP
connect-src 'self')์ ํต์ฌ. - LLM์ ์ง๊ณ๋ ๊ฐ๋งน์ ๋ช (๊ฐ์ธ ๊ฑฐ๋ ํ๋กํ ์๋) ๋ง ๋ค๋ฃจ๋ ๋ก์ปฌ/๊ด๋ฆฌ์ ์คํฌ๋ฆฝํธ๋ก, ํน์ ์ฌ์ฉ์ ํ์ผ์ ์ ๊ทผํ์ง ์์.
- LLM์ ํ๋ณด๋ง ์ ์,
rules/marriottProperties.ts/rules/marriottOverrides.ts๋ฑ๋ก์ ์ฌ๋ ์๋ ์น์ธ. ์๋ ๊ท์นํ ์ ๋ ๊ธ์ง(์คํ ์์น ์ ์ง). - ์ฆ "์ง๊ณ โ (LLM์ด ํ๋ณด ์ ์) โ ์ฌ๋ ๊ฒํ โ ๊ท์น"์์ ํ๋ณด ๋จ๊ณ๋ง ๊ฐ์.
v2.x โ ๋น๊ณต๊ฐ alias ์กฐํ์ฉ ํ์ด๋ธ๋ฆฌ๋ ๋ฐฑ์๋ (์กฐ๊ฑด๋ถยท๋ฏธ์ฐฉ์)
์ฌ์ฉ์ ํ์ธ(2026-07-11): ๋์ค์ ์ํ ์คํ ๊ฐ๋ฅ์ฑ ๊ฒํ ์ฉ ๊ณํ์ด๋ค. ํ์ฌ ๋ฒ์ ์ ์ ์ ยท๋ก์ปฌ ๋ถ์ ๊ตฌ์กฐ๋ฅผ ์ ์งํ๋ฉฐ, ๋ณ๋ ์น์ธ ์ ์๋ ๊ตฌํํ์ง ์๋๋ค.
๋ชฉํ: ๊ณต์ ํธํ DB์ ์ผ๋ฐ ๊ท์น์ ๊ณ์ ๊ณต๊ฐ ํด๋ผ์ด์ธํธ์์ ์ฌ์ฉํ๋, ์ฌ์ฉ์ ์ ๋ณด๋ก ๋ฐ๊ฒฌํ๊ณ ์ด์์๊ฐ ๊ฒ์ฆํ ๊ณ ๊ฐ์น alias๋ง ์๋ฒ์ ๋น๊ณต๊ฐ DB์ ๋ณด๊ดํ๋ค. ์ ์ฒด ์์ ์ ์๋ฒํ์ผ๋ก ์ ํํ์ง ์๊ณ , ๊ณต๊ฐ ๊ท์น์ผ๋ก ํ์ ํ์ง ๋ชปํ ๊ฐ๋งน์ ๋ช ๋ง ์ ํ์ ์ผ๋ก ์กฐํํ๋ ๊ตฌ์กฐ๋ค.
๋์ ํ๋จ ์กฐ๊ฑด โ ๋ค์ ์กฐ๊ฑด์ด ๋ชจ๋ ์ถฉ์กฑ๋ ๋๋ง ๊ตฌํ์ ๊ฒํ ํ๋ค.
- ๊ณต๊ฐ seed์์ ์ฝ๊ฒ ํ์๋์ง ์๋ ์๋ ๊ฒ์ฆ alias๊ฐ ์ถฉ๋ถํ ์์ฌ, ๋ณ๋ ์ด์ ๋ณต์ก์ฑ๊ณผ ๋น์ฉ์ ๊ฐ์ํ ๊ฐ์น๊ฐ ์๋ค.
- ์ฌ์ฉ์๊ฐ ์ ํ์ ์กฐํ ์ ๊ฐ๋งน์ ๋ช ์ด ์๋ฒ๋ก ์ ์ก๋๋ค๋ ๊ณ ์ง๋ฅผ ์์ฉํ ์ ์๋ค.
- DB ๋ฐฑ์ ยท๊ท์น ๊ฒํ ยท์ฅ์ ๋์ยท๋น์ฉ์ ์ด์์๊ฐ ์ง์ํด์ ๊ด๋ฆฌํ ์ ์๋ค.
- ๊ณต๊ฐ API๋ฅผ ํตํ ๊ฐ๋ณ ์ง์๋ก alias ์กด์ฌ ์ฌ๋ถ๊ฐ ์ถ์ ๋ ์ ์๋ค๋ ํ๊ณ๋ฅผ ๊ฐ์ํ๊ฑฐ๋, ํ์ํ๋ฉด ์ธ์ฆ ๋ชจ๋ธ์ ์ถ๊ฐํ ์ ์๋ค.
๋ชฉํ ํ๋ฆ:
- ์์ ํ์ฑยท์ปฌ๋ผ ์ ๊ทํ๋ ์ง๊ธ์ฒ๋ผ 100% ๋ธ๋ผ์ฐ์ ์์ ์ํํ๋ค.
- ๊ณต๊ฐ ๋ธ๋๋/ํธํ seed/known rule๋ก ๋จผ์ ๋ก์ปฌ ํ์ ํ๋ค.
- ์ฌ์ฉ์๊ฐ ๋ช
์์ ์ผ๋ก ์ถ๊ฐ ์กฐํ๋ฅผ ์ ํํ์ ๋๋ง, ๋ฏธ๋งค์นญยทํ์ธ ํ์ ๊ฐ๋งน์ ๋ช
์ ์ ๊ทํํ๊ณ ์ค๋ณต ์ ๊ฑฐํด same-origin
POST /api/classify๋ก ๋ณด๋ธ๋ค. - Next.js App Router์ Route Handler๊ฐ Vercel Function์ผ๋ก ์คํ๋์ด ๋น๊ณต๊ฐ Supabase/Postgres alias ํ ์ด๋ธ์ ์กฐํํ๊ณ , ๋์ผํ ๊ฒฐ์ ์ ๊ท์น์ผ๋ก ํ์ ํ๋ค. ๋ณ๋ Express ์๋ฒ๋ ๋์ง ์๋๋ค.
- ์๋ต์ ์์ฒญ ์์์๋ง ์ฐ๋ ์์ opaque key, ๋งค์นญ ์ฌ๋ถ, ์ ๊ทํ ํธํ
๋ช
,
region,confidence,ruleVersion๋ง ๋ฐํํ๋ค. ํ์ฌ ๊ฑฐ๋ ID๋ ๊ฑฐ๋ ๋ด์ฉ์์ ํ์ํ ID, ์๋ณธ pattern ๋ชฉ๋ก, DB ํ์ ๋ฐํํ์ง ์๋๋ค. - ํฌ์ธํธ ๊ณ์ฐยท๋ฑ๊ธ ํ์ ยทํฉ๊ณยท๋ฌธ์ ๋ฌธ๊ตฌ ์์ฑ์ ๊ณ์ ๋ธ๋ผ์ฐ์ ์์ ์ํํ๋ค. API ์คํจ ์ ๊ณต๊ฐ ๋ก์ปฌ ๊ท์น ๊ฒฐ๊ณผ๋ก ์ ์ fallbackํ๋ค.
์๋ฒ๋ก ์ ๋ ๋ณด๋ด์ง ์์ ๊ฒ:
- ์ ๋ก๋ ํ์ผ/์ํฌ์ํธ ์๋ฌธ, ์ฌ์ฉ์ ์ด๋ฆ, ์นด๋๋ฒํธ
- ๊ฒฐ์ ๊ธ์ก, ๊ฑฐ๋์ผ์, ํฌ์ธํธ ๊ธ์กยท๋ฑ๊ธ, ๊ฐ๋งน์ ๋ช ์ธ์ ๊ฑฐ๋ ํ๋, ์๋ณธ ํ ์์
- ํ์ ์ ํ์ํ์ง ์์ ์ธ์ ์๋ณ์์ ๋ธ๋ผ์ฐ์ ์ ๋ณด
๋ณด์ยทํ๋ผ์ด๋ฒ์ ์กฐ๊ฑด:
- DB ์๊ฒฉ ์ฆ๋ช
์ ์๋ฒ ์ ์ฉ ํ๊ฒฝ ๋ณ์๋ก๋ง ์ฌ์ฉํ๊ณ
NEXT_PUBLIC_์ ์ ๋ ๋ฃ์ง ์๋๋ค. ๊ฐ๋ฅํ๋ฉด ์ ์ฉ read-only DB role/RPC์ ๋ฐํ ํ๋ allowlist๋ฅผ ์ฌ์ฉํ๋ค. RLS๋ฅผ ์ฐํํ๋ service role์ด ๋ถ๊ฐํผํ๋ฉด API์ ๋จ์ผ ๋ฐ์ดํฐ ์ ๊ทผ ๊ณ์ธต ๋ฐ์์๋ ์ฌ์ฉํ์ง ์๋๋ค. - ์์ฒญ schema, ๋ฐฐ์น ๊ฐ์, ๋ฌธ์์ด ๊ธธ์ด๋ฅผ ์ ํํ๊ณ , ๋ฐฐํฌ ์ ๋ถํฐ ์๋ฒ๋ฆฌ์ค์์๋ ์ ์ง๋๋ rate limit/์ฟผํฐ์ abuse ๋ชจ๋ํฐ๋ง์ ๋๋ค. ์๋ฌธ request body๋ฅผ ์ ํ๋ฆฌ์ผ์ด์ ๋ก๊ทธ์ ๋จ๊ธฐ์ง ์๋๋ค.
connect-src 'self'๋ same-origin API ์์ฒด๋ฅผ ํ์ฉํ๋ฏ๋ก, ์๋ฒ ๋์ ํ์๋ CSP๋ง์ผ๋ก "ํ์ผ ๋ฏธ์ ์ก"์ ์ฆ๋ช ํ ์ ์๋ค. ํด๋ผ์ด์ธํธ๊ฐ ์ ์ก ํ๋ allowlist๋ก payload๋ฅผ ์๋ก ๋ง๋ค๊ณ , ์๋ฒ๋ ์ ์ ์๋ ํ๋๋ฅผ ๊ฑฐ๋ถํ๋ฉฐ, ๋คํธ์ํฌ E2E ํ ์คํธ๋ก ๊ธ์ง ํ๋๊ฐ ์ ์ก๋์ง ์๋์ง ๊ฒ์ฆํ๋ค.- ๋น๊ณต๊ฐ DB๋ ๋ฒ๋คยท์์ค์ ์ผ๊ด ๋ ธ์ถ์ ๋ง์ง๋ง, ๊ณต๊ฐ ์กฐํ API์ ๋ฐ๋ณต ํธ์ถ์ ์ํ ์ด๊ฑฐ ๊ฐ๋ฅ์ฑ๊น์ง ์์ ํ ์์ ์ง๋ ๋ชปํ๋ค. ๊ฐํ ๋ ์ ์ฑ์ด ํ์ํด์ง๋ฉด ๋ก๊ทธ์ธยท์ฟผํฐยท์ ๋ฃ ์ ๊ทผ์ ๋ณ๋ ์ ํ ๊ฒฐ์ ์ผ๋ก ๊ฒํ ํ๋ค.
- ์ฌ์ฉ์ ์ฑ์ ํ์ ๊ฒฝ๋ก์๋ LLM์ ๋ฃ์ง ์๋๋ค. ์๋ฒ ์กฐํ๋ ์ฌ๋์ด ์น์ธํ alias๋ฅผ ์ฌ์ฉํ๋ ๊ฒฐ์ ์ ๋ก์ง๋ง ํ์ฉํ๋ค.
๊ถ์ฅ ๊ธฐ์ ์คํ:
- ๊ธฐ์กด Next.js/React/SheetJS ํด๋ผ์ด์ธํธ ์ ์ง
app/api/classify/route.tsRoute Handler + Vercel Function- Supabase Postgres ๋น๊ณต๊ฐ alias ํ ์ด๋ธ + ์๋ฒ ์ ์ฉ DB ํด๋ผ์ด์ธํธ
- ์ ๋ ฅ ๊ฒ์ฆ(Zod ๋๋ ๋๋ฑํ ๊ฒฐ์ ์ schema), Vitest API ํ ์คํธ, Playwright ๋คํธ์ํฌ payload E2E
- ์๋ฒ๋ฆฌ์ค ํธ์ถ ๊ฐ์๋ ์ ์ง๋๋ ํ๋ซํผ ๋๋ ์ธ๋ถ rate-limit ์ ์ฅ์๋ฅผ ์ฒ์๋ถํฐ ์ ์ฉํ๊ณ , ์์ ์์ฒญ ํ๋ยท์ฟผํฐ๋ก ์์
๊ตฌํ ๋จ๊ณ:
- ์์ฌ๊ฒฐ์ ๊ธฐ๋ก โ ์ ์ก๋๋ ๊ฐ๋งน์ ๋ช ๋ฒ์, ๋ช ์์ ๋์ UX, ๋ณด๊ดยท๋ก๊ทธ ์ ์ฑ , ์ด์๋น ์ํ์ ํ์ ํ๋ค.
- ๋ฐ์ดํฐ ๊ณ์ธต โ
active/needs_review/rejected, ์ถ์ฒ, ๊ฒํ ์ผ, rule version์ ๊ฐ์ง alias schema์ ๋ง์ด๊ทธ๋ ์ด์ ยท๋ฐฑ์ ์ ์ฐจ๋ฅผ ๋ง๋ ๋ค. ์ฌ์ฉ์ ํ์ ์๋ ์ฌ๋์ด ์น์ธํactiveํ๋ง ์ฌ์ฉํ๊ณ , ๊ณต๊ฐ seed์ private alias์ ๊ฒฝ๊ณ๋ฅผ ๋ฌธ์ํํ๋ค. - ์ฝ๊ธฐ ์ ์ฉ API โ ์ธ์ฆ์ ๋ณด๋ฅผ ์๋ฒ์๋ง ๋ ์ต์ ์๋ต API๋ฅผ ๊ตฌํํ๊ณ ์ ๋ ฅ ์ ํยทtimeoutยทfallbackยทrate limit์ ์ ์ฉํ๋ค.
- ์ ํ์ ํด๋ผ์ด์ธํธ ์ฐ๊ฒฐ โ ๊ณต๊ฐ ๋ก์ปฌ ํ์ ํ ์ฌ์ฉ์๊ฐ ๋์ํ ๋ฏธํ์ ์ด๋ฆ๋ง ์ ์กํ๋ค. ๊ธฐ๋ฅ ํ๋๊ทธ/env๊ฐ ์์ผ๋ฉด ํ์ฌ ๋ก์ปฌ ์ ์ฉ ๋์์ ์ ์งํ๋ค.
- ๊ฒ์ฆ โ API ๋จ์/ํตํฉ ํ ์คํธ์ Playwright ์์ฒญ ๊ฐ๋ก์ฑ๊ธฐ๋ก ํ์ผยท๊ธ์กยท๋ ์งยท์นด๋๋ฒํธยทํฌ์ธํธ ์ ๋ณด๊ฐ payload์ ์๋์ง ๊ฒ์ฌํ๋ค. DB rule๊ณผ ๊ธฐ์กด ๋ก์ปฌ rule์ ํ๊ท ๊ฒฐ๊ณผ๋ ๋น๊ตํ๋ค.
- ๊ณ ์งยท์ ์ง ๋ฐฐํฌ โ README/UI ๊ฐ์ธ์ ๋ณด ์ค๋ช ์ "ํ์ผ์ ๋ก์ปฌ ์ฒ๋ฆฌ, ์ ํ ์ ๊ฐ๋งน์ ๋ช ๋ง ์ถ๊ฐ ์กฐํ"๋ก ์ ํํ ๋ฐ๊พธ๊ณ preview์์ ํ์ธํ ๋ค ์ ์ง ๋ฐฐํฌํ๋ค. ๋ฌธ์ ๊ฐ ์๊ธฐ๋ฉด ๊ธฐ๋ฅ ํ๋๊ทธ๋ฅผ ๊บผ์ ๋ก์ปฌ ์ ์ฉ ๊ตฌ์กฐ๋ก ์ฆ์ ๋ณต๊ทํ๋ค.
1์ฐจ ๋ฒ์์์ ์ ์ธ: ์์ ์ ๋ก๋ API, ์๋ฒ ์ธก ํฌ์ธํธ ๊ณ์ฐ, ์ฌ์ฉ์ ๊ณ์ /๊ฒฐ์ , ๊ธฐ์กด Apps Script ์ ๋ณด ์์ง๊ธฐ์ DB ์ด๊ด, ์๋ alias ์น์ธ, ์ฌ์ฉ์ ์ฑ ๋ด LLM. ์ด๋ค์ ๊ฐ๊ฐ ๋ณ๋ ํ์์ฑ๊ณผ ์น์ธ์ ๊ฒํ ํ๋ค.
