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 문서 지도 갱신
This commit is contained in:
Yun Chan 2026-09-04 09:25:44 +09:00
commit 56a6e2da93
159 changed files with 145825 additions and 0 deletions

View file

@ -0,0 +1,386 @@
# 데이터 소스 결정 — 크롤링에서 공식 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 다. 소스 관련 새 사실은 여기를 갱신한다.*