deptmatch-api / PRD.md
maninglearchine's picture
feat: ๊ธˆ์œต๊ณต์‹œ ํฌ๋กค๋ง + BGE-M3 ๋ถ€์„œ ์ž๋™ ๋งค์นญ ์„œ๋น„์Šค ์ดˆ๊ธฐ ์ปค๋ฐ‹
12f85b6
|
Raw
History Blame Contribute Delete
26.2 kB

PRD: ๊ธˆ์œต๊ณต์‹œ ๋ฐ ์ฃผ์š” ๊ณต๊ณ  ํฌ๋กค๋ง + ๋ถ€์„œ ์ž๋™ ๋งค์นญ ์„œ๋น„์Šค

๋ฌธ์„œ ๋ฒ„์ „: v2.0
์ตœ์ดˆ ์ž‘์„ฑ์ผ: 2026-06-17
์ตœ์ข… ์ˆ˜์ •์ผ: 2026-06-19
์ฃผ์š” ๋ณ€๊ฒฝ (v1.0 โ†’ v2.0): BGE-M3 ๋ชจ๋ธ ์ „ํ™˜, Cross-Encoder ์žฌ์ˆœ์œ„ํ™” ์ถ”๊ฐ€, ๋‹จ์ผ ๋ชจ๋ธ ๋””๋ ‰ํ„ฐ๋ฆฌ ๊ตฌ์กฐ๋กœ ํ†ตํ•ฉ, ๋ฐฑ์—”๋“œ ์‹ค์ œ ๊ตฌํ˜„ ๋ฐ˜์˜


1. ๊ฐœ์š” (Overview)

1.1 ๋ฐฐ๊ฒฝ

๊ธˆ์œต์œ„์›ํšŒ, ๊ธˆ์œต๊ฐ๋…์›, ๋ฒ•์ œ์ฒ˜ ๋“ฑ ๊ธˆ์œต ์œ ๊ด€ ๊ธฐ๊ด€์€ ๋งค์ผ ์ˆ˜์‹ญ ๊ฑด์˜ ๋ณด๋„์ž๋ฃŒยท๊ณต์‹œยท๊ณต๊ณ ๋ฅผ ๋ฐœํ–‰ํ•œ๋‹ค. ๋‹ด๋‹น ๋ถ€์„œ ์ง์›์€ ์ด๋ฅผ ์ˆ˜๋™์œผ๋กœ ๋ชจ๋‹ˆํ„ฐ๋งํ•˜๊ณ  ๋ณธ์ธ ์—…๋ฌด ๊ด€๋ จ ํ•ญ๋ชฉ์„ ์„ ๋ณ„ํ•ด์•ผ ํ•˜๋Š”๋ฐ, ๊ธฐ๊ด€๋ณ„ ์‚ฌ์ดํŠธ๋ฅผ ์ง์ ‘ ๋ฐฉ๋ฌธํ•ด์•ผ ํ•˜๊ณ  "์ด ๊ณต๊ณ ๊ฐ€ ์–ด๋А ํŒ€ ์†Œ๊ด€์ธ์ง€" ํŒŒ์•…ํ•˜๋Š” ๋ฐ ์ถ”๊ฐ€ ์‹œ๊ฐ„์ด ์†Œ์š”๋œ๋‹ค.

1.2 ์„œ๋น„์Šค ๋ชฉ์ 

  • ์ฃผ์š” ๊ธˆ์œต ๊ธฐ๊ด€์˜ ๊ณต์‹œยท๊ณต๊ณ ๋ฅผ ์‹œ๊ฐ„ ๋‹จ์œ„๋กœ ์ž๋™ ์ˆ˜์ง‘
  • ๊ฐ ๊ณต๊ณ ์˜ ๋ณธ๋ฌธ ๋‚ด์šฉ์„ ๋ถ„์„ํ•˜์—ฌ ์‚ฌ๋‚ด ๋‹ด๋‹น ๋ถ€์„œ๋ฅผ ์ž๋™ ๋งค์นญํ•ด ํ•จ๊ป˜ ๋…ธ์ถœ
  • ๋‹ด๋‹น์ž๊ฐ€ ๋‹จ์ผ ํ™”๋ฉด์—์„œ ๊ด€๋ จ ๊ณต์‹œ๋ฅผ ๋น ๋ฅด๊ฒŒ ํŒŒ์•…ํ•˜๊ณ  ํ›„์† ์กฐ์น˜๋ฅผ ์ทจํ•  ์ˆ˜ ์žˆ๋„๋ก ์ง€์›

1.3 ์ฐธ์กฐ ์„œ๋น„์Šค

  • ํ”„๋ก ํŠธ์—”๋“œ: newstogram.xyz (๊ธˆ์œต๋‹น๊ตญ ๋ชจ๋‹ˆํ„ฐ๋ง ๋ด‡) โ€” ๊ธฐ๊ด€๋ณ„ ์นดํ…Œ๊ณ ๋ฆฌ, ๊ธฐ๊ฐ„ ํ•„ํ„ฐ, Telegram ์—ฐ๋™ ๊ตฌ์กฐ ์ฐธ์กฐ
  • ๋ฐฑ์—”๋“œ ๋ชจ๋ธ: v4.1.ipynb โ€” BAAI/bge-m3 ๊ธฐ๋ฐ˜ ๋‘ ๋‹จ๊ณ„ ํŒŒ์ธํŠœ๋‹ + Cross-Encoder ์žฌ์ˆœ์œ„ํ™”

2. ๋ชฉํ‘œ ๋ฐ ์„ฑ๊ณต ์ง€ํ‘œ (Goals & KPIs)

๋ชฉํ‘œ ์ธก์ • ์ง€ํ‘œ ๋ชฉํ‘œ๊ฐ’
๊ณต์‹œ ์ˆ˜์ง‘ ์ง€์—ฐ ์ตœ์†Œํ™” ๊ธฐ๊ด€ ์›๋ฌธ ๊ฒŒ์‹œ โ†’ ์„œ๋น„์Šค ๋…ธ์ถœ ์†Œ์š” ์‹œ๊ฐ„ โ‰ค 1์‹œ๊ฐ„
๋ถ€์„œ ๋งค์นญ ์ •ํ™•๋„ Top-1 ์ •ํ™•๋„ (๋‚ด๋ถ€ ํ…Œ์ŠคํŠธ์…‹ ๊ธฐ์ค€) โ‰ฅ 80%
๋ถ€์„œ ๋งค์นญ ํ›„๋ณด ํฌํ•จ๋ฅ  Top-5 ๋‚ด ์ •๋‹ต ๋ถ€์„œ ํฌํ•จ์œจ โ‰ฅ 95%
์„œ๋น„์Šค ๊ฐ€์šฉ์„ฑ ์›”๊ฐ„ ์—…ํƒ€์ž„ โ‰ฅ 99.5%
ํฌ๋กค๋ง ์„ฑ๊ณต๋ฅ  ์ˆ˜์ง‘ ์‹œ๋„ ๋Œ€๋น„ ์„ฑ๊ณต ๊ฑด์ˆ˜ โ‰ฅ 99%

3. ์‚ฌ์šฉ์ž ๋ฐ ์ดํ•ด๊ด€๊ณ„์ž (Stakeholders)

