# ============================================================================= # 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 = [] # 예: ["중국", "인도"]