DMF_Crawler/config/config.toml
Yun Chan 56a6e2da93 chore: 저장소 구조 정리 및 문서화, 첫 커밋
- src/dist 산출물 분리 원칙 정리(.gitignore, .gitattributes)
- 루트 및 주요 폴더(config/scripts/prompts/tests/src, 런타임 폴더 5종)에
  안내용 README.md 추가
- CHANGELOG.md, LICENSE, docs/ops/05-release-and-versioning.md 추가
- docs/README.md 문서 지도 갱신
2026-09-04 09:25:44 +09:00

179 lines
12 KiB
TOML

# =============================================================================
# config/config.toml — DMF Crawler 설정 정본
# =============================================================================
# 이 파일 하나가 프로그램의 모든 동작을 결정한다(아키텍처 §6).
#
# ▸ 비밀 값(공공데이터포털 인증키, agy 로그인 토큰, 메신저 주소)은 여기에 절대 넣지 않는다.
# 인증키는 설정 창에서 입력하면 Windows DPAPI 로 암호화되어 사용자 계정에만 풀린다.
# ▸ PC 마다 다른 값(백업 드라이브, 리포트 폴더)은 이 파일을 고치지 말고
# 같은 폴더의 config.local.toml 에 적는다. 같은 키를 덮어쓴다.
# (config.local.toml.example 을 복사해 쓰면 된다. 이 파일은 git 에 올라가지 않는다.)
# ▸ 상대경로는 전부 설치 폴더 기준이다. 다른 드라이브는 "E:/DMF_Backup" 처럼 절대경로로 적는다.
# ▸ 값을 바꾼 뒤 확인: python -m dmf_crawler doctor
# =============================================================================
[general]
timezone = "Asia/Seoul" # 실행일자를 판정하는 기준 시간대
contact_email = "" # 요청 헤더(User-Agent)에 넣을 연락처. 공공 API 예의이자 요구 N1.
# 설정 창에서 입력하면 여기 채워진다. 비워 두어도 동작은 한다.
language = "ko" # 리포트·알림 언어
[schedule]
# --- 자동 실행 트리거 3종 (사용자 확정) -------------------------------------
# ① 매일 정해진 시각 ② 로그온 시 알림 에이전트 ③ PC 가 꺼져 있어 놓친 작업 따라잡기
enable_daily_trigger = true # ① 매일 daily_time 에 수집 실행
enable_logon_trigger = true # ② 로그온하면 밀린 알림을 즉시 표시
enable_missed_task_catchup = true # ③ 06:00 에 PC 가 꺼져 있었으면 켜진 뒤 자동으로 따라잡는다
prevent_concurrent_runs = true # 중복 실행 방지. 같은 날 이미 성공했으면 건너뛰고,
# 다른 창이 실행 중이면 기다리지 않고 조용히 종료한다.
daily_time = "06:00" # 일일 실행 시각(요구 R5.1)
jitter_seconds = 240 # 0~이 값 사이 랜덤 지연. 서버에 정각 부하를 몰지 않는다(상한 300)
startup_delay_minutes = 5 # 부팅 직후 트리거의 대기 시간(네트워크·프로필 준비)
execution_time_limit_minutes = 30 # 이 시간을 넘기면 작업 스케줄러가 강제 종료한다
restart_count = 3 # 실패 시 재시작 횟수
restart_interval_minutes = 10 # 재시작 간격
agent_repeat_minutes = 15 # 알림 에이전트가 도는 주기
agy_update_weekday = "Sunday" # 주간 AI 도구 업데이트 요일
agy_update_time = "14:00" # 그 시각. 사람이 깨어 있는 시간대로 둔다
[source]
# 수집 경로는 2개다: ① 인증키가 있으면 공식 Open API ② 키가 없거나 browser 모드면 AGY headful Chrome.
# 사용자가 요구한 headful 브라우저 수집은 curl/headless 가 아니라 visible Chrome 을 AGY가 조작하는 방식이다.
base_url = "https://apis.data.go.kr/1471000/MdcDmfInfoService01/getMdcDmfList01"
mode = "auto" # auto | api | browser
# auto = 인증키가 있으면 API, 없으면 AGY headful Chrome 으로 xlsx 다운로드
# api = 항상 API 수집. 키가 없으면 마지막 성공 자료로 리포트 생성
# browser = 항상 AGY headful Chrome 으로 xlsx 다운로드
response_type = "json" # json | xml. json 이 실패하면 xml 로 한 번 폴백한다
page_size = 100 # 한 번에 받을 건수(numOfRows)
connect_timeout_seconds = 10.0 # 연결 타임아웃
read_timeout_seconds = 30.0 # 읽기 타임아웃
max_attempts = 4 # 실패 시 최대 시도 횟수
backoff_base_seconds = 5.0 # 재시도 대기 기준(지수적으로 늘어난다)
backoff_cap_seconds = 300.0 # 재시도 대기 상한
min_interval_seconds = 0.7 # 페이지 사이 최소 간격. 서버를 몰아치지 않는다
min_body_bytes = 200 # 이보다 짧은 응답은 '정상 코드지만 빈 응답'으로 보고 실패 처리
archive_retain_days = 180 # data/raw 에 보관한 원문의 보존 기간
[integrity]
# 안전장치(요구 R2.2). 이 중 하나라도 걸리면 그날은 '변경분 비교'를 하지 않는다.
# 잘못된 응답으로 "오늘 5,000건 취하" 같은 거짓 리포트를 만드는 사고를 막는 관문이다.
max_drop_ratio = 0.05 # 전체 건수가 어제보다 이 비율 이상 줄면 차단
max_null_ratio = 0.01 # 필수 항목(등록번호·성분명·업체명)이 빈 비율 상한
max_duplicate_ratio = 0.02 # 등록번호 중복 비율 상한
max_churn_ratio = 0.10 # (신규+변경+취하)/전체 가 이 비율을 넘으면 비교 결과를 폐기하고 차단
max_withdrawn_ratio = 0.02 # 취하 비율 경고선. 차단하지 않고 리포트에 경고만 표시한다
[storage]
sqlite_path = "data/dmf.sqlite3" # 정본 데이터베이스 경로
busy_timeout_ms = 15000 # 다른 프로세스가 쓰는 중일 때 기다릴 시간(ms)
snapshot_retain_days = 0 # 0 = 영구 보존. 과거를 지우지 않는다
[backup]
enabled = true # 성공한 날마다 자동 백업
dir = "backup" # 다른 물리 드라이브를 강력히 권장한다 (예: "E:/DMF_Backup")
keep_count = 30 # 최근 몇 개를 남길지
min_free_gb = 2.0 # 여유 공간이 이보다 적으면 백업을 건너뛰고 경고만 남긴다
[report]
output_dir = "reports" # 리포트 저장 폴더
filename_pattern = "DMF_리포트_{date}.xlsx" # 일자별 파일명. {date} 자리에 YYYY-MM-DD 가 들어간다
latest_link_name = "DMF_리포트_최신.xlsx" # 항상 최신본을 가리키는 파일. 비우면 만들지 않는다
dashboard_first = true # 파일을 열면 '00_대시보드' 시트가 먼저 보인다(사용자 확정)
font = "맑은 고딕" # Windows 에 기본 탑재된 한글 폰트
palette = "okabe_ito" # 색각이상에도 구분되는 팔레트
top_n = 10 # 성분별·업체별 시트의 상위 N
trend_days = 90 # 추이 시트가 보여줄 기간(일)
retain_days = 365 # 리포트 파일 보존 기간
lock_retries = 3 # 파일이 엑셀로 열려 있을 때 재시도 횟수
# (끝내 실패하면 다른 이름으로 저장하고 알려 준다)
[agy]
# AI 요약 계층. 이 계층이 통째로 실패해도 리포트는 항상 나온다(요구 R4.4).
# 기본은 꺼짐: Google 로그인은 사용자가 AI 요약을 켜겠다고 선택한 뒤에만 요구한다.
enabled = false
binary_path = "" # 비우면 %LOCALAPPDATA%\agy\bin\agy.exe 를 쓴다
model = "gemini-3.7-flash-medium" # 사용할 모델
effort = "medium" # low | medium | high
print_timeout = "10m" # 이 시간을 넘기면 포기하고 리포트만 만든다
skip_if_diff_empty = true # 변화가 없는 날은 호출하지 않는다(비용 절약)
max_json_retries = 1 # 응답 형식이 깨졌을 때 다시 물어볼 횟수
daily_token_cap = 300000 # 하루 토큰 상한. 넘으면 그날은 호출하지 않는다
max_events_in_prompt = 200 # 변경이 많은 날 프롬프트에 넣을 상위 건수
prompt_path = "prompts/daily_briefing.md"
schema_path = "prompts/daily_briefing.schema.json"
[health]
# 연속 실패가 쌓이면 잠시 호출을 멈추는 안전장치(서킷 브레이커).
# 고장난 상태로 매일 서버를 두드리지 않기 위한 것이다.
source_failure_threshold = 3 # 자료 수집이 이 횟수 연속 실패하면 잠시 멈춘다
source_cooldown_hours = 24 # 그 후 다시 시도하기까지의 시간
agy_failure_threshold = 3 # AI 요약이 이 횟수 연속 실패하면 잠시 멈춘다
agy_cooldown_hours = 24
[notify]
# 알림 채널은 두 갈래다(사용자 확정): ① 화면 창(토스트·복구 창) ② 메신저(웹훅)
# 어느 쪽이 실패해도 알림은 state/alerts.json 과 Windows 이벤트 로그에 항상 남는다.
# --- 감지 ---
watchdog_stale_minutes = 120 # 마지막 성공이 이 시간보다 오래되면 "배치가 안 돌았다"고 판단
pump_stamp_stale_minutes = 45 # 알림 에이전트가 이 시간 넘게 조용하면 PowerShell 폴백이 뜬다
consecutive_failure_critical = 3 # 같은 문제가 이 횟수 반복되면 등급을 올린다
# --- 표시 ---
toast_seconds = 12 # 자동으로 사라지는 알림이 떠 있는 시간(초)
modal_repeat_minutes = 60 # 심각한 알림을 다시 띄우는 주기
snooze_minutes = 60 # [나중에] 를 누르면 조용해지는 시간
show_info_toast = false # 사소한 정보까지 창으로 띄울지
# --- 알림 폭주 억제 (요구 R7.8) ---
cooldown_minutes = 240 # 같은 문제를 이 시간 안에는 다시 띄우지 않는다
merge_threshold = 2 # 이 건수 이상이면 한 장으로 묶어서 보여 준다
max_toasts_per_hour = 6 # 시간당 알림 상한
daily_alert_cap = 20 # 하루 알림 상한(심각한 알림은 이 상한을 적용받지 않는다)
# --- 문제가 계속될 때 (에스컬레이션) ---
escalation_days = 3 # 3일 연속 실패 → 강제 복구 창 + 진단 자동 표시
escalation_webhook_days = 5 # 5일 → 메신저로도 강제 발송
escalation_stop_after_days = 7 # 7일 → 자동 실행을 멈추고 사람의 확인을 요구한다
# --- 기록 ---
eventlog_source = "DMF Crawler" # Windows 이벤트 로그에 표시될 이름
# --- 메신저(웹훅) ---
# 주소는 여기에 적지 않는다. 설정 창에서 입력하면 암호화되어 저장된다.
webhook_enabled = true # 메신저 알림 사용
webhook_kind = "discord" # discord | slack | telegram | generic
webhook_min_severity = "CRITICAL" # 이 등급 이상만 메신저로 보낸다(알림 피로 방지)
webhook_timeout_seconds = 10.0
deadman_enabled = false # PC 가 통째로 꺼진 것까지 외부에서 감시할지(선택)
[logging]
dir = "logs" # 로그 루트. 실행마다 logs/run_YYYYMMDD_HHMMSS/ 가 만들어진다
level = "INFO" # DEBUG | INFO | WARNING | ERROR | CRITICAL
retain_days = 90 # 실행 로그 보존 기간
console = true # 콘솔에도 출력할지(예약 실행에서는 보이지 않는다)
mask_patterns = ["serviceKey", "access_token"] # 로그에 기록하기 전에 가릴 비밀 값의 이름
[watchlist]
# 관심 목록. 기능은 켜 두고 목록은 비운 채 시작한다(사용자 확정).
# 여기에 적은 값은 최초 1회 데이터베이스로 옮겨 심는 '씨앗'이고, 이후에는 설정 창에서 관리한다.
# 목록이 전부 비어 있으면 리포트의 '05_워치리스트' 시트는 만들어지지 않는다.
enabled = true
match_mode = "contains" # contains(부분 일치) | exact(완전 일치)
ingredients = [] # 예: ["메트포르민염산염", "아토르바스타틴칼슘"]
applicants = [] # 예: ["(주)대웅제약"]
manufacturers = [] # 예: ["Teva Pharmaceutical"]
countries = [] # 예: ["중국", "인도"]