๊ตฌ๋ถ„ ์—ญํ•  ์ฃผ์š” ๋‹ˆ์ฆˆ
๋‚ด๋ถ€ ์—…๋ฌด ๋‹ด๋‹น์ž 1์ฐจ ์‚ฌ์šฉ์ž ๋ณธ์ธ ๋ถ€์„œ ์†Œ๊ด€ ๊ณต๊ณ  ๋น ๋ฅธ ํŒŒ์•…
๋ถ€์„œ ๊ด€๋ฆฌ์ž 2์ฐจ ์‚ฌ์šฉ์ž ํŒ€ ์ „์ฒด ๋ชจ๋‹ˆํ„ฐ๋ง ํ˜„ํ™ฉ ํŒŒ์•…
์ปดํ”Œ๋ผ์ด์–ธ์Šค ํŒ€ ์ดํ•ด๊ด€๊ณ„์ž ๊ทœ์ œ ๋ณ€ํ™” ๋Œ€์‘ ํ˜„ํ™ฉ ์ถ”์ 
IT/๊ฐœ๋ฐœํŒ€ ์šด์˜์ž ํฌ๋กค๋Ÿฌ ์•ˆ์ •์„ฑ, ๋ชจ๋ธ ์žฌํ•™์Šต ๊ด€๋ฆฌ

4. ํฌ๋กค๋ง ๋Œ€์ƒ ๋ฐ ๋ฒ”์œ„ (Data Sources)

4.1 ์ˆ˜์ง‘ ๊ธฐ๊ด€ ๋ฐ ์นดํ…Œ๊ณ ๋ฆฌ

๊ธฐ๊ด€ ์นดํ…Œ๊ณ ๋ฆฌ ์ˆ˜์ง‘ ์ฃผ๊ธฐ URL ํŒจํ„ด
๊ธˆ์œต์œ„์›ํšŒ ๋ณด๋„์ž๋ฃŒ, ๊ณ ์‹œยท๊ณต๊ณ , ์˜๊ฒฐ ๊ฒฐ๊ณผ ๋งค 1์‹œ๊ฐ„ fsc.go.kr
๊ธˆ์œต๊ฐ๋…์› ๋ณด๋„์ž๋ฃŒ, ์ œ์žฌยท์ œ๋„ ๊ณต์‹œ ๋งค 1์‹œ๊ฐ„ fss.or.kr
๊ธˆ์œต์ •๋ณด๋ถ„์„์› (KoFIU) ๋ณด๋„์ž๋ฃŒ, ๊ณ ์‹œ ๋งค 1์‹œ๊ฐ„ kofiu.go.kr
๋ฒ•๋ นํ•ด์„ํฌํ„ธ (๋ฒ•์ œ์ฒ˜) ๋ฒ•๋ นํ•ด์„, ์˜๊ฒฌ์„œ ๋งค 2์‹œ๊ฐ„ moleg.go.kr
ํ•œ๊ตญ์€ํ–‰ ๊ธˆํ†ต์œ„ ์˜๊ฒฐ, ๋ณด๋„์ž๋ฃŒ ๋งค 2์‹œ๊ฐ„ bok.or.kr

์ฐธ์กฐ: newstogram.xyz์˜ ๊ธฐ๊ด€ ๊ตฌ์กฐ(๊ธˆ์œต๊ฐ๋…์›ยท๊ธˆ์œต์œ„์›ํšŒยท๊ธˆ์œต์ •๋ณด๋ถ„์„์›ยท๋ฒ•๋ นํ•ด์„ํฌํ„ธยทํ•œ๊ตญ์€ํ–‰)๋ฅผ ๊ธฐ๋ฐ˜์œผ๋กœ ์„ค๊ณ„

4.2 ์ˆ˜์ง‘ ํ•„๋“œ

- ์ œ๋ชฉ (title)
- ๋ฐœํ–‰ ๊ธฐ๊ด€ (source_agency)
- ์นดํ…Œ๊ณ ๋ฆฌ (category)
- ๊ฒŒ์‹œ์ผ์‹œ (published_at)
- ์›๋ฌธ URL (url)
- ๋ณธ๋ฌธ ํ…์ŠคํŠธ (body_text)
- ๋‹ด๋‹น๋ถ€์„œ๋ช… (author_dept) โ€” ์›๋ฌธ์— ๊ธฐ์žฌ๋œ ๊ฒฝ์šฐ ์ถ”์ถœ
- ๋‹ด๋‹น์ž๋ช… / ์—ฐ๋ฝ์ฒ˜ (contact_info) โ€” ์›๋ฌธ์— ๊ธฐ์žฌ๋œ ๊ฒฝ์šฐ ์ถ”์ถœ
- ์ฒจ๋ถ€ํŒŒ์ผ ๋งํฌ ๋ชฉ๋ก (attachments)

5. ๋ถ€์„œ ์ž๋™ ๋งค์นญ ๊ธฐ๋Šฅ (Department Auto-Matching)

5.1 ๋ชจ๋ธ ๊ฐœ์š” (v4.1.ipynb ๊ธฐ๋ฐ˜)

ํ•ต์‹ฌ ๊ตฌ์กฐ: BGE-M3 Hybrid Retrieval โ†’ Cross-Encoder Re-ranking

๊ณต์‹œ ๋ณธ๋ฌธ ํ…์ŠคํŠธ (query, ๊ธธ์ด ์ œํ•œ ์—†์Œ โ€” BGE-M3 ์ตœ๋Œ€ 8192 ํ† ํฐ)
        โ†“ preprocess(): ์ œ๋ชฉ + ๋‹ด๋‹น๋ถ€์„œ ํžŒํŠธ + ๋ณธ๋ฌธ ๊ตฌ์กฐํ™”
  โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
  โ”‚  ํ•™์Šต ํŒŒ์ดํ”„๋ผ์ธ (Google Colab GPU, v4.1.ipynb)       โ”‚
  โ”‚                                                     โ”‚
  โ”‚  Stage 1: ๋„๋ฉ”์ธ ์ ์‘                                โ”‚
  โ”‚    Base  : BAAI/bge-m3 (570M ํŒŒ๋ผ๋ฏธํ„ฐ, 8192 ํ† ํฐ)    โ”‚
  โ”‚    ๋ฐ์ดํ„ฐ : ์—…๋ฌด๋‹ด๋‹น๊ทœ์ •.docx (๋ฌธ๋‹จ ๋‹จ์œ„ self-positive) โ”‚
  โ”‚    Loss  : MultipleNegativesRankingLoss              โ”‚
  โ”‚       โ†“ ์ €์žฅ โ†’ bge_m3_finetuned/                   โ”‚
  โ”‚                                                     โ”‚
  โ”‚  Stage 2: ๋ถ€์„œ ๋งค์นญ ํŠนํ™”                              โ”‚
  โ”‚    Base  : bge_m3_finetuned/ (Stage 1 ์ถœ๋ ฅ)          โ”‚
  โ”‚    ๋ฐ์ดํ„ฐ : dept.xlsx (๋‹จ์ผ ์—…๋ฌด๋ฌธ์žฅ + ๋ถ€์„œ+์—…๋ฌด ์กฐํ•ฉ   โ”‚
  โ”‚            + ๋ฌด์ž‘์œ„ 3~5๊ฐœ ์—…๋ฌด ๊ฒฐํ•ฉ ์ฆ๊ฐ•)             โ”‚
  โ”‚    Loss  : MultipleNegativesRankingLoss              โ”‚
  โ”‚       โ†“ ์ €์žฅ โ†’ bge_m3_finetuned/ (๋ฎ์–ด์“ฐ๊ธฐ)         โ”‚
  โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜
        โ†“ (์ตœ์ข… ๋ชจ๋ธ: bge_m3_finetuned/)
  โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
  โ”‚ Dense Score  โ”‚ TF-IDF Score โ”‚ BM25 Score   โ”‚
  โ”‚ BGE-M3 ์ž„๋ฒ ๋”ฉโ”‚ (char 2-5gram)โ”‚ (ํ† ํฐ ๊ธฐ๋ฐ˜)  โ”‚
  โ”‚    55%       โ”‚    25%       โ”‚    20%       โ”‚
  โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜
        โ†“ Min-Max ์ •๊ทœํ™” + ๊ฐ€์ค‘ ํ•ฉ์‚ฐ
  ์ƒ์œ„ 120๊ฐœ ํ›„๋ณด (CANDIDATE_K=120)
        โ†“
  โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
  โ”‚  Cross-Encoder Re-ranking (RERANK_TOP_K=10) โ”‚
  โ”‚  ๋ชจ๋ธ: BAAI/bge-reranker-v2-m3              โ”‚
  โ”‚  confidence = sigmoid(s0)*0.7 + gap*0.3     โ”‚
  โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜
        โ†“
  ์ตœ์ข… ๋ถ€์„œ 1๊ฑด + confidence_score ๋ฐ˜ํ™˜

