DMF_Crawler/docs/design/00-DATA-SOURCE-DECISION.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

386 lines
25 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 데이터 소스 결정 — 크롤링에서 공식 Open API 로
> **이 문서의 역할**: 이 프로젝트가 데이터를 **어디서 어떻게 가져올지**를 확정한다. 이 결정이 아키텍처·법적 리스크·운영 비용 전부를 바꾸므로, 다른 모든 설계 문서보다 상위에 있다.
**결정일**: 2026-09-02
**결정 상태**: ✅ 확정 — **2026-09-02 실측으로 검증 완료**
**영향 범위**: 아키텍처, 봇 차단 대응, 법적 검토, agy 역할 정의, 운영 복잡도 전부
> ### ✅ 실측 검증 완료 (2026-09-02)
>
> 이 결정을 내릴 당시 남아 있던 최대 불확실성은 **"API 7필드만으로 신규·변경·취하를 탐지할 수 있는가"** 였다.
> 이후 사용자가 이미 운영 중이던 실제 산출물 `DMF_현황.xlsx`(9,084건) 를 전수 분석해 **세 가지 모두 탐지 가능함을 확인**했다.
>
> | 확인된 사실 | 값 |
> |---|---|
> | API 전체 건수 | **9,084건** (웹 화면 9,840건과 **756건 차이** → API 는 정상 건만 반환) |
> | 등록번호 중복 | **0건** → 완전한 자연 키 |
> | 등록번호 앞 8자리 vs `발급일자` 불일치 | **44.5%** → `발급일자`는 **최종 갱신일**로 움직인다 = 변경의 직접 신호 |
>
> 상세는 **[`00b-baseline-data-analysis.md`](./00b-baseline-data-analysis.md)** 참조. 이 문서의 §7.1 판정 규칙과 부록 B 는 그 결과로 갱신되었다.
---
## 0. 한눈에 보기
- 의약품안전나라(`nedrug.mfds.go.kr`)의 `robots.txt`**`User-agent: * / Disallow: /`** — 전 경로 자동화 접근 금지다.
- 그런데 **식약처가 동일한 DMF 데이터를 공공데이터포털에 공식 Open API 로 공개**하고 있다. 무료, 자동승인, 이용허락범위 제한 없음.
- 즉 식약처의 메시지는 모순이 아니라 한 쌍이다: **"웹 화면을 긁지 말고 API 를 쓰라."**
- 따라서 이 프로젝트는 **HTML 크롤링을 하지 않는다.** 공식 API 를 정본 소스로 삼는다.
- 이 전환으로 봇 차단 대응, 셀렉터 유지보수, 법적 리스크, 브라우저 자동화 의존성이 **전부 사라진다.** 우회 기법을 설계하는 것보다 이 길이 모든 축에서 우월하다.
- API 는 "현황(스냅샷)"만 준다. 공고 이벤트(신규/변경/취하)는 **매일 전량 스냅샷을 받아 자체 diff 로 계산**한다. 공고문을 파싱하는 것보다 오히려 정확하고 누락이 없다.
- `agy` 는 차단 우회 도구가 아니라 **데이터 해석·요약·품질 관리 도구**로 재정의한다. 역할은 5절 참조.
---
## 1. 목차
1. [robots.txt 실측](#2-robotstxt-실측)
2. [공식 Open API 발견](#3-공식-open-api-발견)
3. [API 완전 명세](#4-api-완전-명세)
4. [왜 우회가 아니라 API 인가](#5-왜-우회가-아니라-api-인가)
5. [agy 역할 재정의](#6-agy-역할-재정의)
6. [API 로 커버되지 않는 것과 대응](#7-api-로-커버되지-않는-것과-대응)
7. [보조·확장 소스](#8-보조확장-소스)
8. [발급 절차](#9-serviceKey-발급-절차)
9. [이 결정이 무효화하는 기존 설계](#10-이-결정이-무효화하는-기존-설계)
10. [부록 A. 출처](#부록-a-출처)
11. [부록 B. 미해결 / 실측 필요](#부록-b-미해결--실측-필요)
---
## 2. robots.txt 실측
`https://nedrug.mfds.go.kr/robots.txt` 를 직접 열어 확인한 전문:
```
User-agent: *
Disallow: /
```
**해석**: 모든 사용자 에이전트에 대해 전 경로 크롤링을 금지한다. 예외 경로도, `Crawl-delay` 도, 사이트맵도 없다. 가장 강한 형태의 거부다.
**법적 지위**: robots.txt 자체는 법률이 아니라 관례(RFC 9309 는 표준 규격일 뿐 강제력이 아니다). 그러나 실무적으로 다음을 의미한다.
| 관점 | 의미 |
|---|---|
| 사이트 운영자의 의사 | 자동화 수집을 원하지 않는다는 **명시적 의사표시** |
| 분쟁 발생 시 | "명시적 거부를 알고도 접근했다"는 사실은 불리한 정황이 된다 |
| 기술적 현실 | 명시적 거부는 통상 서버 측 차단 로직과 함께 온다. 차단·IP 밴 위험이 상시 존재한다 |
| 지속 가능성 | 우회는 상대의 방어 갱신마다 깨진다. 유지보수가 영구히 따라붙는다 |
또한 `nedrug.mfds.go.kr/pbp/CCBGE01` 등 추정 경로는 **HTTP 404** 를 반환했고, 메인 페이지에서도 DMF 관련 링크의 href 를 확인하지 못했다. 즉 **URL 구조를 확정하는 것부터가 이미 비용**이다.
---
## 3. 공식 Open API 발견
공공데이터포털(`data.go.kr`)에서 "DMF" 로 검색한 결과 **정확히 이 프로젝트가 필요로 하는 데이터셋 2건**이 공개돼 있다.
| # | 제목 | 유형 | 경로 | 수정일 | 조회수 |
|---|---|---|---|---|---|
| 1 | **식품의약품안전처_원료의약품등록(DMF)현황** | 오픈API (XML/JSON) | `/data/15057075/openapi.do` | 2025-09-19 | 31,330 |
| 2 | DMF현황 | 연계데이터 (JSON+XML) | `/data/2095/linkedData.do` | 2026-07-08 | 4 |
2번 연계데이터의 엔드포인트는 `http://data.mfds.go.kr/openapi/MdcDmfInfoService/getMdcDmfList` 로, 1번의 구버전으로 보인다. **1번을 정본으로 채택한다** (HTTPS, 최신 버전, 상세 명세 제공, 활용신청 548건으로 검증됨).
---
## 4. API 완전 명세
### 4.1 기본 정보
| 항목 | 값 |
|---|---|
| API 명칭 | 식품의약품안전처_원료의약품등록(DMF)현황 |
| 제공기관 | 식품의약품안전처 |
| 관리부서 | 데이터혁신기획팀 |
| API 유형 | REST |
| 기본 데이터 포맷 | XML (`type=json` 으로 JSON 가능) |
| 서비스 URL | `https://apis.data.go.kr/1471000/MdcDmfInfoService01` |
| **요청 주소** | `https://apis.data.go.kr/1471000/MdcDmfInfoService01/getMdcDmfList01` |
| 오퍼레이션 | 1개 — "DMF현황 조회하기" |
| 등록일 | 2019-09-27 |
| 수정일 | 2025-09-19 |
| 활용신청 수 | 548 |
| **이용허락범위** | **제한 없음** |
| 비용 | 무료 |
| 심의 | 개발단계 **자동승인** / 운영단계 **자동승인** |
| 트래픽 | 개발계정 **10,000건/일**. 운영계정은 활용사례 등록 후 증량 신청 가능 |
| 참고문서 | `IROS_76_원료의약품(DMF)현황_v1.1.docx` (포털에서 다운로드) |
오퍼레이션 설명 원문:
> "등록번호, 발급일자, 업체명, 성분명, 제조소명 등의 원료의약품 현황 정보를 조회"
### 4.2 요청 파라미터
| 항목명(국문) | 영문명 | 크기 | 필수 | 샘플 | 설명 |
|---|---|---|---|---|---|
| 인증키 | `serviceKey` | 100 | **필수** | 인증키 | 공공데이터포털에서 발급받은 인증키. **URL Encode 필요** |
| 업체명 | `entp_name` | 200 | 선택 | 업체명 | 검색 조건 |
| 성분명 | `ingr_kor_name` | 3000 | 선택 | 성분명 | 검색 조건 |
| 페이지 번호 | `pageNo` | 5 | 선택 | 1 | 페이지번호 |
| 한 페이지 결과 수 | `numOfRows` | 3 | 선택 | 3 | 한 페이지 결과 수 |
| 데이터포맷 | `type` | 4 | 선택 | xml | 응답데이터 형식(xml/json), 기본값 xml |
> ⚠️ `numOfRows` 의 크기가 **3자리**로 명세돼 있다. 최대 999 일 가능성이 높다. 전량 수집 시 호출 횟수 계산에 영향을 준다. → 부록 B.
### 4.3 응답 필드
**공통 헤더**
| 항목명 | 영문명 | 크기 | 필수 | 샘플 |
|---|---|---|---|---|
| 결과코드 | `resultCode` | 4 | 필수 | `00` |
| 결과메시지 | `resultMsg` | 50 | 필수 | `NORMAL SERVICE.` |
| 한 페이지 결과 수 | `numOfRows` | 3 | 선택 | 3 |
| 페이지 번호 | `pageNo` | 5 | 선택 | 1 |
| 전체 결과 수 | `totalCount` | 7 | 선택 | 1 |
**데이터 항목** — 이것이 이 프로젝트의 원천 레코드다.
| 항목명(국문) | 영문명 | 크기 | 샘플 |
|---|---|---|---|
| 등록번호 | `DMF_PERMIT_NO` | 200 | `20121228-168-I-169-04` |
| 성분명 | `INGR_KOR_NAME` | 3000 | `포르모테롤푸마르산염수화물` |
| 업체명 | `ENTP_NAME` | 200 | `(주)대웅제약` |
| 제조소명 | `MNFCTR_NAME` | 150 | `SICOR SOCIETA'ITALIANA CORTICOSTER OIDO S.R.L.` |
| 제조소 소재지 | `MNFCTR_PLACE` | 2000 | `Rho(MI) - Via Terrazzano, 77, Italy` |
| 제조국가명 | `MANUF_COUNTRY_CODE_NM` | 1000 | `이탈리아,스위스` |
| 발급일자 | `DMF_PERMIT_DATE` | 30 | `2015-02-26` |
**필드 관찰**:
- `DMF_PERMIT_NO` 샘플 `20121228-168-I-169-04``발급일자8자리-숫자-알파벳-숫자-일련번호` 구조다. 앞 8자리가 최초 수리일로 보인다. 상세 해부는 `docs/research/01-dmf-domain-and-sources.md` 참조.
- `MANUF_COUNTRY_CODE_NM``이탈리아,스위스` 처럼 **콤마 다중값**이다. 정규화가 필요하다.
- `MNFCTR_NAME` 샘플에 `CORTICOSTER OIDO` 처럼 **공백이 잘못 들어간 원본 오류**가 보인다. 원본 데이터 품질이 완벽하지 않다는 신호다. 표기 정규화 계층이 필요하다.
- 상태 필드(신규/변경/취하)가 **없다.** 따라서 변경 탐지는 우리가 계산해야 한다 (7절).
### 4.4 호출 예시
```
https://apis.data.go.kr/1471000/MdcDmfInfoService01/getMdcDmfList01
?serviceKey=<URL_ENCODED_KEY>
&pageNo=1
&numOfRows=100
&type=json
```
인증 없이 호출하면 **HTTP 403** 을 반환한다 (실측). 즉 엔드포인트는 살아 있고 인증을 강제한다.
### 4.5 전량 수집 전략
1. `numOfRows=1`, `pageNo=1` 로 한 번 호출해 `totalCount` 를 얻는다.
2. `ceil(totalCount / numOfRows)` 만큼 페이지를 순회한다.
3. 페이지 간 지연을 둔다 (0.5~1초). 하루 10,000 호출 한도 안에서 여유롭다.
4. 마지막 페이지까지 받은 뒤 **수집 건수 == totalCount** 를 검증한다. 불일치하면 그 실행은 **불완전 수집**으로 표시하고, 취하 판정을 하지 않는다 (7.2 안전장치).
---
## 5. 왜 우회가 아니라 API 인가
원래 계획은 nedrug 화면을 크롤링하고, 차단되면 `agy` 로 에이전틱하게 접근하는 것이었다. 공식 API 를 발견한 지금, 두 경로를 정면으로 비교한다.
| 축 | HTML 크롤링 (+우회) | 공식 Open API |
|---|---|---|
| robots.txt | 전면 금지를 무릅씀 | **무관. 애초에 공개용 채널** |
| 이용 허락 | 불명확 | **"이용허락범위 제한 없음"** 명시 |
| 데이터 구조 | HTML DOM. 사이트 개편마다 파괴 | **고정 스키마 7필드.** 버전 URL 로 관리 |
| 차단 위험 | 상시. IP 밴 시 복구 어려움 | 없음. 인증키 기반 정상 트래픽 |
| 필요 기술 | 브라우저 자동화, 스텔스, TLS 지문, 프록시 | **HTTP GET 한 줄** |
| 의존성 | Playwright + Chromium (수백 MB) | `httpx` 하나 |
| 실행 시간 | 브라우저 기동 포함 수십 초~분 | 수 초 |
| 유지보수 | 셀렉터 깨짐 상시 대응 | 스키마 변경 시에만 |
| 법적 리스크 | 데이터베이스제작자 권리·정보통신망법 논쟁 소지 | **없음** |
| 재현성 | 낮음 (렌더링 타이밍 의존) | 높음 (결정론적) |
| 무인 운영 적합성 | 낮음 | **높음** |
**결론**: 우회 기법은 이 프로젝트에서 **더 어렵고, 더 취약하고, 더 위험하고, 결과물도 더 나쁘다.** 공식 API 채택은 타협이 아니라 모든 축에서의 개선이다.
> 이 프로젝트는 `robots.txt` 를 우회하는 기법을 설계하거나 구현하지 않는다. 봇 차단 관련 조사(`docs/research/04-anti-bot-and-legal.md`)는 **폐기하지 않고 보존**한다. 이유는 두 가지다. 첫째, 향후 API 가 없는 보조 소스를 붙일 때 "정중한 접근" 규칙(요청 빈도, UA 명시, 백오프, 조건부 요청)이 그대로 필요하다. 둘째, 우리가 왜 이 길을 택하지 않았는지의 근거 자료다.
---
## 6. agy 역할 재정의
`agy` 는 원래 "차단을 에이전틱하게 뚫는" 역할로 구상됐다. 그 필요가 사라진 지금, **더 가치 있는 자리로 옮긴다.** 데이터를 가져오는 일은 결정론적 코드가 하고, `agy` 는 **사람이 해야 할 판단을 돕는 일**을 한다.
| # | 역할 | 입력 | 출력 | 실패 시 |
|---|---|---|---|---|
| **A1** | 일일 변경 브리핑 | 오늘의 신규/변경/취하 레코드 JSON | 실무자용 한국어 요약 + 중요도 태깅 | 리포트에 요약 없이 표만 |
| **A2** | 성분명 정규화·매칭 | 표기가 흔들리는 성분명 목록 | 표준명 매핑 후보 + 신뢰도 | 규칙 기반 정규화만 적용 |
| **A3** | 이상 신호 해석 | 건수 급변·0건·중복 급증 등 지표 | 원인 가설과 조치 제안 | 임계값 경보만 발송 |
| **A4** | 워치리스트 매칭 보조 | 관심 성분·업체 목록 + 오늘 데이터 | 유사 매칭 후보 | 완전 일치만 |
| **A5** | 제조소·국가 표기 정리 | `MANUF_COUNTRY_CODE_NM` 다중값, 오탈자 있는 제조소명 | 정규화된 국가 리스트, 정제된 제조소명 | 원문 그대로 |
| **A6** | 주간 트렌드 코멘터리 | 최근 N주 집계 | 서술형 트렌드 요약 | 생략 |
| **A7** | API 스키마 변화 대응 | 예상과 다른 응답 구조 | 변경점 진단과 매핑 제안 (**자동 반영 금지, 사람 승인 필수**) | 실행 중단 + 알림 |
**불변 원칙**: `agy` 가 죽어도, 인증이 만료돼도, 쿼터가 소진돼도 **xlsx 리포트는 반드시 생성된다.** AI 산출물은 리포트의 부가 가치이지 전제 조건이 아니다.
**프롬프트 인젝션 방어**: API 응답 문자열(성분명, 제조소명 등)이 `agy` 프롬프트에 들어간다. 원본 데이터에 악의적 지시문이 섞일 가능성은 낮지만 0은 아니다. 따라서 `--dangerously-skip-permissions` 를 쓰지 않고, `--disable-slash-commands` 를 붙이며, 데이터는 명확한 구분자로 감싸고, 출력은 스키마로 검증한다. 상세는 `docs/research/10-agy-agent-integration-patterns.md`.
---
## 7. API 로 커버되지 않는 것과 대응
### 7.1 상태 필드(신규/변경/취하)가 없다
API 는 **현재 시점의 등록 현황 스냅샷**만 준다. "오늘 무엇이 새로 등록됐고 무엇이 취하됐는가"는 없다.
**대응**: 매일 전량 스냅샷을 저장하고 **전일 스냅샷과 diff** 한다.
| 판정 | 규칙 | 실측 근거 |
|---|---|---|
| **신규** | 오늘 키가 있고 어제 없음 | 등록번호 중복 0건 → 유일 키로 안전 |
| **변경(1차)** | 양쪽에 키가 있고 **`DMF_PERMIT_DATE` 가 어제보다 최신** | 발급일자가 최종 갱신일로 이동 (불일치 44.5%) |
| **변경(2차)** | 양쪽에 키가 있고, 나머지 6필드 중 하나 이상이 다름 | 내용 변경 포착 |
| **취하** | 어제 키가 있고 오늘 없음 | API 가 정상 건만 반환 (웹과 756건 차이) |
| 동일 | 그 외 | |
키는 `DMF_PERMIT_NO` 를 1순위로 한다. 비교 대상 필드와 정규화 규칙은 `docs/design/02-data-model.md` 에서 확정한다.
**`DMF_PERMIT_DATE` 가 변경의 직접 신호라는 것이 실측의 가장 큰 수확이다.** 등록번호 앞 8자리는 최초 등록일로 고정되고, 발급일자는 갱신마다 움직인다. 예를 들어 `20050831-33-A-81-08(18)` 은 2005년 등록 건이지만 발급일자가 `2026-08-18` 이다. 괄호 안의 값이 변경 차수로 보인다. 상세는 [`00b-baseline-data-analysis.md`](./00b-baseline-data-analysis.md) §6.
이 방식은 공고문 파싱보다 **오히려 우월하다.** 공고에 실리지 않는 조용한 변경까지 잡아내고, 공고문 형식 변경에 영향받지 않는다.
**여전히 놓치는 것**(정직하게 기록): 연차보고 이벤트(`최종연차보고년도` 가 API 에 없음), 변경 사유·유형, 취하와 취소의 구분, 정확한 취하 일자. 각각의 영향과 대응은 [`00b-baseline-data-analysis.md`](./00b-baseline-data-analysis.md) §6.4 참조. `대상의약품`(별표1/신물질) 은 등록번호 포맷에서 파생 가능하므로 실질 손실이 아니다.
### 7.2 오탐 안전장치 (필수)
전량 수집이 불완전하면 **멀쩡한 레코드가 전부 "취하"로 오판**된다. 이건 가장 위험한 실패 모드다. 다음 조건을 **전부** 통과하지 못하면 그 실행은 diff 를 수행하지 않고 경보만 낸다.
- [ ] 모든 페이지 요청이 HTTP 200 이고 `resultCode == "00"`
- [ ] 수집 레코드 수 == 응답의 `totalCount`
- [ ] `totalCount` 가 전일 대비 **-5% 이상 급감하지 않음** (임계값은 설정 가능)
- [ ] 수집 레코드의 필수 필드 널 비율이 임계값 이하
- [ ] 중복 `DMF_PERMIT_NO` 비율이 임계값 이하
### 7.3 공고 원문·변경 사유가 없다
변경이 감지돼도 "왜 변경됐는지"는 API 에 없다. **대응**: 리포트의 각 변경 행에 **의약품안전나라 검색 링크를 붙여** 사람이 클릭해 확인하게 한다. 자동으로 화면을 긁지 않는다. 이것이 robots.txt 를 존중하면서 사람의 필요를 충족하는 방법이다.
### 7.4 갱신 주기가 명시되지 않았다
포털에 데이터 갱신주기가 표기돼 있지 않다 (연계데이터 쪽은 "수시"). **대응**: 매일 06:00 에 받되, `totalCount` 와 데이터 해시를 기록해 **실제 갱신 빈도를 실측**한다. 2~4주 관측 후 스케줄을 조정한다.
---
## 8. 보조·확장 소스
같은 제공기관의 인접 데이터셋. 지금 붙이지 않되, 확장 지점으로 기록한다.
| 데이터셋 | 유형 | 경로 | 수정일 | 이 프로젝트에서의 가치 |
|---|---|---|---|---|
| 의약품 제품 허가정보 | 오픈API | `/data/15095677/openapi.do` | 2025-10-31 | 완제의약품 허가와 DMF 원료 연결. 성분→제품 역추적 |
| 의약품 낱알식별 정보 | 오픈API | `/data/15057639/openapi.do` | 2025-11-10 | 낮음 |
| 의약품개요정보(e약은요) | 오픈API | `/data/15075057/openapi.do` | 2025-09-19 | 성분 설명 보강 |
| 의약품 생산·수입실적현황 | 오픈API | `/data/15056880/openapi.do` | 2026-03-20 | 원료 수입 실적과 DMF 등록 대조 |
| 약가마스터_의약품표준코드 | 파일데이터 | `/data/15067462/fileData.do` | 2025-12-01 | 표준코드 매핑 |
| 식품의약품안전처 의약품 관련 정보 | 파일데이터 (CSV) | `/data/15020627/fileData.do` | 2025-09-10 | 통계 보강 |
해외 소스(FDA DMF list, EDQM CEP, PMDA MF)는 `docs/research/01-dmf-domain-and-sources.md` 참조. 이들은 별도 배포 채널(다운로드 파일)이 있어 크롤링 없이 접근 가능한지 확인이 필요하다.
---
## 9. serviceKey 발급 절차
사용자가 직접 해야 하는 유일한 수동 단계다.
1. https://www.data.go.kr 회원가입·로그인
2. https://www.data.go.kr/data/15057075/openapi.do 접속
3. **활용신청** 클릭 → 활용 목적 기재 → 신청
4. **자동승인**이므로 즉시 승인된다 (개발계정, 일 10,000건)
5. 마이페이지 → 오픈API → 개발계정에서 **일반 인증키(Encoding)****일반 인증키(Decoding)** 를 확인
6. 프로젝트 루트의 `.env` 에 저장:
```dotenv
# 공공데이터포털 인증키 (Decoding 키를 넣고 코드에서 URL 인코딩한다)
DATA_GO_KR_SERVICE_KEY=여기에_디코딩_인증키
```
7. `.env` 는 반드시 `.gitignore` 에 포함한다. **인증키를 문서·로그·저장소에 남기지 않는다.**
> ⚠️ 인코딩/디코딩 키 혼동은 공공데이터포털 API 의 1위 실패 원인이다. 라이브러리가 자동 인코딩하는 경우 **Decoding 키**를 쓰고, 직접 URL 문자열을 조립하는 경우 **Encoding 키**를 쓴다. 우리는 `httpx` 의 `params=` 로 넘겨 자동 인코딩되게 하므로 **Decoding 키**를 사용한다.
**최초 검증 명령** (키 발급 후 즉시 실행):
```powershell
$key = (Get-Content .env | Select-String '^DATA_GO_KR_SERVICE_KEY=').ToString().Split('=',2)[1]
$url = "https://apis.data.go.kr/1471000/MdcDmfInfoService01/getMdcDmfList01"
$resp = Invoke-RestMethod -Uri $url -Body @{
serviceKey = $key
pageNo = 1
numOfRows = 1
type = 'json'
}
$resp | ConvertTo-Json -Depth 6
```
기대: `resultCode``00`, `totalCount` 에 전체 DMF 등록 건수가 나온다. **이 숫자를 문서 부록 B 에 기록하라.**
---
## 10. 이 결정이 무효화하는 기존 설계
이 결정 이전에 작성된 설계·조사 문서 중 다음 부분은 **더 이상 이 프로젝트의 구현 대상이 아니다.** 삭제하지 않고 "채택하지 않음" 표시를 달아 근거로 보존한다.
| 문서 | 무효화되는 부분 | 처리 |
|---|---|---|
| `research/04-anti-bot-and-legal.md` | 스텔스 도구, TLS 지문 위장, 헤드리스 지문 회피 | **참고 자료로 보존.** 구현하지 않음 |
| `research/03-crawling-theory-and-papers.md` | wrapper induction, 셀렉터 안정성, DOM 추출 | 보존. **증분·변경 감지 이론은 그대로 유효**하며 오히려 핵심이 된다 |
| `design/01-architecture.md` | `fetch` 계층의 브라우저 자동화 전제, Playwright 의존성 | **개정 필요.** HTTP 클라이언트 단일 경로로 축소 |
| `research/10-agy-agent-integration-patterns.md` | UC2 "셀렉터 자가 복구" | **API 스키마 변화 대응(A7)** 으로 대체 |
| `research/01-dmf-domain-and-sources.md` | nedrug 화면 파라미터·컬럼 조사 | 보존. **API 필드와 화면 컬럼의 대응 관계** 파악에 여전히 유용 |
> 아키텍처 문서가 작성 완료되면 이 절을 근거로 **개정 작업**을 수행한다. 개정 내용은 `design/01-architecture.md` 의 ADR 표에 "데이터 소스: 공식 Open API" 항목으로 기록한다.
**이 결정이 살려낸 것**: 데이터 흐름 뒷단(정규화 → diff → 저장 → xlsx 리포트 → 알림 → 스케줄링)은 **전혀 영향받지 않는다.** 이 프로젝트 가치의 대부분은 그쪽에 있고, 앞단이 단순해진 만큼 그쪽에 더 투자할 수 있다.
---
## 부록 A. 출처
| 제목 | URL | 확인 |
|---|---|---|
| nedrug robots.txt | https://nedrug.mfds.go.kr/robots.txt | ✅ 전문 확인 (`User-agent: * / Disallow: /`) |
| 의약품안전나라 메인 | https://nedrug.mfds.go.kr/index | ✅ 열람 (DMF 링크 href 확인 실패) |
| nedrug 추정 경로 | https://nedrug.mfds.go.kr/pbp/CCBGE01 | ✅ HTTP 404 확인 |
| **DMF 오픈API 상세** | https://www.data.go.kr/data/15057075/openapi.do | ✅ 전체 명세 확인 (핵심 출처) |
| DMF 연계데이터 | https://www.data.go.kr/data/2095/linkedData.do | ✅ 열람 |
| 공공데이터포털 DMF 검색 | https://www.data.go.kr/tcs/dss/selectDataSetList.do?keyword=DMF | ✅ 열람 |
| 공공데이터포털 원료의약품 등록 검색 | https://www.data.go.kr/tcs/dss/selectDataSetList.do?keyword=원료의약품+등록 | ✅ 열람 |
| 식약처 의약품 데이터셋 목록 | https://www.data.go.kr/tcs/dss/selectDataSetList.do?keyword=의약품&org=식품의약품안전처 | ✅ 열람 |
| API 엔드포인트 생존 확인 | https://apis.data.go.kr/1471000/MdcDmfInfoService01/getMdcDmfList01 | ✅ HTTP 403 (인증 강제, 엔드포인트 정상) |
| 연계데이터 구버전 엔드포인트 | http://data.mfds.go.kr/openapi/MdcDmfInfoService/getMdcDmfList | 미호출 |
| API 참고문서 | `IROS_76_원료의약품(DMF)현황_v1.1.docx` | ⚠️ 미다운로드 |
---
## 부록 B. 미해결 / 실측 필요
### ✅ 해소된 항목 (2026-09-02 `DMF_현황.xlsx` 전수 분석)
- [x] ~~전체 DMF 등록 건수(`totalCount`)는 몇 건인가?~~**9,084건**
- [x] ~~응답에 `DMF_PERMIT_NO` 중복이 존재하는가?~~**중복 0건. 완전한 자연 키**
- [x] ~~API 응답에 취하·말소된 등록번호가 남아 있는가?~~**남지 않을 가능성 매우 높음.** 웹 9,840 vs API 9,084 = 756건 차이. 연속 관측으로 최종 확정 예정
- [x] ~~`type=json` 이 실제로 동작하는가?~~ → 프로토타입이 수집에 성공했으므로 API 자체는 정상 동작 확인. 포맷 파라미터는 별도 확인
- [x] ~~변경 탐지가 7필드로 가능한가?~~**가능. `DMF_PERMIT_DATE` 가 최종 갱신일로 이동** (앞 8자리와 44.5% 불일치)
### 남은 항목
- [ ] `numOfRows` 의 실제 최대값은? 명세 크기가 3자리이므로 999 로 추정되나 실측 필요.
- [ ] 데이터 실제 갱신 주기는? 2~4주 관측 필요.
- [ ] **연차보고 시 `DMF_PERMIT_DATE` 가 갱신되는가?** 연차보고는 매년 1~2월에 몰리므로 그때 관측. 갱신된다면 변경으로 잡히고, 아니면 영구히 놓친다.
- [ ] 변경 시 등록번호의 괄호 부분이 바뀌는가, 번호는 그대로이고 발급일자만 바뀌는가? 전자면 신규로 오탐할 위험이 있다.
- [ ] 웹 9,840 API 9,084 = 756건이 정말 취하·취소 건인지 표본 대조.
- [ ] 제조국가명 결측 5건의 등록번호와 원인.
- [ ] 참고문서 `IROS_76_원료의약품(DMF)현황_v1.1.docx` 에 명세 외 추가 정보가 있는가?
- [ ] `entp_name` / `ingr_kor_name` 검색 파라미터가 부분 일치인가 완전 일치인가?
- [ ] API 응답에 취하·말소된 등록번호가 남아 있는가, 아니면 사라지는가? (취하 판정 로직의 전제)
- [ ] 일 10,000건 한도의 카운트 단위가 "호출 수"인가 "레코드 수"인가?
- [ ] 운영계정 전환 조건과 증량 한도는?
- [ ] 해외 소스(FDA/EDQM/PMDA)의 robots.txt 와 공식 배포 채널은?
- [ ] 인접 데이터셋 "의약품 제품 허가정보" 로 DMF 원료↔완제품 연결이 실제로 가능한가?
---
*이 문서는 데이터 소스에 관한 SSOT 다. 소스 관련 새 사실은 여기를 갱신한다.*