DMF_Crawler/docs/HANDOFF.md
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

36 KiB
Raw Blame History

핸드오프 — DMF Crawler

다음 세션은 이 문서부터 읽어라. 지금까지 확정된 것, 실측으로 밝혀진 것, 진행 중인 작업, 다음에 할 일이 전부 여기 있다.

작성 시각: 2026-09-03 프로젝트 루트: D:\workspace\DMF_Crawler 상태: 구현 진행 중 / 실제 9,084건 기준선 import 완료 / API 키 선택 정책 복구 / xlsx·Windows GUI 디자인 RED 통과 / 온보딩 스크롤·문장 UX 수정 / AGY headful CCBAC03 xlsx 수집 수동 smoke 성공(9,816건) / 전체 79개 테스트 통과

2026-09-03 추가 사용자 지적 — 스크롤·상단 안내문·AGY headful 수집

사용자 지적:

  • 온보딩 스크롤창이 실제 항목/텍스트 위에서 휠 스크롤되지 않는다.
  • 상단 노란 안내 박스의 문제가 색/박스 자체라기보다 문장이 무엇/왜/어떻게/다음 식으로 AI스럽고 한국어 UX 문장으로 부자연스럽다.
  • AGY가 사람처럼 headful Chrome 으로 수집해야 한다고 여러 번 말했는데, 현재 구현은 공식 API/기존 DB 중심이고 AGY는 요약용 부가 단계처럼만 동작한다.