์ด์ „ ๋ชจ๋ธ(v3.ipynb) ๋Œ€๋น„ ๋ณ€๊ฒฝ ์‚ฌํ•ญ:

ํ•ญ๋ชฉ v3 (๊ตฌ) v4.1 (ํ˜„์žฌ)
Base ๋ชจ๋ธ jhgan/ko-sroberta-multitask (122M) BAAI/bge-m3 (570M)
์ตœ๋Œ€ ์ž…๋ ฅ ๊ธธ์ด 512 ํ† ํฐ (๊ธธ์ด ์ดˆ๊ณผ ์‹œ ์ •๋ณด ์†์‹ค) 8192 ํ† ํฐ (์ „์ฒด ๋ณธ๋ฌธ ์ฒ˜๋ฆฌ ๊ฐ€๋Šฅ)
์–ธ์–ด ์ง€์› ํ•œ๊ตญ์–ด ์ „์šฉ ๋‹ค๊ตญ์–ด (ํ•œ๊ตญ์–ด ํฌํ•จ)
Re-ranking ์—†์Œ Cross-Encoder (bge-reranker-v2-m3)
๋ชจ๋ธ ํ›„๋ณด ์ˆ˜์ง‘ top-80 โ†’ ๋‹ค์ˆ˜๊ฒฐ top-5 top-120 โ†’ Cross-Encoder top-10 โ†’ top-1
์‹ ๋ขฐ๋„ ๊ณ„์‚ฐ bi-encoder ์ ์ˆ˜ ์ง์ ‘ ์‚ฌ์šฉ sigmoid ์ •๊ทœํ™” + gap ๊ฐ€์ค‘์น˜
๋ชจ๋ธ ์ €์žฅ ๊ฒฝ๋กœ stage1_domain_retriever/, stage2_dept_retriever/ bge_m3_finetuned/ (๋‹จ์ผ ๊ฒฝ๋กœ)

5.2 ์ž…๋ ฅ ๋ฐ์ดํ„ฐ (dept.xlsx)

์ปฌ๋Ÿผ ์„ค๋ช…
HNG_BR_NM ๋ถ€์„œ๋ช… (ํ•œ๊ธ€)
OFWRK_NM ํ•ด๋‹น ๋ถ€์„œ์˜ ์—…๋ฌด ์„ค๋ช…
  • ๋ถ€์„œ๋ณ„ 5ํ–‰ ๋‹จ์œ„ chunk๋กœ ๋ถ„ํ• ํ•˜์—ฌ ๊ฒ€์ƒ‰ corpus ๊ตฌ์„ฑ (DEPT_CHUNK_SIZE=5)
  • corpus ํ…์ŠคํŠธ ํ˜•์‹: [๋ถ€์„œ๋ช…] {dept}\n[๋‹ด๋‹น์—…๋ฌด]\n- {work1}\n- {work2}...

5.3 ์ฟผ๋ฆฌ ์ „์ฒ˜๋ฆฌ (preprocess)

# BGE-M3 (max 8192 tokens) โ€” ๊ธธ์ด ์ œํ•œ ์—†์ด ์ „์ฒด ๋ณธ๋ฌธ ๊ตฌ์กฐํ™”
# 1. ์ฒซ ์ค„์„ ์ œ๋ชฉ์œผ๋กœ ๋ถ„๋ฆฌ
# 2. "๋‹ด๋‹น ๋ถ€์„œ/๊ณผ/ํŒ€" ํžŒํŠธ ๋ฌธ์žฅ ์ตœ๋Œ€ 3๊ฐœ ์šฐ์„  ๋ฐฐ์น˜
# 3. ๋‚˜๋จธ์ง€ ๋ณธ๋ฌธ ์ด์–ด ๋ถ™์ž„
โ†’ "{์ œ๋ชฉ} {ํžŒํŠธ๋ฌธ์žฅ1} {ํžŒํŠธ๋ฌธ์žฅ2} {๋ณธ๋ฌธ์ „์ฒด}"

5.4 ๋งค์นญ ๊ฒฐ๊ณผ ์ŠคํŽ™

{
  "dept": "๋””์ง€ํ„ธ๊ธˆ์œต์ด๊ด„๊ณผ",
  "confidence": 0.87,
  "needs_review": false
}
  • needs_review: confidence < CONFIDENCE_REVIEW(0.4) ์‹œ true
  • confidence: Cross-Encoder sigmoid ์ •๊ทœํ™” ๊ธฐ๋ฐ˜ (s0 * 0.7 + gap * 0.3)
    • Reranker ๋ฏธ์‚ฌ์šฉ ์‹œ bi-encoder top-1 ์ ์ˆ˜๋กœ ๋Œ€์ฒด

5.5 ๋ชจ๋ธ ์šด์˜ ๋ฐฉ์‹

ํ•ญ๋ชฉ ๋‚ด์šฉ
ํ•™์Šต ํ™˜๊ฒฝ Google Colab GPU (CUDA, A100 ๊ถŒ์žฅ)
ํ•™์Šต ์ฃผ๊ธฐ dept.xlsx ์—…๋ฐ์ดํŠธ ์‹œ Stage 2 ์žฌํ•™์Šต (์›” 1ํšŒ ์ด์ƒ)
๋ชจ๋ธ ์ €์žฅ bge_m3_finetuned/ โ€” Stage 1 ์ €์žฅ ํ›„ Stage 2 ๋ฎ์–ด์“ฐ๊ธฐ (๋‹จ์ผ ๊ฒฝ๋กœ)
Drive ๋ฐฑ์—… Google Drive /bge_m3_finetuned/ ์— ์ž๋™ ์ €์žฅ ํ›„ ๊ฐ€์ค‘์น˜ ํŒŒ์ผ ์กด์žฌ ๊ฒ€์ฆ
์ถ”๋ก  ํ™˜๊ฒฝ CPU ์„œ๋ฒ„ (์ธํผ๋Ÿฐ์Šค๋Š” GPU ๋ถˆํ•„์š”)
fallback ์ˆœ์„œ bge_m3_finetuned/ โ†’ HF ์บ์‹œ BAAI/bge-m3 ๋‹ค์šด๋กœ๋“œ
reranker fallback reranker_model/ โ†’ HF ์บ์‹œ BAAI/bge-reranker-v2-m3 / ๋กœ๋“œ ์‹คํŒจ ์‹œ bi-encoder ๋‹จ๋… ์‚ฌ์šฉ
์‹ ๋ขฐ๋„ ์ž„๊ณ„๊ฐ’ CONFIDENCE_AUTO=0.7 (์ž๋™ ํ™•์ •) / CONFIDENCE_REVIEW=0.4 (์ˆ˜๋™ ๊ฒ€ํ† )

