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)
```python
# BGE-M3 (max 8192 tokens) โ€” ๊ธธ์ด ์ œํ•œ ์—†์ด ์ „์ฒด ๋ณธ๋ฌธ ๊ตฌ์กฐํ™”
# 1. ์ฒซ ์ค„์„ ์ œ๋ชฉ์œผ๋กœ ๋ถ„๋ฆฌ
# 2. "๋‹ด๋‹น ๋ถ€์„œ/๊ณผ/ํŒ€" ํžŒํŠธ ๋ฌธ์žฅ ์ตœ๋Œ€ 3๊ฐœ ์šฐ์„  ๋ฐฐ์น˜
# 3. ๋‚˜๋จธ์ง€ ๋ณธ๋ฌธ ์ด์–ด ๋ถ™์ž„
โ†’ "{์ œ๋ชฉ} {ํžŒํŠธ๋ฌธ์žฅ1} {ํžŒํŠธ๋ฌธ์žฅ2} {๋ณธ๋ฌธ์ „์ฒด}"
```
### 5.4 ๋งค์นญ ๊ฒฐ๊ณผ ์ŠคํŽ™
```json
{
"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 (๊ณต์‹œ ํ…Œ์ด๋ธ”)
```sql
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 (๋ถ€์„œ ๋งค์นญ ๊ฒฐ๊ณผ)
```sql
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 (๋ถ€์„œ ๋ชฉ๋ก)
```sql
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 (ํฌ๋กค๋ง ์ด๋ ฅ)
```sql
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 โ€” ํฌ๋กค๋Ÿฌ + ๊ธฐ๋ณธ ํ”ผ๋“œ โœ… ์™„๋ฃŒ
- [x] ๊ธˆ์œต์œ„์›ํšŒ, ๊ธˆ๊ฐ์›, KoFIU, ๋ฒ•์ œ์ฒ˜, ํ•œ๊ตญ์€ํ–‰ ํฌ๋กค๋Ÿฌ ๊ตฌํ˜„ ๋ฐ DB ์ ์žฌ
- [x] FastAPI ๊ธฐ๋ฐ˜ ๊ธฐ๋ณธ API ์„œ๋ฒ„ (๋ชฉ๋ก/์ƒ์„ธ ์กฐํšŒ)
- [x] ํ”„๋ก ํŠธ์—”๋“œ ๋ฉ”์ธ ํ”ผ๋“œ ํ™”๋ฉด (Vanilla JS, `API_BASE: http://localhost:8000/api/v1`)
- [x] ์ค‘๋ณต ๊ฐ์ง€ (URL unique) ๋ฐ APScheduler ๊ธฐ๋ฐ˜ ์Šค์ผ€์ค„๋Ÿฌ ์„ค์ •
- [x] SQLite ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค (`data/announcements.db`)
### Phase 2 โ€” ๋ถ€์„œ ๋งค์นญ ํ†ตํ•ฉ โœ… ์™„๋ฃŒ
- [x] BGE-M3 (BAAI/bge-m3) ๊ธฐ๋ฐ˜ Hybrid Retrieval ๋งค์นญ ์„œ๋น„์Šค ๊ตฌํ˜„
- [x] Cross-Encoder (BAAI/bge-reranker-v2-m3) ์žฌ์ˆœ์œ„ํ™” ํ†ตํ•ฉ
- [x] ๋‘ ๋‹จ๊ณ„ ํŒŒ์ธํŠœ๋‹ ํŒŒ์ดํ”„๋ผ์ธ (v4.1.ipynb) โ€” ๋‹จ์ผ `bge_m3_finetuned/` ๊ฒฝ๋กœ ์ €์žฅ
- [x] ํ”„๋ก ํŠธ์—”๋“œ ๋ถ€์„œ ๋งค์นญ ๊ฒฐ๊ณผ UI ํ†ตํ•ฉ (์‹ค์ œ AI ๋งค์นญ ๊ฒฐ๊ณผ ๋…ธ์ถœ ํ™•์ธ)
- [x] ์‹ ๋ขฐ๋„ ์ž„๊ณ„๊ฐ’ ๊ธฐ๋ฐ˜ ์ž๋™ ํ™•์ • / ์ˆ˜๋™ ๊ฒ€ํ†  ๋ถ„๋ฅ˜
### 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 ํ•™์Šต ์†์‹ค ํ•จ์ˆ˜ |