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)
โ "{์ ๋ชฉ} {ํํธ๋ฌธ์ฅ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),
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,
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 โ ํฌ๋กค๋ฌ + ๊ธฐ๋ณธ ํผ๋ โ
์๋ฃ
Phase 2 โ ๋ถ์ ๋งค์นญ ํตํฉ โ
์๋ฃ
Phase 3 โ ์๋ฆผ ๋ฐ ๊ณ ๋ํ (์์ )
Phase 4 โ ์์ ํ ๋ฐ ์ด์ ์๋ํ (์์ )
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 ํ์ต ์์ค ํจ์ |