6. ํ”„๋ก ํŠธ์—”๋“œ ์š”๊ตฌ์‚ฌํ•ญ (Frontend Requirements)

newstogram.xyz ๊ตฌ์กฐ๋ฅผ ์ฐธ์กฐํ•˜๋˜, ๋ถ€์„œ ๋งค์นญ ๊ฒฐ๊ณผ ๋…ธ์ถœ์ด ํ•ต์‹ฌ ์ถ”๊ฐ€ ๊ธฐ๋Šฅ

6.1 ์ฃผ์š” ํ™”๋ฉด ๊ตฌ์„ฑ

6.1.1 ๋ฉ”์ธ ํ”ผ๋“œ ํ™”๋ฉด

โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚  [๋กœ๊ณ ] ๊ธˆ์œต๊ณต์‹œ ๋ชจ๋‹ˆํ„ฐ๋ง        Telegram | About โ”‚
โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค
โ”‚  ๊ธฐ๊ด€ ํƒญ: [์ „์ฒด] [๊ธˆ์œต์œ„] [๊ธˆ๊ฐ์›] [KoFIU] [๋ฒ•์ œ์ฒ˜] [ํ•œ๊ตญ์€ํ–‰] โ”‚
โ”‚  ๊ธฐ๊ฐ„ ํ•„ํ„ฐ: [์˜ค๋Š˜] [์ด๋ฒˆ์ฃผ] [์ด๋ฒˆ๋‹ฌ] [์ „์ฒด]          โ”‚
โ”‚  ๋ถ€์„œ ํ•„ํ„ฐ: [์ „์ฒด ๋ถ€์„œ โ–ผ]   ์นดํ…Œ๊ณ ๋ฆฌ: [์ „์ฒด โ–ผ]       โ”‚
โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค
โ”‚ โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”‚
โ”‚ โ”‚ [๊ธˆ์œต์œ„์›ํšŒ] ๋ณด๋„์ž๋ฃŒ  2026-06-17 14:30      โ”‚ โ”‚
โ”‚ โ”‚ "๋งˆ์ด๋ฐ์ดํ„ฐ ํ™œ์šฉ ๊ธˆ๋ฆฌ์ธํ•˜์š”๊ตฌ ์„œ๋น„์Šค" ํ˜์‹ ๊ธˆ์œต 3๊ฑด ์ง€์ • โ”‚ โ”‚
โ”‚ โ”‚                                             โ”‚ โ”‚
โ”‚ โ”‚ ๋งค์นญ ๋ถ€์„œ: ๐Ÿข ๋””์ง€ํ„ธ๊ธˆ์œต์ด๊ด„๊ณผ  โ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–‘โ–‘ 87% โ”‚ โ”‚
โ”‚ โ”‚ ํ›„๋ณด: ํ•€ํ…Œํฌํ˜์‹ ๊ณผ 71% | ์ „์ž๊ธˆ์œต๊ณผ 60%         โ”‚ โ”‚
โ”‚ โ”‚                          [์›๋ฌธ ๋ณด๊ธฐ] [๊ณต์œ ]   โ”‚ โ”‚
โ”‚ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ”‚
โ”‚ โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”‚
โ”‚ โ”‚ [๊ธˆ์œต์œ„์›ํšŒ] ๋ณด๋„์ž๋ฃŒ  2026-06-17 10:15      โ”‚ โ”‚
โ”‚ โ”‚ ์—ฐ์ฒด ์ฑ„๋ฌด์ž ์ฑ„๊ถŒ๋งค๊ฐ ๊ด€ํ–‰ ๊ฐœ์„ ๋ฐฉ์•ˆ             โ”‚ โ”‚
โ”‚ โ”‚                                             โ”‚ โ”‚
โ”‚ โ”‚ ๋งค์นญ ๋ถ€์„œ: ๐Ÿข ์„œ๋ฏผ๊ธˆ์œต๊ณผ        โ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–‘โ–‘ 83% โ”‚ โ”‚
โ”‚ โ”‚           [์›๋ฌธ ๋ณด๊ธฐ] [๊ณต์œ ]                  โ”‚ โ”‚
โ”‚ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜

6.1.2 ๊ณต์‹œ ์ƒ์„ธ ํ™”๋ฉด

  • ์›๋ฌธ ์ „์ฒด ๋ณธ๋ฌธ ํ‘œ์‹œ
  • ๋งค์นญ ๋ถ€์„œ ์ •๋ณด (Top-5 ์‹ ๋ขฐ๋„ ๋ฐ” ์ฐจํŠธ)
  • ์›๋ฌธ ๊ธฐ์žฌ ๋‹ด๋‹น๋ถ€์„œ / ๋‹ด๋‹น์ž / ์—ฐ๋ฝ์ฒ˜ (์กด์žฌ ์‹œ)
  • ์›๋ฌธ URL ๋ฐ”๋กœ๊ฐ€๊ธฐ
  • ์ฒจ๋ถ€ํŒŒ์ผ ๋‹ค์šด๋กœ๋“œ ๋งํฌ

6.1.3 ๋ถ€์„œ๋ณ„ ๋Œ€์‹œ๋ณด๋“œ

  • ๋‚ด ๋ถ€์„œ ํ•„ํ„ฐ ์„ค์ • ํ›„ ์ €์žฅ
  • ํ•ด๋‹น ๋ถ€์„œ ๊ด€๋ จ ๊ณต์‹œ๋งŒ ๋ชจ์•„๋ณด๊ธฐ
  • ๋ฏธํ™•์ธ ๊ฑด์ˆ˜ ๋ฑƒ์ง€ ํ‘œ์‹œ

6.2 UI ์ปดํฌ๋„ŒํŠธ ์š”๊ตฌ์‚ฌํ•ญ