TDD 처리:

  • 추가 RED:
    • test_scroll_frame_mousewheel_scrolls_when_pointer_is_over_content
    • test_onboarding_status_panel_names_blockers_instead_of_generic_yellow_info_box
    • test_headful_browser_prompt_contract_forbids_headless_http_and_secrets
    • test_headful_browser_collect_validates_download_and_returns_raw_records
    • test_headful_browser_collect_rejects_incomplete_downloads
    • test_headful_browser_collect_reports_mcp_permission_denial_from_stderr
    • test_headful_browser_collect_reports_execute_url_permission_denial_from_stderr
    • test_headful_mcp_allow_rule_is_scoped_and_does_not_use_dangerous_skip
    • test_headful_browser_prompt_blocks_wrong_review_result_export
    • test_headful_browser_accepts_public_ccbac03_xlsx_headers
    • test_source_auto_without_api_key_routes_to_headful_browser
    • test_source_browser_mode_routes_to_headful_even_when_api_key_exists
    • test_agy_google_login_is_optional_when_ai_disabled_for_api_mode
    • test_agy_is_prompted_for_headful_collection_when_auto_has_no_api_key
    • test_agy_is_prompted_for_forced_browser_mode_even_with_api_key
  • RED 실패 확인:
    • 콘텐츠 라벨 위에서 <MouseWheel> 발생 시 canvas.yview() 가 이동하지 않음.
    • 상단 안내문에 무엇:, 왜:, 어떻게:, 다음: 라벨이 실제로 노출됨.
    • dmf_crawler.agy.browser_collect 모듈 및 pipeline._fetch_with_headful_browser 경로가 없음.
    • 첫 실제 headful smoke 에서 AGY가 mcp permission auto-deny 로 빈 응답을 냄.
    • mcp(chrome-devtools/*)를 top-level 에만 넣었을 때 실제 AGY shared config 에 매칭되지 않아, userSettings.globalPermissionGrants.allow 도 필요함을 확인.
    • 다음 smoke 에서 execute_url permission auto-deny 발생.
    • CCBAC03 실제 xlsx 헤더(신청인, 제조국가, 최초등록일자, 최종변경일자)를 기존 API/baseline 헤더(업체명, 제조국가명, 발급일자)만 요구하던 파서가 거부함.
    • 진단 화면이 agy.enabled=false만 보고 auto+API 키 없음 또는 browser 모드에서도 AGY/Google 로그인을 “필요 없음”으로 표시할 수 있었음.
    • AGY가 처음에는 같은 파일명(의약품등심사결과공개.xlsx)을 wrong export 로 보냈다고 판단했으나, parser alias 보강 후 실제 CCBAC03 데이터 9,816건으로 확인됨. 파일명은 사이트가 generic 하게 붙이는 이름이다.
  • 수정:
    • ScrollFrame 이 canvas 뿐 아니라 내부 텍스트/카드 위의 wheel 이벤트도 처리하도록 event.widget 부모 체인을 검사하는 전역 wheel binding 으로 변경.
    • 상단 안내문을 자연스러운 한국어 2~3문장으로 교체. 예: 먼저 자료 보관소, 리포트 저장 폴더를 확인해 주세요.
    • src/dmf_crawler/agy/browser_collect.py 추가. AGY 프롬프트는 visible/headful Chrome + chrome-devtools MCP 를 명시하고 curl/headless/API 직접 호출과 비밀번호/토큰 prompt 입력을 금지한다.
    • source.modeauto | api | browser 로 확장. auto 에서 API 키가 없으면 AGY headful 수집을 시도하고, browser 는 키가 있어도 headful 수집을 강제한다.
    • AGY headful 다운로드는 .crdownload/.tmp 없음, xlsx zip/XML 구조, 행 수, sheet XML 파싱을 통과해야 FetchResult 로 인정한다.
    • headful 권한 준비 함수 ensure_headful_mcp_permission() 추가. 실제 AGY CLI가 읽는 top-level permissions.allowuserSettings.globalPermissionGrants.allow 양쪽에 mcp(chrome-devtools/*), execute_url(nedrug.mfds.go.kr)만 좁게 추가한다. mcp(*), execute_url(*), --dangerously-skip-permissions는 금지.
    • 프롬프트는 ambiguous 버튼 클릭 대신 visible Chrome 에서 https://nedrug.mfds.go.kr/pbp/CCBAC03/getExcel 을 열도록 좁혔다.
    • prototype_xlsx.read_rows() 는 CCBAC03 웹 xlsx 헤더 alias(신청인, 제조국가, 최초등록일자, 최종변경일자)를 API 표준 RawRecord 필드로 매핑한다. 날짜는 최종변경일자를 우선하고 없으면 발급일자, 최초등록일자 순으로 사용한다.
    • checks.py 는 AGY 필요 여부를 agy.enabled만으로 보지 않고 source.mode와 API 키 존재까지 함께 본다. API 모드+AI 요약 꺼짐이면 AGY 불필요, auto+키 없음/browser면 수집용 AGY 준비를 WARN으로 안내한다.
  • 검증:
    • pytest tests -q79 passed
    • 실제 수동 smoke: ./.venv/Scripts/python.exe -m dmf_crawler run --trigger manual --force --skip-agySUCCESS exit=0
    • 실측 run_id: run_20260903_130332
    • 다운로드: data/headful_downloads/run_20260903_130332/의약품등심사결과공개.xlsx (사이트 generic 파일명)
    • 수집/정규화: 9,816건 → 9,816건, persist/report/backup 성공
    • 리포트: reports/DMF_리포트_2026-09-03.xlsx (9,903행급)
  • 남은 확인:
    • 전용 Chrome profile Preferences, CDP 포트 sentinel 은 RED backlog 에 남아 있다.
    • 06:00 스케줄러가 실제 로그인 데스크톱 세션에서 headful Chrome 을 안정적으로 띄우는지는 별도 수동 smoke 가 필요하다.

2026-09-03 릴리즈 배포본 생성 완료

사용자 요청: 카카오톡으로 전달할 수 있게 원드라이브 바탕화면에 압축 배포본과 가이드를 만들어 둘 것.

산출물:

  • 폴더: C:\Users\encep\OneDrive\바탕 화면\DMF_Crawler_v0.1.0_20260903
  • zip: C:\Users\encep\OneDrive\바탕 화면\DMF_Crawler_v0.1.0_20260903.zip
  • SHA256: C:\Users\encep\OneDrive\바탕 화면\DMF_Crawler_v0.1.0_20260903.sha256.txt
  • 사용자 첫 안내: 00_먼저_읽어주세요.txt
  • 검증 요약: 릴리즈_검증.txt

포함:

  • bootstrap.cmd, README.md, pyproject.toml, src, config, scripts, prompts, docs 일부, fixture 샘플 리포트 1개.

제외 감사 통과:

  • .venv, data, logs, 실제 reports, backup, state, tests, __pycache__, *.pyc, *.lnk, config.local.toml, service_key, oauth-token 없음.

검증:

  • pytest tests -q → 79 passed
  • compileall → exit 0
  • dmf_crawler --help → exit 0
  • 최종 zip contents audit → 필수 파일 누락 없음 / 금지 파일 없음
  • 최종 zip 압축 해제본 compileall → exit 0
  • 최종 zip 압축 해제본 dmf_crawler --help → exit 0
  • 현재 개발 폴더 doctor --json 은 exit 2: Excel 이 DMF_리포트_2026-09-03.xlsx 를 열고 있어 report_writable CRITICAL. 배포본 결함은 아님. Excel 닫으면 기존처럼 WARN 수준으로 내려갈 것.

주의:

  • zip 안에는 실제 DB/리포트/로그가 없다. 새 사용자 PC는 압축 해제 후 bootstrap.cmd 를 먼저 실행해야 한다.
  • fresh PC 에서 bootstrap.cmddoctor.venv가 없어 CRITICAL이 정상이다.
  • 인증키 없이 최신 수집을 원하면 AGY 설치/Google 로그인 후 headful Chrome 경로를 사용한다.

2026-09-03 사용자 실측 UI 회귀 — RED 고정·수정 완료

사용자가 새로 제보한 스크린샷:

  • C:\Users\encep\AppData\Local\Temp\pi-clipboard-5df73a57-a813-41be-b025-819e48bd73c2.png

관찰된 문제:

  • 온보딩/진단 창 하단 footer 버튼들이 글자 없이 빈 버튼처럼 보임. 이전에 “하단 버튼 최소 크기” RED를 추가했지만 실제 화면 결함을 충분히 잡지 못했다.
  • 일부 버튼이 여전히 짜부라지거나 clipped 된다.
  • “새 버전” 화면에서 보여야 할 버튼/액션이 아예 안 보이는 경우가 있다.
  • 현재 테스트 test_onboarding_footer_buttons_have_readable_minimum_size 는 단순 width/요청 크기만 보므로, 실제 렌더링에서 텍스트가 사라지는 문제를 놓쳤다.

TDD 처리:

  • 스크린샷 상태를 재현하는 fixture/check 결과를 주입해, refresh() 이후 실제 버튼 text, winfo_ismapped(), winfo_width/height(), winfo_reqwidth/height(), 창 밖 clipping, scroll viewport overflow 를 검사하도록 RED를 강화했다.
  • 추가 RED:
    • test_onboarding_footer_buttons_render_visible_text_after_refresh
    • test_onboarding_footer_buttons_keep_text_when_required_items_exist
    • test_onboarding_row_action_buttons_are_not_clipped_inside_scroll_view
    • test_onboarding_new_grouped_layout_keeps_primary_actions_visible
  • RED 실패 확인:
    • footer 버튼 winfo_ismapped() == 0 으로 실제 화면에 배치되지 않음.
    • 창 요청 높이 771px 이 실제 높이 700px 을 넘어 footer가 잘림.
    • 긴 한글/경로 제목에서 scroll 내부 요청 폭 1745px 이 viewport 867px 을 넘어 overflow.
  • 수정:
    • src/dmf_crawler/gui/app.py 의 shell layout 을 pack 누적 방식에서 grid 기반 고정 footer/가변 scroll 구조로 변경.
    • SCROLL_VIEW_HEIGHT=300 으로 1366×768 노트북 예산 안에서 footer를 보존.
    • 긴 행 제목/detail/fix_hint 에 wraplength 를 적용해 행 액션 버튼이 viewport 밖으로 밀리지 않게 함.
    • CRITICAL 실패가 있으면 footer [지금 실행] 버튼을 disabled 로 유지.
  • 검증:
    • pytest tests/test_ux_regressions.py tests/test_gui_design_contracts.py -q14 passed
    • pytest tests -q65 passed
  • 남은 확인: 실제 Windows tkinter 창을 열어 육안 확인은 아직 하지 않았다. 스크린샷 기반 결함은 자동화 RED로 고정됐다.

2026-09-03 API 키 선택 정책 복구

  • 사용자 지적에 따라 회귀 수정: 공공데이터포털 인증키는 선택이다.
  • 키가 없으면 preflight에서 BLOCKED 하지 않는다. 공식 API fetchSKIPPED 처리하고 ctx.stale=True로 마지막 성공 스냅샷 리포트 경로를 사용한다.
  • doctor/온보딩에서도 api_key_present, api_key_validWARN 선택 기능이다. 실패해도 [지금 실행]을 막지 않는다.
  • GUI 그룹에서 인증키는 필수 설정이 아니라 선택 기능으로 이동했다.
  • 추가/수정 RED:
    • test_run_without_service_key_uses_existing_baseline_instead_of_blocking
    • test_public_data_api_key_is_optional_in_doctor
    • test_public_data_api_key_is_grouped_as_optional_in_onboarding
    • test_public_data_api_key_optional_policy_is_documented_in_ssot
  • 주의: 키 없는 최신 웹/엑셀 수집 소스는 AGY headful Chrome 경로로 구현·수동 smoke 성공했다. 다만 06:00 스케줄러에서 로그인 데스크톱 세션/Chrome 프로필/9222 CDP 포트가 항상 준비되는지는 아직 별도 RED·수동 smoke 가 필요하다.

2026-09-03 디자인 업그레이드 완료

  • design.md 생성: 컨셉은 감사 가능한 조용한 계기판. Windows 앱은 tkinter/한국어/Fluent식 그룹화, xlsx는 규제 원장/출처/접근성 중심.
  • Windows 앱 개선:
    • 상단 요약 strip: 필수 N / 권장 N / 정상 N.
    • 체크 항목을 필수 설정 / 선택 기능 / 운영 상태로 그룹화.
    • 공식 API 인증키와 AI 요약은 선택 기능입니다. 없어도 기존 자료 리포트 생성은 막지 않습니다. 문구를 화면 구조에 포함.
    • 각 행에 StatusBadge + 텍스트 상태(통과/필수 조치/권장 조치) + 최소 폭 action button 적용.
  • xlsx 개선:
    • 워크북 문서 속성 추가: title/subject/author/category/keywords/comments/created.
    • 00_대시보드!A1에 스크린리더용 목적문 추가.
    • dashboard 내부 링크, 각 데이터 시트의 ← 대시보드 역링크를 RED로 검사.
    • 외부 링크 문구를 조회에서 원문 조회로 변경.
    • 집계 내부 링크 문구를 원장 보기, 워치리스트 링크를 변경분 보기로 변경.
    • 데이터 시트 visible column width cap 검사 추가. 01_오늘변경분 변경내용 열은 52 → 48로 줄여 긴 값은 wrap 처리.
  • 새/강화 테스트:
    • tests/test_gui_design_contracts.py
    • tests/test_report_e2e_design.py
  • 검증:
    • python -m compileall -q src tests PASS
    • pytest tests -q58 passed
  • 실제 9,084건 리포트 재생성:
    • reports/DMF_리포트_2026-09-02.xlsx
    • 크기: 1,273,793 bytes
    • OOXML 확인: A1 목적문 존재, dashboard internal links 8개, 원문 조회 포함, core title 포함, visible max column width <= 48.71.
    • 주의: reports/~$DMF_리포트_최신.xlsx 잠금 파일이 있어 reports/DMF_리포트_최신.xlsx는 갱신 실패. Excel에서 최신본을 닫고 재실행해야 최신본 복사가 된다.

1. 프로젝트가 무엇인가

Windows 11 PC 에서 매일 06:00 에 한국 식약처 원료의약품 등록(DMF) 데이터를 수집하고, 전일 대비 신규·변경·취하를 탐지해, 탭별로 연동된 보기 좋은 xlsx 리포트를 만든다.

AI CLI 는 Google Antigravity CLI (agy) 를 두 역할로 쓴다. 수집용 AGY는 visible/headful Chrome 을 조작하고, 요약용 AGY는 사용자가 AI 요약을 켰을 때만 headless/non-interactive 로 호출한다. Claude Code 가 아니다. 쓰는 사람은 개발자가 아니다. 더블클릭 한 번으로 설치·설정이 끝나야 하고, 문제가 생기면 창이 떠서 고치는 법을 알려줘야 한다.

전체 요구사항: docs/00-REQUIREMENTS.md (R1R8, N1N8)


2. 사용자가 확정한 것 (인터뷰 답변)

항목 사용자 결정
PC 상태 매일 다르다. 켜둘 때도 끌 때도 있음 → 트리거 3개(매일 06:00 + 로그온 시 + 놓친 작업 따라잡기), 중복 실행 방지 잠금 필수
관리자 승격 설치 시 1회 허용
API 키 아직 없음. 발급받은 적 없음. 공공데이터포털 인증키는 선택이며 없어도 기존 자료 리포트 생성은 막지 않는다.
수집 방식 source.mode=auto: API 키가 있으면 공식 API, 없으면 AGY headful Chrome 으로 CCBAC03 xlsx 다운로드. source.mode=browser: 키가 있어도 headful 강제. source.mode=api: 공식 API만 사용하고 키 없으면 기존 성공 자료로 폴백.
크롤링 방식 agy 가 headful Chrome 을 몰아 사람처럼 접근. 실제 수동 smoke에서 CCBAC03/getExcel 다운로드 및 9,816건 정규화 성공.
브라우저 로그인 프로필/세션 유지로 처리한다. 비밀번호를 agy 프롬프트에 넣거나 모델에게 입력시키지 않는다.
리포트 공유 본인이 보되 공유 옵션도 설정 가능해야 함
리포트 첫 화면 대시보드
워치리스트 필요함. 목록은 나중에 등록
알림 화면 창 + 메신저 둘 다

아직 사용자에게 못 받은 답

  • 의약품안전나라 계정을 갖고 있는가? 로그인 시 익명보다 더 보이는 정보가 있는가?
  • 메신저는 어느 것인가 (디스코드/텔레그램/슬랙/이메일)
  • 워치리스트 초기 목록
  • DMF_현황.xlsx 를 만든 것이 본인인가 타인인가 (카카오톡으로 받음)

3. 실측으로 밝혀진 결정적 사실

이것들은 전부 실제로 실행해서 확인한 것이다. 추측이 아니다.

3.1 기존 프로토타입이 이미 있었다

사용자가 준 C:\Users\encep\OneDrive\문서\카카오톡 받은 파일\DMF_현황.xlsx 를 전수 분석했다. 정본: docs/design/00b-baseline-data-analysis.md

  • 시트 3개: 전체(9,084건) / 신규(헤더만) / 갱신이력(2026-09-02, 9084, 0)
  • 컬럼 8개 = API 7필드 + 최초수집일(파생). 즉 이미 공식 API 로 수집하고 있었다.
  • 서식이 전혀 없다 — 틀 고정·자동필터·조건부서식·차트가 전부 0. "예쁘게" 요구가 겨냥하는 지점.

3.2 API 7필드로 신규·변경·취하 전부 탐지 가능하다

사실
API 전체 건수 9,084건
웹 화면 건수 9,840건
차이 756건 → API 는 정상 건만 반환 → 취하 = 레코드 소멸로 판정 가능
등록번호 중복 0건 → 완전한 자연 키
등록번호 앞8자리 vs 발급일자 44.5% 불일치발급일자는 최종 갱신일로 움직인다 = 변경의 직접 신호

예: 20050831-33-A-81-08(18) 의 발급일자가 2026-08-18. 괄호가 변경 차수.

놓치는 것: 연차보고, 변경 사유, 취하 vs 취소 구분. 대상의약품은 등록번호 포맷에서 파생 가능( 접두어 = 신물질).

3.3 nedrug 은 봇 차단이 없다

curl/8.10.1 UA 그대로 200 OK, HTML 382,506 bytes 수신. Cloudflare/Akamai/DataDome 없음. Apache + JSESSIONID + Elevisor for J2EE(WAS 모니터링).

즉 차단 회피 기술은 원래부터 불필요했다. 브라우저 방식의 근거는 "블락 방지"가 아니라 robots.txt 에 대한 태도와 로그인 세션 대비다.

3.4 엑셀 다운로드 실측 성공 + 함정 2개

POST /pbp/CCBAC03/getExcel9,841행 / 1,379,451 bytes 1회 수신 성공. 요청 2회(세션 GET + 엑셀 POST).

  • ⚠️ 날짜 파라미터를 비우면 헤더만 든 3,575 bytes 빈 파일이 온다. 조용한 실패.
  • ⚠️ 받은 xlsx 를 openpyxl 로 열면 0행으로 읽힌다. <dimension ref="A1"/> 때문. sheet1.xml 직접 파싱 필수.

3.5 robots.txt 는 자기모순

robots.txtUser-agent: * / Disallow: / (26 bytes) 인데, /bbs/117 HTML 에는 <meta name="robots" content="index,follow"/> 가 있다.

3.6 법적 선

  • 형사: 대법원 2022.5.12. 2021도1533 — 정보통신망법 §48 등 전부 무죄 확정(공개정보 + 보호조치 없음 + 약관상 이용제한은 접근제한 아님)
  • 민사: 야놀자 사건 10억(부정경쟁방지법 성과도용), 잡코리아 v 사람인 2.5억(저작권법 §93)
  • 두 민사 판결의 공통 결정요소는 "재배포·경쟁 이용"
  • 지켜야 할 선: 내부 리포트 전용, 재배포 없음. 이 선 안이면 리스크 Low

3.7 agy CLI 실측

정본: docs/research/05a-agy-cli-ssot.md (1,079줄)

  • v1.1.22, 경로 %LOCALAPPDATA%\agy\bin\agy.exe
  • OAuth 토큰이 키링이 아니라 평문 파일: ~/.gemini/antigravity-cli/antigravity-oauth-token → 작업 스케줄러를 동일 사용자 계정으로 돌리면 인증 통과. SYSTEM 금지.
  • ⚠️ --json-schema 를 신뢰하면 안 된다. 실측에서 structured_output 이 없었고 response 에 JSON 이 4회 반복 + 한국어 산문 혼입, 토큰 3배. 자체 파싱·검증 필수.
  • ⚠️ 첫 호출 오버헤드 input 28,317 토큰 / 33.7초. 호출을 묶어야 한다.
  • chrome-devtools MCP 가 이미 등록·활성화됨 → agy 가 Chrome 제어 가능
  • 배치 체크리스트는 SSOT §16

3.8 Windows 실행 방식 제약

  • 이 계정(Administrators 미소속)은 S4U 태스크 등록이 거부된다. Interactive 만 성공. 실측 확인.
  • 사용자가 관리자 승격 1회를 허용했으므로 승격하면 S4U 가능한지는 미확인
  • ⚠️ BOM 없는 UTF-8 .ps1 을 Windows PowerShell 5.1 이 CP949 로 오독해 한글이 깨진다. 실측 재현. 모든 .ps1 은 UTF-8 with BOM 필수.
  • 콘솔 창을 원리적으로 없애려면 GUI 서브시스템 호스트(wscript.exe/pythonw.exe)를 최상위에 둬야 한다

3.9 공공데이터 API 제약

  • 트래픽 10,000 = 호출 횟수(레코드 아님). 하루 수십 호출이면 1% 미만. 운영계정 전환·활용사례 등록 영원히 불필요
  • ⚠️ 인증키는 포털 계정당 1개. 재발급하면 기존 키 자동 폐기. 사용자가 무관한 목적으로 재발급하면 배치가 죽는다
  • ⚠️ 인증키에 사용 기한 있음(오류코드 31). 24개월설은 미확정 → 하드코딩 금지, 오류 코드 관측으로 감지

4. 확정된 아키텍처

정본: docs/design/01-architecture.md (1,380줄, ADR 18개)

뼈대: 후보 3안을 독립 생성 → 3개 렌즈(운영 단순성/실패 내성/진화 가능성)로 심사 → SQLite 이벤트 소싱 안을 뼈대로 채택하고 나머지 두 안의 좋은 부분을 이식.

핵심 결정:

  • 저장소: SQLite 단일 파일 + 이벤트 소싱(append-only 스냅샷 + 이벤트)
  • 의존성 3개만: httpx, XlsxWriter, jsonschema. pandas·openpyxl·PyYAML 기각
  • 집계는 SQL GROUP BY로. pandas 안 씀
  • xlsx 는 XlsxWriter 100%, 매일 새 파일 통째 생성
  • 모든 숫자는 파이썬이 계산해 값으로 씀(LibreOffice 재계산 안 함)
  • GUI 는 stdlib tkinter(외부 모듈 의존 시 "설치 안 되면 알림도 안 됨" 순환 실패)
  • 배치는 UI 를 절대 안 띄운다. alert 의도만 기록 → 로그온 세션의 Interactive 에이전트가 15분마다 읽어 창을 띄움
  • 온보딩=복구는 단일 컴포넌트. checks.py 진단 엔진을 CLI(doctor)와 GUI(onboard)가 공유
  • agy 는 두 계층이다. 수집 계층은 fetch 단계에서 headful Chrome 으로 CCBAC03 xlsx 를 받을 수 있고, 요약 계층은 diff·저장 후 선택적으로만 호출된다. enrichment 테이블을 events 와 물리 분리해 R4.4(AI 요약 실패해도 리포트 생성)를 스키마 레벨에서 강제
  • 오케스트레이션은 순차 STAGES 리스트 + 체크포인트. DAG 아님

이번 방향 전환으로 이미 반영된 ADR 보정

ADR 현재 정리
ADR-01 1차 소스는 공식 API지만, API 키가 없거나 source.mode=browser이면 AGY headful Chrome CCBAC03 xlsx 수집을 사용한다.
ADR-02 별도 거대 소스 추상화 대신 source.mode 분기와 browser_collect.fetch_all()을 둔다.
ADR-06 Playwright/Selenium 직접 의존은 쓰지 않고, 브라우저가 필요할 때 AGY + chrome-devtools MCP + visible Chrome 을 쓴다.
ADR-10 headful 수집은 로그인 데스크톱 세션이 필요하다. 06:00 스케줄 smoke 는 남아 있다.
ADR-15 AGY 요약은 diff 후 선택 단계, AGY 수집은 fetch 단계의 별도 경로다. 두 AGY를 혼동하지 않는다.

5. 문서 현황 (총 5.6만 줄)

문서 역할
00-REQUIREMENTS.md 244 요구사항 SSOT. 여기서 시작
00-PROJECT-OVERVIEW.md 1,051 프로젝트 개요·로드맵·리스크·용어집
README.md 97 문서 지도
design/00-DATA-SOURCE-DECISION.md 386 데이터 소스 결정 + API 완전 명세
design/00b-baseline-data-analysis.md 591 기존 프로토타입 전수 분석. 실측 근거의 원천
design/01-architecture.md 1,380 아키텍처 확정안. ADR 18개
design/02-data-model.md 2,894 스키마 DDL·정규화·변경탐지
design/03-xlsx-report-spec.md 3,554 시트별 셀 단위 명세
design/04-onboarding-wizard.md 4,861 온보딩=복구 GUI 명세
ops/01-scheduling-and-resilience.md 2,379 스케줄 등록·재부팅 내성·워치독
ops/02-failure-alerting.md 4,724 알림 등급·문구·버튼 액션
ops/03-api-usage-policy.md 1,796 API 제약·오류 코드·키 만료 대응
ops/04-official-data-request-channels.md 401 필드 추가 공식 요청 절차 + 문구 초안
research/01~10 30,018 조사 정본 10종
research/_raw/*.raw.md 원본 조사 덤프 8개(1.8MB). WebFetch 428건 + WebSearch 224건 결과 보존

⚠️ WebSearch 예산이 이 세션에서 소진됐다(200/200). 다음 세션에서는 다시 쓸 수 있을 것이나, 안 되면 https://www.bing.com/search?q=... 를 WebFetch 하는 우회를 쓴다.


6. 진행 중인 작업 / 남은 검증

  • AGY headful CCBAC03 xlsx 수집은 구현했고, 실제 수동 smoke 에서 9,816건 수집·정규화·저장·리포트 생성까지 성공했다.
  • 남은 핵심은 스케줄러 환경 검증이다. 06:00 태스크가 로그인 데스크톱 세션에서 visible Chrome, chrome-devtools MCP, execute_url(nedrug.mfds.go.kr) 권한, 다운로드 폴더를 안정적으로 준비하는지 확인해야 한다.
  • 전용 Chrome profile Preferences, CDP 포트 sentinel, headful 실패 시 온보딩 복구 버튼은 backlog 다.

7. 코드 현황

주요 모듈은 현재 존재하며, 전체 smoke 를 통과했다.

  • CLI/pipeline/storage/repo/report/gui/notify/agy 모듈 구현됨.
  • src/dmf_crawler/agy/browser_collect.py: 수집용 AGY headful Chrome 경로.
  • src/dmf_crawler/importers/prototype_xlsx.py: 기존 baseline xlsx + CCBAC03 웹 xlsx 헤더 alias 파싱.
  • tests/: 79개 통과.

검증된 명령:

.\.venv\Scripts\python.exe -m compileall -q src tests
.\.venv\Scripts\python.exe -m dmf_crawler --help
.\.venv\Scripts\python.exe -m dmf_crawler doctor --json
.\.venv\Scripts\python.exe -m pytest tests -q
.\.venv\Scripts\python.exe -m dmf_crawler run --trigger manual --force --skip-agy

8. 다음 세션이 할 일 (우선순위 순)

  1. 스케줄러에서 headful Chrome 이 실제 로그인 세션에 뜨는지 수동 smoke 한다.
  2. Chrome profile/다운로드 폴더/CDP 9222 sentinel RED를 추가하고 구현한다.
  3. headful 실패 원인별 온보딩 문구와 복구 버튼을 다듬는다. 특히 mcp/execute_url 권한 부족은 사용자가 바로 고칠 수 있어야 한다.
  4. 실제 리포트를 Excel에서 열어 디자인/내용을 육안 감사하고, 깨지는 부분을 RED로 추가한다.
  5. 사용자에게 미해결 질문 확인: 메신저 종류, 워치리스트 초기 목록, 의약품안전나라 로그인 필요 여부.

9. 반드시 지킬 것 (함정 모음)

  • .ps1 은 UTF-8 with BOM. 아니면 한글 깨짐
  • openpyxl 로 nedrug xlsx 를 열지 마라. 0행으로 읽힌다. sheet1.xml 직접 파싱
  • agy --json-schema 결과를 믿지 마라. 자체 JSON 추출 + jsonschema 검증
  • agy 는 절대 경로로 호출, AGY_CLI_DISABLE_AUTO_UPDATE=true, --disable-slash-commands, stdout/stderr 분리
  • 작업 스케줄러는 사용자 계정으로. SYSTEM 은 agy 토큰에 접근 못 함
  • 수집 완결성 검증 없이 diff 하지 마라. 전건 취하 오탐이 최악의 실패 모드. 안전장치 5종 + 취하 비율 임계
  • 비밀번호를 agy 프롬프트에 넣지 마라. 클라우드 모델로 전송된다. 브라우저 로그인은 프로필 세션 유지 또는 CDP 직접 입력으로
  • API 키·OAuth 토큰을 로그·문서·저장소에 남기지 마라
  • 재배포하지 마라. 법적 선이 거기 있다
  • 여러 에이전트에게 같은 파일을 맡기지 마라. 이 세션에서 실제로 충돌이 났다

10. 이 세션에서 있었던 일 (요약)

  1. 첫 리서치 워크플로우 8축이 사용자 중간 메시지로 인터럽트됨 → 트랜스크립트에서 전량 복구docs/research/_raw/ 에 보존
  2. 그 원본을 opus 로 정제해 research/01~10 생성
  3. robots.txt 전면 금지 발견 → 공식 API 로 방향 전환
  4. 공식 Open API 발견 → 우회 불필요 판단
  5. 아키텍처 3안 → 3렌즈 심사 → 확정
  6. 사용자가 DMF_현황.xlsx 제공 → 프로토타입 존재 확인, API 7필드 충분성 입증
  7. 온보딩 마법사·API 제약·공식 요청 채널 설계
  8. 사용자가 agy headful 브라우저 방식 요구 → 실현성 실측 착수
  9. 사용자 인터뷰로 운영 조건 확정
  10. 구현 착수 (진행 중)

11. 새로 추가된 작업 원칙 — TDD/RED/디자인 감사

사용자가 2026-09-03에 확정한 새 원칙:

  • TDD 를 최상위 개발 원칙으로 둔다. No RED, No Code.
  • 기능뿐 아니라 xlsx 리포트와 tkinter GUI 디자인도 RED 로 감사한다.
  • 디자인 RED 는 중앙 정렬 남용, UX 붕괴, 시각적 치우침, 안 보이는 폰트, overflow, flex/grid 오류, input padding, 아이콘-텍스트 vertical align, 폼 라벨, 실제 텍스트 입력/버튼 클릭/복합 시나리오까지 잡는다.
  • RED 는 계속 늘리기만 하지 않고, 사용자 유즈케이스 기준으로 통폐합·삭제·강화한다.

새 정본:

  • docs/research/11-tdd-red-and-design-audit-theory.md — Kent Beck/GDS/Google/Playwright/Testing Library/WCAG/NN/g/Vercel/LLM-TDD 논문 근거 아카이브
  • docs/design/07-tdd-red-system.md — 프로젝트 적용 SSOT
  • AGENTS.md — AI agent 실무 지침
  • CLAUDE.md — Claude Code 포인터

이번 세션 완료

  • R0 계약 RED 를 tests/test_project_contracts.py 로 코드화했다.
  • 첫 RED 실패는 기대대로 dmf_crawler.gui.app, dmf_crawler.notify.pump 누락이었다.
  • 최소 구현으로 src/dmf_crawler/gui/app.py, src/dmf_crawler/notify/pump.py 를 추가했다.
  • 검증 결과:
    • python -m compileall -q src tests 통과
    • 전체 import scan 통과
    • python -m dmf_crawler --help 통과
    • python -m dmf_crawler doctor --json 은 API 키 없음으로 exit 2 + JSON 출력(정상)
    • pytest tests -q → 44 passed

추가 사용자 제보 UX 결함 수정(2026-09-03 04시대)

사용자 실행 로그/스크린샷으로 확인된 문제:

  • cp949 콘솔에서 문자를 출력하다가 UnicodeEncodeError 로 CLI 가 다시 죽음.
  • Google 로그인/agy 가 기본 온보딩에서 필수처럼 보임. 사용자가 “Google 로그인 불가, 애당초 필요하면 물어봐야 한다”고 지적.
  • 온보딩 하단 버튼 텍스트가 세로로 찌그러져 읽기 어려움.

TDD 처리:

  • tests/test_ux_regressions.py 추가.
    • test_cli_result_output_survives_cp949_console
    • test_doctor_json_survives_cp949_console
    • test_agy_google_login_is_optional_when_ai_disabled
    • test_google_login_optional_policy_is_documented_in_ssot
    • test_onboarding_footer_buttons_have_readable_minimum_size
  • RED 실패 확인 후 수정:
    • src/dmf_crawler/cli.py: 모든 사용자 출력/JSON 출력에 cp949 안전 _safe_print 적용, em dash 구분자 제거.
    • src/dmf_crawler/checks.py: agy.enabled=false 면 agy 설치/Google 로그인 체크를 OK + fix_action=None 으로 처리.
    • src/dmf_crawler/config.py, config/config.toml: AI 요약 기본값 false. Google 로그인은 사용자가 AI 요약을 켠 뒤에만.
    • src/dmf_crawler/gui/widgets.py, src/dmf_crawler/gui/app.py: 버튼 padding/최소 width 토큰 추가.
    • docs/00-REQUIREMENTS.md, docs/design/04-onboarding-wizard.md, AGENTS.md: Google 로그인은 선택 정책 반영.
  • 검증 결과:
    • pytest tests/test_ux_regressions.py -q → 5 passed
    • pytest tests -q → 54 passed
    • compileall 통과
    • 전체 import scan 통과
    • doctor --json cp949 출력에서 UnicodeEncodeError 없음(exit 2는 API 키 없음으로 정상)

xlsx E2E 산출물 결함 수정(사용자 지적: “유의미한 xlsx를 단 한번도 배출한 적 없음”)

사용자 지적에 따라 완료 기준을 “파일 존재”가 아니라 seeded DB → 실제 xlsx 생성 → zip/XML 디자인 감사 통과로 올렸다.

TDD 처리:

  • tests/test_report_e2e_design.py 추가.
    • 기준선 2건 + 둘째 날 신규/변경/취하/워치리스트/AI 코멘트 fixture 를 실제 SQLite DB 에 적재.
    • build_report() 로 실제 .xlsx 생성.
    • zip/XML 로 검사: 파일 구조, 시트 순서, sharedStrings 내용, freeze panes, filter/table, 조건부서식 수, 탭 색, Excel table 이름.
  • RED 실패 1: report.datav_current_records.ingredient_key 를 읽지만 DB view 에 컬럼 없음.
    • 수정: src/dmf_crawler/storage/migrations/0004_report_views.sql 추가. 기존 0001 checksum 을 깨지 않고 view 재생성.
  • RED 실패 2: 05_워치리스트 시트에 필터/표 없음.
    • 수정: src/dmf_crawler/report/sheets/s05_watchlist.pyT_WATCH Excel table 추가.
  • 과특정 RED 1건 정정: 출처 문구의 OOXML/문장 순서 정확 일치 대신 출처: + 식품의약품안전처 포함으로 SSOT 정합화.
  • 검증 결과:
    • pytest tests/test_report_e2e_design.py -q → 1 passed
    • pytest tests -q → 55 passed
    • compileall 통과
  • 사용자 확인용 SAMPLE 산출물 생성:
    • reports/sample/DMF_리포트_SAMPLE_2026-09-04.xlsx
    • 크기: 약 48 KB
    • 시트: 00_대시보드, 01_오늘변경분, 02_전체현황, 03_성분별, 04_업체별, 05_워치리스트, 06_추이, 99_메타
    • 주의: fixture 기반 샘플이며 실제 식약처 데이터가 아니다. 현재 실제 데이터 리포트는 공식 API 키 또는 AGY headful CCBAC03 xlsx 수집으로 생성 가능하다.

실제 기존 데이터 import 완료(사용자 지적: “기존 데이터는 다 어디 갔어”)

확인 결과:

  • 기존 원본 파일은 살아 있음:
    • C:\Users\encep\OneDrive\문서\카카오톡 받은 파일\DMF_현황.xlsx
    • 크기: 757,331 bytes
    • 전체 시트: A1:H9085 = 헤더 1 + 데이터 9,084건
  • 이전까지 프로젝트 DB 는 비어 있었음:
    • records=0, snapshots=0, events=0, fetch_stats=0
    • API 키 없음으로 BLOCKED 실행 기록만 존재

TDD 처리:

  • tests/test_import_prototype_xlsx.py 추가.
    • openpyxl 없이 zip/XML 로 기존 프로토타입 xlsx 구조를 읽는 importer RED.
    • 한글 헤더(등록번호, 성분명, 업체명, 제조소명, 제조소소재지, 제조국가명, 발급일자)를 API 표준 컬럼으로 매핑.
    • baseline DB 적재 + 실제 새 xlsx 리포트 생성까지 검사.
  • RED 실패: dmf_crawler.importers 모듈 없음.
  • 수정:
    • src/dmf_crawler/importers/__init__.py
    • src/dmf_crawler/importers/prototype_xlsx.py

실제 기존 파일 적용 결과:

  • 실행:
    • 원본: C:\Users\encep\OneDrive\문서\카카오톡 받은 파일\DMF_현황.xlsx
    • run_id: import_prototype_20260902
    • run_date: 2026-09-02
  • DB:
    • schema_version=4
    • record_versions=9084
    • records=9084
    • snapshots=9084
    • fetch_stats=1
    • quality_checks=7
    • events=0 (기준선 수립일이므로 정상)
  • 실제 리포트:
    • reports/DMF_리포트_2026-09-02.xlsx
    • reports/DMF_리포트_최신.xlsx
    • 크기: 1,273,258 bytes
    • 포함 시트: 00_대시보드, 01_오늘변경분, 02_전체현황, 03_성분별, 04_업체별, 06_추이, 99_메타
    • 원본 문자열 검증: 프레가발린, 젬시타빈염산염, 출처:, 식품의약품안전처, 기준선 포함 확인
    • 워치리스트 시트는 현재 watchlist DB 가 0건이라 생성되지 않음(조건부 생성 설계)
  • 검증:
    • compileall 통과
    • pytest tests -q → 56 passed

다음 구현 작업은 실제 리포트 파일을 사람이 열어본 뒤 디자인 결함을 스크린샷/RED로 추가한다. 그 다음 API 키 입력 후 실제 다음 수집을 수행해 9,084 기준선 대비 신규/변경/취하 이벤트를 만들어야 한다.


이 문서는 세션 간 인수인계용이다. 프로젝트 정본은 docs/00-REQUIREMENTS.md, docs/design/01-architecture.md, docs/design/07-tdd-red-system.md 다.