์ปดํฌ๋„ŒํŠธ ์„ค๋ช…
๊ณต์‹œ ์นด๋“œ ๊ธฐ๊ด€ ๋ฐฐ์ง€, ์นดํ…Œ๊ณ ๋ฆฌ, ์ œ๋ชฉ, ๊ฒŒ์‹œ์ผ์‹œ, ๋งค์นญ ๋ถ€์„œ + ์‹ ๋ขฐ๋„
๋ถ€์„œ ๋งค์นญ ๋ฐ” ์‹ ๋ขฐ๋„ % ์‹œ๊ฐํ™” (progress bar), ์ƒ์œ„ 3๊ฐœ ํ›„๋ณด ํ‘œ์‹œ
๊ธฐ๊ฐ„ ํ•„ํ„ฐ D(์˜ค๋Š˜) / W(์ด๋ฒˆ์ฃผ) / M(์ด๋ฒˆ๋‹ฌ) / ALL
๊ธฐ๊ด€ ํ•„ํ„ฐ ํƒญ ๊ธฐ๊ด€๋ณ„ ํƒญ + ๋ฏธํ™•์ธ ๊ฑด์ˆ˜ ๋ฑƒ์ง€
๋ถ€์„œ ํ•„ํ„ฐ ๋“œ๋กญ๋‹ค์šด ๋ถ€์„œ๋ช… ๊ฒ€์ƒ‰ ๊ฐ€๋Šฅํ•œ select
์•Œ๋ฆผ ์„ค์ • Telegram / ์ด๋ฉ”์ผ ์ˆ˜์‹  ์กฐ๊ฑด ์„ค์ •
์‹ ๋ขฐ๋„ ๊ฒฝ๊ณ  ์‹ ๋ขฐ๋„ < 40% ์‹œ "โš ๏ธ ์ˆ˜๋™ ๊ฒ€ํ†  ํ•„์š”" ํ‘œ์‹œ

6.3 Telegram Bot ์—ฐ๋™

  • ์‹ ๊ทœ ๊ณต์‹œ ์ˆ˜์ง‘ ์ฆ‰์‹œ ๊ตฌ๋… ์ฑ„๋„์— ํ‘ธ์‹œ
  • ๋ฉ”์‹œ์ง€ ํ˜•์‹: [๊ธฐ๊ด€] ์ œ๋ชฉ | ๋งค์นญ๋ถ€์„œ: {dept} ({score}%) | {url}
  • ๋ถ€์„œ๋ณ„ ์ฑ„๋„ ๋ถ„๋ฆฌ ๊ตฌ๋… ์ง€์›

7. ๋ฐฑ์—”๋“œ ์•„ํ‚คํ…์ฒ˜ (Backend Architecture)

7.1 ์ „์ฒด ๊ตฌ์„ฑ (ํ˜„์žฌ ๊ตฌํ˜„)

โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚                          ์™ธ๋ถ€ ๊ธฐ๊ด€ ์‚ฌ์ดํŠธ                          โ”‚
โ”‚  ๊ธˆ์œต์œ„ | ๊ธˆ๊ฐ์› | KoFIU | ๋ฒ•์ œ์ฒ˜ | ํ•œ๊ตญ์€ํ–‰                       โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜
                      โ”‚ HTTP/Playwright
โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ–ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚                     ํฌ๋กค๋Ÿฌ ๋ ˆ์ด์–ด (Crawler Layer)                  โ”‚
โ”‚  - ๊ธฐ๊ด€๋ณ„ ํฌ๋กค๋Ÿฌ ๋ชจ๋“ˆ (๋…๋ฆฝ ์Šค์ผ€์ค„, APScheduler)                   โ”‚
โ”‚  - ์ค‘๋ณต ๊ฐ์ง€ (URL unique constraint)                              โ”‚
โ”‚  - ๋ณธ๋ฌธ ์ •์ œ (HTML โ†’ plain text)                                  โ”‚
โ”‚  - ์›๋ฌธ ๋‹ด๋‹น๋ถ€์„œ/์—ฐ๋ฝ์ฒ˜ ์ถ”์ถœ (์ •๊ทœ์‹)                               โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜
                      โ”‚ ์ˆ˜์ง‘ ์ฆ‰์‹œ ๋งค์นญ ์ฒ˜๋ฆฌ (๋™๊ธฐ)
โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ–ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚              ๋ถ€์„œ ๋งค์นญ ์„œ๋น„์Šค (matcher.py)                         โ”‚
โ”‚  - BGE-M3 Hybrid Retrieval (Dense 55% + TF-IDF 25% + BM25 20%) โ”‚
โ”‚  - Cross-Encoder Re-ranking (bge-reranker-v2-m3)                โ”‚
โ”‚  - confidence = sigmoid(s0)*0.7 + gap*0.3                       โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜
                      โ”‚
โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ–ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚                   ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค (SQLite / ์šด์˜: PostgreSQL ๊ฒ€ํ† )     โ”‚
โ”‚  announcements, dept_matches, departments, crawl_logs            โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜
                      โ”‚
โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ–ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚                    API ์„œ๋ฒ„ (FastAPI + uvicorn)                   โ”‚
โ”‚  GET /api/v1/announcements  |  GET /api/v1/announcements/{id}    โ”‚
โ”‚  GET /api/v1/departments    |  POST /api/v1/match                โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜
                      โ”‚
             โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
             โ–ผ                   โ–ผ
       ์›น ํ”„๋ก ํŠธ์—”๋“œ           Telegram Bot
       (Vanilla JS)           (์•Œ๋ฆผ ๋ฐœ์†ก, ์˜ˆ์ •)
       API_BASE: localhost:8000

7.2 ํฌ๋กค๋Ÿฌ ์„ค๊ณ„ ์›์น™

  • ๊ธฐ๊ด€๋ณ„ ๋…๋ฆฝ ๋ชจ๋“ˆ: ๊ฐ ๊ธฐ๊ด€์˜ ๊ตฌ์กฐ ๋ณ€๊ฒฝ์ด ๋‹ค๋ฅธ ๊ธฐ๊ด€์— ์˜ํ–ฅ ์—†๋„๋ก ๋ถ„๋ฆฌ
  • ์ ์ง„์  ํฌ๋กค๋ง: ๋งˆ์ง€๋ง‰ ์ˆ˜์ง‘ ID/๋‚ ์งœ ์ดํ›„ ์‹ ๊ทœ ๊ฑด๋งŒ ์ˆ˜์ง‘
  • ์ค‘๋ณต ๋ฐฉ์ง€: (source_agency, url) unique constraint + ์ œ๋ชฉ+๋‚ ์งœ ํ•ด์‹œ ์ฒดํฌ
  • ์š”์ฒญ ๊ฐ„๊ฒฉ ์ค€์ˆ˜: ๊ธฐ๊ด€๋ณ„ robots.txt ์กด์ค‘, ์š”์ฒญ ๊ฐ„ 1~3์ดˆ ๋”œ๋ ˆ์ด
  • ์—๋Ÿฌ ์ฒ˜๋ฆฌ: ์ˆ˜์ง‘ ์‹คํŒจ ์‹œ ์ง€์ˆ˜ ๋ฐฑ์˜คํ”„ ์žฌ์‹œ๋„ (์ตœ๋Œ€ 3ํšŒ)
  • ์Šค์ผ€์ค„๋Ÿฌ: APScheduler (BackgroundScheduler) โ€” ๊ธฐ๊ด€๋ณ„ ๋…๋ฆฝ ์ฃผ๊ธฐ ์„ค์ •

7.3 ๋ถ€์„œ ๋งค์นญ ๋ชจ๋ธ ์„œ๋น™

ํ•ญ๋ชฉ ๋‚ด์šฉ
ํ”„๋ ˆ์ž„์›Œํฌ FastAPI + uvicorn
๋ชจ๋ธ ๋กœ๋”ฉ ์„œ๋ฒ„ ์‹œ์ž‘ ์‹œ 1ํšŒ ๋กœ๋“œ (get_matcher() singleton), ๋ฉ”๋ชจ๋ฆฌ ์ƒ์ฃผ
์ž„๋ฒ ๋” ๋กœ๋“œ ์ˆœ์„œ bge_m3_finetuned/ (๊ฐ€์ค‘์น˜ ํŒŒ์ผ ์กด์žฌ ํ™•์ธ) โ†’ HF ์บ์‹œ BAAI/bge-m3
Reranker ๋กœ๋“œ ์ˆœ์„œ reranker_model/ โ†’ HF ์บ์‹œ BAAI/bge-reranker-v2-m3 โ†’ ์—†์œผ๋ฉด bi-encoder ๋‹จ๋…
์ถ”๋ก  ๋‹จ์œ„ ๊ณต์‹œ 1๊ฑด๋‹น predict() 1ํšŒ ํ˜ธ์ถœ
์‘๋‹ต SLA ๊ฑด๋‹น โ‰ค 500ms (CPU ๊ธฐ์ค€)
๊ฐ€์ค‘์น˜ ํŒŒ์ผ ๊ฒ€์ฆ _is_valid_model_dir() โ€” config.json + model.safetensors ๋˜๋Š” pytorch_model.bin ๋ชจ๋‘ ์กด์žฌ ์‹œ์—๋งŒ ๋กœ๋“œ

8. ๋ฐ์ดํ„ฐ ๋ชจ๋ธ (Data Schema)

ํ˜„์žฌ ๊ตฌํ˜„: SQLite (data/announcements.db)
์šด์˜ ํ™•์žฅ: PostgreSQL ์ „ํ™˜ ๊ฒ€ํ† 

8.1 announcements (๊ณต์‹œ ํ…Œ์ด๋ธ”)

CREATE TABLE announcements (
    id              INTEGER      PRIMARY KEY AUTOINCREMENT,
    source_agency   VARCHAR(50)  NOT NULL,  -- ๊ธˆ์œต์œ„์›ํšŒ, ๊ธˆ๊ฐ์› ๋“ฑ
    category        VARCHAR(50)  NOT NULL,  -- ๋ณด๋„์ž๋ฃŒ, ๊ณ ์‹œ๊ณต๊ณ , ์˜๊ฒฐ๊ฒฐ๊ณผ ๋“ฑ
    title           TEXT         NOT NULL,
    body_text       TEXT,
    published_at    DATETIME     NOT NULL,
    url             TEXT         NOT NULL UNIQUE,
    author_dept_raw VARCHAR(100),           -- ์›๋ฌธ ๊ธฐ์žฌ ๋‹ด๋‹น๋ถ€์„œ
    contact_raw     TEXT,                   -- ์›๋ฌธ ๊ธฐ์žฌ ๋‹ด๋‹น์ž/์—ฐ๋ฝ์ฒ˜
    crawled_at      DATETIME     DEFAULT CURRENT_TIMESTAMP,
    is_matched      BOOLEAN      DEFAULT 0
);
CREATE INDEX idx_ann_published ON announcements(published_at DESC);
CREATE INDEX idx_ann_agency    ON announcements(source_agency);

8.2 dept_matches (๋ถ€์„œ ๋งค์นญ ๊ฒฐ๊ณผ)

CREATE TABLE dept_matches (
    id                  INTEGER PRIMARY KEY AUTOINCREMENT,
    announcement_id     INTEGER REFERENCES announcements(id),
    final_dept          VARCHAR(100),
    confidence_score    REAL,
    needs_review        BOOLEAN DEFAULT 0,
    match_method        VARCHAR(50),        -- dense+tfidf+bm25+crossencoder
    model_version       VARCHAR(30),
    matched_at          DATETIME DEFAULT CURRENT_TIMESTAMP,
    is_manual_override  BOOLEAN DEFAULT 0,
    override_dept       VARCHAR(100)
);

8.3 departments (๋ถ€์„œ ๋ชฉ๋ก)

CREATE TABLE departments (
    id          INTEGER PRIMARY KEY AUTOINCREMENT,
    dept_name   VARCHAR(100) NOT NULL UNIQUE,  -- HNG_BR_NM
    is_active   BOOLEAN DEFAULT 1,
    updated_at  DATETIME DEFAULT CURRENT_TIMESTAMP
);

8.4 crawl_logs (ํฌ๋กค๋ง ์ด๋ ฅ)

CREATE TABLE crawl_logs (
    id              INTEGER PRIMARY KEY AUTOINCREMENT,
    source_agency   VARCHAR(50),
    started_at      DATETIME,
    finished_at     DATETIME,
    collected_count INTEGER,
    error_count     INTEGER,
    error_detail    TEXT
);

9. API ์„ค๊ณ„ (API Spec)

9.1 ๊ณต์‹œ ๋ชฉ๋ก ์กฐํšŒ

GET /api/v1/announcements
Query Params:
  - source_agency: string (optional)
  - category: string (optional)
  - dept: string (optional)           โ† ๋งค์นญ ๋ถ€์„œ ํ•„ํ„ฐ
  - range: D|W|M|ALL (default: W)
  - page: int (default: 1)
  - per_page: int (default: 20)
  - min_confidence: float (optional)  โ† ์‹ ๋ขฐ๋„ ์ž„๊ณ„๊ฐ’ ํ•„ํ„ฐ

Response 200:
{
  "total": 142,
  "page": 1,
  "items": [
    {
      "id": 1001,
      "source_agency": "๊ธˆ์œต์œ„์›ํšŒ",
      "category": "๋ณด๋„์ž๋ฃŒ",
      "title": "๋งˆ์ด๋ฐ์ดํ„ฐ ํ™œ์šฉ ๊ธˆ๋ฆฌ์ธํ•˜์š”๊ตฌ ์„œ๋น„์Šค...",
      "published_at": "2026-06-17T14:30:00+09:00",
      "url": "https://fsc.go.kr/...",
      "matched_dept": "๋””์ง€ํ„ธ๊ธˆ์œต์ด๊ด„๊ณผ",
      "confidence_score": 0.87,
      "needs_review": false
    }
  ]
}

9.2 ๊ณต์‹œ ์ƒ์„ธ ์กฐํšŒ

GET /api/v1/announcements/{id}
Response 200: ๊ณต์‹œ ์ „์ฒด ํ•„๋“œ + ๋งค์นญ ๊ฒฐ๊ณผ + ์›๋ฌธ ๋‹ด๋‹น๋ถ€์„œ/์—ฐ๋ฝ์ฒ˜

9.3 ๋ถ€์„œ ๋ชฉ๋ก ์กฐํšŒ

GET /api/v1/departments
Response 200: [{ "dept_name": "๋””์ง€ํ„ธ๊ธˆ์œต์ด๊ด„๊ณผ" }, ...]

9.4 ์ˆ˜๋™ ๋งค์นญ ์žฌ์š”์ฒญ

POST /api/v1/announcements/{id}/rematch
Body: { "override_dept": "ํ•€ํ…Œํฌํ˜์‹ ๊ณผ" }  โ† ์ƒ๋žต ์‹œ ๋ชจ๋ธ ์žฌ์ถ”๋ก 
Response 200: ๊ฐฑ์‹ ๋œ ๋งค์นญ ๊ฒฐ๊ณผ

10. ๋น„๊ธฐ๋Šฅ ์š”๊ตฌ์‚ฌํ•ญ (Non-Functional Requirements)

ํ•ญ๋ชฉ ์š”๊ตฌ์‚ฌํ•ญ
์„ฑ๋Šฅ ๊ณต์‹œ ๋ชฉ๋ก API ์‘๋‹ต โ‰ค 200ms (DB ์กฐํšŒ ๊ธฐ์ค€)
๋ณด์•ˆ API ์ธ์ฆ (JWT or API Key), HTTPS ํ•„์ˆ˜
๋กœ๊น… ํฌ๋กค๋ง ์‹คํŒจ, ๋งค์นญ ์˜ค๋ฅ˜, API ์˜ค๋ฅ˜ ์ค‘์•™ ๋กœ๊ทธ ์ˆ˜์ง‘
๋ชจ๋‹ˆํ„ฐ๋ง ํฌ๋กค๋Ÿฌ ์ˆ˜์ง‘ ๊ฑด์ˆ˜, ๋งค์นญ ์„ฑ๊ณต๋ฅ , ์‹ ๋ขฐ๋„ ๋ถ„ํฌ ๋Œ€์‹œ๋ณด๋“œ
ํ™•์žฅ์„ฑ ํฌ๋กค๋ง ๋Œ€์ƒ ๊ธฐ๊ด€ ์ถ”๊ฐ€ ์‹œ ๊ธฐ์กด ์ฝ”๋“œ ์ˆ˜์ • ์ตœ์†Œํ™”
๋ฐ์ดํ„ฐ ๋ณด๊ด€ ๊ณต์‹œ ์›๋ฌธ 2๋…„ ๋ณด๊ด€, ํฌ๋กค๋ง ๋กœ๊ทธ 6๊ฐœ์›” ๋ณด๊ด€
์•Œ๋ฆผ ์‹ ๋ขฐ์„ฑ Telegram ๋ฉ”์‹œ์ง€ ๋ฐœ์†ก ์‹คํŒจ ์‹œ ์žฌ์‹œ๋„ 3ํšŒ

11. ๊ฐœ๋ฐœ ๋กœ๋“œ๋งต (Milestones)

Phase 1 โ€” ํฌ๋กค๋Ÿฌ + ๊ธฐ๋ณธ ํ”ผ๋“œ โœ… ์™„๋ฃŒ

  • ๊ธˆ์œต์œ„์›ํšŒ, ๊ธˆ๊ฐ์›, KoFIU, ๋ฒ•์ œ์ฒ˜, ํ•œ๊ตญ์€ํ–‰ ํฌ๋กค๋Ÿฌ ๊ตฌํ˜„ ๋ฐ DB ์ ์žฌ
  • FastAPI ๊ธฐ๋ฐ˜ ๊ธฐ๋ณธ API ์„œ๋ฒ„ (๋ชฉ๋ก/์ƒ์„ธ ์กฐํšŒ)
  • ํ”„๋ก ํŠธ์—”๋“œ ๋ฉ”์ธ ํ”ผ๋“œ ํ™”๋ฉด (Vanilla JS, API_BASE: http://localhost:8000/api/v1)
  • ์ค‘๋ณต ๊ฐ์ง€ (URL unique) ๋ฐ APScheduler ๊ธฐ๋ฐ˜ ์Šค์ผ€์ค„๋Ÿฌ ์„ค์ •
  • SQLite ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค (data/announcements.db)

Phase 2 โ€” ๋ถ€์„œ ๋งค์นญ ํ†ตํ•ฉ โœ… ์™„๋ฃŒ

  • BGE-M3 (BAAI/bge-m3) ๊ธฐ๋ฐ˜ Hybrid Retrieval ๋งค์นญ ์„œ๋น„์Šค ๊ตฌํ˜„
  • Cross-Encoder (BAAI/bge-reranker-v2-m3) ์žฌ์ˆœ์œ„ํ™” ํ†ตํ•ฉ
  • ๋‘ ๋‹จ๊ณ„ ํŒŒ์ธํŠœ๋‹ ํŒŒ์ดํ”„๋ผ์ธ (v4.1.ipynb) โ€” ๋‹จ์ผ bge_m3_finetuned/ ๊ฒฝ๋กœ ์ €์žฅ
  • ํ”„๋ก ํŠธ์—”๋“œ ๋ถ€์„œ ๋งค์นญ ๊ฒฐ๊ณผ UI ํ†ตํ•ฉ (์‹ค์ œ AI ๋งค์นญ ๊ฒฐ๊ณผ ๋…ธ์ถœ ํ™•์ธ)
  • ์‹ ๋ขฐ๋„ ์ž„๊ณ„๊ฐ’ ๊ธฐ๋ฐ˜ ์ž๋™ ํ™•์ • / ์ˆ˜๋™ ๊ฒ€ํ†  ๋ถ„๋ฅ˜

Phase 3 โ€” ์•Œ๋ฆผ ๋ฐ ๊ณ ๋„ํ™” (์˜ˆ์ •)

  • Telegram Bot ์•Œ๋ฆผ ์—ฐ๋™
  • ๋ถ€์„œ ํ•„ํ„ฐ / ๊ตฌ๋… ์„ค์ • ๊ธฐ๋Šฅ
  • ํฌ๋กค๋ง ๋ชจ๋‹ˆํ„ฐ๋ง ๋Œ€์‹œ๋ณด๋“œ
  • PostgreSQL ์ „ํ™˜ (์šด์˜ ํ™˜๊ฒฝ ์Šค์ผ€์ผ์—… ์‹œ)

Phase 4 โ€” ์•ˆ์ •ํ™” ๋ฐ ์šด์˜ ์ž๋™ํ™” (์˜ˆ์ •)

  • ๋ชจ๋ธ ์„ฑ๋Šฅ ํ‰๊ฐ€ (ํ…Œ์ŠคํŠธ์…‹ ๊ธฐ๋ฐ˜ Top-1/Top-5 ์ •ํ™•๋„ ์ธก์ •)
  • dept.xlsx ์—…๋ฐ์ดํŠธ ์‹œ ์ž๋™ ๋ชจ๋ธ ์žฌํ•™์Šต ํŒŒ์ดํ”„๋ผ์ธ
  • ๋ถ€์„œ๋ณ„ ๋ฏธํ™•์ธ ๊ฑด์ˆ˜ ๋ฑƒ์ง€ ๋ฐ ์ฝ์Œ ์ฒ˜๋ฆฌ
  • ์ฒจ๋ถ€ํŒŒ์ผ ๋‹ค์šด๋กœ๋“œ ๊ธฐ๋Šฅ

12. ๋ฆฌ์Šคํฌ ๋ฐ ์ œ์•ฝ์‚ฌํ•ญ (Risks & Constraints)

๋ฆฌ์Šคํฌ ์˜ํ–ฅ๋„ ์ƒํƒœ ๋Œ€์‘ ๋ฐฉ์•ˆ
๊ธฐ๊ด€ ์‚ฌ์ดํŠธ ๊ตฌ์กฐ ๋ณ€๊ฒฝ์œผ๋กœ ํฌ๋กค๋Ÿฌ ํŒŒ์‹ฑ ์‹คํŒจ ์ค‘ ์ง„ํ–‰ ์ค‘ ๊ธฐ๊ด€๋ณ„ ํฌ๋กค๋Ÿฌ ๋…๋ฆฝ ๋ชจ๋“ˆํ™” + ์‹คํŒจ ์•Œ๋ฆผ
๋ถ€์„œ ๋งค์นญ ์ •ํ™•๋„ ์ €ํ•˜ (dept.xlsx ๋ฏธ๊ฐฑ์‹ ) ์ค‘ ์ง„ํ–‰ ์ค‘ ์›” 1ํšŒ dept.xlsx ์—…๋ฐ์ดํŠธ ๋ฐ ์žฌํ•™์Šต ํ”„๋กœ์„ธ์Šค ์ˆ˜๋ฆฝ
GPU ์—†์ด ๋ชจ๋ธ ์žฌํ•™์Šต ๋ถˆ๊ฐ€ ์ค‘ ์ง„ํ–‰ ์ค‘ Google Colab GPU ํ™œ์šฉ (Drive ์ €์žฅ + ๊ฐ€์ค‘์น˜ ๊ฒ€์ฆ ์ž๋™ํ™”)
ํฌ๋กค๋ง ์ฐจ๋‹จ (IP ๋ธ”๋ก, robots.txt ์œ„๋ฐ˜) ๋†’ ์ง„ํ–‰ ์ค‘ User-Agent ์„ค์ •, ์š”์ฒญ ๋”œ๋ ˆ์ด ์ค€์ˆ˜, ํ•„์š” ์‹œ IP ๋กœํ…Œ์ด์…˜
๊ณต์‹œ ๋ณธ๋ฌธ ๊ธธ์ด ์ดˆ๊ณผ๋กœ ์ •๋ณด ์†์‹ค โ€” ํ•ด์†Œ BGE-M3 ๋„์ž…์œผ๋กœ ์ตœ๋Œ€ 8192 ํ† ํฐ ์ฒ˜๋ฆฌ ๊ฐ€๋Šฅ
Colab ์„ธ์…˜ ์ข…๋ฃŒ๋กœ ๋ชจ๋ธ ์ €์žฅ ์‹คํŒจ ์ค‘ ์™„ํ™” Google Drive ์šฐ์„  ์ €์žฅ + ๊ฐ€์ค‘์น˜ ํŒŒ์ผ ์กด์žฌ ๊ฒ€์ฆ์œผ๋กœ ๋ฌด์Œ ์‹คํŒจ ๋ฐฉ์ง€
๋™์ผ ๊ธฐ๊ด€ ๋‚ด ๋ถ€์„œ๋ช… ์œ ์‚ฌ์„ฑ์œผ๋กœ ๋งค์นญ ํ˜ผ๋™ ์ค‘ ์ง„ํ–‰ ์ค‘ Cross-Encoder ์žฌ์ˆœ์œ„ํ™” + ๋งค์นญ ๋ถˆ๊ฐ€ ์ž„๊ณ„๊ฐ’(0.4) + ์ˆ˜๋™ ๊ฒ€ํ†  ํ ์šด์˜

13. ์šฉ์–ด ์ •๋ฆฌ (Glossary)

์šฉ์–ด ์ •์˜
HNG_BR_NM dept.xlsx ๋‚ด ํ•œ๊ธ€ ๋ถ€์„œ๋ช… ์ปฌ๋Ÿผ
OFWRK_NM dept.xlsx ๋‚ด ์—…๋ฌด ์„ค๋ช… ์ปฌ๋Ÿผ
BGE-M3 BAAI/bge-m3 โ€” 570M ํŒŒ๋ผ๋ฏธํ„ฐ, 8192 ํ† ํฐ ์ง€์› ๋‹ค๊ตญ์–ด ์ž„๋ฒ ๋”ฉ ๋ชจ๋ธ
Cross-Encoder ์ฟผ๋ฆฌ-๋ฌธ์„œ ์Œ์„ ์ง์ ‘ ์ž…๋ ฅ๋ฐ›์•„ ๊ด€๋ จ๋„ ์ ์ˆ˜๋ฅผ ์ถœ๋ ฅํ•˜๋Š” ์žฌ์ˆœ์œ„ํ™” ๋ชจ๋ธ (BAAI/bge-reranker-v2-m3)
Bi-Encoder ์ฟผ๋ฆฌ์™€ ๋ฌธ์„œ๋ฅผ ๊ฐ๊ฐ ์ž„๋ฒ ๋”ฉํ•œ ๋’ค ์ฝ”์‚ฌ์ธ ์œ ์‚ฌ๋„๋กœ ๋น„๊ตํ•˜๋Š” ๊ฒ€์ƒ‰ ๋ชจ๋ธ (BGE-M3)
Dense Score BGE-M3 ์ž„๋ฒ ๋”ฉ ์ฝ”์‚ฌ์ธ ์œ ์‚ฌ๋„ (๊ฐ€์ค‘์น˜ 55%)
TF-IDF Score char 2~5 gram ๊ธฐ๋ฐ˜ ํฌ์†Œ ๋ฒกํ„ฐ ์œ ์‚ฌ๋„ (๊ฐ€์ค‘์น˜ 25%)
BM25 Score ํ† ํฐ ๊ธฐ๋ฐ˜ ํ™•๋ฅ ์  ๊ฒ€์ƒ‰ ์ ์ˆ˜ (๊ฐ€์ค‘์น˜ 20%)
CANDIDATE_K 1๋‹จ๊ณ„ Hybrid Retrieval์—์„œ ์ˆ˜์ง‘ํ•˜๋Š” ํ›„๋ณด ์ˆ˜ (ํ˜„์žฌ 120)
RERANK_TOP_K Cross-Encoder์— ๋„˜๊ธฐ๋Š” ์ตœ์ข… ํ›„๋ณด ์ˆ˜ (ํ˜„์žฌ 10)
Top-1 ์ •ํ™•๋„ ์ตœ์ข… ์˜ˆ์ธก ๋ถ€์„œ๊ฐ€ ์ •๋‹ต์ธ ๋น„์œจ
confidence ๋งค์นญ ํ™•์‹ ๋„ (0~1), sigmoid(s0)*0.7 + gap*0.3 ๊ณต์‹ ์ ์šฉ
needs_review confidence < 0.4 ์‹œ true, ์ˆ˜๋™ ๊ฒ€ํ†  ํ ๋Œ€์ƒ
CONFIDENCE_AUTO ์ž๋™ ํ™•์ • ์ž„๊ณ„๊ฐ’ (0.7)
CONFIDENCE_REVIEW ์ˆ˜๋™ ๊ฒ€ํ†  ์ž„๊ณ„๊ฐ’ (0.4)
bge_m3_finetuned/ Stage 1 ๋„๋ฉ”์ธ ์ ์‘ ํ›„ Stage 2 ๋ถ€์„œ ๋งค์นญ ํŒŒ์ธํŠœ๋‹ ๊ฒฐ๊ณผ๋ฅผ ๋ฎ์–ด์“ด ์ตœ์ข… ๋‹จ์ผ ๋ชจ๋ธ ๋””๋ ‰ํ„ฐ๋ฆฌ
APScheduler ๋ฐฑ์—”๋“œ ํฌ๋กค๋Ÿฌ ์ฃผ๊ธฐ ์‹คํ–‰์— ์‚ฌ์šฉํ•˜๋Š” Python ์Šค์ผ€์ค„๋Ÿฌ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ
MultipleNegativesRankingLoss ๋ฐฐ์น˜ ๋‚ด ํƒ€ ์ƒ˜ํ”Œ์„ ์ž๋™ negative๋กœ ํ™œ์šฉํ•˜๋Š” sentence-transformers ํ•™์Šต ์†์‹ค ํ•จ์ˆ˜