DMF_Crawler/docs/research/07-pharma-excel-dashboard-design.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

197 KiB
Raw Permalink Blame History

의약 정보 엑셀 대시보드 시각화 설계

⚠️ 문서 상태 — 정정 반영 (2026-09-02)

이 문서는 design/00b-baseline-data-analysis.md(기존 운영 산출물 DMF_현황.xlsx 전수 실측) 이전에 작성됐다. 작성 당시의 핵심 전제 하나가 그 실측으로 반증되어 아래와 같이 정정한다.

반증된 전제: "OpenAPI 는 7필드뿐이라 diff 판정에 부족하다 → nedrug 웹 화면(CCBAC03) 스크래핑이 주 소스여야 한다"(구 §5.7 주석). 반증 근거 3가지(00b 실측):

  • API 전체 9,084건 vs 웹 화면 9,840건 = 756건 차이 → API 는 취하 건을 제외하고 정상 건만 반환한다. 레코드 소멸 = 취하로 판정할 수 있다.
  • 등록번호 앞 8자리(최초 등록일)와 발급일자44.5% 불일치하며, 불일치 건은 전부 괄호 붙은 갱신 건이다 → 발급일자는 최종 갱신일로 동작한다. 변경의 직접 신호.
  • 등록번호 9,084건 중 중복 0건 → 완전한 자연 키. diff 의 기준 키로 그대로 쓸 수 있다.

여전히 유효한 것: 대시보드 설계 원칙(§2~4), 색 접근성 근거(§6), 규제 인텔리전스(RA) 실무 관점(§4, §12), 시각화 형식 선택(§2.7, §10), 레이아웃 아이디어(§9). 이 부분은 그대로 참고한다.

정정된 것: §8 의 시트 컬럼 스펙. API 미제공 컬럼(최종변경일자, 취소/취하구분, 최종연차보고년도, 문서번호 등)을 원본 컬럼으로 전제했던 부분을 API 7필드 + 파생 필드 기준으로 다시 썼다(신설 §8.0, 재작성 §8.3·§8.4).

정본 위치: 최종 xlsx 스펙의 정본(SSOT)은 design/03-xlsx-report-spec.md 다. 이 문서는 그 설계의 근거 자료(research)이지 정본이 아니다. 본문에 남아 있는 "확정"·"최종"이라는 표현은 모두 "제안"·"권고"로 읽는다. 문서 끝 "이 문서와 확정 설계의 관계" 절에 절별 상태를 정리했다.

이 문서의 역할: DMF_Crawler 가 매일 06:00 에 생성하는 xlsx 리포트의 시트 구성·컬럼·서식·색·레이아웃을 설계하기 위한 조사·근거 자료. 최종 확정은 design/03-xlsx-report-spec.md 에서 이루어진다.


0. 한눈에 보기

  • 대시보드는 "1화면·5초"가 기준이다. Stephen Few 의 정의(원문): "A dashboard is a visual display of the most important information needed to achieve one or more objectives; consolidated and arranged on a single screen so the information can be monitored at a glance." → 스크롤·필터·범례 없이 5초 안에 "오늘 뭐가 터졌나"가 보여야 한다.
  • KPI 타일은 6개로 고정한다. (신규 / 변경 / 취하 / 오늘 총 변경 / 워치리스트 히트 / 전체 누적). 권고치는 "핵심 34개로 시작, 최대 68개"이므로 6개는 상한 안쪽이다.
  • 색은 3개 의미색 + 1개 강조색 + 무채색. 상태색은 Okabe-Ito 색각이상 대응 팔레트에서만 뽑는다: 신규 #009E73, 변경 #E69F00, 취하 #D55E00, 강조/링크 #0072B2. 순수 red/green 조합은 절대 쓰지 않는다 (Color Universal Design 명시 금지 조합).
  • 색 단독 전달 금지(WCAG 1.4.1). 모든 상태는 상태 텍스트 컬럼 + 기호( / ◆ / ✕) + 배경색의 3중 코딩으로 표시한다. 본문 대비비는 전부 7:1 이상(AAA)으로 맞췄다(§6 표).
  • 시트는 8개를 제안한다(정본은 design/03-xlsx-report-spec.md): 대시보드 / 오늘 변경분 / 전체 누적 / 성분별 / 업체별 / 워치리스트 / 스냅샷·로그 / 메타. 시트 간 이동은 write_url('internal:...') 로 대시보드 ↔ 각 시트 양방향 링크를 건다.
  • 피벗테이블은 파이썬에서 못 만든다. openpyxl 공식 문서: "it is not intended that client code should be able to create pivot tables." XlsxWriter 도 피벗 캐시 생성 API 가 없다. → pandas 로 집계한 정적 표 + add_table() + add_chart() 를 기본으로 하고, 진짜 피벗이 필요하면 "피벗이 미리 들어있는 템플릿 xlsx + pivot.cache.refreshOnLoad = True" 를 쓴다(§9).
  • 한국 DMF 도메인 특유의 축은 5개: 성분명 / 신청인(등록업체) / 제조소·제조국 / 등록구분(등록·변경등록·연차보고·사전등록) / 취소·취하. 데일리팜 기사가 실제로 쓰는 축이 "제조국별 비중"과 "연도별 건수"이므로 대시보드에도 이 두 축을 넣는다.
  • 등록번호는 파싱 가능한 구조체다. 20110531-71-B-317-05(1~9) = 등록수리일자-별표1 성분 일련번호-시행일군 알파벳-접수순 일련번호-동일성분 일련번호(괄호=허여서 등록순). → 등록번호에서 등록수리일자와 성분 코드를 파생 컬럼으로 뽑아 타임라인·정렬에 쓴다(§5.3).
  • 전일 대비 diff 는 "행 전체 강조 + 상태 컬럼"으로 구현한다. XlsxWriter conditional_formattype: 'formula'=$L2="신규" 처럼 열은 절대·행은 상대 참조를 써서 행 전체에 서식을 건다.
  • 환경은 검증됨: xlsxwriter 3.2.1, openpyxl 3.1.5, pandas 2.2.3 이 이미 설치되어 있다(실측). 본 문서의 코드는 이 버전 기준이다.

1. 목차

제목
0 한눈에 보기
1 목차
2 엑셀 대시보드 설계 원칙 (출처 포함)
3 KPI 타일(카드) 해부
4 규제 인텔리전스 실무의 DMF 추적 관점
5 한국 DMF 도메인 데이터 구조 (실측)
6 색 접근성: 팔레트·상태 매핑·대비비
7 공개 데이터(FDA/EMA/식약처) 컬럼·시트 구성에서 배울 점
8 시트 구성 제안 (구현 명세, §8.0 정정 반영)
9 대시보드 레이아웃 텍스트 와이어프레임
10 피벗테이블 문제와 대안
11 구현 레시피: 동작하는 코드
12 한국 제약 실무자가 실제로 보고 싶어하는 것
부록 A 출처 목록 (전체 URL)
부록 B 미해결 질문 / 실측 필요 항목

2. 엑셀 대시보드 설계 원칙 (출처 포함)

2.1 대시보드의 정의 — Stephen Few

Perceptual Edge 의 Dashboard Confusion(Stephen Few, 2004-03-20) PDF 를 직접 열어 본문에서 추출한 정의(원문 그대로):

"A dashboard is a visual display of the most important information needed to achieve one or more objectives; consolidated and arranged on a single screen so the information can be monitored at a glance."

같은 문서의 부수 원칙:

  • 대시보드의 의미는 정보의 종류(KPI 여부)가 아니라 정보를 배치하는 방식에서 나온다.
  • "Display mechanisms that clearly state their message without taking up much space are required so the entire collection of information will fit into the limited real estate of a single screen."한 화면에 들어가게 하려면 표현 매체가 공간을 적게 먹어야 한다. 스파크라인·데이터바·아이콘셋이 정당화되는 근거.
  • 연료계·신호등·온도계 같은 게이지는 적절할 때만 쓴다. 게이지를 고집하는 것은 잘못이다.

DMF 리포트에 적용: 대시보드 시트는 세로 스크롤 없이 한 화면에 끝나야 한다. 표는 Top 10 까지만, 전체 목록은 별도 시트로 보낸다.

2.2 5초 규칙 (5-second rule)

출처: Den Otter Solutions — Dashboard design 5 seconds rule (WebFetch 확인)

"A user should be able to see whether the organisation is on track within five seconds of opening a dashboard — without filtering, scrolling or reading a legend."

같은 문서가 준 구체 수치·배치 규칙:

항목 규칙
레이아웃 F-패턴. 좌상단 = KPI Zone, 중앙 = Trend Zone(라인 차트), 우하단 = Detail Zone(표)
KPI 개수 대시보드당 최대 6~8개, 시작은 3~4개
KPI 크기 좌상단에 3~4개를 큰 글자 + 적/녹 신호
기능적으로만 사용, 최대 3색, 각 색에 고정 의미 (red = action needed, green = on-track)
차트 선택 정확한 비교에는 파이보다 막대
여백 화이트스페이스가 처리 속도를 돕는다
맥락 숫자는 비교 기준(예산·전년·목표)이 있어야 의미를 가진다

DataCamp 튜토리얼도 같은 규칙을 반복한다: "Apply the five-second rule — critical information should be extractable within five seconds", "Use colorblind-friendly palettes, avoiding red-green combinations". 출처: DataCamp — How to Create a Dashboard in Excel (WebFetch 확인)

DataHub Pro 도 동일: "if a viewer cannot grasp the headline in about five seconds, the dashboard is trying to say too much", "Dashboards must fit one screen without scrolling". 출처: DataHub Pro — Excel Dashboard Templates (WebFetch 확인)

2.3 KPI 배치와 개수

  • 가장 중요한 KPI 는 좌상단에 크게. 근접성(proximity)으로 관련 차트·지표를 묶으면 뇌가 블록 단위로 빠르게 처리한다. 넉넉한 화이트스페이스가 "숨 쉴 공간"을 준다. (검색 #2 종합)
  • 6~8개가 상한: "every additional KPI competes for attention and slows interpretation."
  • DataHub Pro: 3~6개 KPI 카드로 리드하고, 각 KPI 는 "총계 + 비교 + 변화율"의 3요소를 가져야 한다. 기간 대비 계산식은 =(ThisMonth-LastMonth)/LastMonth, 백분율 서식.
  • 시각 위계: KPI 카드는 상단 가로 배치, 가장 중요한 차트는 좌상단(시선이 처음 닿는 곳), 보조 차트는 그 아래.
  • 마감 처리: 눈금선(gridlines)과 행/열 머리글을 숨겨 깨끗한 캔버스를 만들고, 그리드에 정렬, 여백을 넉넉히.
  • Chandoo.org 참고: 원조 "Making a Simple KPI Dashboard using MS Excel" 글(2011-08-25)은 지금도 월 12,000명이 본다고 소개된다. (WebFetch 는 403 으로 차단 — ⚠️ 본문 미검증)

2.4 색 최소화 — 강조색 1개

검색 #5 가 모은 실무 규칙(여러 출처가 같은 결론):

"Pick a neutral base and exactly one accent color, with the accent appearing only on the thing you want noticed — the key KPI, the alert, the call to action. The rule is simple: three to five working colors, and that is it. Neutrals are free. One accent does the pointing."

  • 권장 구조: 회색 뉴트럴 1 + 브랜드 프라이머리 1(예: 네이비) + 경보 액센트 1(예: 코랄) = 3색 팔레트.
  • "Every bright color you add costs you a little attention elsewhere. A neutral base, one accent for 'look here,' and semantic green/red used sparingly."
  • Chandoo.org 포럼 요지: 배경은 흰색이 최선, 어두운 색은 피하고 밝은 톤으로. 색 구성은 작업 시작 전에 확정하고, 숫자 없는 손그림 목업으로 레이아웃부터 잡고, 페이지/표시 크기를 초기에 고정하라(나중에 바꾸기 어렵다). 셀 스타일을 써서 동일 정보는 동일 서식으로.
  • GoSkills 의 12원칙(제목 원문):
    1. "What story are you trying to tell?"
    2. "Decide on the layout" — 중요 정보는 상단·좌측
    3. "Think of your audience"
    4. "Use the right visuals"
    5. "Make it interactive" — 필터·정렬·내비게이션
    6. "Remove the fluff"
    7. "Add context" — 목표·비교 기준 제공
    8. "Use contrast"
    9. "Organize and group elements"
    10. "Be consistent with fonts and colors"
    11. "Use colors and labels effectively"
    12. "Access to more detail" — 한 페이지에 욱여넣지 말고 링크로

출처: GoSkills — 12 Best Practices in Dashboard Design (WebFetch 확인)

2.5 데이터 잉크 비율 (Tufte)

출처: 검색 #9 종합, Declutter your Chart with Data-Ink Ratio

Tufte 의 원칙 4개:

  1. Above all else show data
  2. Maximize the data-ink ratio
  3. Erase non-data-ink
  4. Erase redundant data-ink

실무 번역:

  • 스토리를 지지하지 않는 것은 전부 제거: 눈금선, 테두리, 3D 효과, 그림자, 장식.
  • 색은 강조에만. "one or two colors used effectively are more powerful than a rainbow without meaning."
  • 단, 과도한 미니멀리즘 경계: "we need to be careful not to go too far in pursuing minimalism, as we can end up with dashboards that feel clinical, cold, or just plain boring."

2.6 대시보드 ≠ 스토리

출처: storytelling with data — from dashboard to story (WebFetch 확인)

  • 대시보드는 탐색(exploration)에 최적화된 것이지, 특정 인사이트의 전달(communication)이 아니다. 행동을 이끌어내려면 별도의 프레젠테이션이 낫다.
  • 레이아웃: 화이트스페이스, 정렬, 그리드 레이아웃을 활용해 모든 탐색 수준에서 읽고 이동하기 쉽게.
  • 차트 선택 예시: 추이는 막대보다 라인 그래프, 단순 비교는 닷 플롯, 상대 비중 비교는 누적 막대.
  • 정리: "items that do not add information value, like gridlines and borders" 를 제거하고 색 수를 줄여 시각적 복잡도를 낮춘다.
  • "단어 + 색"의 조합이 핵심 포인트 강조에 가장 강력하다. 능동형 제목과 명시적 행동 권고를 붙여라.

DMF 리포트에 적용: 대시보드 시트 상단 부제(row 3)에 문장형 헤드라인을 자동 생성해 넣는다. 예: 오늘 신규 12건 · 변경 3건 · 취하 1건 — 워치리스트 2건 적중(피타바스타틴칼슘, 다파글리플로진).

2.7 차트 종류 선택 기준 (본 프로젝트 제안)

보여줄 것 차트 XlsxWriter 타입 이유
최근 30일 일별 신규/변경/취하 누적 세로 막대 {'type':'column','subtype':'stacked'} 일별 총량 + 구성비를 동시에. 라인은 0 이 많은 일별 카운트에 부적합
최근 12개월 월별 등록 추이 라인 {'type':'line'} 시계열 방향성
제조국 Top 8 가로 막대 {'type':'bar'} 카테고리명(국가명)이 길다 → 가로가 읽기 쉬움. 파이 금지
Top 성분 / Top 업체 표 + 데이터바 표 + conditional_format data_bar 순위표는 차트보다 표가 정확
KPI 타일 내 30일 미니 추이 스파크라인(column) add_sparkline(..., {'type':'column'}) 셀 1칸, 데이터 잉크 비율 최고
"오늘 변경 있었나" 30일 이력 스파크라인(win_loss) {'type':'win_loss'} 이진 결과 전용

파이 차트는 전 시트에서 금지. 5초 규칙 문서가 명시적으로 "정확한 비교에는 파이보다 막대"라고 했고, 제조국 비중은 8개 이상 카테고리라 파이가 판독 불가가 된다.


3. KPI 타일(카드) 해부

3.1 카드 1장의 구성 요소

PK: An Excel Expert — Flooring Installation KPI Scorecard 의 실제 스코어카드 분해(WebFetch 확인). 카드 1장에 세로로 7요소:

  1. KPI 이름 (상단)
  2. 신호등 (red/amber/green 상태 표시)
  3. 큰 값 (본 숫자, 가장 큰 타이포)
  4. 목표 값
  5. 절대 변화량
  6. 색이 붙은 화살표 + 변화율
  7. 12개월 스파크라인 (하단)

원문: "each showing the KPI name, the traffic light, the value in large type, the target value, the absolute change, the percentage change with a coloured arrow, and a twelve-month sparkline." 레이아웃은 10장을 5열로 배치.

이미지 참고(원문에 실린 URL 그대로):

  • https://www.pk-anexcelexpert.com/wp-content/uploads/2026/08/Flooring-Installation-KPI-Scorecard-2-1210x642.jpg
  • https://nextgentemplates.com/wp-content/uploads/2026/08/Flooring-Installation-KPI-Scorecard-3-scaled.jpg (KPI Analysis 페이지)
  • https://nextgentemplates.com/wp-content/uploads/2026/08/Flooring-Installation-KPI-Scorecard-4.jpg (KPI Trend 페이지)

3.2 델타 화살표 — 커스텀 숫자 서식 (핵심 기법)

출처: How To Excel — Create Amazing Key Performance Indicator Data Cards In Excel (WebFetch 확인)

퍼센트 변화에 방향 지시자를 넣는 커스텀 숫자 서식(원문 그대로):

▲ 0.0 %;▼ -0.0 %

양수면 ▲, 음수면 ▼ 가 자동으로 붙는다.

본 프로젝트에서 쓸 변형(건수는 정수, 0 도 표기해야 함):

용도 num_format 문자열
전일 대비 건수 델타 ▲ #,##0;▼ -#,##0; 0
전일 대비 비율 델타 ▲ 0.0%;▼ -0.0%; 0.0%
건수(천단위) #,##0"건"
날짜 yyyy-mm-dd
날짜+시각(메타 시트) yyyy-mm-dd hh:mm:ss

같은 문서의 카드 제작 방식: 병합 셀 대신 4개의 사각형 도형(배경 1 + 투명 지표 2 + 흰 테두리 제목 1)을 정렬·그룹화. 다만 본 프로젝트는 도형을 쓰지 않는다 — XlsxWriter 로 도형 그룹을 만들 수 없고, 셀+테두리+병합으로 동일 효과를 낼 수 있으며 파일이 가볍다. (§9 와이어프레임 참조)

3.3 폰트 크기

Big Excel Energy — Excel KPI Card Design (WebFetch 403 — 검색 스니펫으로만 확인, ⚠️ 부분 미검증):

"A KPI card is a single tile that shows one metric as a large value, a small caption label, and a small colored delta against a target or prior period." 지표 숫자는 2844 pt, bold, 헤딩 폰트로 카드 안에서 단독 최대 요소.

본 프로젝트 제안 타이포:

요소 폰트 크기 굵기
시트 제목 맑은 고딕 18 pt bold #1F2933
부제/헤드라인 맑은 고딕 11 pt regular #5B6770
KPI 라벨 맑은 고딕 10 pt bold #5B6770
KPI 값 맑은 고딕 28 pt bold 상태색(§6)
KPI 델타 맑은 고딕 10 pt bold #005A32 / #8A1D00 / #5B6770
표 헤더 맑은 고딕 10 pt bold #FFFFFF on #1F2933
표 본문 맑은 고딕 10 pt regular #1F2933
각주/출처 맑은 고딕 9 pt regular #5B6770

28 pt 를 택한 이유: 6장을 가로 한 줄에 배치해야 하므로 타일 폭이 26 char units(≈198px). 44 pt 는 5자리 숫자에서 넘친다. 28 pt 면 1,234 까지 안전.

3.4 본 프로젝트의 KPI 6종 (제안)

# KPI 이름 델타 기준 스파크라인
1 오늘 신규 오늘 신규 등록 건수 전일 대비 30일 일별 신규(column) #009E73
2 오늘 변경 오늘 변경등록 건수 전일 대비 30일 일별 변경(column) #E69F00
3 오늘 취하 오늘 취소/취하 건수 전일 대비 30일 일별 취하(column) #D55E00
4 오늘 총 변경분 1+2+3 전일 대비 30일 일별 합계(column) #0072B2
5 워치리스트 히트 워치리스트 매칭 건수 전일 대비 30일 win_loss #CC79A7
6 전체 누적 등록 누적 유효 등록 건수 전일 대비 30일 누적(line) #1F2933

4. 규제 인텔리전스 실무의 DMF 추적 관점

4.1 상용 도구가 실제로 제공하는 기능 (벤치마크)

도구 확인된 기능 출처
Leucine FDA Tracker 승인 벤더 리스트 업로드 또는 경쟁사 워치리스트 구축 → 대상이 warning letter / 483 observation / import alert / recall 받는 즉시 알림. FDA enforcement 데이터 수집 → 문서 간 엔티티 연결자사 제품·사이트 관련도 점수화(relevance score) → 선제 알림 leucine.ai/fda-tracker (403, 검색 스니펫만) ⚠️
Vistaar AI 스마트 알림, 워치리스트, 프로젝트 트래킹 포함 워크플로 관리, 200만+ 규정/가이던스 문서 컨텍스트 검색 vistaar.ai
Thomson Reuters Regulatory Intelligence 다중 소스 수동 추적 부담 제거, 단일 솔루션에서 글로벌 규제 변화 예측·관리 regintel-content.thomsonreuters.com
RegASK Life Sciences/소비재용 agentic AI 규제 인텔리전스. 글로벌 변화 모니터 → 영향도 평가(impact assessment) → 컴플라이언스 워크플로 자동화 regask.com
Ideagen regulatory change tracking (품질 솔루션 내) ideagen.com
Visualping 웹페이지 변경 감지 기반 법령 모니터링·알림 visualping.io/regulatory-intelligence
Compliance.ai / AgencyIQ / Clarivate Cortellis / DDi 규제 인텔리전스 DB·모니터링 부록 A
PharmaCompass FDA DMF 를 필터링 가능한 형태로 재가공한 서드파티 DB. USFDA DMF + CEP/COS + 각국 승인약 통합 검색. Q3 2024 는 신규 DMF 접수 기록 경신, 중국 Type II DMF 제출 58% 급증(분기 단위로 추적됨) pharmacompass.com/us-drug-master-files-dmfs (403) ⚠️

본 프로젝트가 베낄 것: ① 워치리스트 업로드/매칭, ② 변경 즉시 알림, ③ 관련도 점수(= 워치리스트 가중치), ④ 엔티티 연결(같은 제조소·같은 성분의 과거 이력 연결). 베끼지 않을 것: AI 영향도 평가(초기 범위 밖), 200만 문서 검색.

4.2 규제 인텔리전스 프로그램의 표준 운영 (PDA)

PDA Letter — A Comprehensive Review of Regulatory Intelligence: Exploring Tools and Program Maturities — WebFetch 403 ⚠️ 미검증. 검색 #34 에서 확보된 실무 원칙:

  • 규제기관 뉴스레터 구독 + RSS 피드 로 변경 모니터링.
  • 모든 추적 항목과 후속 액션은 regulatory intelligence repository / tracker 에 아카이브하고, 대시보드·뉴스레터·알림으로 이해관계자에게 타깃 배포.
  • 알림은 산업·기능 영역별로 애널리스트가 수동 검증하고, 수신 주기를 커스터마이즈하며, 여러 알림을 묶어 뉴스레터로 변환해 공유.

DMF 리포트에 적용: 스냅샷·로그 시트가 곧 repository/tracker 다. 매일의 원본 스냅샷 해시와 diff 결과를 append-only 로 남긴다.

4.3 뉴스레터 구조 벤치마크 (ICON)

ICON — Global Regulatory Intelligence monthly newsletter (WebFetch 확인)

  • 기관별 섹션: FDA, EMA, PMDA, NMPA, MHRA, TGA 등.
  • 주제 카테고리: marketing authorization(라벨링·post-approval submissions 포함), scientific guidance(quality/safety/efficacy), GxP guidance, 특수 주제(희귀약·백신·조기접근).
  • 요약 방식: "The format is designed to give you the key points with relevant links so you can scan quickly and find more information on the source topics online."핵심 포인트 + 원문 링크, 스캔 가능하게.
  • 주기: 월간, 이메일.

DMF 리포트에 적용: 오늘 변경분 시트의 각 행은 "핵심 필드 + 원문 공고 링크(하이퍼링크)"를 반드시 갖는다. 스캔 가능성이 최우선이므로 행 높이는 고정, 자동 줄바꿈은 제조소소재지 컬럼만 허용.

4.4 상태 뱃지 · 집계 축 · 워치리스트 · diff · 타임라인 (설계 결론)

요구사항 구현 방식
상태 뱃지 (신규/변경/취하) 상태 텍스트 컬럼(값: 신규 변경 취하 연차보고 사전등록) + 기호 컬럼( ) + 행 전체 조건부서식. 색 단독 의존 금지
성분별 집계 성분별 시트: 성분명 × (신규/변경/취하/누적/업체수/제조국수) 정적 피벗
업체별 집계 업체별 시트: 신청인 × (신규/변경/취하/누적/성분수/주요 제조국)
제조국별 집계 대시보드 우측 가로 막대 Top 8 + 업체별 시트 보조 컬럼
경쟁사 워치리스트 워치리스트 시트에 사용자가 관리하는 키(성분명/업체명/제조소명/제조국) → 매칭 결과 자동 채움. 매칭은 정규화 후 부분일치
주간/월간 추이 대시보드 30일 누적 막대 + 12개월 라인. 원천 데이터는 메타 시트 우측 히든 영역
전일 대비 diff 오늘 변경분 시트 = 전일 스냅샷과의 diff 결과만. 전체 누적 시트에서는 최종변경일자 = 오늘 인 행을 조건부서식으로 강조
등록번호별 타임라인 전체 누적 시트에 등록수리일자(등록번호에서 파싱) · 최초등록일자 · 최종변경일자 · 최종연차보고년도 · 취소/취하일자 5개 날짜 컬럼을 나란히 두고, 각 날짜에 3색 컬러스케일 대신 단색 데이터바 적용

5. 한국 DMF 도메인 데이터 구조 (실측)

5.1 소스 3종

소스 URL 확인 성격
의약품안전나라 DMF 공고 https://nedrug.mfds.go.kr/pbp/CCBAC03 WebFetch 성공 공고 목록(표) — 주 소스
의약품안전나라 DMF 정보 게시판 https://nedrug.mfds.go.kr/bbs/117 WebFetch 성공 주/격주 공고 게시글 목록
공공데이터포털 OpenAPI https://www.data.go.kr/data/15057075/openapi.do WebFetch 성공 정형 JSON/XML — 교차검증용

⚠️ 정정(design/00b-baseline-data-analysis.md 실측): 위 "주 소스/교차검증용" 구분은 작성 당시의 추정이었다. 실측 결과 OpenAPI 7필드만으로 신규·변경·취하 판정이 가능함이 확인됐다(§8.0). nedrug CCBAC03 은 여전히 유용하지만(대상의약품·연차보고년도 등 API 미제공 항목 및 사람이 직접 확인하는 원문 링크), "diff 판정의 주 소스"라는 역할은 더 이상 필수가 아니다.

추가 경로(문서로만 확인): 식약처 전자민원창구 http://ezdrug.mfds.go.kr (자료실 > 의약품 등) 에 주간 원료의약품 등록현황(신규등록, 변경등록, 연차보고) 게시.

5.2 nedrug DMF 공고 페이지(CCBAC03) 실제 컬럼 15개 — WebFetch 로 확인

# 컬럼명(원문) 비고
1 순번 페이지 내 일련번호. 수집 시 버림
2 대상의약품 분류값. 관측된 값 예: 별표1, 신물질
3 등록번호 구조화된 키(§5.3)
4 성분명
5 신청인 = 등록업체(제조업체)
6 제조소명
7 제조소소재지 긴 문자열, 줄바꿈 필요
8 제조국가 집계 핵심 축
9 최초등록일자
10 최종변경일자 diff 판정의 1차 신호
11 최종연차보고년도 연도(YYYY)
12 취소/취하구분 관측값: 정상 (취소·취하 시 값 변경)
13 취소/취하일자
14 문서번호
15 연계심사문서번호 완제-원료 연계심사

검색 필터: 발급일자 범위(시작~종료), 업소명, 등록번호, 성분명.

신규/변경/취하 구분 방법: 페이지 자체는 취소/취하구분 컬럼으로만 상태를 구분한다(관측 시점엔 전부 정상). → "신규/변경"은 페이지가 알려주지 않으므로 전일 스냅샷 diff 로 우리가 판정해야 한다. 이것이 본 크롤러의 존재 이유다.

5.3 등록번호 체계 — 완전 해설 (DMF 해설서 제5개정판 Q51, PDF 실측 추출)

예: 20110531-71-B-317-05 (괄호 변형: 20110531-71-B-317-05(1~9))

세그먼트 의미
1 20110531 등록수리일자 (YYYYMMDD)
2 71 「원료의약품 등록에 관한 규정」(식약처고시) [별표1]에 따른 각 성분의 일련번호
3 B 규정 부칙의 시행일을 고려한 일련번호 (아래 표)
4 317 동일 알파벳 군에서 접수된 성분 순으로 부여한 일련번호
5 05 동일 성분에 대하여 부여한 일련번호 (단, 일련번호 J 는 제외)
괄호 (1~9) 동일제조소·동일성분에 대하여 허여서로 등록된 순서로 부여한 일련번호

시행일군 알파벳(원문 그대로):

알파벳 대상 성분 시행
A~E [별표1] 제1호 ~ 제99호의 성분 2003.1.1 ~ 2008.1.1
F [별표1] 제100호 ~ 제113호 2009.1.1
G [별표1] 제114호 ~ 제123호 2010.1.1
H [별표1] 제124호 ~ 제141호 2011.1.1
I [별표1] 제142호 ~ 제208호 2013.1.1
J [별표1] 209호 「의약품동등성 확보 필요 대상 의약품 지정」성분, 210호 주사제 원료의약품(2017.12.25 시행), 211호 신청사가 제조(수입)하여 등록하고자 하는 원료의약품(2018.1.1 시행) 2017/2018
K [별표1의2] 제1호 ~ 제19호 2018.1.1

파생 컬럼(구현 필수):

import re
DMF_NO_RE = re.compile(
    r"^(?P<accept_date>\d{8})-(?P<ingr_seq>\d+)-(?P<cohort>[A-Z])-"
    r"(?P<serial>\d+)(?:-(?P<sub>\d+))?(?:\((?P<share>[^)]+)\))?$"
)

def parse_dmf_no(no: str) -> dict:
    """등록번호를 파생 컬럼으로 분해. 형식 불일치 시 raw 만 채우고 나머지는 None."""
    s = (no or "").strip().replace(" ", "")
    m = DMF_NO_RE.match(s)
    if not m:
        return {"등록번호": s, "등록수리일자": None, "성분일련번호": None,
                "시행군": None, "접수일련번호": None, "성분내일련번호": None,
                "허여서순번": None, "번호형식정상": False}
    g = m.groupdict()
    d = g["accept_date"]
    return {
        "등록번호": s,
        "등록수리일자": f"{d[0:4]}-{d[4:6]}-{d[6:8]}",
        "성분일련번호": int(g["ingr_seq"]),
        "시행군": g["cohort"],
        "접수일련번호": int(g["serial"]),
        "성분내일련번호": int(g["sub"]) if g["sub"] else None,
        "허여서순번": g["share"],
        "번호형식정상": True,
    }

5.4 등록·변경·연차보고·취하의 법적 정의 (실측)

「의약품 등의 안전에 관한 규칙」 제16조(원료의약품 등록대장과 등록증 등) — 공고 항목 5가지가 법에 못 박혀 있다(PDF 원문):

식품의약품안전처장은 원료의약품 등록을 한 경우에는 원료의약품 등록대장(전자문서로 된 대장을 포함한다)에 다음 각 호의 사항을 적고, 신청인에게 별지 제17호서식의 원료의약품 등록증을 발급하여야 한다. 이 경우 식품의약품안전처장은 다음 각 호의 사항을 인터넷 등으로 공고하여야 한다.

  1. 원료의약품의 성분ᆞ명칭
  2. 등록자의 성명
  3. 등록번호 및 등록 연월일
  4. 제조소의 명칭 및 소재지
  5. 제15조제2항에 따라 자료를 첨부하지 않은 경우 그 사실

제17조(변경등록 신청 등) 요지:

  • 중요한 사항 변경 → 변경등록(신청서 + 등록증 + 변경사유서 + 증빙). 변경등록 대상 예: 제조소 소재지의 변경(단, 행정구역 개편 제외), 제조방법 중 원료약품(촉매·유기용매 포함)·제조공정 변경 등.
  • 그 밖의 사항 변경 → 변경보고: 등록증을 첨부하여 매년 1월 31일까지 보고. = 실무상 연차보고.
  • 변경등록/변경보고를 받으면 등록대장·등록증에 기재하고 변경사항을 인터넷 등에 공고.

DMF 해설서(제5개정판, 2020.12) 원문:

DMF 변경관리는 기 등록한 DMF 내용과 비교하여 변경수준에 따라 「의약품 등의 안전에 관한 규칙」제17조제1항에 따른 **중요한 변경(Major Changes)**과 같은 조 제2항에 따른 **경미한 변경(Minor Changes)**으로 구분하고 있으며 중요한 변경사항은 「변경등록」으로, 경미한 변경사항은 「변경보고(연차보고)」로 갈음될 수 있음.

변경등록 대상인 경우에는 변경등록이 완료된 후 해당 원료의약품을 제조(수입)·판매하여야 하며, 변경보고 대상인 경우에는 변경된 내용으로 제품을 제조(수입)·판매하되, 다음해 1월 말까지 동 변경사항을 식약처장에게 보고하여야 함.

변경등록 vs 변경보고 비교표(해설서 붙임1 발췌 — 실측 원문):

항목 판정 근거 원문
신청인의 성명, 제조(영업)소 명칭·소재지 변경 변경보고 "변경보고 대상"
양도양수로 인한 신청인 변경 변경등록 "양도양수로 인한 신청인 변경은 변경등록 대상"
제조소 이전으로 인한 소재지 변경 변경등록(필요시 실태조사) 규정 제4조 자료요건 자료 제출
실제 제조장소 변경 없는 제조소 명칭·행정구역 변경 변경보고 공식기관 문서/공증자료, 변경된 GMP 증명서, 행정구역 변경 문서 중 1
인수합병에 의한 상호 변경 변경보고 "단순 명칭변경으로 판단하여 변경보고 대상"
제조소 책임자명·이메일·전화번호 변경 변경보고 불필요
제조소 내 단순 Layout(제조설비·시험장비) 변경 대상 제외
동일 지번 내 생산 site 변경(신축·증축·공장동 이동) 변경보고 (Site Master File 제출)
제조방법 변경을 수반하는 site 변경 변경등록
구조결정 기기분석 자료·단순 오기 정정 변경보고
공정서 수재 성분 명칭 변경(일반명/화학명/CAS No.) 변경보고 한글 맞춤법 등 단순 오기 포함
성상 표기방법 변경(예: 백색 → 흰색) 변경보고
유연물질 자료 변경(추가) 변경등록 유연물질 요약자료 + 검출/분석법 자료
자료 변경 없는 업데이트(문서개정 등) 불필요
사용기간(재시험기간) 변경 변경등록 3로트 안정성시험 자료(1로트 근거자료 포함)
최초 등록 시 stability protocol 로 사용기간 인정받은 경우의 추가 안정성 자료 변경보고
저장조건 변경 변경등록 3로트 안정성시험 자료 + 포장용기 자료(변경 시)
중요공정(발효공정 포함 핵심중간체 생성공정 및 그 이후) 변경, 제조단위 규모 10배 이상 변경 변경등록 제조방법 자료 + 변경 전후 동등성 입증자료(불순물 profile 통계분석[최소 3배치], UV/IR/X-선 회절 등) + 시험성적 3배치(근거 1배치) + 불순물 고찰
초기단계 공정변경 등 핵심중간체 이전 공정 변경 변경보고 (핵심중간체 동일·불순물 정보 동일 조건)

취하: 「민원 처리에 관한 법률 시행령」 제25조제3항에 따른 자진취하 수리([붙임9] 민원 신청 자진취하 수리 양식). 또한 자료공유허여서 관련: "등록 취하 신청 시 자료공유허여서를 제출한 모든 등록자를 대상으로 품목 양도·양수 여부(양수업체와 원제조소간의 계약여부 확인 포함) 또는 취하여부를 확인하고 그 결과를 제출할 것".

5.5 공고에 실제로 찍히는 "등록구분" 값 (해설서 Q60 실측)

※ 인터넷 공고 예시 • (주간 공고) 등록, 변경등록, 연차보고, 연계 등록 원료의약품(사전등록), 연계 등록 원료의약품(사전등록) 전환, 연계 등록 원료의약품(사전등록) 변경 • (총 등록 공고) '등록구분'항 신설 ⇒ 연계 등록 원료의약품(사전등록)

주간 공고의 등록구분은 6종이다. 사전등록 품목도 등록증이 발급되고 등록번호가 부여되며, 등록증·인터넷 공고에 "사전등록"으로 표시된다.

설계 반영: 상태 컬럼의 도메인은 {신규, 변경, 취하, 연차보고, 사전등록, 사전등록전환, 사전등록변경} 7값. 대시보드 KPI 는 앞의 3+워치리스트로 압축하고, 오늘 변경분 시트에서는 7값 전부를 보여준다.

5.6 처리기간·수수료 (MFDS 지침서 GRP-MaPP-허가업무-04, 2025.2.20 6개정 — PDF 실측)

대상 처리기간
원료의약품 등록 신청 (기본) 20일
신약의 원료의약품 90일
「의약품 등의 안전에 관한 규칙」제15조제1항제1호 가목 단서에 따른 등록 (동 지침 표 참조)
원료의약품 변경등록 신청 (기본, 양도양수 포함) 20일
항목 수수료
등록대상 원료의약품 등록 신청 802,000원 / 887,000원
등록대상 원료의약품 변경등록 신청 401,000원 / 443,000원

공고 절차(지침서 4.8):

식품의약품안전처장은 관련 규정에 따라 원료의약품 등록, 변경등록, 변경보고 현황을 우리처 홈페이지에 공고한다. * 「의약품 통합정보시스템」에서 식약처 담당자가 원료의약품 민원 처리 완료시 의약품안전나라(https://nedrug.mfds.go.kr)에 등록, 변경등록, 변경보고, 현황이 공고됨 ※ 관련 규정 : 의약품 등의 안전에 관한 규칙 제16조, 제17조

크롤러 설계 함의: 공고는 민원 처리 완료 시점에 즉시 반영된다(주간 배치가 아니라). 따라서 일 1회 06:00 폴링이 유효하며, 주간 공고 게시글은 보조 확인용이다.

5.7 OpenAPI 필드 (data.go.kr 15057075 — WebFetch 확인)

엔드포인트: https://apis.data.go.kr/1471000/MdcDmfInfoService01/getMdcDmfList01 API 명: 식품의약품안전처_원료의약품등록(DMF)현황 갱신주기: 문서에 명시 없음 ⚠️

출력 필드:

한글 영문 필드명 설명
등록번호 DMF_PERMIT_NO Registration number
성분명 INGR_KOR_NAME Ingredient name
업체명 ENTP_NAME Company name
제조소명 MNFCTR_NAME Manufacturing facility name
제조소 소재지 MNFCTR_PLACE Manufacturing facility location
제조국가명 MANUF_COUNTRY_CODE_NM Manufacturing country
발급일자 DMF_PERMIT_DATE Issue date

요청 파라미터:

한글 파라미터 필수 설명
인증키 serviceKey URL 인코딩된 인증키
업체명 entp_name
성분명 ingr_kor_name
페이지 번호 pageNo
한 페이지 결과 수 numOfRows
데이터포맷 type xml / json

주의(작성 당시 판단 — 반증됨): OpenAPI 는 7필드만 준다. 최종변경일자, 취소/취하구분, 연계심사문서번호 가 없다 → diff 판정에 부족. 따라서 nedrug CCBAC03 이 주 소스, OpenAPI 는 건수 교차검증용으로 쓴다(§8.9 메타 시트의 "건수 검증").

⚠️ 정정(design/00b-baseline-data-analysis.md 실측, 2026-09-02): 위 판단은 반증됐다. 발급일자(DMF_PERMIT_DATE)가 정적인 최초 등록일이 아니라 최종 갱신일로 동작하고(등록번호 앞 8자리와 44.5% 불일치, 전부 괄호 갱신 건), API 응답에서 등록번호가 소멸하면 취하로 판정할 수 있다(웹 9,840건 vs API 9,084건, 756건 차이). 즉 최종변경일자·취소/취하구분이 없어도 발급일자의 이동 + 레코드 소멸 관찰로 같은 정보를 얻을 수 있다. 상세 판정 규칙은 §8.0.

5.8 시장 통계 (대시보드 벤치마크 수치 — 데일리팜 실측)

지표 출처
2025년 상반기 DMF 등록 653건 데일리팜 ID=324241
2024년 상반기 256건 (전년 대비 2.8배) 동일
2024년 전체 545건 동일
종전 역대 최고(상반기) 2021년 상반기 537건 — 2025 상반기가 이를 초과 동일
규제완화 원료의약품 등록 시 GMP 평가를 GMP 증명서로 대체 가능(2025년부터) 동일
2023년 전체 DMF 249건, 그중 233건이 해외업체 데일리팜 ID=307819
2023년 제조국별 인도 120건, 중국 60건, 이탈리아 10건, 일본 6건 동일
2023년 국산 16건 = 6.4% (2005년 제도 본격 시행 이후 최저, 최초로 10% 미만) 동일
인도+중국 합계 비중 72.3% (180/249) — 최초로 70% 돌파 동일
인도 단독 비중 48.2% (2018년 36.8% → 상승) 동일
인도+중국 추이 2018년 59.7% → 2023년 72.3% 동일
원료 자급률 2021년 24.4% → 2022년 11.9% 동일
가격 요인 인도·중국산이 국산보다 20~30% 저렴 동일
그 외 국가필수의약품 및 공급중단 보고대상 의약품에 DMF 유예 허용 데일리팜 32332
그 외 DMF 등록 원료도 해외제조소 등록 대상이며 제조소 이전 시 신규 등록 필요 데일리팜 67688

DMF 해설서에 실린 연도별 등록 공고 현황(20.11.30. 기준, 취하 포함, 괄호=재공고 수) 원문 발췌:

구분              03 04 05 06 07 08 09 10  11      12      13      14      15       16
신약 원료약품      9   12  18  21  28  30  54  60   64(28)  75(17)  93(27)  97(23)  122(32)  123(32)
별도지정 고시품목 등 -   -  457  50 110 108 101 142(3) 844(637) 623(375) 676(...)

대시보드 반영: 제조국 Top 8 가로 막대는 이 통계 축(인도/중국/이탈리아/일본/한국/…)과 정확히 맞물린다. 업체별 시트에 국산여부 플래그 컬럼을 두고, 대시보드 하단에 국산 비중 % 를 보조 지표로 표시한다.


6. 색 접근성: 팔레트·상태 매핑·대비비

6.1 Okabe-Ito 팔레트 (기준 팔레트)

Masataka Okabe & Kei Ito, Color Universal Design(CUD) 제정. Nature Methods 가 명시적으로 권장하고, Claus Wilke 의 Fundamentals of Data Visualization기본 범주형 스케일이다.

# 이름 HEX RGB 흰 배경 대비비 검정 텍스트 대비비 본 프로젝트 용도
1 Orange #E69F00 230,159,0 2.25 9.32 변경
2 Sky Blue #56B4E9 86,180,233 2.31 보조/연차보고 차트 계열
3 Bluish Green #009E73 0,158,115 3.42 신규
4 Yellow #F0E442 240,228,66 1.32 15.88 사용 금지(흰 배경 대비 부족)
5 Blue #0072B2 0,114,178 5.19 강조색 1개 / 링크 / 합계
6 Vermillion #D55E00 213,94,0 3.87 취하
7 Reddish Purple #CC79A7 204,121,167 3.06 워치리스트 히트
8 Black #000000 0,0,0 21.0 사용 안 함(대신 #1F2933)

핵심 설계 사상(검색 #4 원문):

The Okabe-Ito palette avoids pure red and pure green entirely. Instead it uses: Orange (#E69F00) — perceived as yellow-orange even in protanopia, and Bluish Green/Teal (#009E73) — which contains enough blue channel to survive the red-green merge.

6.2 Paul Tol 팔레트 (대안 / 차트 다계열용)

출처: sronpersonalpages.nl/~pault/ (WebFetch 성공. 참고: personal.sron.nl/~pault/ 는 DNS 실패 getaddrinfo ENOTFOUND — 옛 주소이므로 sronpersonalpages.nl 를 쓸 것)

스킴 HEX 목록 흰 배경 대비비(순서대로) 설명(원문)
bright #4477AA #EE6677 #228833 #CCBB44 #66CCEE #AA3377 #BBBBBB 4.70 / 3.09 / 4.53 / 1.95 / 1.84 / 6.09 / 1.92 "colour-blind safe", 범주형 데이터의 1순위 권장
vibrant #EE7733 #0077BB #33BBEE #EE3377 #CC3311 #009988 #BBBBBB "equally colour-blind safe", TensorBoard 용으로 설계
high-contrast #004488 #DDAA33 #BB5566 9.62 / 2.13 / 4.56 "colour-blind safe and optimized for contrast" — 모노크롬 시각·흑백 인쇄에서도 잘 작동
bad data grey #888888 3.54 결측/무효 데이터 표준 색

본 프로젝트 선택: 상태색은 Okabe-Ito(3색 의미 고정), 결측·무효는 Paul Tol #888888, 흑백 인쇄를 대비해야 하는 요약표는 high-contrast 3색을 예비로 둔다.

6.3 Color Universal Design 원칙 (jfly.uni-koeln.de/color/ — WebFetch 확인)

CUD 8색: Vermilion(순수 red 아님, RGB 100%/32%/0% = #FF2000), Orange, Yellow, Bluish Green, Blue, Sky Blue, Reddish Purple(violet 아님), Black.

규칙(원문 요지):

  1. 중복 코딩(Redundant Coding): "Use not only different colors but also a combination of different shapes, positions, line types and coloring patterns."
  2. 금지 조합: 차트/형광 이미징에서 red 와 green 을 절대 함께 쓰지 말 것. green 과 짝지어야 한다면 red 대신 magenta(red+blue) 를 쓸 것.
  3. 구현: 얇은 선·가는 폰트 대신 굵은 선과 볼드 폰트(Arial, Helvetica). 색 범례 대신 요소에 직접 라벨링. 해칭 패턴·실선/점선·다른 기호 사용. 객체와 배경 사이의 명도·채도 대비를 충분히.
  4. 텍스트: "Avoid indicating objects only by names" — 색만이 아니라 위치·모양도 함께 서술.

6.4 Claus Wilke — 색 함정 (clauswilke.com/dataviz/color-pitfalls.html — WebFetch 확인)

  1. 정보 과다 인코딩: 범주형 색 스케일은 3~5개 카테고리가 최선, 8~10색을 넘으면 독자의 매칭 부담이 급증. "Use direct labeling instead of colors when you need to distinguish between more than about eight categorical items."
  2. 목적 없는 채색: 무지개 막대 차트처럼 미적 이유만의 색은 시각 공간 낭비.
  3. 비단조 스케일: 레인보우는 순환적(시작/끝 색이 유사)이고 명도 변화가 불균일 → 그레이스케일 변환 시 결함 노출.
  4. CVD: 순차형(sequential)은 명암 그라데이션 유지로 대체로 안전. 발산형(diverging)은 위험 — red-green 은 deutan/protan 에게, blue-green 은 tritan 에게 구별 불가. 범주형은 Okabe-Ito 로.
  5. 색 요소의 크기: "Colors are much easier to distinguish when they are applied to large areas than to small ones or thin lines." CVD 시뮬레이션에서 이 격차가 더 벌어진다.
  6. 권고: "안전하다는" 팔레트를 믿지 말고 CVD 시뮬레이터로 실제 검증하라.

DMF 리포트 반영: 상태색은 셀 배경 전체(큰 면적)에 칠하고 폰트색만 바꾸지 않는다. 제조국 Top 8 막대는 단색 #0072B2 로 하고 색으로 국가를 구분하지 않는다(카테고리 8개 = 위험 구간). 국가 구분은 축 라벨(직접 라벨링)로 한다.

6.5 WCAG 1.4.1 — 색 단독 전달 금지

출처: 검색 #12, accessibility.chat — When Color Coding Fails (WebFetch 확인)

  • 흔한 실수: "a common mistake is to color-code cells (e.g., red for 'Overdue,' green for 'On Time'), but a user who is color-blind will miss this information."
  • 규정: 색은 정보 전달의 유일한 시각 수단이 되어서는 안 된다. 최소 1개의 다른 시각 지시자를 병행.
  • 엑셀 모범: 인접 컬럼에 텍스트 라벨("Overdue"/"On Time"), 보편적 아이콘(✓ 성공, ✕ 오류, ! 경고)을 색과 함께.
  • 유병률: 남성 약 1/12(8%), 여성 약 1/200(0.5%).
  • accessibility.chat 권장 다중 채널:
    • 텍스트 라벨: "Online", "Degraded", "Offline"
    • 아이콘: ✓, ⚠, ✕
    • 패턴: 활성=solid fill, 경고=hatched, 오류=stippled
    • 형태: 정상=원, 경고=사각, 치명=삼각
    • 핵심: "successful implementations pair visual indicators with multiple channels of information" — 명확성은 단일 채널의 완벽함이 아니라 채널 간 중복 신호에서 온다. 디자인 시스템 컴포넌트 자체에 이 요건을 박아 넣어 색 단독 패턴을 만들 수 없게 하라.

6.6 상태색 매핑표 (구현 상수, 제안)

⚠️ 팔레트 충돌: research/06 §7.1 이 같은 상태에 다른 HEX 값을 제시한다. 최종 정본은 design/03-xlsx-report-spec.md 가 확정한다. 이 문서의 값은 WCAG 대비비 실측 근거가 있으므로 정본 후보로 제안한다.

모든 상태는 [배경색 + 폰트색 + 텍스트 라벨 + 기호] 4중 코딩. 대비비는 계산값(WCAG 2.x 상대휘도 공식).

상태 라벨 기호 배경 HEX 폰트 HEX 대비비 등급 액센트(차트/타일 숫자) HEX 액센트 흰배경 대비
신규 신규 #E3F3EA #005A32 7.28 AAA #009E73 3.42
변경 변경 #FDF0DC #7A3E00 7.42 AAA #E69F00 2.25
취하/취소 취하 #FBE5DC #8A1D00 7.69 AAA #D55E00 3.87
연차보고 연차보고 #E5F1F8 #00456B 8.85 AAA #0072B2 5.19
사전등록 계열 사전등록 #F7E9F0 #7A2E55 7.61 AAA #CC79A7 3.06
워치리스트 히트 #F7E9F0 #7A2E55 7.61 AAA #CC79A7 3.06
변동 없음 - · #FFFFFF #5B6770 5.80 AA #888888 3.54
오류/결측 ? #EFEFEF #3A3A3A 9.89 AAA #888888 3.54

보조 색(구조 요소):

용도 HEX 대비비
본문 텍스트 #1F2933 on #FFFFFF 14.76 (AAA)
본문 텍스트 on 연회색 #1F2933 on #F7F7F7 13.77 (AAA)
보조 텍스트 #5B6770 on #FFFFFF 5.80 (AA)
표 헤더 #FFFFFF on #1F2933 14.76 (AAA)
시트 배경 #FFFFFF
타일 배경 #F7F7F7
구분선/테두리 #D0D7DE 1.45 (장식용, 텍스트 아님)
강조(액센트) #0072B2 5.19 (AA)

참고 — 엑셀 기본 조건부서식 색과의 비교: XlsxWriter 문서 예제의 기본색은 {"bg_color": "#FFC7CE", "font_color": "#9C0006"}(대비 5.92)와 {"bg_color": "#C6EFCE", "font_color": "#006100"}(대비 6.14)이다. 둘 다 red/green 쌍이라 CUD 금지 조합이고 대비도 우리 팔레트보다 낮다 → 사용하지 않는다.

6.7 아이콘셋 사용 지침

XlsxWriter 가 지원하는 icon_style 전체 목록(문서 실측): 3_arrows, 3_arrows_gray, 3_flags, 3_signs, 3_symbols, 3_symbols_circled, 3_traffic_lights, 3_traffic_lights_rimmed, 4_arrows, 4_arrows_gray, 4_ratings, 4_red_to_black, 4_traffic_lights, 5_arrows, 5_arrows_gray, 5_quarters, 5_ratings

본 프로젝트 선택: 3_symbols_circled (○ / ! / ✕ 형태 — 모양이 다르므로 CVD 안전) 또는 3_arrows_gray(무채색 화살표, 방향만 전달). 금지: 3_traffic_lights, 3_traffic_lights_rimmed, 4_traffic_lights — 신호등은 red/green 조합 + 동일 형태(원) 이라 CVD 에서 정보가 소실된다.

# 전일 대비 델타 컬럼(예: 성분별 시트 K열)에 방향만 표시. 값은 그대로 보이게 icons_only=False
worksheet.conditional_format('K3:K200', {
    'type': 'icon_set',
    'icon_style': '3_arrows_gray',
    'icons': [{'criteria': '>=', 'type': 'number', 'value': 1},
              {'criteria': '<',  'type': 'number', 'value': 1}],
})

icons 파라미터 규칙(문서 실측): criteria>= 또는 < 만 가능(기본 >=), typenumber / percentile / percent / formula(기본 percent), 값은 가장 높은 것부터 내림차순으로 나열해야 한다. 부가 옵션: reverse_icons: True, icons_only: True.


7. 공개 데이터(FDA/EMA/식약처) 컬럼·시트 구성에서 배울 점

7.1 FDA DMF 목록 (List of Drug Master Files)

  • 파일 형식: Excel 로 다운로드 제공. 분기(quarterly) 갱신.
  • 컬럼 5개: DMF Number, Type, Status(활동상태), Holder(보유자명), Subject(제목).
  • 상태 코드: A = active, I = inactive. 문서 원문: "changes to the DMF activity status (A=active ; I=inactive), DMF type, holder name and subject (title) are tracked in the published list."
  • 컷오프 명시: "The list of DMFs, which is updated quarterly, contains DMFs received by December 31, 2025, for which acknowledgment letters were sent before January 22, 2026.""수령 기준일"과 "acknowledgment letter 발송 기준일"을 둘 다 명시한다.
  • BDO 가이드(WebFetch 확인) 원문: "CDER maintains a database containing a publicly available list of DMFs filed with CDER. The list contains information about the DMF number, activity status (active or inactive), type, holder name and subject (title)."

DMF Type 정의 (Wikipedia 실측 + BDO):

Type 내용
Type I "Manufacturing Site, Facilities, Operating Procedures and Personnel"
Type II 원료의약품(Drug substances), 중간체, 제조용 물질
Type III 포장재(Packaging materials), 용기·마개 시스템
Type IV 부형제(Excipients), 착색제, 향료
Type V Type II~IV 에 해당하지 않는 FDA 수용 참조정보(시설·공정·임상 데이터 등)

지역 변형: 유럽 = EDMF / ASMF(Active Substance Master File), 미국 = US-DMF. Wikipedia 문서에는 한국(KDMF) 관련 서술이 없다 (⚠️ 미검증 영역).

John Snow Labs 재가공판 필드(WebFetch 확인) — 정형화 참고:

필드 타입
DMF_Id integer
Is_DMF_Status_Active boolean
Type_of_DMF string
Submission_Date date
DMF_Holder_Name string
DMF_Subject string
갱신주기 Quarterly
기간 커버리지 19392022

배울 점 1 — 상태를 boolean 이 아니라 코드+라벨로: FDA 는 A/I 코드를 쓰고, John Snow Labs 는 Is_DMF_Status_Active boolean 으로 바꿨다. 우리는 사람이 읽는 파일이므로 라벨(정상/취하)을 1급으로 두고 코드는 히든 컬럼으로 둔다. 배울 점 2 — 컷오프 날짜를 명시: 메타 시트에 "수집 기준 시각"과 "공고 반영 기준일"을 분리해 기록한다. 배울 점 3 — 5컬럼의 절제: FDA 공개 목록은 5컬럼뿐이다. 우리의 대시보드 요약 표도 5컬럼을 넘기지 않는다.

⚠️ 미검증: fda.gov/drugs/drug-master-files-dmfs/list-drug-master-files-dmfsfda.gov/drugs/forms-submission-requirements/drug-master-files-dmfs 는 WebFetch 시 HTTP 404(2026-09-02). 실제 xlsx 다운로드 URL(fda.gov/media/...)은 확인하지 못했다.

7.2 FDA Orange Book 데이터 파일

  • 파일 3종: products.txt, patent.txt, exclusivity.txt.
  • products.txt 컬럼 14개(2019-03 기준, 36,771행): Ingredient, DF;Route, Trade_Name, Applicant, Strength, Appl_Type, Appl_No, Product_No, TE_Code, Approval_Date, RLD, RS, Type, Applicant_Full_Name.
  • 컬럼 의미: Appl_No 형식은 nnnnnn. Applicant최대 20자 고유 문자열로 압축된 회사명, Applicant_Full_Name 은 전체명. Appl_TypeA(ANDA, 제네릭) 또는 N(NDA) 두 값 배타. TE_Code 는 제네릭 대 오리지널의 치료적 동등성 등급. RLD 는 FD&C Act 505(c) 승인 대조약 표시.
  • 구분자: paracelsus 문서에 명시 없음 ⚠️. 공식 필드 설명은 https://www.fda.gov/drugs/informationondrugs/ucm129689.htm#descriptions.

배울 점 4 — 축약명 + 전체명을 둘 다 준다: Applicant(20자) / Applicant_Full_Name. 우리도 신청인_표시(30자 축약, 표 폭 유지용)와 신청인_전체(히든) 를 둔다. 업체명이 길어 표가 무너지는 문제를 이 방식으로 해결한다. 배울 점 5 — 배타적 코드 컬럼: Appl_Type 처럼 값 도메인이 좁은 컬럼은 데이터 유효성 검사(drop-down) 를 걸어 사용자가 오염시키지 못하게 한다(워치리스트 시트에 적용).

7.3 Drugs@FDA 데이터 파일

  • 배포: drugsatfda.zip 1개 → 12개 텍스트 테이블로 압축 해제. 전 필드 탭 구분(tab-delimited).
  • 테이블 예: ActionTypes_Lookup, ApplicationDocs, Applications 등. 각 테이블마다 primary key, data type, field length, nulls 를 문서화하고 ERD 를 제공(2025-01-10 기준).
  • 갱신: 매일 아침, 월~금. data.gov 메타데이터의 accrual periodicity = R/P1D(일 단위), 최종 갱신 2025-07-16. 라이선스 ODbL.
  • openFDA API(open.fda.gov/apis/drug/drugsfda/) 필드(WebFetch 확인): application_number, sponsor_name, openfda(조화된 식별자), products(product_number, brand_name, active_ingredients, marketing_status, dosage_form, route), submissions(submission_type, submission_number, submission_status, submission_status_date, submission_classification_code, review_priority). 갱신 Daily (Monday-Friday), 커버리지 1939~현재, 1998년 이후 승인건은 문서 완비.

배울 점 6 — 룩업 테이블 분리: ActionTypes_Lookup 처럼 코드→라벨 매핑을 별 테이블로 뺀다. 우리는 메타 시트 하단에 코드북(상태코드·국가코드·시행군 알파벳) 을 둔다. 배울 점 7 — 제출 이력(submissions)이 별 테이블: 하나의 신청번호에 여러 제출 이벤트가 붙는 구조. 우리의 등록번호 1개 : 이벤트 N개 구조와 동일 → 스냅샷·로그 시트가 이 역할. 배울 점 8 — ERD 와 필드 정의를 함께 배포: 메타 시트에 컬럼 정의표를 넣어, 파일만 받아도 이해 가능하게 한다.

⚠️ 미검증: fda.gov/drugs/drug-approvals-and-databases/drugsfda-data-files, .../orange-book-data-files 모두 WebFetch HTTP 404(2026-09-02).

7.4 EDQM CEP 데이터베이스

출처: 검색 #24 (원 페이지 edqm.eu/en/-/deployment-of-a-new-edqm-cep-database-feature-full-download-of-cep-dataHTTP 403 ⚠️)

  • 전체 다운로드에 포함되는 주요 정보: CEP number, holder, status, European Pharmacopoeia monograph number, substance name.
  • 추가 컬럼: holder 의 SPOR ORG 및 LOC-ID(있을 경우), renewal date(아직 갱신되지 않은 경우), 마지막 절차의 closure date.
  • 검색 기준: substance name, CEP number, holder name. 부여된 CEP 목록·type·물질명·전체 CEP 번호·발급일·유효 상태로 검색 가능. Certification Database 는 매일 갱신.
  • 절차 이력: 선택한 CEP 의 revision type, 완료된 절차의 결과와 closure date 를 표 형태로 조회·인쇄 가능.

배울 점 9 — 조직 식별자를 별도 컬럼으로: SPOR ORG / LOC-ID 처럼 회사·소재지의 표준 ID를 둔다. 한국 DMF 에는 그런 공식 ID 가 없으므로 정규화 키를 직접 생성한다: 업체키 = 신청인 문자열 정규화(공백/괄호/(주)/㈜/Co.,Ltd. 제거 + 소문자화 + NFKC). 이 키가 있어야 업체별 집계와 워치리스트 매칭이 안정된다. 배울 점 10 — "절차 이력" 뷰: CEP 는 인증서 1건의 revision 이력을 표로 준다. 우리도 전체 누적 시트에서 등록번호를 클릭하면 스냅샷·로그 의 해당 이력으로 점프하는 내부 링크를 건다.

7.5 식약처 통계연보 / 공공데이터

배울 점 11 — 연보 스타일의 "기준일" 표기: "2023년 제25호(2022년말 기준)"처럼 간행 시점과 데이터 기준 시점을 분리 표기. 메타 시트에서 그대로 채택.


8. 시트 구성 제안 (구현 명세)

8.0 API 필드 충분성 — 정정

기존 주장과 그것이 왜 틀렸는가

이 문서는 처음에 §5.7 에서 다음과 같이 판단했다: "OpenAPI 는 7필드만 준다. 최종변경일자, 취소/취하구분, 연계심사문서번호 가 없다 → diff 판정에 부족. 따라서 nedrug CCBAC03 이 주 소스, OpenAPI 는 건수 교차검증용으로 쓴다." 근거는 "웹 화면에 있는 컬럼이 API 에 없으면 그 정보를 알 수 없다"는 가정이었다.

design/00b-baseline-data-analysis.md 가 기존 운영 산출물 DMF_현황.xlsx(API 7필드만으로 만들어진 파일)를 전수 분석한 결과, 이 가정은 틀렸다. API 응답 자체에 간접 신호가 이미 들어 있었다.

근거 실측 의미
건수 차이 웹 화면 9,840건 vs API 9,084건 = 756건 API 는 취하·취소 건을 제외하고 정상 건만 반환한다
발급일자 이동 등록번호 앞 8자리(최초 등록일)와 발급일자44.5% 불일치, 불일치 건은 전부 괄호 붙은 갱신 건 발급일자(DMF_PERMIT_DATE)는 정적 최초 등록일이 아니라 최종 갱신일로 동작
등록번호 유일성 9,084건 중 중복 0건 diff 의 기준 키로 그대로 쓸 수 있는 완전한 자연 키

세 이벤트 판정 규칙 (00b §6.3 을 그대로 채택)

판정 규칙 근거
신규 오늘 스냅샷에 등록번호가 있고, 어제 스냅샷에 없음 등록번호가 유일 키
변경 등록번호 동일 + 발급일자가 어제보다 최신 발급일자가 최종 갱신일로 동작
변경(보조 신호) 등록번호 동일 + 성분명·업체명·제조소명·제조소소재지·제조국가명 중 하나 이상이 어제와 다름 내용 diff
취하 어제 스냅샷에 등록번호가 있고, 오늘 스냅샷에 없음 API 가 정상 건만 반환

이로써 §5.7 의 "API 단독으로는 변경·취하 탐지 요구를 충족할 수 없다"는 결론은 반증된다. nedrug CCBAC03 스크래핑은 더 이상 diff 판정의 필수 전제가 아니다.

⚠️ 아직 완전한 증명은 아니다. 00b 도 명시하듯 이틀 이상 연속 수집해 실제로 레코드가 사라지는 것을 관찰해야 확정된다. 그때까지 design/00-DATA-SOURCE-DECISION.md §7.2 의 안전장치(건수 급감 시 diff 중단)를 유지한다.

API 로 여전히 놓치는 것 — 정직하게 기록한다 (00b §6.4 근거)

놓치는 것 이유 영향 대응
연차보고 최종연차보고년도 가 API 에 없고, 연차보고 시 발급일자가 갱신되는지도 불명 중간. 연차보고는 매년 1월 말에 몰림 리포트에서 별도 이벤트로 다루지 않는다. 실측 필요(부록 B)
변경 사유·유형 어떤 항목이 왜 바뀌었는지 API 에 없음 낮음 각 행에 nedrug 검색 링크를 붙여 사람이 직접 확인
취하 vs 취소 구분 소멸했다는 사실만 알고 사유는 모름 낮음 "목록에서 사라짐"으로만 표기
취하 일자 실제 취하일이 아니라 소멸을 감지한 날짜만 앎 낮음 최종변경감지일로 기록하되 근사치임을 명시
문서번호 · 연계심사문서번호 API 미제공. 00b 도 다루지 않아 대체 가능성이 확인되지 않음 미평가 §8.3·§8.4 컬럼에서 제외. 재조사 필요(부록 B)

이 표가 §8.3(오늘 변경분)·§8.4(전체 누적) 컬럼 재설계의 기준이다. 정정 전 원안(구 컬럼표)은 각 절 하단에 <details> 로 보존했다.

8.1 공통 규칙

항목 규칙
워크북 파일명 DMF_Daily_YYYYMMDD.xlsx (예: DMF_Daily_20260902.xlsx)
시트 순서 대시보드 → 오늘 변경분 → 전체 누적 → 성분별 → 업체별 → 워치리스트 → 스냅샷·로그 → 메타
첫 활성 시트 대시보드 (worksheet.activate())
기본 폰트 맑은 고딕 10pt
눈금선 데이터 시트는 유지, 대시보드hide_gridlines(2)
확대 데이터 시트 100%, 대시보드 set_zoom(90)
헤더 행 모든 데이터 시트 1행 = 제목/역링크, 2행 = 컬럼 헤더, 3행부터 데이터
틀 고정 데이터 시트 전부 freeze_panes(2, 2) 이상 (헤더 2행 + 좌측 열 고정)
자동 필터 add_table() 사용 시 자동 부여. 표를 안 쓰는 시트는 autofilter() 명시
표 스타일 'Table Style Light 11' (줄무늬 약함, 데이터 잉크 비율 우선)
인쇄 set_landscape(), fit_to_pages(1, 0), repeat_rows(0, 1)
셀 주석 컬럼 헤더에 write_comment() 로 정의 툴팁
값 없음 빈 문자열이 아니라 - (정렬 일관성)

표 스타일 근거 — XlsxWriter add_table() 옵션(문서 실측): style 기본값 'Table Style Medium 9', None 으로 해제 가능. header_row(기본 True), autofilter(기본 True, header_row 필요), banded_rows(기본 True), banded_columns(기본 False), first_column(기본 False), total_row(기본 False), name(기본 Table1, Table2…). 컬럼 옵션: header, header_format, formula, format, total_function(sum/average/count/max/min/std_dev/var/count_nums), total_string, total_value.

⚠️ 주의: 문서 원문 — "tables aren't available in XlsxWriter when Workbook 'constant_memory' mode is enabled."constant_memory 를 켜지 말 것.

8.2 시트 (1): 대시보드 — 탭색 #0072B2

  • 역할: 5초 안에 오늘 상황 파악. 스크롤 없음.
  • 눈금선 숨김, 줌 90%, 인쇄 1페이지 가로. 레이아웃은 §9 와이어프레임 참조.
셀/범위 내용 서식
B2:R2 (merge) DMF 일일 모니터링 리포트 — 2026-09-02(화) 18pt bold, #1F2933, 좌측정렬, 행높이 30
B3:R3 (merge) 자동 생성 헤드라인 문장(§2.6) 11pt, #5B6770, 행높이 18
B5:C5Q5:R5 KPI 라벨 6개 10pt bold, #5B6770, 가운데, 배경 #F7F7F7, 상단 테두리 굵게(상태색)
B6:C6Q6:R6 KPI 값 6개 28pt bold, 상태색, 가운데, #F7F7F7, #,##0
B7:C7Q7:R7 전일 대비 델타 10pt bold, ▲ #,##0;▼ -#,##0; 0
B8:C8Q8:R8 30일 스파크라인 add_sparkline, 행높이 14
B10 차트1 앵커: 최근 30일 일별 신규/변경/취하 (누적 세로 막대) insert_chart('B10', ch1, {'x_scale':1.65,'y_scale':1.05})
K10 차트2 앵커: 제조국 Top 8 (가로 막대) insert_chart('K10', ch2, {'x_scale':1.65,'y_scale':1.05})
B26:F26 Top 10 성분 표 헤더: 순위 / 성분명 / 오늘 / 30일 / 누적
H26:L26 Top 10 업체 표 헤더: 순위 / 신청인 / 오늘 / 30일 / 누적
N26:R26 워치리스트 히트 표 헤더: 키워드 / 매칭 / 성분명 / 신청인 / 상태
B42:R42 내비게이션 링크 6개 (내부 하이퍼링크) write_url('internal:...')
B48:R48 출처·면책 문구 9pt #5B6770

시트 간 링크 방향: 대시보드 → 나머지 7시트(단방향 발신). 각 데이터 시트 A1 에는 ← 대시보드 역링크(수신).

8.3 시트 (2): 오늘 변경분 — 탭색 #D55E00

  • 역할: 전일 스냅샷 대비 diff 결과만. 이 시트가 리포트의 본체.
  • 정렬: 상태 커스텀 순서(취하 → 신규 → 변경) → 워치리스트히트 내림차순 → 성분명 오름차순. (구안의 사전등록·연차보고 세분류는 API 단독으로 구분할 수 없어 제외 — §8.0)
  • 커스텀 정렬은 엑셀 사용자 정렬로 보존되지 않으므로 히든 정렬키 숫자 컬럼(A열) 을 만들어 파이썬에서 미리 정렬하고, 사용자가 다시 정렬해도 복구 가능하게 한다. (검색 #35 원문: "A numeric helper column approach (1, 2, 3) can match your desired sort order, and this approach offers portability across files.")
  • freeze_panes(2, 3) — 헤더 2행 + A,B,C 고정.

컬럼표 (정정: API 7필드 + 파생 필드 기준, §8.0 근거)

컬럼명 출처 타입 열너비 정렬 표시형식 조건부서식 비고
A 정렬키 파생 정수 4 가운데 0 히든. 상태 정렬 순서용
B 기호 파생(상태 기반) 문자 4 가운데 @ 상태별 폰트색(§6.6) ◆ ✕
C 상태 파생(diff 결과) 문자열 10 가운데 @ 행 전체 규칙의 판정 열 도메인 3종: 신규/변경/취하. 판정 규칙은 §8.0 표. 구 7종 도메인의 연차보고·사전등록 세분류는 API 로 구분 불가
D 등록번호 API 원본 DMF_PERMIT_NO 문자열 24 왼쪽 @ 내부 링크 → 전체 누적 자연키, 중복 0건 실측(00b §5.1)
E 성분명 API 원본 INGR_KOR_NAME 문자열 30 왼쪽 @ 워치 매칭 시 bold 표기 흔들림 21그룹 실측(00b §8.2)
F 업체명 API 원본 ENTP_NAME 문자열 26 왼쪽 @ 워치 매칭 시 bold nedrug 화면 표기는 "신청인"(00b §3)
G 제조소명 API 원본 MNFCTR_NAME 문자열 26 왼쪽 @ 형식 오류 42건 실측(00b §8.4). 콤마로 다중 제조소가 이어붙지만 분해하지 않음(Co., Ltd. 자체에 콤마 포함)
H 제조소소재지 API 원본 MNFCTR_PLACE 문자열 40(상한) 왼쪽 @, 자동 줄바꿈 최대 473자 실측(00b §8.6). 열너비 40 상한 고정 + text_wrap + 행높이 고정. 표시는 앞 60자만 노출하고 전체는 셀 메모(write_comment)로
I 제조국가명 API 원본 MANUF_COUNTRY_CODE_NM 문자열 16 가운데 @ 대한민국 포함 시 #E3F3EA 콤마 다중값, 같은 국가 반복 표기 496건 실측(00b §8.1)
J 정규화국가 파생 문자열 16 가운데 @ 히든. 콤마 분해 → 중복 제거 → 정렬 → 재결합. 정규화 후 49개국(00b §7.1/8.1)
K 발급일자 API 원본 DMF_PERMIT_DATE 날짜 12 가운데 yyyy-mm-dd 오늘 날짜면 bold 최종 갱신일로 동작(정적 발급일이 아님). 변경 판정의 1차 신호(00b §6)
L 등록수리일자 파생(등록번호 파싱, §5.3) 날짜 12 가운데 yyyy-mm-dd 등록번호 앞 8자리 = 최초 등록일. 발급일자와 다르면 갱신 이력이 있는 건(00b §6.1)
M 변경차수 파생(등록번호 괄호 파싱) 문자열 10 가운데 @ 값 있으면 #FDF0DC 괄호 안 값(A, 1, 18 등). 표준+괄호 2,973건 실측(00b §5.2)
N 대상의약품구분 파생(등록번호 포맷) 문자열 12 가운데 @ 접두어 → 신물질(1,882건 실측), 그 외 → 별표1(00b §5.2, D4)
O 변경필드 파생(diff 결과) 문자열 34 왼쪽 @ 자동 줄바꿈 성분명·업체명·제조소명·소재지·국가 중 어제와 값이 다른 필드명 목록
P 변경내용(before→after) 파생(diff 결과) 문자열 46 왼쪽 @, 9pt 자동 줄바꿈 예: 제조소소재지: A → B
Q 워치리스트히트 파생 정수 8 가운데 0;;- >0#F7E9F0 + bold 매칭 개수
R 워치키워드 파생 문자열 20 왼쪽 @ 쉼표 구분
S 원문 확인 파생(URL 생성) 문자열 12 가운데 @ write_url(url, string='nedrug 검색') nedrug CCBAC03 검색 URL(등록번호 파라미터로 조립). 수집 대상 아님 — 사람이 직접 확인하는 보조 링크(변경 사유·취하 사유 확인용, 00b §6.4 대응)
T 최초수집일 파생 날짜 12 가운데 yyyy-mm-dd 이 시스템이 처음 이 레코드를 본 날(00b §3, D9)
U 최종변경감지일 파생 날짜 12 가운데 yyyy-mm-dd diff 로 변경/소멸을 감지한 날. 실제 취하일이 아니라 감지일 근사치임을 명시(00b §6.4)
V 수집시각 파생 일시 18 가운데 yyyy-mm-dd hh:mm 실행 타임스탬프

제외된 컬럼: 문서번호, 연계심사문서번호 — OpenAPI 미제공이고 00b 실측도 대체 가능성을 확인하지 못했다(§8.0). 대안이 나올 때까지 이 시트에 넣지 않는다. 최종연차보고년도, 취소/취하구분, 취소/취하일자(nedrug 원본)는 위 파생 필드(상태, 최종변경감지일)로 대체됐다.

조건부서식 (행 전체, 적용 범위 B3:V1000)

rules = [
    ("신규", {"bg_color": "#E3F3EA", "font_color": "#005A32"}),
    ("변경", {"bg_color": "#FDF0DC", "font_color": "#7A3E00"}),
    ("취하", {"bg_color": "#FBE5DC", "font_color": "#8A1D00", "font_strikeout": True}),
]
for label, props in rules:
    fmt = workbook.add_format(props)
    ws.conditional_format("B3:V1000", {
        "type": "formula",
        # 열은 절대($C), 행은 상대(3 = 적용범위의 첫 행)
        "criteria": '=$C3="' + label + '"',
        "format": fmt,
    })

star = workbook.add_format({"bold": True, "font_color": "#7A2E55"})
ws.conditional_format("Q3:Q1000",
                      {"type": "cell", "criteria": ">", "value": 0, "format": star})

중요: XlsxWriter 조건부서식 수식은 US 스타일 구분자(세미콜론이 아니라 쉼표) 를 쓰고 범위는 절대 표기를 권장한다(문서 명시). openpyxl 문서도 동일 경고: "the formula uses an absolute reference to the column; but a relative row number."

정정 전 원안 — API 미제공 컬럼(최종변경일자, 취소/취하구분, 문서번호 등)을 원본 컬럼으로 전제했던 구 컬럼표
컬럼명 정렬 표시형식 조건부서식 / 수식 비고
A 정렬키 4 가운데 0 히든 {'hidden':1}
B 기호 4 가운데 @ 상태별 폰트색(§6.6) ◆ ✕ ↻ ◇
C 상태 10 가운데 @ 행 전체 규칙의 판정 열 값 도메인 7종
D 등록번호 24 왼쪽 @ 내부 링크 → 전체 누적 하이퍼링크
E 성분명 30 왼쪽 @ 워치 매칭 시 bold
F 신청인_표시 26 왼쪽 @ 워치 매칭 시 bold 30자 축약
G 제조소명 26 왼쪽 @
H 제조국가 12 가운데 @ 국내/대한민국#E3F3EA
I 대상의약품 12 가운데 @ 별표1 / 신물질
J 최초등록일자 12 가운데 yyyy-mm-dd
K 최종변경일자 12 가운데 yyyy-mm-dd 오늘이면 bold
L 취소/취하구분 10 가운데 @ 취하면 취소선
M 취소/취하일자 12 가운데 yyyy-mm-dd
N 변경필드 34 왼쪽 @ 자동 줄바꿈 diff 로 잡힌 필드명 목록
O 변경내용(before→after) 46 왼쪽 @ 자동 줄바꿈, 9pt 제조소소재지: A → B
P 워치리스트히트 8 가운데 0;;- >0#F7E9F0 + bold 매칭 개수
Q 워치키워드 20 왼쪽 @ 쉼표 구분
R 문서번호 16 왼쪽 @
S 연계심사문서번호 18 왼쪽 @ 값 있으면 #E5F1F8
T 원문링크 12 가운데 @ write_url(url, string='공고 보기') 외부 링크
U 수집시각 18 가운데 yyyy-mm-dd hh:mm

이 원안은 L(취소/취하구분), M(취소/취하일자), R(문서번호), S(연계심사문서번호), I(대상의약품, nedrug 원본), J(최초등록일자, nedrug 원본)를 nedrug CCBAC03 스크래핑이 있어야만 채울 수 있다고 전제했다. §8.0 의 정정으로 이 전제가 필요 없어졌다.

8.4 시트 (3): 전체 누적 — 탭색 #5B6770

  • 역할: 유효 등록 전체 목록. 필터·정렬·틀고정으로 사용자가 직접 파고드는 시트.
  • add_table() 로 감싸 자동 필터 부여. freeze_panes(2, 3). 정렬은 등록수리일자 내림차순.
  • 행 수 수천~수만(실측 기준선 9,084행, 00b §7.1) → 행 높이 고정(15), 자동 줄바꿈 없음.
  • 정정: API 는 애초에 취하·취소 건을 제외한 정상 건만 반환한다(00b §4). 따라서 이 시트에 나타나는 모든 행은 이미 유효 등록이며, 별도의 취소/취하구분='정상' 필터가 필요 없다. 취하는 "행이 다음날 사라짐"으로 나타나므로 이력은 스냅샷·로그 시트에서 확인한다.

컬럼표 (정정: API 7필드 + 파생 필드 기준, §8.0 근거)

컬럼명 출처 타입 열너비 정렬 표시형식 조건부서식 비고
A 등록번호 API 원본 DMF_PERMIT_NO 문자열 24 왼쪽 @ 중복값 강조 {'type':'duplicate'}(이상 탐지용, 정상 시 0건) 자연키, 중복 0건 실측(00b §5.1)
B 성분명 API 원본 INGR_KOR_NAME 문자열 30 왼쪽 @ 워치 매칭 시 #F7E9F0
C 업체명 API 원본 ENTP_NAME 문자열 26 왼쪽 @ 워치 매칭 시 #F7E9F0 nedrug 화면 표기는 "신청인"
D 제조소명 API 원본 MNFCTR_NAME 문자열 26 왼쪽 @
E 제조소소재지 API 원본 MNFCTR_PLACE 문자열 40(상한) 왼쪽 @, 자동 줄바꿈 헤더에 주석(툴팁) 최대 473자 실측(00b §8.6). 열너비 40 상한 + text_wrap + 행높이 고정 필수
F 제조국가명 API 원본 MANUF_COUNTRY_CODE_NM 문자열 16 가운데 @ text containing '대한민국'#E3F3EA 원문 그대로(콤마 다중값). 국가 중복 표기 496건 실측(00b §8.1)
G 정규화국가 파생 문자열 16 가운데 @ 히든. 정규화 후 49개국(00b §7.1/8.1)
H 상태 파생(diff 결과) 문자열 10 가운데 @ 상태색(§6.6) 도메인: 신규/변경/정상(무변동). 취하는 레코드 자체가 사라지므로 이 시트에는 값으로 남지 않는다
I 발급일자 API 원본 DMF_PERMIT_DATE 날짜 12 가운데 yyyy-mm-dd 오늘 = #FDF0DC(전일 대비 diff 강조) 최종 갱신일로 동작(00b §6)
J 등록수리일자 파생(등록번호 파싱, §5.3) 날짜 12 가운데 yyyy-mm-dd 데이터바 #0072B2 등록번호 앞 8자리 = 최초 등록일
K 변경차수 파생(등록번호 괄호 파싱) 문자열 10 가운데 @ 값 있으면 옅은 강조 괄호 안 값. 표준+괄호 2,973건 실측(00b §5.2)
L 대상의약품구분 파생(등록번호 포맷) 문자열 12 가운데 @ 접두어 → 신물질(1,882건), 그 외 → 별표1(00b D4)
M 성분일련번호 파생(§5.3 파서) 정수 8 가운데 0 히든
N 시행군 파생(§5.3 파서) 문자 6 가운데 @ 히든
O 접수일련번호 파생(§5.3 파서) 정수 8 가운데 0 히든
P 성분내일련번호 파생(§5.3 파서) 정수 8 가운데 0 히든
Q 허여서순번 파생(§5.3 파서) 문자 10 가운데 @ 히든
R 정규화성분키 파생 문자열 26 왼쪽 @ 히든(정규화 키) 표기 흔들림 21그룹 정규화(00b §8.2)
S 정규화업체키 파생 문자열 26 왼쪽 @ 히든(정규화 키) 표기 흔들림 2그룹 정규화(00b §8.3)
T 최초수집일 파생 날짜 12 가운데 yyyy-mm-dd 크롤러가 처음 본 날
U 최종변경감지일 파생 날짜 12 가운데 yyyy-mm-dd diff 로 변경을 감지한 날
V 행해시 파생 문자열 16 왼쪽 @ 히든. diff 판정용 SHA1 앞 12자

제외된 컬럼: 최종연차보고년도, 취소/취하구분, 취소/취하일자, 문서번호, 연계심사문서번호 — API 미제공(§8.0). 연차보고는 대체 신호가 없어 실질적 손실로 남는다(00b §6.4, 부록 B 확인 필요).

링크: A 열 등록번호 → 스냅샷·로그 의 해당 등록번호 이력(수신).

정정 전 원안 — API 미제공 컬럼(최종연차보고년도, 취소/취하구분, 문서번호 등)을 원본 컬럼으로 전제했던 구 컬럼표
컬럼명 정렬 표시형식 조건부서식 / 수식
A 등록번호 24 왼쪽 @ 중복값 강조 {'type':'duplicate'} (동일번호 재공고 탐지)
B 성분명 30 왼쪽 @ 워치 매칭 시 #F7E9F0
C 신청인_표시 26 왼쪽 @ 워치 매칭 시 #F7E9F0
D 제조소명 26 왼쪽 @
E 제조소소재지 40 왼쪽 @ 헤더에 주석(툴팁)
F 제조국가 12 가운데 @ text containing '대한민국'#E3F3EA
G 대상의약품 12 가운데 @
H 등록구분 12 가운데 @ 상태색(§6.6)
I 등록수리일자 12 가운데 yyyy-mm-dd 데이터바 #0072B2
J 최초등록일자 12 가운데 yyyy-mm-dd
K 최종변경일자 12 가운데 yyyy-mm-dd 오늘 = #FDF0DC (전일 대비 diff 강조)
L 최종연차보고년도 12 가운데 0000 3년 이상 경과 → #EFEFEF
M 취소/취하구분 10 가운데 @ 정상 아니면 행 전체 #FBE5DC + 취소선
N 취소/취하일자 12 가운데 yyyy-mm-dd
O 문서번호 16 왼쪽 @
P 연계심사문서번호 18 왼쪽 @ 값 있으면 #E5F1F8
Q 성분일련번호 8 가운데 0 히든(파생)
R 시행군 6 가운데 @ 히든(파생)
S 접수일련번호 8 가운데 0 히든(파생)
T 성분내일련번호 8 가운데 0 히든(파생)
U 허여서순번 10 가운데 @ 히든(파생)
V 업체키 26 왼쪽 @ 히든(정규화 키)
W 성분키 26 왼쪽 @ 히든(정규화 키)
X 최초관측일 12 가운데 yyyy-mm-dd 크롤러가 처음 본 날
Y 최종관측일 12 가운데 yyyy-mm-dd
Z 행해시 16 왼쪽 @ 히든. diff 판정용 SHA1 앞 12자

이 원안은 H(등록구분), L(최종연차보고년도), M(취소/취하구분), N(취소/취하일자), O(문서번호), P(연계심사문서번호)를 nedrug CCBAC03 스크래핑이 있어야만 채울 수 있다고 전제했다. §8.0 의 정정으로 등록구분·취소/취하구분·취소/취하일자는 API 파생 필드로 대체 가능함이 확인됐고, 나머지 3개는 실질적 손실로 §8.0 표에 남는다.

8.5 시트 (4): 성분별 — 탭색 #009E73

  • 역할: 성분(원료명) 기준 정적 집계표(피벗 대체). 정렬: 오늘 합계 내림차순 → 누적 내림차순.
  • add_table() + total_row: True.
컬럼명 정렬 표시형식 조건부서식 / 수식
A 순위 6 가운데 0 total_string: '합계'
B 성분명 34 왼쪽 @ 워치 매칭 시 #F7E9F0 + bold
C 오늘 신규 9 가운데 #,##0;;- >0#E3F3EA, total_function:'sum'
D 오늘 변경 9 가운데 #,##0;;- >0#FDF0DC, total_function:'sum'
E 오늘 취하 9 가운데 #,##0;;- >0#FBE5DC, total_function:'sum'
F 오늘 합계 9 가운데 #,##0;;- total_function:'sum'
G 최근 7일 9 가운데 #,##0;;- 데이터바 #0072B2
H 최근 30일 9 가운데 #,##0;;- 데이터바 #0072B2
I 누적 유효등록 11 가운데 #,##0 데이터바 #0072B2, total_function:'sum'
J 등록업체 수 9 가운데 #,##0 >=5 → bold (공급처 다변화 신호)
K 제조국 수 9 가운데 #,##0
L 주요 제조국 14 가운데 @ 최다 건수 국가
M 국산 보유 8 가운데 @ Y/N, Y#E3F3EA
N 30일 추이 14 가운데 스파크라인(column), 원천은 메타! 히든 영역
O 전일 대비 9 가운데 ▲ #,##0;▼ -#,##0; 0 icon_set 3_arrows_gray
P 상세 8 가운데 @ 내부 링크 → 전체 누적

8.6 시트 (5): 업체별 — 탭색 #009E73

구조는 성분별과 대칭.

컬럼명 정렬 표시형식 조건부서식 / 수식
A 순위 6 가운데 0 total_string: '합계'
B 신청인_표시 30 왼쪽 @ 워치 매칭 시 #F7E9F0 + bold
C 신청인_전체 40 왼쪽 @ 히든
D 업체키 26 왼쪽 @ 히든
E 오늘 신규 9 가운데 #,##0;;- >0#E3F3EA
F 오늘 변경 9 가운데 #,##0;;- >0#FDF0DC
G 오늘 취하 9 가운데 #,##0;;- >0#FBE5DC
H 오늘 합계 9 가운데 #,##0;;- total_function:'sum'
I 최근 30일 9 가운데 #,##0;;- 데이터바
J 누적 유효등록 11 가운데 #,##0 데이터바, total_function:'sum'
K 보유 성분 수 9 가운데 #,##0
L 제조소 수 9 가운데 #,##0
M 주요 제조국 14 가운데 @
N 국산여부 8 가운데 @ 국산/해외, 국산#E3F3EA
O 최근 등록일 12 가운데 yyyy-mm-dd 90일 이상 경과 → #EFEFEF
P 30일 추이 14 가운데 스파크라인(column)
Q 워치리스트 8 가운데 @ /-, #F7E9F0
R 상세 8 가운데 @ 내부 링크 → 전체 누적

8.7 시트 (6): 워치리스트 — 탭색 #CC79A7

  • 역할: 사용자가 직접 편집하는 유일한 시트. 다음 실행 시 이 시트를 읽어 매칭한다.
  • A~G = 입력 영역(사용자 편집), I~O = 결과 영역(프로그램 생성, 시트 보호로 잠금).

입력 영역

컬럼명 정렬 표시형식 유효성 / 규칙
A 번호 6 가운데 0
B 대상유형 12 가운데 @ data_validation list: 성분명,신청인,제조소명,제조국가,등록번호
C 키워드 30 왼쪽 @ 필수. 빈 값이면 #FBE5DC
D 매칭방식 12 가운데 @ list: 부분일치,정확일치,정규식
E 우선순위 8 가운데 0 list: 1,2,3 (1 = 최상)
F 활성 6 가운데 @ list: Y,N
G 메모 26 왼쪽 @ 자유 입력

결과 영역

컬럼명 정렬 표시형식 조건부서식
I 오늘 매칭 9 가운데 #,##0;;- >0#F7E9F0 + bold
J 최근 7일 9 가운데 #,##0;;- 데이터바
K 최근 30일 9 가운데 #,##0;;- 데이터바
L 누적 매칭 9 가운데 #,##0
M 마지막 매칭일 12 가운데 yyyy-mm-dd 오늘이면 #F7E9F0
N 마지막 매칭 대상 34 왼쪽 @
O 상세 8 가운데 @ internal:'오늘 변경분'!A1

하단 매칭 상세 표 (A20 이하): 키워드 / 상태 / 등록번호 / 성분명 / 신청인 / 제조국가 / 최종변경일자 7컬럼.

입력 보존 규칙(구현 필수): 매일 새 xlsx 를 만들므로, 직전 파일의 워치리스트 시트 A:G 영역을 openpyxl 로 읽어 새 파일에 이월한다. 더 안전한 방식은 config/watchlist.csv 를 SSOT 로 두고 엑셀은 미러로 쓰는 것(§11.5).

8.8 시트 (7): 스냅샷·로그 — 탭색 #888888

  • 역할: append-only 감사 로그 + 원본 스냅샷 지문. regulatory intelligence 의 "tracker/repository"(§4.2).
  • 정렬: 수집시각 내림차순. freeze_panes(2, 1).
컬럼명 정렬 표시형식 조건부서식 / 비고
A 수집시각 18 가운데 yyyy-mm-dd hh:mm:ss
B 실행ID 14 왼쪽 @ UUID 앞 8자
C 소스 16 가운데 @ nedrug / openapi
D 소스URL 40 왼쪽 @ 외부 하이퍼링크
E 이벤트 12 가운데 @ 신규/변경/취하/재공고/무변동
F 등록번호 24 왼쪽 @ 내부 링크 → 전체 누적
G 필드 20 왼쪽 @ 변경된 필드명
H 이전값 34 왼쪽 @ 9pt
I 이후값 34 왼쪽 @ 9pt, #FDF0DC
J 행해시_이전 14 왼쪽 @ 히든
K 행해시_이후 14 왼쪽 @ 히든
L 스냅샷파일 30 왼쪽 @ 로컬 경로
M 스냅샷SHA256 20 왼쪽 @ 앞 16자
N HTTP상태 8 가운데 0 !=200#FBE5DC
O 소요(ms) 9 가운데 #,##0 >10000#FDF0DC
P 에이전트 14 가운데 @ agy -p / requests
Q 비고 30 왼쪽 @ 에러 메시지 원문 그대로 보존

보존 정책: 시트 행이 50,000 을 넘으면 logs/archive/YYYYMM.parquet 로 롤오프하고 시트에는 최근 90일만 남긴다.

8.9 시트 (8): 메타 — 탭색 #888888

블록 1 — 수집 메타 (A2:D14)

항목 값 예 표시형식
리포트 기준일 2026-09-02 yyyy-mm-dd
수집 시작 시각 2026-09-02 06:00:03 yyyy-mm-dd hh:mm:ss
수집 종료 시각 2026-09-02 06:04:41 yyyy-mm-dd hh:mm:ss
공고 반영 기준일 2026-09-01 yyyy-mm-dd
실행 ID a1b2c3d4 @
실행 호스트 DESKTOP-XXXX @
실행 방식 Task Scheduler / agy -p @
파이썬 버전 3.12.x @
xlsxwriter 3.2.1 @
openpyxl 3.1.5 @
pandas 2.2.3 @
직전 스냅샷 snapshots/20260901.json @
직전 리포트 out/DMF_Daily_20260901.xlsx @ (외부 링크)

블록 2 — 소스 목록 (A17:E24): 소스명 / URL / HTTP상태 / 수집행수 / 비고. 최소 3행(nedrug 공고 CCBAC03, nedrug 게시판 bbs/117, OpenAPI getMdcDmfList01).

블록 3 — 건수 검증 (A27:E36)

검증 항목 기대 실제 판정 규칙
nedrug 총 행수 전일 총계 + diff 실측 OK/불일치 항등식
OpenAPI totalCount nedrug 총 행수 ± 허용치 실측 교차검증
신규+변경+취하 = 오늘 총 변경분 항등식
등록번호 중복 0
등록번호 형식 불일치 0 번호형식정상 = False 개수
필수 컬럼 결측 0
제조국가 미매핑 0 코드북에 없는 국가명
워치리스트 유효 항목 활성=Y 개수

판정 셀 조건부서식: text containing 'OK'#E3F3EA/#005A32; '불일치'#FBE5DC/#8A1D00.

블록 4 — 코드북 (A39:C80): 구분 / 코드 / 설명. 상태 7종, 시행군 알파벳 AK(§5.3 표 그대로), DMF Type IV(참고), 제조국 표기 정규화 매핑.

히든 데이터 영역 (AA1:BZ400): 스파크라인·차트가 참조하는 원천 시계열. 컬럼 폭 0 + {'hidden': 1}.

  • AA: 날짜(최근 30일)
  • AB: 일별 신규 / AC: 일별 변경 / AD: 일별 취하 / AE: 일별 합계 / AF: 워치 히트(1/0, win_loss 용)
  • AH:AI: 제조국 Top 8 (국가명, 건수)
  • AK:BZ: 성분별·업체별 30일 시계열(행 = 항목, 열 = 일자)

8.10 시트 탭색 · 링크 방향 요약

# 시트명 탭색 링크 발신 → 링크 수신 ←
1 대시보드 #0072B2 전 시트(7개) 모든 데이터 시트 A1← 대시보드
2 오늘 변경분 #D55E00 전체 누적(등록번호), 외부 공고 URL 대시보드, 워치리스트
3 전체 누적 #5B6770 스냅샷·로그(등록번호 이력) 대시보드, 오늘 변경분, 성분별, 업체별
4 성분별 #009E73 전체 누적 대시보드
5 업체별 #009E73 전체 누적 대시보드
6 워치리스트 #CC79A7 오늘 변경분 대시보드
7 스냅샷·로그 #888888 대시보드, 전체 누적
8 메타 #888888 외부 소스 URL 대시보드

내부 링크 문법 (XlsxWriter 문서 실측)

worksheet.write_url("A1", "internal:Sheet2!A1")
worksheet.write_url("A3", "internal:'Sales Data'!A1")   # 시트명에 공백 → 홑따옴표
worksheet.write_url("A1", "internal:Sheet2!A1", string="Link to sheet2", tip="Click here")

본 프로젝트 시트명에는 공백이 있으므로(오늘 변경분, 전체 누적, 스냅샷·로그) 반드시 홑따옴표로 감싼다: "internal:'오늘 변경분'!A1". 관련 이슈: XlsxWriter #609 — write_url when internally referencing a sheet (with spaces), #350 — write_url produces broken hyperlink, #511 — 여러 URL 포함 셀.


9. 대시보드 레이아웃 텍스트 와이어프레임

9.1 열 폭 / 행 높이 그리드

열 폭 (set_column, 단위 = 문자폭)

역할
A 1.5 좌 여백
B, C 13, 13 KPI 타일 1 / 좌측 차트 / Top 성분 표
D 1.5 거터
E, F 13, 13 KPI 타일 2
G 1.5 거터
H, I 13, 13 KPI 타일 3 / Top 업체 표
J 1.5 거터
K, L 13, 13 KPI 타일 4 / 우측 차트
M 1.5 거터
N, O 13, 13 KPI 타일 5 / 워치 표
P 1.5 거터
Q, R 13, 13 KPI 타일 6
S 1.5 우 여백

총 폭 ≈ 1.5 + (13×12) + (1.5×6) + 1.5 = 168 문자폭 ≈ 1,270 px. 줌 90% 적용 시 ≈ 1,143 px → 1366×768 노트북에서도 가로 스크롤 없음.

행 높이 (set_row)

높이 역할
1 6 상단 여백
2 30 제목
3 18 헤드라인 문장
4 8 여백
5 18 KPI 라벨
6 34 KPI 값 (28pt)
7 16 KPI 델타
8 14 KPI 스파크라인
9 10 여백
1024 20 (×15) 차트 영역 (총 300 px)
25 10 여백
26 18 표 헤더
2740 16 (×14) 표 본문(10행 + 여유)
41 10 여백
42 20 내비게이션
4347 8 여백
48 14 출처·면책

총 높이 ≈ 6+30+18+8+18+34+16+14+10+300+10+18+224+10+20+40+14 = 790 px. 줌 90% ≈ 711 px → 한 화면(768px)에 거의 맞음. 세로 스크롤 최소.

9.2 텍스트 와이어프레임

     A   B         C         D  E         F         G  H         I         J  K         L         M  N         O         P  Q         R         S
  ┌───┬───────────────────┬──┬───────────────────┬──┬───────────────────┬──┬───────────────────┬──┬───────────────────┬──┬───────────────────┬───┐
 1│                                                        (상단 여백 h=6)                                                                       │
 2│   │ B2:R2  ▌DMF 일일 모니터링 리포트 — 2026-09-02(화)                                                    18pt bold #1F2933              │   │
 3│   │ B3:R3  오늘 신규 12건 · 변경 3건 · 취하 1건 — 워치리스트 2건 적중(피타바스타틴칼슘, 다파글리플로진)   11pt #5B6770                    │   │
 4│                                                        (여백 h=8)                                                                            │
 5│   │ B5:C5             │  │ E5:F5             │  │ H5:I5             │  │ K5:L5             │  │ N5:O5             │  │ Q5:R5             │   │
   │   │   오늘 신규       │  │   오늘 변경       │  │   오늘 취하       │  │  오늘 총 변경분   │  │  워치리스트 히트  │  │  전체 누적 등록   │   │
 6│   │ B6:C6   ┏━━━━━┓   │  │ E6:F6             │  │ H6:I6             │  │ K6:L6             │  │ N6:O6             │  │ Q6:R6             │   │
   │   │           12      │  │          3        │  │          1        │  │         16        │  │          2        │  │       9,084       │   │
   │   │  28pt #009E73     │  │  28pt #E69F00     │  │  28pt #D55E00     │  │  28pt #0072B2     │  │  28pt #CC79A7     │  │  28pt #1F2933     │   │
 7│   │ B7:C7  ▲ 5        │  │ E7:F7  ▼ -2       │  │ H7:I7   0        │  │ K7:L7  ▲ 3        │  │ N7:O7  ▲ 2        │  │ Q7:R7  ▲ 11       │   │
 8│   │ B8:C8 ▁▂▅▁▃█▂▁    │  │ E8:F8 ▁▁▂▁▁▃▁▁    │  │ H8:I8 ▁▁▁▁█▁▁▁    │  │ K8:L8 ▂▃▅▂▄█▃▂    │  │ N8:O8 ▁▁█▁▁█▁▁    │  │ Q8:R8     │   │
   │   │ sparkline column  │  │ sparkline column  │  │ sparkline column  │  │ sparkline column  │  │ sparkline win_loss│  │ sparkline line    │   │
 9│                                                        (여백 h=10)                                                                           │
10│   ┌──────────────────────────────────────────────────────────┐   ┌──────────────────────────────────────────────────────────┐              │
  │   │ B10:I24                                                  │   │ K10:R24                                                  │              │
  │   │  [차트1] 최근 30일 일별 등록 동향 (누적 세로 막대)         │   │  [차트2] 제조국 Top 8 (가로 막대, 단색 #0072B2)           │              │
  │   │  x=일자(30), y=건수                                       │   │  x=건수, y=국가명 (직접 라벨링, 색 구분 없음)             │              │
  │   │  series: 신규 #009E73 / 변경 #E69F00 / 취하 #D55E00        │   │  인도 ████████████████ 3,351                              │              │
24│   │  insert_chart('B10', {'x_scale':1.65,'y_scale':1.05})     │   │  중국 ███████████ 2,231  대한민국 ████ 845 …               │              │
  │   └──────────────────────────────────────────────────────────┘   └──────────────────────────────────────────────────────────┘              │
25│                                                        (여백 h=10)                                                                           │
26│   │ B26:F26  Top 10 성분              │  │ H26:L26  Top 10 업체              │  │ N26:R26  워치리스트 히트                 │                │
  │   │ 순위│성분명    │오늘│30일│누적   │  │ 순위│신청인   │오늘│30일│누적    │  │ 키워드│매칭│성분명│신청인│상태          │                │
27│   │  1  │히알루론…│ 3 │ 11 │  104   │  │  1  │(주)삼오제약│ 4 │ 15 │  392  │  │ 다파글…│ 1 │…  │…    │ 신규         │                │
40│   │ … (10행, 데이터바 #0072B2)        │  │ … (10행)                          │  │ … (최대 10행)                            │                │
41│                                                        (여백 h=10)                                                                           │
42│   │ B42 [오늘 변경분] │ E42 [전체 누적] │ H42 [성분별] │ K42 [업체별] │ N42 [워치리스트] │ Q42 [스냅샷·로그 / 메타]                      │
43│                                                        (여백 h=8 ×5)                                                                         │
48│   │ B48:R48  출처: 의약품안전나라 DMF 공고(nedrug.mfds.go.kr/pbp/CCBAC03) · 공공데이터포털 OpenAPI(15057075) │ 수집 2026-09-02 06:00 KST   │
  └───┴───────────────────┴──┴───────────────────┴──┴───────────────────┴──┴───────────────────┴──┴───────────────────┴──┴───────────────────┴───┘

예시 수치 출처: 전체 누적 등록(9,084), 제조국 Top 표(인도 3,351 · 중국 2,231 · 대한민국 845), Top 10 성분 1위(히알루론산나트륨, 누적 104), Top 10 업체 1위((주)삼오제약, 누적 392)는 design/00b-baseline-data-analysis.md §7 실측값이다. 오늘 신규/변경/취하 등 일별 델타는 diff 예시일 뿐 실측이 아니다(00b 는 단일 시점 스냅샷이라 일별 값을 제공하지 않는다). 전체 국가 분포는 00b §7.3 참조.

9.3 F-패턴 검증

5초 규칙 문서의 F-패턴 요구와 대조:

요구 본 레이아웃
좌상단 = KPI Zone (3~4개 핵심) B5:R8 최상단 가로 밴드. 좌측 3개(신규/변경/취하)가 핵심
중앙 = Trend Zone (라인 차트) B10:R24 차트 밴드. 좌측이 시계열
우하단 = Detail Zone (표) B26:R40 표 밴드, 우측이 워치리스트 상세
한 화면, 스크롤 없음 계산상 711 px @ 90%
최대 6~8 KPI 6개
색 3개 + 의미 고정 신규/변경/취하 3색 + 강조 #0072B2
범례 읽지 않아도 됨 차트2 는 단색 + 직접 라벨링, 차트1 은 KPI 타일 색과 동일 매핑

9.4 KPI 타일의 셀 단위 구현 (병합 없이)

도형(shape) 대신 테두리 + 배경 + 병합으로 카드를 만든다.

TILE_BG = "#F7F7F7"
BORDER  = "#D0D7DE"

def tile_formats(wb, accent):
    """KPI 타일 4행(라벨/값/델타/스파크라인)용 서식 세트."""
    common = {"font_name": "맑은 고딕", "bg_color": TILE_BG,
              "align": "center", "valign": "vcenter",
              "left": 1, "right": 1, "left_color": BORDER, "right_color": BORDER}
    label = wb.add_format({**common, "font_size": 10, "bold": True,
                           "font_color": "#5B6770",
                           "top": 5, "top_color": accent})     # 상단 굵은 액센트 선
    value = wb.add_format({**common, "font_size": 28, "bold": True,
                           "font_color": accent, "num_format": "#,##0"})
    delta = wb.add_format({**common, "font_size": 10, "bold": True,
                           "font_color": "#5B6770",
                           "num_format": "▲ #,##0;▼ -#,##0; 0"})
    spark = wb.add_format({**common, "bottom": 1, "bottom_color": BORDER})
    return label, value, delta, spark

TILES = [
    ("오늘 신규",       "B", "C", "#009E73"),
    ("오늘 변경",       "E", "F", "#E69F00"),
    ("오늘 취하",       "H", "I", "#D55E00"),
    ("오늘 총 변경분",  "K", "L", "#0072B2"),
    ("워치리스트 히트", "N", "O", "#CC79A7"),
    ("전체 누적 등록",  "Q", "R", "#1F2933"),
]

def write_tiles(wb, ws, values, deltas, spark_ranges):
    """values/deltas: 길이 6 리스트. spark_ranges: 길이 6 의 '메타'!$AB$2:$AB$31 형태."""
    for i, (label_text, c1, c2, accent) in enumerate(TILES):
        f_label, f_value, f_delta, f_spark = tile_formats(wb, accent)
        ws.merge_range(f"{c1}5:{c2}5", label_text, f_label)
        ws.merge_range(f"{c1}6:{c2}6", values[i], f_value)
        ws.merge_range(f"{c1}7:{c2}7", deltas[i], f_delta)
        ws.merge_range(f"{c1}8:{c2}8", "", f_spark)
        stype = "win_loss" if i == 4 else ("line" if i == 5 else "column")
        ws.add_sparkline(f"{c1}8", {
            "range": spark_ranges[i],
            "type": stype,
            "series_color": accent,
            "high_point": True,
            "last_point": True,
        })

add_sparkline() 옵션 전체(XlsxWriter 문서 실측): range, type(line 기본 / column / win_loss), markers, high_point, low_point, first_point, last_point, negative_points, axis, style(16, 20), series_color(hex), reverse, location(그룹 스파크라인용 셀 배열).

문서 예제(원문 그대로):

import xlsxwriter

workbook = xlsxwriter.Workbook("sparklines2.xlsx")
worksheet1 = workbook.add_worksheet()
worksheet2 = workbook.add_worksheet()

# Line sparkline with markers
worksheet1.add_sparkline("A6", {"range": "Sheet2!A1:J1", "markers": True})

# Column with custom color
worksheet1.add_sparkline(
    "A18", {"range": "Sheet2!A2:J2", "type": "column", "series_color": "#E965E0"}
)

# Grouped sparklines
worksheet1.add_sparkline(
    "A27",
    {
        "location": ["A27", "A28", "A29"],
        "range": ["Sheet2!A5:J5", "Sheet2!A6:J6", "Sheet2!A7:J7"],
        "markers": True,
    },
)

workbook.close()

스파크라인 데이터 포인트 수: 실무 권고는 1224개가 최적, 100개 이상은 피하라. 우리는 30개를 쓰므로 상한에 가깝다 — type: 'column' 으로 개별 값을 구분 가능하게 하고, 라인은 누적 KPI 에만 쓴다. (검색 #37)


10. 피벗테이블 문제와 대안

10.1 문제 — 파이썬에서 피벗테이블을 만들 수 없다

openpyxl 공식 문서 원문(WebFetch 확인, https://openpyxl.readthedocs.io/en/stable/pivot.html):

Creation: openpyxl cannot create new pivot tables. The docs state: "it is not intended that client code should be able to create pivot tables." Existing Pivot Tables: 읽기 지원 + 기존 피벗 편집 가능. "edit and manipulate existing pivot tables, eg. change their ranges or whether they should update automatically settings." Access Method: 관리 API 가 없으므로 워크시트의 _pivots 리스트를 직접 순회해야 한다.

예제 코드(문서 원문):

from openpyxl import load_workbook
wb = load_workbook("campaign.xlsx")
ws = wb["Results"]
pivot = ws._pivots[0]
pivot.cache.refreshOnLoad = True

XlsxWriter 도 마찬가지: 차트 기능은 있으나 피벗테이블을 처음부터 생성하는 기능은 없다(검색 #3).

근본 원인: 피벗테이블은 pivotCacheDefinition, pivotCacheRecords, pivotTableDefinition 3종 XML 파트와 relationship 을 모두 정합적으로 써야 하는데, 어느 라이브러리도 이 캐시를 생성하는 API 를 제공하지 않는다.

10.2 대안 3가지 비교

# 방식 장점 단점 채택
A pandas 집계 → 정적 표 + add_table() + add_chart() 의존성 최소, 크로스플랫폼, 파일 안정, Excel 미설치 PC 에서도 생성 가능, 자동필터·정렬·합계행 모두 제공 사용자가 축을 바꿔 드릴다운 불가 기본 채택
B 피벗이 미리 들어있는 템플릿 xlsx + openpyxl 로 데이터 시트만 교체 + pivot.cache.refreshOnLoad = True 진짜 피벗(사용자가 축 변경 가능), Excel 미설치 PC 에서도 생성 가능 템플릿을 수동으로 1회 제작·유지해야 함. 데이터 행 수가 템플릿 범위를 넘으면 범위 재설정 필요 🔶 2단계 옵션
C pywin32 / xlwings 로 Excel COM 을 띄워 실제 PivotTable 생성 완전 자유 Excel 설치 필수, 06:00 무인 실행에서 COM 이 자주 실패, 좀비 EXCEL.EXE 누수, 세션 0 문제 비채택 (무인 스케줄러 환경과 상극)

10.3 방식 A 구현 — 정적 피벗 (채택안)

import pandas as pd

def build_ingredient_pivot(df_all: pd.DataFrame, df_today: pd.DataFrame,
                           df_30d: pd.DataFrame) -> pd.DataFrame:
    """성분별 시트용 집계표. pandas 로 '피벗'을 만들고 정적 표로 내보낸다."""
    today = (df_today
             .pivot_table(index="성분명", columns="상태", values="등록번호",
                          aggfunc="count", fill_value=0))
    for col in ("신규", "변경", "취하"):
        if col not in today.columns:
            today[col] = 0
    today = today[["신규", "변경", "취하"]]
    today.columns = ["오늘 신규", "오늘 변경", "오늘 취하"]
    today["오늘 합계"] = today.sum(axis=1)

    d30 = df_30d.groupby("성분명")["등록번호"].count().rename("최근 30일")
    d7 = (df_30d[df_30d["일자"] >= df_30d["일자"].max() - pd.Timedelta(days=6)]
          .groupby("성분명")["등록번호"].count().rename("최근 7일"))

    # 정정(§8.0): API 는 애초에 정상 건만 반환하므로 별도 '취소/취하구분' 필터가 필요 없다.
    cum = (df_all
           .groupby("성분명")
           .agg(**{"누적 유효등록": ("등록번호", "count"),
                   "등록업체 수":   ("정규화업체키", "nunique"),
                   "제조국 수":     ("제조국가명", "nunique")}))

    main_country = (df_all.groupby(["성분명", "제조국가"])["등록번호"].count()
                    .reset_index()
                    .sort_values("등록번호", ascending=False)
                    .drop_duplicates("성분명")
                    .set_index("성분명")["제조국가"].rename("주요 제조국"))

    has_kr = (df_all.assign(_kr=df_all["제조국가"].isin(["대한민국", "한국", "국내"]))
              .groupby("성분명")["_kr"].any()
              .map({True: "Y", False: "N"}).rename("국산 보유"))

    out = (cum.join([today, d7, d30, main_country, has_kr], how="left")
              .fillna({"오늘 신규": 0, "오늘 변경": 0, "오늘 취하": 0,
                       "오늘 합계": 0, "최근 7일": 0, "최근 30일": 0,
                       "주요 제조국": "-", "국산 보유": "N"})
              .astype({"오늘 신규": int, "오늘 변경": int, "오늘 취하": int,
                       "오늘 합계": int, "최근 7일": int, "최근 30일": int})
              .sort_values(["오늘 합계", "누적 유효등록"], ascending=False)
              .reset_index())
    out.insert(0, "순위", range(1, len(out) + 1))
    return out[["순위", "성분명", "오늘 신규", "오늘 변경", "오늘 취하", "오늘 합계",
                "최근 7일", "최근 30일", "누적 유효등록", "등록업체 수", "제조국 수",
                "주요 제조국", "국산 보유"]]

pandas → XlsxWriter 표 변환(문서 원문 패턴):

# 헤더 없이 1행 아래부터 쓰고, add_table 로 헤더를 만든다
df.to_excel(writer, sheet_name="성분별", startrow=2, header=False, index=False)

column_settings = [{"header": c} for c in df.columns]
(max_row, max_col) = df.shape
worksheet.add_table(1, 0, max_row + 1, max_col - 1, {
    "columns": column_settings,
    "style": "Table Style Light 11",
    "name": "TblIngredient",
})

10.4 방식 B 구현 — 템플릿 피벗 + 캐시 갱신 (2단계 옵션)

  1. 사람이 Excel 로 templates/dmf_pivot_template.xlsx 를 1회 제작:
    • RawData 시트에 헤더만 있는 테이블(Table1) 을 만들고,
    • 성분피벗·업체피벗 시트에 Table1 을 소스로 하는 피벗테이블을 배치.
  2. 파이썬은 매일 RawData 시트를 채우고 테이블 ref 를 늘린 뒤 캐시를 갱신 표시:
from openpyxl import load_workbook
from openpyxl.utils import get_column_letter

def refresh_template_pivots(template_path, out_path, df):
    wb = load_workbook(template_path)
    ws = wb["RawData"]

    # 기존 데이터 영역 비우기(헤더 1행 유지)
    if ws.max_row > 1:
        ws.delete_rows(2, ws.max_row - 1)
    for row in df.itertuples(index=False):
        ws.append(list(row))

    # 테이블 ref 확장
    last = f"{get_column_letter(df.shape[1])}{df.shape[0] + 1}"
    for tbl in ws.tables.values():
        tbl.ref = f"A1:{last}"

    # 모든 피벗을 열 때 새로고침하도록 표시 (openpyxl 공식 예제 패턴)
    for sheet in wb.worksheets:
        for pivot in getattr(sheet, "_pivots", []):
            pivot.cache.refreshOnLoad = True

    wb.save(out_path)

한계: 피벗 자체는 파이썬이 계산하지 않으므로, 사용자가 파일을 Excel 로 열기 전까지 피벗 셀은 이전 값이다. 따라서 대시보드의 KPI·차트는 절대 피벗에 의존시키지 않는다(방식 A 로 계산된 정적 값만 사용).

10.5 차트로 피벗을 대체하기 (XlsxWriter)

pandas 데이터프레임 범위를 직접 차트 시리즈로 지정한다(문서 원문 패턴):

chart = workbook.add_chart({"type": "column"})
(max_row, max_col) = df.shape
chart.add_series({"values": ["Sheet1", 1, 1, max_row, 1]})
worksheet.insert_chart(1, 3, chart)

시리즈 참조는 두 방식 모두 가능:

  • 문자열: "categories": "=Sheet1!$A$2:$A$7", "values": "=Sheet1!$B$2:$B$7"
  • 배열: ["Sheet1", first_row, first_col, last_row, last_col] (0-based)

완전한 컬럼 차트 예제(XlsxWriter 문서 원문):

import xlsxwriter

workbook = xlsxwriter.Workbook("chart_column.xlsx")
worksheet = workbook.add_worksheet()
bold = workbook.add_format({"bold": 1})

headings = ["Number", "Batch 1", "Batch 2"]
data = [
    [2, 3, 4, 5, 6, 7],
    [10, 40, 50, 20, 10, 50],
    [30, 60, 70, 50, 40, 30],
]

worksheet.write_row("A1", headings, bold)
worksheet.write_column("A2", data[0])
worksheet.write_column("B2", data[1])
worksheet.write_column("C2", data[2])

# Default column chart
chart1 = workbook.add_chart({"type": "column"})
chart1.add_series({
    "name": "=Sheet1!$B$1",
    "categories": "=Sheet1!$A$2:$A$7",
    "values": "=Sheet1!$B$2:$B$7",
})
chart1.add_series({
    "name": ["Sheet1", 0, 2],
    "categories": ["Sheet1", 1, 0, 6, 0],
    "values": ["Sheet1", 1, 2, 6, 2],
})
chart1.set_title({"name": "Results of sample analysis"})
chart1.set_x_axis({"name": "Test number"})
chart1.set_y_axis({"name": "Sample length (mm)"})
chart1.set_style(11)
worksheet.insert_chart("D2", chart1, {"x_offset": 25, "y_offset": 10})

# Stacked column chart
chart2 = workbook.add_chart({"type": "column", "subtype": "stacked"})
chart2.add_series({
    "name": "=Sheet1!$B$1",
    "categories": "=Sheet1!$A$2:$A$7",
    "values": "=Sheet1!$B$2:$B$7",
})
chart2.add_series({
    "name": "=Sheet1!$C$1",
    "categories": "=Sheet1!$A$2:$A$7",
    "values": "=Sheet1!$C$2:$C$7",
})
chart2.set_title({"name": "Stacked Chart"})
chart2.set_x_axis({"name": "Test number"})
chart2.set_y_axis({"name": "Sample length (mm)"})
chart2.set_style(12)
worksheet.insert_chart("D18", chart2, {"x_offset": 25, "y_offset": 10})

# Percentage stacked column chart
chart3 = workbook.add_chart({"type": "column", "subtype": "percent_stacked"})
chart3.add_series({
    "name": "=Sheet1!$B$1",
    "categories": "=Sheet1!$A$2:$A$7",
    "values": "=Sheet1!$B$2:$B$7",
})
chart3.add_series({
    "name": "=Sheet1!$C$1",
    "categories": "=Sheet1!$A$2:$A$7",
    "values": "=Sheet1!$C$2:$C$7",
})
chart3.set_title({"name": "Percent Stacked Chart"})
chart3.set_x_axis({"name": "Test number"})
chart3.set_y_axis({"name": "Sample length (mm)"})
chart3.set_style(13)
worksheet.insert_chart("D34", chart3, {"x_offset": 25, "y_offset": 10})

workbook.close()

크기 조정: chart.set_size({'x_scale': 2, 'y_scale': 2}) 또는 insert_chart(..., {'x_scale':…, 'y_scale':…}).

본 프로젝트 주의: set_style(11) 같은 내장 스타일은 엑셀 기본 팔레트를 강제하므로 쓰지 않는다. 대신 add_series 마다 {'fill': {'color': '#009E73'}} 로 §6 팔레트를 직접 지정한다.


11. 구현 레시피: 동작하는 코드

11.1 환경 (실측 확인)

xlsxwriter 3.2.1   openpyxl 3.1.5   pandas 2.2.3

PDF 파싱 보조 라이브러리도 확인됨: pypdf 4.3.1, pdfplumber, pymupdf(fitz).

11.2 워크북 뼈대

import pandas as pd
import xlsxwriter

SHEETS = [
    ("대시보드",     "#0072B2"),
    ("오늘 변경분",  "#D55E00"),
    ("전체 누적",    "#5B6770"),
    ("성분별",       "#009E73"),
    ("업체별",       "#009E73"),
    ("워치리스트",   "#CC79A7"),
    ("스냅샷·로그",  "#888888"),
    ("메타",         "#888888"),
]

def build_workbook(path, ctx):
    # constant_memory 는 절대 켜지 말 것 (add_table 불가)
    wb = xlsxwriter.Workbook(path, {"default_date_format": "yyyy-mm-dd",
                                    "constant_memory": False})
    wb.set_size(1600, 900)
    ws = {}
    for name, tab in SHEETS:
        ws[name] = wb.add_worksheet(name)
        ws[name].set_tab_color(tab)

    dash = ws["대시보드"]
    dash.hide_gridlines(2)      # 화면 + 인쇄 모두 숨김
    dash.set_zoom(90)
    dash.activate()             # 파일 열면 대시보드가 먼저 보이게
    dash.set_landscape()
    dash.fit_to_pages(1, 1)

    for name, _ in SHEETS[1:]:
        ws[name].freeze_panes(2, 2)
        ws[name].set_landscape()
        ws[name].fit_to_pages(1, 0)
        ws[name].repeat_rows(0, 1)
        # 역링크
        ws[name].write_url("A1", "internal:'대시보드'!A1",
                           string="← 대시보드", tip="대시보드로 이동")
    return wb, ws

메서드 시그니처 참조(XlsxWriter worksheet.html 실측):

메서드 시그니처
freeze_panes freeze_panes(row, col[, top_row, left_col]) worksheet.freeze_panes(1, 0)
autofilter autofilter(first_row, first_col, last_row, last_col) worksheet.autofilter('A1:D10')
set_column set_column(first_col, last_col, width[, cell_format, options]) worksheet.set_column('A:A', 20) / worksheet.set_column('D:D', 20, None, {'hidden': 1}) / worksheet.set_column('B:G', None, None, {'level': 1})
set_tab_color set_tab_color(color) worksheet.set_tab_color('#FF0000')
activate activate() worksheet3.activate()
hide_gridlines hide_gridlines([option]) worksheet.hide_gridlines()
set_zoom set_zoom(scale) worksheet.set_zoom(150)
set_row set_row(row, height[, cell_format, options]) worksheet.set_row(0, 20)
write_url write_url(row, col, url[, cell_format, string, tip]) worksheet.write_url('A1', 'internal:Sheet2!A1')
data_validation data_validation(first_row, first_col, last_row, last_col, options) worksheet.data_validation('B13', {'validate': 'list', 'source': ['open', 'high', 'close']})

11.3 pandas → 서식 있는 시트 (문서 원문 패턴)

import pandas as pd

df = pd.DataFrame({"Data": [10, 20, 30, 20, 15, 30, 45]})
writer = pd.ExcelWriter("pandas_simple.xlsx", engine="xlsxwriter")
df.to_excel(writer, sheet_name="Sheet1")

workbook  = writer.book
worksheet = writer.sheets["Sheet1"]

format1 = workbook.add_format({"num_format": "#,##0.00"})
format2 = workbook.add_format({"num_format": "0%"})
worksheet.set_column(1, 1, 18, format1)     # 폭 + 서식
worksheet.set_column(2, 2, None, format2)   # 서식만

to_excel()freeze_panes 파라미터를 지원한다: freeze_panes=(1, 1) = 첫 행 + 첫 열 고정. 자동필터는 ws.autofilter(0, 0, df.shape[0] - 1, df.shape[1] - 1).

11.4 전일 대비 diff — 스냅샷 비교

접근법 출처: matthewkudija.com — Excel Diff (WebFetch 확인). 원문은 셀 단위 A-->B 마킹 후 텍스트 조건부서식으로 강조하는 방식:

import pandas as pd

df_OLD = pd.read_excel(path_OLD).fillna(0)
df_NEW = pd.read_excel(path_NEW).fillna(0)

dfDiff = df_OLD.copy()
for row in range(dfDiff.shape[0]):
    for col in range(dfDiff.shape[1]):
        value_OLD = df_OLD.iloc[row,col]
        try:
            value_NEW = df_NEW.iloc[row,col]
            if value_OLD==value_NEW:
                dfDiff.iloc[row,col] = df_NEW.iloc[row,col]
            else:
                dfDiff.iloc[row,col] = ('{}-->{}').format(value_OLD,value_NEW)
        except:
            dfDiff.iloc[row,col] = ('{}-->{}').format(value_OLD, 'NaN')

강조 서식(원문):

writer = pd.ExcelWriter(fname, engine='xlsxwriter')
workbook = writer.book
worksheet = writer.sheets['DIFF']

highlight_fmt = workbook.add_format({'font_color': '#FF0000', 'bg_color':'#B1B3B3'})
grey_fmt = workbook.add_format({'font_color': '#E0E0E0'})

worksheet.conditional_format('A1:ZZ1000', {'type': 'text',
                                           'criteria': 'containing',
                                           'value':'→',
                                           'format': highlight_fmt})

2019 업데이트판은 위치 인덱싱 대신 df.loc[] + 인덱스 컬럼으로 행 추가/삭제를 추적하며, 변경=빨강 / 신규행=초록 / 삭제행=회색으로 색을 나눈다.

본 프로젝트 변형 (중요): 위 원문은 ① O(rows×cols) 파이썬 루프라 수만 행에서 느리고, ② red/green 조합이라 CUD 위반, ③ 셀 안에 A-->B 를 넣으면 정렬·필터가 깨진다. → 키 기반 머지 + 행 해시로 바꾸고, 변경 내용은 별도 변경내용 컬럼에 담는다.

정정(§8.0 반영): 아래 DIFF_FIELDS 는 원래 nedrug 원본 13필드로 짜여 있었다. API 7필드 설계에서는 이 중 실제로 diff 가능한 필드만 남는다 — 발급일자를 반드시 포함해야 한다(내용이 같아도 발급일자만 갱신되는 재확인 건을 "변경"으로 잡기 위해, 00b §6).

import hashlib
import pandas as pd

DIFF_FIELDS = ["성분명", "업체명", "제조소명", "제조소소재지", "제조국가명", "발급일자"]

def row_hash(row) -> str:
    raw = "\x1f".join(str(row.get(f, "")) for f in DIFF_FIELDS)
    return hashlib.sha1(raw.encode("utf-8")).hexdigest()[:12]

def diff_snapshots(prev: pd.DataFrame, curr: pd.DataFrame) -> pd.DataFrame:
    """등록번호를 키로 신규/변경/취하를 판정하고 변경 상세를 문자열로 만든다."""
    prev = prev.set_index("등록번호", drop=False)
    curr = curr.set_index("등록번호", drop=False)
    prev["행해시"] = prev.apply(row_hash, axis=1)
    curr["행해시"] = curr.apply(row_hash, axis=1)

    new_keys     = curr.index.difference(prev.index)
    gone_keys    = prev.index.difference(curr.index)
    common_keys  = curr.index.intersection(prev.index)
    changed_keys = [k for k in common_keys
                    if curr.at[k, "행해시"] != prev.at[k, "행해시"]]

    rows = []
    for k in new_keys:
        r = curr.loc[k].to_dict()
        r.update({"상태": "신규", "기호": "", "정렬키": 2,
                  "변경필드": "-", "변경내용(before→after)": "-"})
        rows.append(r)

    for k in changed_keys:
        r = curr.loc[k].to_dict()
        fields, details = [], []
        for f in DIFF_FIELDS:
            a, b = prev.at[k, f], curr.at[k, f]
            if str(a) != str(b):
                fields.append(f)
                details.append(f"{f}: {a}{b}")
        # 정정(§8.0): 취하는 gone_keys(레코드 소멸)로만 판정한다.
        # API 에는 '취소/취하구분' 필드 자체가 없으므로 여기서 승격 판정을 하지 않는다.
        r.update({
            "상태": "변경",
            "기호": "◆",
            "정렬키": 3,
            "변경필드": ", ".join(fields),
            "변경내용(before→after)": " | ".join(details),
        })
        rows.append(r)

    for k in gone_keys:      # 목록에서 사라진 경우 = 삭제/취하 의심
        r = prev.loc[k].to_dict()
        r.update({"상태": "취하", "기호": "✕", "정렬키": 1,
                  "변경필드": "목록에서 제거",
                  "변경내용(before→after)": "이전 스냅샷에 있었으나 오늘 목록에 없음"})
        rows.append(r)

    out = pd.DataFrame(rows)
    if out.empty:
        return out
    return out.sort_values(["정렬키", "워치리스트히트", "성분명"],
                           ascending=[True, False, True],
                           na_position="last").reset_index(drop=True)

전체 누적 시트에서 "오늘 변경된 행"을 강조하는 조건부서식(전일 대비 diff 강조):

today_fmt = workbook.add_format({"bg_color": "#FDF0DC", "font_color": "#7A3E00"})
ws_all.conditional_format("A3:Z50000", {
    "type": "formula",
    "criteria": '=$I3=TODAY()',      # I = 발급일자(§8.4) — 최종 갱신일로 동작(00b §6)
    "format": today_fmt,
})

중복 등록번호(재공고) 탐지 — XlsxWriter duplicate 규칙(문서 원문):

worksheet.conditional_format('A1:F10', {'type':   'duplicate',
                                        'format': format1})

문자열 규칙(문서 원문). criteria 옵션은 'containing', 'not containing', 'begins with', 'ends with':

worksheet.conditional_format('A1:A4', {'type':     'text',
                                       'criteria': 'containing',
                                       'value':    'foo',
                                       'format':   format1})

수식 규칙(문서 원문):

worksheet.conditional_format('A1:A4', {'type':     'formula',
                                       'criteria': '=$A$1>5',
                                       'format':   format1})

cell 타입 예제(문서 실측 — 색상값 원문): 포맷 {"bg_color": "#FFC7CE", "font_color": "#9C0006"} / {"bg_color": "#C6EFCE", "font_color": "#006100"}, 범위 "B3:K12", 기준 ">="/"<" 50, "between"(30~70)/"not between", 비연속 범위는 "multi_range": "B3:K6 B9:K12". → 이 기본 색쌍은 red/green 이라 본 프로젝트에서는 §6.6 팔레트로 대체한다.

11.5 워치리스트 매칭

import re
import unicodedata

CORP_NOISE = re.compile(r"\(주\)|㈜|주식회사|\bco\.,?\s*ltd\.?|\binc\.?|\bltd\.?|\bcorp\.?|\s+")

def norm_key(s: str) -> str:
    s = unicodedata.normalize("NFKC", str(s or "")).lower()
    return CORP_NOISE.sub("", s)

def match_watchlist(df: pd.DataFrame, watch: pd.DataFrame) -> pd.DataFrame:
    """df 에 워치리스트히트/워치키워드 컬럼을 채워 반환."""
    field_map = {"성분명": "성분명", "신청인": "업체명",
                 "제조소명": "제조소명", "제조국가": "제조국가명",
                 "등록번호": "등록번호"}
    hits = [0] * len(df)
    kws  = [[] for _ in range(len(df))]

    active = watch[watch["활성"].astype(str).str.upper() == "Y"]
    for _, w in active.iterrows():
        col = field_map.get(str(w["대상유형"]).strip())
        if col is None or col not in df.columns:
            continue
        kw, mode = str(w["키워드"]).strip(), str(w["매칭방식"]).strip()
        if not kw:
            continue
        series = df[col].astype(str)
        if mode == "정확일치":
            mask = series.map(norm_key) == norm_key(kw)
        elif mode == "정규식":
            try:
                mask = series.str.contains(kw, regex=True, na=False)
            except re.error:
                continue                      # 잘못된 정규식은 조용히 건너뛰고 메타에 기록
        else:                                  # 부분일치(기본)
            nk = norm_key(kw)
            mask = series.map(norm_key).str.contains(re.escape(nk), na=False)
        for i in df.index[mask]:
            pos = df.index.get_loc(i)
            hits[pos] += 1
            kws[pos].append(kw)

    df = df.copy()
    df["워치리스트히트"] = hits
    df["워치키워드"] = [", ".join(k) if k else "" for k in kws]
    return df

워치리스트 이월 — 직전 xlsx 에서 사용자 입력 영역만 읽어온다:

from openpyxl import load_workbook

def carry_over_watchlist(prev_xlsx_path):
    """직전 리포트의 워치리스트 A:G 를 DataFrame 으로 회수. 없으면 빈 DF."""
    cols = ["번호", "대상유형", "키워드", "매칭방식", "우선순위", "활성", "메모"]
    try:
        wb = load_workbook(prev_xlsx_path, read_only=True, data_only=True)
        ws = wb["워치리스트"]
    except Exception:
        return pd.DataFrame(columns=cols)
    rows = []
    for r in ws.iter_rows(min_row=3, min_col=1, max_col=7, values_only=True):
        if r[2] in (None, ""):        # 키워드가 비면 종료
            continue
        rows.append(dict(zip(cols, r)))
    return pd.DataFrame(rows, columns=cols)

데이터 유효성 검사(드롭다운):

ws_watch.data_validation(2, 1, 500, 1,
    {"validate": "list", "source": ["성분명", "신청인", "제조소명", "제조국가", "등록번호"]})
ws_watch.data_validation(2, 3, 500, 3,
    {"validate": "list", "source": ["부분일치", "정확일치", "정규식"]})
ws_watch.data_validation(2, 4, 500, 4,
    {"validate": "list", "source": [1, 2, 3]})
ws_watch.data_validation(2, 5, 500, 5,
    {"validate": "list", "source": ["Y", "N"]})

11.6 대시보드 차트 2종 (팔레트 고정)

STATUS_COLORS = {"신규": "#009E73", "변경": "#E69F00", "취하": "#D55E00"}

def add_trend_chart(wb, dash, meta_sheet="메타", n=30):
    ch = wb.add_chart({"type": "column", "subtype": "stacked"})
    for i, (label, color) in enumerate(STATUS_COLORS.items()):
        col = 27 + i                       # AB=27, AC=28, AD=29 (0-based)
        ch.add_series({
            "name":       label,
            "categories": [meta_sheet, 1, 26, n, 26],      # AA2:AA31 = 날짜
            "values":     [meta_sheet, 1, col, n, col],
            "fill":       {"color": color},
            "border":     {"none": True},
            "gap":        40,
        })
    ch.set_title({"name": "최근 30일 일별 등록 동향",
                  "name_font": {"name": "맑은 고딕", "size": 11, "bold": True,
                                "color": "#1F2933"}})
    ch.set_x_axis({"num_font": {"name": "맑은 고딕", "size": 8, "color": "#5B6770"},
                   "line": {"color": "#D0D7DE"}, "major_tick_mark": "none"})
    ch.set_y_axis({"num_font": {"name": "맑은 고딕", "size": 8, "color": "#5B6770"},
                   "major_gridlines": {"visible": True,
                                       "line": {"color": "#EFEFEF", "width": 0.75}},
                   "line": {"none": True}, "major_tick_mark": "none"})
    ch.set_legend({"position": "top", "font": {"name": "맑은 고딕", "size": 9}})
    ch.set_chartarea({"border": {"color": "#D0D7DE"}, "fill": {"color": "#FFFFFF"}})
    ch.set_plotarea({"border": {"none": True}, "fill": {"none": True}})
    dash.insert_chart("B10", ch, {"x_scale": 1.65, "y_scale": 1.05})

def add_country_chart(wb, dash, meta_sheet="메타", n=8):
    ch = wb.add_chart({"type": "bar"})
    ch.add_series({
        "name":       "건수",
        "categories": [meta_sheet, 1, 33, n, 33],   # AH2:AH9 = 국가명
        "values":     [meta_sheet, 1, 34, n, 34],   # AI2:AI9 = 건수
        "fill":       {"color": "#0072B2"},         # 단색: 카테고리 8개는 색 구분 금지
        "border":     {"none": True},
        "data_labels": {"value": True, "position": "outside_end",
                        "font": {"name": "맑은 고딕", "size": 8, "color": "#1F2933"}},
        "gap":        50,
    })
    ch.set_title({"name": "제조국 Top 8 (최근 30일)",
                  "name_font": {"name": "맑은 고딕", "size": 11, "bold": True,
                                "color": "#1F2933"}})
    ch.set_x_axis({"visible": False})               # 데이터 라벨이 있으므로 축 제거(데이터 잉크)
    ch.set_y_axis({"num_font": {"name": "맑은 고딕", "size": 9, "color": "#1F2933"},
                   "line": {"color": "#D0D7DE"}, "major_tick_mark": "none",
                   "reverse": True})               # 1위가 위로
    ch.set_legend({"none": True})
    ch.set_chartarea({"border": {"color": "#D0D7DE"}, "fill": {"color": "#FFFFFF"}})
    ch.set_plotarea({"border": {"none": True}, "fill": {"none": True}})
    dash.insert_chart("K10", ch, {"x_scale": 1.65, "y_scale": 1.05})

11.7 openpyxl 조건부서식 (템플릿 경로를 쓸 때만)

openpyxl 문서 원문 예제:

# FormulaRule
ws.conditional_formatting.add('E1:E10',
    FormulaRule(formula=['ISBLANK(E1)'], stopIfTrue=True, fill=redFill))

ws.conditional_formatting.add('E1:E10',
    FormulaRule(formula=['E1=0'], font=myFont, border=myBorder, fill=redFill))

# CellIsRule
ws.conditional_formatting.add('C2:C10',
    CellIsRule(operator='lessThan', formula=['C$1'], stopIfTrue=True, fill=redFill))

ws.conditional_formatting.add('D2:D10',
    CellIsRule(operator='between', formula=['1','5'], stopIfTrue=True, fill=redFill))

# ColorScaleRule
rule = ColorScaleRule(start_type='percentile', start_value=10, start_color='FFAA0000',
                      mid_type='percentile', mid_value=50, mid_color='FF0000AA',
                      end_type='percentile', end_value=90, end_color='FF00AA00')

# IconSetRule
rule = IconSetRule('5Arrows', 'percent', [10, 20, 30, 40, 50],
                   showValue=None, percent=None, reverse=None)

# DataBarRule
rule = DataBarRule(start_type='percentile', start_value=10,
                   end_type='percentile', end_value='90',
                   color="FF638EC6", showValue="None", minLength=None, maxLength=None)

# 문자열 규칙 (containsText)
rule = Rule(type="containsText", operator="containsText", text="highlight", dxf=dxf)
rule.formula = ['NOT(ISERROR(SEARCH("highlight",A1)))']

openpyxl Table + TableStyleInfo 예제(문서 원문):

from openpyxl import Workbook
from openpyxl.worksheet.table import Table, TableStyleInfo

wb = Workbook()
ws = wb.active

data = [
    ['Apples', 10000, 5000, 8000, 6000],
    ['Pears',   2000, 3000, 4000, 5000],
    ['Bananas', 6000, 6000, 6500, 6000],
    ['Oranges',  500,  300,  200,  700],
]

ws.append(["Fruit", "2011", "2012", "2013", "2014"])
for row in data:
    ws.append(row)

tab = Table(displayName="Table1", ref="A1:E5")
style = TableStyleInfo(name="TableStyleMedium9", showFirstColumn=False,
                       showLastColumn=False, showRowStripes=True, showColumnStripes=True)
tab.tableStyleInfo = style
ws.add_table(tab)
wb.save("table.xlsx")

openpyxl 제약(문서 원문):

  • "Table headings and column headings must always contain strings." (write-only 모드에서는 헤더 문자열이 셀 값과 정확히 일치해야 함)
  • "Filters will be added automatically to tables that contain header rows. It is not possible to create tables with header rows without filters."
  • ws.add_table() 를 써야 워크북 전체에서 이름 유일성이 보장된다.
  • 틀 고정: sheet.freeze_panes = 'A2'.

12. 한국 제약 실무자가 실제로 보고 싶어하는 것

12.1 근거 — 국내 매체가 DMF 를 다루는 방식

기사/자료 실제로 쓴 집계 축 강조한 숫자
데일리팜 — 규제완화 여파…원료약 등록 1년새 237→653건 연도별(반기별) 총 건수, 전년 동기 배수 653 / 256 / 545 / 537, 2.8배, 역대 최고
데일리팜 — 인도·중국산 등록비중 70% 돌파…외면받는 국산 원료약 제조국별 건수와 비중, 국산 비중, 연도별 추이 249건 중 233건 해외 / 인도 120·중국 60·이탈리아 10·일본 6 / 국산 16건(6.4%) / 인·중 72.3% / 인도 48.2% / 2018년 59.7% → 2023년 72.3% / 자급률 24.4%→11.9% / 인·중산 20~30% 저렴
데일리팜 — 필수약·공급중단 보고대상 의약품 'DMF 등록' 유예 제도 변화(유예) 국가필수의약품·공급중단 보고대상
데일리팜 — 의약품 해외제조소 등록 안하면 2차 위반시 허가취소 제조소 규제 연계 DMF 등록 원료도 해외제조소 등록 대상, 제조소 이전 시 신규 등록
의학신문 — 식약처, 원료의약품 등록 제도(DMF) 해설서 발간 제도 해설 연계심사 절차 반영
KHIDI 제약산업정보포털 — 등록대상 원료의약품(DMF) 등록 공고(7월 둘째주) 주간 공고 원문 재배포 첨부 표 컬럼: 등록번호 / 성분명 / 등록업체 / 제조원 / 제조국 / 등록구분(신규·변경·취하)

결론: 한국 실무자가 보는 축은 정확히 ① 총 건수와 전년 대비 ② 제조국(특히 인도·중국 vs 국산) ③ 등록구분(신규/변경/취하) ④ 성분 ⑤ 업체·제조원 다섯이다. 본 설계의 시트·차트가 이 다섯을 모두 커버한다.

12.2 규제 인텔리전스 뉴스레터가 담는 항목 (국제 실무)

검색 #34 종합: FDA 데이터셋 기대치 명확화, 라벨링 업데이트, 품목허가 신청(marketing authorization applications), 허가 후 변경(post-approval submissions), 품질·비임상·유효성·안전성 과학적 가이던스. 형식: "a daily synthesis of complex policy—helping you understand what's changing, why it matters, and what comes next."무엇이 바뀌었는가 / 왜 중요한가 / 다음에 무엇이 오는가 3단.

DMF 리포트에 적용: 오늘 변경분 시트의 변경내용(before→after) 컬럼이 "무엇이 바뀌었나"를, 워치리스트히트가 "왜 중요한가"를 담당한다. "다음에 무엇이 오는가"는 최종연차보고년도 3년 경과 경고와 취소/취하 예고로 대체한다.

12.3 실무자 체크리스트 → 시트 매핑

실무 질문 답을 주는 위치
"어제 오늘 사이에 뭐가 새로 등록됐나?" 대시보드 KPI 1 + 오늘 변경분 (상태=신규)
"우리가 쓰는 원료가 변경등록됐나?" 워치리스트 결과 영역 + 오늘 변경분 워치리스트히트
"경쟁사 ○○제약이 뭘 등록했나?" 업체별 시트 + 워치리스트(대상유형=신청인)
"이 성분 국내 공급처가 몇 곳인가?" 성분별 등록업체 수 / 제조국 수 / 국산 보유
"제조소가 중국에서 인도로 바뀌었나?" 오늘 변경분 변경필드제조국가·제조소명 포함된 행
"우리 원료 DMF 가 취하됐나?" (공급 리스크) 대시보드 KPI 3 + 오늘 변경분 (상태=취하, 취소선)
"이 등록번호 언제 등록됐고 몇 번 바뀌었나?" 전체 누적 5개 날짜 컬럼 → 스냅샷·로그 이력
"국산 원료 비중이 계속 떨어지나?" 대시보드 제조국 Top 8 + 업체별 국산여부
"연차보고 3년째 안 한 등록이 있나?" 전체 누적 최종연차보고년도 조건부서식
"이 숫자 믿어도 되나? 크롤링 실패는 없었나?" 메타 블록 3(건수 검증) + 스냅샷·로그 HTTP상태

12.4 한국 특유의 주의사항

  1. 성분명 표기 흔들림: 한글 성분명은 띄어쓰기·괄호·염 표기가 제각각(아토르바스타틴칼슘삼수화물 vs 아토르바스타틴 칼슘 삼수화물). → 성분키 정규화 컬럼 필수.
  2. 업체명 접두/접미: (주), , 주식회사, Co., Ltd., 영문/한글 혼용. → 업체키 정규화 필수(§11.5 norm_key).
  3. 제조국가 표기: 대한민국/한국/국내, 중국/중화인민공화국, 인도/India 혼용 가능. → 메타 코드북에 매핑표를 두고 미매핑 값은 검증 블록에 카운트.
  4. "사전등록(연계 등록 원료의약품)": 완제 허가와 연계된 별도 상태. 일반 신규와 혼동하면 KPI 가 부풀려진다. → 상태 도메인 7종을 분리 유지(§5.5).
  5. 연차보고는 "변경"이 아니다: 연차보고(변경보고)는 경미한 변경이므로 오늘 변경 KPI 에 넣지 않는다. 별도 상태로만 표시.
  6. 재공고: DMF 해설서 통계표에 ( )의 숫자는 재공고된 숫자를 의미함 이라는 주석이 있다. → 동일 등록번호가 다시 나타나는 경우를 재공고 이벤트로 스냅샷·로그에 기록하고, KPI 신규에서 제외.
  7. hwp/hwpx 첨부: 주간 공고 게시글의 첨부는 hwp 계열일 수 있다(원료의약품 등록에 관한 규정 최신판은 .hwpx). → 첨부 파싱은 2단계 과제로 남기고 1단계는 HTML 표만 쓴다.

부록 A. 출처 목록

범례 — 확인여부: ✅ FETCH = WebFetch 로 본문 확인 / ⚠️ 403 = 접근 거부 / ⚠️ 404 = 없음 / ⚠️ DNS = 도메인 해석 실패 / 🔍 검색 = 검색 결과 링크로만 등장(본문 미확인) / 📄 PDF = PDF 로 내려받아 텍스트 추출 성공

A.1 엑셀 대시보드 설계 원칙

제목 URL 확인여부
Dashboard design 5 seconds rule — Den Otter Solutions https://denottersolutions.com/en/data-insights/dashboard-design-5-seconds-rule/ FETCH
12 Best Practices in Dashboard Design — GoSkills https://www.goskills.com/excel/resources/best-practices-in-dashboard-design FETCH
How to Create a Dashboard in Excel: A Step-by-Step Tutorial — DataCamp https://www.datacamp.com/tutorial/create-excel-dashboards-tutorial FETCH
Excel Dashboard Templates: Free Downloads + How to Build One (2026) — DataHub Pro https://www.datahubpro.co.uk/tutorials/excel-dashboard-templates FETCH
from dashboard to story — storytelling with data https://www.storytellingwithdata.com/blog/from-dashboard-to-story FETCH
Dashboard Confusion (Stephen Few, 2004-03-20) — Perceptual Edge https://www.perceptualedge.com/articles/ie/dashboard_confusion.pdf 📄 PDF (4p, 10,305자 추출)
Build an Interactive Excel Dashboard with Pivot Tables — Excel Campus https://www.excelcampus.com/charts/excel-dahsboard-tutorial-full-buildout/ FETCH
Excel Dashboard Design Principles: Make the Answer Obvious — Big Excel Energy https://bigexcelenergy.com/excel-dashboard-design-principles/ ⚠️ 403
Excel Dashboards — Templates, Tutorials, Downloads and Examples — Chandoo.org https://chandoo.org/wp/excel-dashboards/ ⚠️ 403
Making a Simple KPI Dashboard using MS Excel — Chandoo.org https://chandoo.org/wp/2011/08/25/simple-kpi-dashboard-using-excel/ ⚠️ 403
kpi dashboards Archives — Chandoo.org https://chandoo.org/wp/tag/kpi-dashboards/ 🔍 검색
Dashboard Design Concepts — Chandoo.org Forums https://chandoo.org/forum/threads/dashboard-design-concepts.19536/ 🔍 검색
Dashboard Ideas — Chandoo.org Forums https://chandoo.org/forum/threads/dashboard-ideas.21929/ 🔍 검색
How to design the dashboard — Chandoo.org Forums https://chandoo.org/forum/threads/how-to-design-the-dashboard.12296/ 🔍 검색
What is the best Excel Background Design that WOWed you...? — Chandoo.org Forums https://chandoo.org/forum/threads/what-is-the-best-excel-background-design-that-wowed-you.29586/ 🔍 검색
Learn how to create these 11 amazing dashboards — Chandoo.org https://chandoo.org/wp/2014/02/14/dashboard-training-course/ 🔍 검색
18 Tips to Make you an Excel Formatting Pro — Chandoo.org http://chandoo.org/wp/2011/12/05/excel-formatting-tips/ 🔍 검색
How to create a fully interactive Project Dashboard with Excel — Chandoo.org https://chandoo.org/wp/interactive-project-dashboard-with-excel/ 🔍 검색
Introducing Excel Dashboard Templates from Chandoo.org https://chandoo.org/wp/introducing-excel-dashboard-templates/ 🔍 검색
How to highlight overdue items, coming up and completed in Excel? — Chandoo.org https://chandoo.org/wp/highlight-due-dates-excel/ 🔍 검색
Excel Campus (홈) https://www.excelcampus.com/ 🔍 검색
Use AI to Design Excel Dashboards Fast — Excel Campus https://www.excelcampus.com/charts/use-ai-to-design-excel-dashboard/ 🔍 검색
7 Excel Dashboard Tools & Tips for the Quick Access Toolbar — Excel Campus https://www.excelcampus.com/charts/7-excel-dashboard-tips-qat/ 🔍 검색
Introduction to Pivot Tables and Dashboards [Video 1 of 3] — Excel Campus https://www.excelcampus.com/pivot-tables/pivot-tables-dashboards-part-1/ 🔍 검색
About Jon Acampora — Excel Campus https://www.excelcampus.com/about/ 🔍 검색
Video Review of the Excel Dashboard Course — Excel Campus https://www.excelcampus.com/charts/excel-dashboard-course-review/ 🔍 검색
Excel Campus - Jon — YouTube https://www.youtube.com/user/ExcelCampus 🔍 검색
Jon Acampora — LinkedIn https://www.linkedin.com/in/jonacampora/ 🔍 검색
Dashboard Design Best Practices for Better Data Visualization — Rosalie (Medium) https://rosalie24.medium.com/dashboard-design-best-practices-for-better-data-visualization-3dec5d71761b 🔍 검색
Dashboard Design Best Practices: Layouts & Examples — Domo https://www.domo.com/learn/article/dashboard-design-examples-best-practices 🔍 검색
10 Key Dashboard Design Principles — Yellowfin https://www.yellowfinbi.com/blog/key-dashboard-design-principles-analytics-best-practice 🔍 검색
Five Rules for Building Impressive Excel Dashboards — Plum Solutions https://plumsolutions.com.au/five-rules-for-building-impressive-excel-dashboards/ 🔍 검색
8 Dashboard Design Best Practices for Insight-Led Decisions — Vizule https://vizule.io/dashboard-design-best-practices/ 🔍 검색
Effective Dashboard Color Schemes — insightsoftware https://insightsoftware.com/blog/effective-color-schemes-for-analytics-dashboards/ 🔍 검색
Best Admin Dashboard Color Schemes: A Developer's Guide — AdminLTE https://adminlte.io/blog/best-admin-dashboard-color-schemes/ 🔍 검색
8 Excel Dashboard Palettes + 4 Chart Color Sets — Big Excel Energy https://bigexcelenergy.com/excel-dashboard-color-palette/ 🔍 검색
Best Hex Code Palettes for Professional Spreadsheets — GSheets https://www.gsheets.com/blog/beautiful-spreadsheet-color-palettes 🔍 검색
What are the most effective UI/UX design principles for an Excel-based Sales Dashboard? — iCert Global https://www.icertglobal.com/community/excel-dashboard-ui-ux-design-tips 🔍 검색
Incredible dashboard design principles that make data land — Forsta https://www.forsta.com/resources/blog/dashboard-design-principles-land/ 🔍 검색
Design for Data Storytelling: Less Clutter, More Clarity — Locus IT https://locusit.com/learning/business-applications/design-for-data-storytelling-less-clutter-more-clarity/ 🔍 검색

A.2 데이터 스토리텔링 · 데이터 잉크 비율

제목 URL 확인여부
Declutter your Chart with Data-Ink Ratio — Sudharsan Asaithambi (Medium) https://sudharsanasai.medium.com/declutter-your-chart-with-data-ink-ratio-6f6908727842 🔍 검색
The Art and Science of Data-Ink Ratios For Effective Data Storytelling — John Loewen (Medium) https://medium.com/data-storytelling-corner/the-art-and-science-of-simple-data-visual-design-for-effective-storytelling-1d7667c1bfd8 🔍 검색
Data Storytelling in Data Visualisation (CHI 2024) — ACM DL https://dl.acm.org/doi/10.1145/3613904.3643022 🔍 검색
Visualization 101: 9 essential principles of data storytelling — Bunker DB https://bunkerdb.com/en/blog/data-visualization-101-storytelling-principles 🔍 검색
Master Data Storytelling — Techcanvass https://businessanalyst.techcanvass.com/data-storytelling/ 🔍 검색
Telling your Data Story in Seconds - Part 2: Color — DataSelf https://www.dataself.com/telling-story-seconds-part-2-color/ 🔍 검색
3.4 Storytelling with data visualisation — Alan Turing Institute RDS Course https://alan-turing-institute.github.io/rds-course/modules/m3/3.4-StoryTelling.html 🔍 검색
Ten practical tips for effective data visualization — School of Cities https://schoolofcities.github.io/urban-data-storytelling/urban-data-visualization/data-visualization-practical-tips/data-visualization-practical-tips.html 🔍 검색
storytelling with data (홈) https://www.storytellingwithdata.com/ 🔍 검색
storytelling with data — makeovers https://www.storytellingwithdata.com/makeovers 🔍 검색
storytelling with data — podcast https://www.storytellingwithdata.com/podcast 🔍 검색
storytelling with data — podcast archive https://www.storytellingwithdata.com/podcast/archive 🔍 검색
Ep 1 the art of feedback — SWD podcast https://www.storytellingwithdata.com/podcast/feedback
storytelling with data — books https://www.storytellingwithdata.com/books 🔍 검색
SWD community — Aug 2021 dashboard stories challenge https://community.storytellingwithdata.com/challenges/aug-2021-dashboard-stories/are-we-undercharging-for-data-visualization-work 🔍 검색
Storytelling with Data (Cole Nussbaumer Knaflic) — Wiley https://www.wiley.com/en-us/Storytelling+with+Data:+A+Data+Visualization+Guide+for+Business+Professionals-p-9781119002253 🔍 검색
Storytelling with Data — Amazon https://www.amazon.com/Storytelling-Data-Visualization-Business-Professionals/dp/1119002257 🔍 검색
Storytelling with data: What a book! — Sam Campitiello (Medium) https://medium.com/@sam.campitiello/storytelling-with-data-what-a-book-1cea585ca7c0 🔍 검색
Book Summary - Storytelling with Data — Readingraphics https://readingraphics.com/book-summary-storytelling-with-data/ 🔍 검색
Storytelling with data (PDF) — Vizalyst https://vizalyst.com/wp-content/uploads/2025/03/Cole-Knaflic-Storytelling-with-data-a-data-visualization-guide-for-business-professionals-Knaflic-Cole-Nussbaumer-Storytelling-with-data-a-dat.pdf 🔍 검색
Storytelling with Data — eBay 리스팅 https://www.ebay.de/itm/116667462617 🔍 검색

A.3 색 접근성 · 팔레트

제목 URL 확인여부
Color Universal Design (Okabe & Ito) — jfly https://jfly.uni-koeln.de/color/ FETCH
Paul Tol — Colour Schemes (bright / vibrant / high-contrast, bad-data grey) https://sronpersonalpages.nl/~pault/ FETCH
Paul Tol — 구 주소 (DNS 실패) https://personal.sron.nl/~pault/ ⚠️ DNS (getaddrinfo ENOTFOUND)
Fundamentals of Data Visualization — Color Pitfalls (Claus Wilke) https://clauswilke.com/dataviz/color-pitfalls.html FETCH
Okabe-Ito Palette Hex Codes for Python, R & CSS — ConceptViz https://conceptviz.app/blog/okabe-ito-palette-hex-codes-complete-reference FETCH
When Color Coding Fails: Why Status Indicators Need More Than Pretty Colors — accessibility.chat https://www.accessibility.chat/articles/when-color-coding-fails-why-status-indicators-need-more-than-pretty-colors FETCH
Okabe-Ito Colorblind-Safe Palette: Copy HEX + Download — sci-draw https://sci-draw.com/blog/colorblind-safe-palettes-okabe-ito-reference 🔍 검색
Best Scientific Color Palettes: Hex Codes & Examples — ConceptViz https://conceptviz.app/blog/scientific-color-palette-for-research-papers-and-posters 🔍 검색
Okabe-Ito Palette Hex Codes: Full 8-Color Reference [2026] — Figviz https://figviz.com/blog/okabe-ito-palette-hex-codes-full-8-color-reference-with-code-examples-2026-wlt9th1n 🔍 검색
Colorblind-Safe Palette Generator — Okabe-Ito Set https://www.generatorcollection.com/en/generators/colors/colorblind-safe-palette-generator/ 🔍 검색
Okabe-Ito Palette: Gold Standard for Science — Vizcept https://vizcept.com/blog/okabe-ito-palette-guide 🔍 검색
Color scales — Claus Wilke SDS375 slides https://wilkelab.org/SDS375/slides/color-scales.html 🔍 검색
Fundamentals of Data Visualization — Google Books https://books.google.com/books/about/Fundamentals_of_Data_Visualization.html?id=L3ajtgEACAAJ 🔍 검색
Fundamentals of Data Visualization — O'Reilly https://www.oreilly.com/library/view/fundamentals-of-data/9781492031079/ 🔍 검색
Fundamentals of Data Visualization — Amazon https://www.amazon.com/Fundamentals-Data-Visualization-Informative-Compelling/dp/1492031089 🔍 검색
Fundamentals of Data Visualization — Goodreads https://www.goodreads.com/book/show/40014286-fundamentals-of-data-visualization 🔍 검색
Book Summary: Fundamentals of Data Visualization — Lucky Bookshelf https://www.luckybookshelf.com/fundamentals-of-data-visualization-by-claus-o-wilke/ 🔍 검색
Fundamentals of Data Visualization (전문) — dokumen.pub https://dokumen.pub/fundamentals-of-data-visualization-a-primer-on-making-informative-and-compelling-figures-1nbsped-1492031089-9781492031086.html 🔍 검색
Creating Accessible Excel Spreadsheets: A Comprehensive Guide — Benetech https://benetech.org/creating-accessible-excel-spreadsheets-a-comprehensive-guide/ 🔍 검색
Excel Accessibility: How to Make Excel Documents Accessible — Continual Engine https://www.continualengine.com/blog/excel-accessibility/ 🔍 검색
WCAG 1.4.1: Don't rely solely on color — wcag.dock.codes https://wcag.dock.codes/documentation/wcag141/ 🔍 검색
Understanding WCAG SC 1.4.1 Use of Color — DigitalA11Y https://www.digitala11y.com/understanding-sc-1-4-1-use-of-color/ 🔍 검색
1.4.1 Use of Color — AAArdvark https://aaardvarkaccessibility.com/wcag-plain-english/1-4-1-use-of-color/ 🔍 검색
WCAG 1.4.1 Use of Color - Complete Guide — Accessibility.build https://accessibility.build/wcag/1-4-1 🔍 검색
WCAG 1.4.1 - Use of Color Fix It Series — Accessibility Checker https://accessibilitychecker.com/news/wcag-fix-it-1-4-1-use-of-color/

A.4 KPI 카드 · 스파크라인

제목 URL 확인여부
Create Amazing Key Performance Indicator Data Cards In Excel — How To Excel https://www.howtoexcel.org/kpi-data-cards/ FETCH
Flooring Installation KPI Scorecard in Excel — PK: An Excel Expert https://www.pk-anexcelexpert.com/flooring-installation-kpi-scorecard-in-excel/ FETCH
Excel KPI Card Design: Build Clean Metric Cards — Big Excel Energy https://bigexcelenergy.com/excel-kpi-card-design/ ⚠️ 403
Excel Sparklines - A Complete Guide with Examples — TrumpExcel https://trumpexcel.com/sparklines/ FETCH
Creating a Big Numbers KPI Dashboard in Tableau — Senturus https://senturus.com/blog/creating-a-big-numbers-kpi-dashboard-in-tableau/ 🔍 검색
How To Build Dynamic KPI Dashboards in Excel — Vena Solutions https://www.venasolutions.com/blog/build-kpi-dashboard-excel 🔍 검색
Card Visualization: The Tile Visualization or Large Number Tiles — Dashboard Builder https://dashboardbuilder.net/card-visualization 🔍 검색
Spark Line Chart: What It Is and When to Use It — Domo https://www.domo.com/learn/charts/sparkline-chart 🔍 검색
Win/Loss Sparklines — MyExcelOnline https://www.myexcelonline.com/blog/winloss-sparklines-in-excel/ 🔍 검색
How to Create Sparkline Dashboard in Excel — ElyxAI https://getelyxai.com/en/how-to/create-sparkline-dashboard 🔍 검색
Column Sparkline — ElyxAI Excel Glossary https://getelyxai.com/en/excel-glossary/column-sparkline 🔍 검색
Win/Loss Sparkline — ElyxAI Excel Glossary https://getelyxai.com/en/excel-glossary/win-loss-sparkline 🔍 검색
Create Win-Loss Sparklines In Excel — Simple Sheets https://www.simplesheets.co/blog/complete-guide-for-create-win-loss-sparklines-in-excel 🔍 검색
Mastering Sparklines in Excel: Win-Loss Sparklines (Part II) — Vishal CPA Prep https://vishalcpaprep.com/blogs/news/mastering-sparklines-in-excel-win-loss-sparklines-part-ii 🔍 검색
Win/Loss Sparkline for Streaks — DFW Excel https://dfwexcel.com/excel-formulas/win-loss-sparkline/ 🔍 검색

A.5 XlsxWriter / openpyxl / pandas

제목 URL 확인여부
Working with Conditional Formatting — XlsxWriter https://xlsxwriter.readthedocs.io/working_with_conditional_formats.html FETCH
Example: Sparklines (Advanced) — XlsxWriter https://xlsxwriter.readthedocs.io/example_sparklines2.html FETCH
Working with Pandas and XlsxWriter https://xlsxwriter.readthedocs.io/working_with_pandas.html FETCH
Working with Worksheet Tables — XlsxWriter https://xlsxwriter.readthedocs.io/working_with_tables.html FETCH
Example: Column Chart — XlsxWriter https://xlsxwriter.readthedocs.io/example_chart_column.html FETCH
Example: Adding hyperlinks — XlsxWriter https://xlsxwriter.readthedocs.io/example_hyperlink.html FETCH
The Worksheet Class — XlsxWriter https://xlsxwriter.readthedocs.io/worksheet.html FETCH
Example: Conditional Formatting — XlsxWriter https://xlsxwriter.readthedocs.io/example_conditional_format.html FETCH
Example: Sparklines (Simple) — XlsxWriter https://xlsxwriter.readthedocs.io/example_sparklines1.html 🔍 검색
Example: Freeze Panes and Split Panes — XlsxWriter https://xlsxwriter.readthedocs.io/example_panes.html 🔍 검색
Example: Charts with Data Tables — XlsxWriter https://xlsxwriter.readthedocs.io/example_chart_data_table.html 🔍 검색
Contents — XlsxWriter https://xlsxwriter.readthedocs.io/contents.html 🔍 검색
XlsxWriter — working_with_conditional_formats.rst (GitHub) https://github.com/jmcnamara/XlsxWriter/blob/master/dev/docs/source/working_with_conditional_formats.rst 🔍 검색
XlsxWriter — worksheet.rst (GitHub) https://github.com/jmcnamara/XlsxWriter/blob/main/dev/docs/source/worksheet.rst 🔍 검색
XlsxWriter — working_with_pandas.rst (GitHub) https://github.com/jmcnamara/XlsxWriter/blob/main/dev/docs/source/working_with_pandas.rst 🔍 검색
XlsxWriter — examples/chart_column.py (GitHub) https://github.com/jmcnamara/XlsxWriter/blob/master/examples/chart_column.py 🔍 검색
XlsxWriter Issue #609 — write_url internal sheet reference with spaces https://github.com/jmcnamara/XlsxWriter/issues/609 🔍 검색
XlsxWriter Issue #350 — write_url produces broken hyperlink https://github.com/jmcnamara/XlsxWriter/issues/350 🔍 검색
XlsxWriter Issue #511 — hyperlinking cells containing multiple URLs https://github.com/jmcnamara/XlsxWriter/issues/511 🔍 검색
libxlsxwriter — hyperlinks.c 예제 https://libxlsxwriter.github.io/hyperlinks_8c-example.html 🔍 검색
xlsxwriter.lua — Adding hyperlinks https://xlsxwriterlua.readthedocs.io/example_hyperlink.html 🔍 검색
XlsxWriter 0.5.8 — PyPI https://pypi.org/project/XlsxWriter/0.5.8 🔍 검색
Python XlsxWriter - Sparklines — TutorialsPoint https://www.tutorialspoint.com/python_xlsxwriter/python_xlsxwriter_sparklines.htm 🔍 검색
Python XlsxWriter - Conditional Formatting — TutorialsPoint https://www.tutorialspoint.com/python_xlsxwriter/python_xlsxwriter_conditional_formatting.htm 🔍 검색
Python XlsxWriter - Adding Charts — TutorialsPoint https://www.tutorialspoint.com/python_xlsxwriter/python_xlsxwriter_adding_charts.htm 🔍 검색
Python XlsxWriter - Hyperlinks — TutorialsPoint https://www.tutorialspoint.com/python_xlsxwriter/python_xlsxwriter_hyperlinks.htm 🔍 검색
Python Plotting column charts in excel sheet using XlsxWriter — GeeksforGeeks https://www.geeksforgeeks.org/python/python-plotting-column-charts-in-excel-sheet-using-xlsxwriter-module/
Create excel charts with xlsxwriter — riptutorial https://riptutorial.com/python/example/17392/create-excel-charts-with-xlsxwriter 🔍 검색
Python and Excel: Writing Data Using XlsxWriter — Charlie (Medium) https://medium.com/@charliepython/python-and-excel-writing-data-using-xlsxwriter-78d602ca5048 🔍 검색
Pivot Tables — openpyxl (stable) https://openpyxl.readthedocs.io/en/stable/pivot.html FETCH
Conditional Formatting — openpyxl (stable) https://openpyxl.readthedocs.io/en/stable/formatting.html FETCH
Worksheet Tables — openpyxl (stable) https://openpyxl.readthedocs.io/en/stable/worksheet_tables.html FETCH
Pivot Tables — openpyxl 2.5.15 https://openpyxl.readthedocs.io/en/2.5/pivot.html 🔍 검색
Pivot Tables — openpyxl 3.0.10 https://openpyxl.readthedocs.io/en/3.0/pivot.html 🔍 검색
Pivot Tables — openpyxl 3.1.3 https://openpyxl.readthedocs.io/en/3.1/pivot.html 🔍 검색
Pivot Tables — openpyxl latest https://openpyxl.readthedocs.io/en/latest/pivot.html 🔍 검색
openpyxl.pivot.cache module — 3.1.1 https://openpyxl.readthedocs.io/en/3.1.1/api/openpyxl.pivot.cache.html 🔍 검색
openpyxl.pivot.cache module — stable https://openpyxl.readthedocs.io/en/stable/api/openpyxl.pivot.cache.html 🔍 검색
openpyxl.pivot.cache module — latest https://openpyxl.readthedocs.io/en/latest/api/openpyxl.pivot.cache.html 🔍 검색
openpyxl.pivot.table module — stable https://openpyxl.readthedocs.io/en/stable/api/openpyxl.pivot.table.html 🔍 검색
openpyxl.formatting.rule module — 3.0.10 https://openpyxl.readthedocs.io/en/3.0/api/openpyxl.formatting.rule.html 🔍 검색
openpyxl.formatting.rule module — latest https://openpyxl.readthedocs.io/en/latest/api/openpyxl.formatting.rule.html 🔍 검색
openpyxl.formatting.rule module — stable https://openpyxl.readthedocs.io/en/stable/api/openpyxl.formatting.rule.html 🔍 검색
openpyxl.formatting.rule module — 3.1.1 https://openpyxl.readthedocs.io/en/3.1.1/api/openpyxl.formatting.rule.html 🔍 검색
Conditional Formatting — openpyxl 3.1.2 https://openpyxl.readthedocs.io/en/3.1.2/formatting.html 🔍 검색
Conditional Formatting — openpyxl 3.1.1 https://openpyxl.readthedocs.io/en/3.1.1/formatting.html 🔍 검색
Conditional Formatting — openpyxl 3.1 https://openpyxl.readthedocs.io/en/3.1/formatting.html 🔍 검색
openpyxl.worksheet.table module — stable https://openpyxl.readthedocs.io/en/stable/api/openpyxl.worksheet.table.html 🔍 검색
Worksheet Tables — openpyxl 3.1 https://openpyxl.readthedocs.io/en/3.1/worksheet_tables.html 🔍 검색
Worksheet Tables — openpyxl 2.4.11 https://openpyxl.readthedocs.io/en/2.4/worksheet_tables.html 🔍 검색
openpyxl.worksheet.table — 2.5.2 (미러) https://ssopenpyxl.readthedocs.io/en/2.5.2/openpyxl.worksheet.table.html 🔍 검색
Using filters and sorts — openpyxl https://openpyxl.readthedocs.io/en/stable/filters.html 🔍 검색
Conditional Format — Master OpenPyXL https://www.openpyxl.com/conditional-format.html 🔍 검색
Adding Conditional Formatting to Excel Using Python Openpyxl — GeeksforGeeks https://www.geeksforgeeks.org/python/adding-conditional-formatting-to-excel-using-python-openpyxl/ 🔍 검색
Adding Conditional Formatting (Video) — Real Python https://realpython.com/lessons/conditional-formatting/ 🔍 검색
How to Implement Excel-Style Conditional Logic with Openpyxl — Statology https://www.statology.org/how-to-implement-excel-style-conditional-logic-with-openpyxl-in-python/ 🔍 검색
openpyxl Part 13 Conditional Styling — Prospero Coder https://prosperocoder.com/science/openpyxl-part-13-conditional-styling/ 🔍 검색
Python openpyxl Conditional Formatting Guide — PyTutorial https://pytutorial.com/python-openpyxl-conditional-formatting-guide/ 🔍 검색
Chapter 13 Working with Excel Spreadsheets — Automate the Boring Stuff https://automatetheboringstuff.com/2e/chapter13/ 🔍 검색
Diff Two Excel Files with Python — Matthew Kudija https://matthewkudija.com/blog/2018/07/21/excel-diff/ FETCH
pandas.DataFrame.to_excel — pandas 3.0.5 https://pandas.pydata.org/docs/reference/api/pandas.DataFrame.to_excel.html 🔍 검색
Enhance Your Pandas Excel Output: Freezing Panes, Filtering, Adjusting Column Widths — velog https://velog.io/@sacross93/Enhance-Your-Pandas-Excel-Output-Freezing-Panes-Filtering-and-Adjusting-Column-Widths 🔍 검색
Pandas DataFrame.to_excel() Function — Delft Stack https://www.delftstack.com/api/python-pandas/pandas-dataframe-dataframe.to_excel-function/ 🔍 검색
pyspark.pandas.DataFrame.to_excel — Databricks https://api-docs.databricks.com/python/pyspark/latest/pyspark.pandas/api/pyspark.pandas.DataFrame.to_excel.html 🔍 검색
pandas PR #62994 (diff) https://github.com/pandas-dev/pandas/pull/62994.diff 🔍 검색
Excel Tips — Scribd https://www.scribd.com/document/44201206/Excel-Tips 🔍 검색

A.6 엑셀 정렬 · 커스텀 리스트

제목 URL 확인여부
Sort data using a custom list — Microsoft Support https://support.microsoft.com/en-us/office/sort-data-using-a-custom-list-cba3d67a-c5cb-406f-9b14-a02205834d72 🔍 검색
Excel SORTBY function - custom sort with formula — Ablebits https://www.ablebits.com/office-addins-blog/excel-sortby-function-custom-sort-formula/ 🔍 검색
Custom Sort Order in Excel - Made Simple — Excel Easy https://www.excel-easy.com/examples/custom-sort-order.html 🔍 검색
How to Create Custom Sort Order in Excel — ElyxAI https://getelyxai.com/en/how-to/create-custom-sort-order 🔍 검색
How to Sort Data in Excel Custom Order, Filters & Pro Tips — Great Learning https://www.mygreatlearning.com/blog/sort-data-in-excel/ 🔍 검색
Custom Sorting with SORT & MATCH — Excel Cookbook https://www.excel-cookbook.com/recipes/sort-by-custom-order-match 🔍 검색
Organise Your Data Like a Pro: Custom Sort Order in Excel — techmemore https://techmemore.substack.com/p/organise-your-data-like-a-pro-how 🔍 검색
Custom Sort — MrExcel Message Board https://www.mrexcel.com/board/threads/custom-sort.471612/ 🔍 검색
Excel custom sorting using Office scripts — Microsoft Learn Q&A https://learn.microsoft.com/en-us/answers/questions/1527424/excel-custom-sorting-using-office-scripts 🔍 검색
OfficeTalk: Bringing Improvements to Tables in Excel 2007 (Part 5 of 6) — Microsoft Learn https://learn.microsoft.com/en-us/previous-versions/office/developer/officetalk2007/bb963879(v=office.11) 🔍 검색

A.7 한국 DMF (식약처 · 법령 · 업계)

제목 URL 확인여부
의약품안전나라 > 원료의약품등록(DMF) 공고 https://nedrug.mfds.go.kr/pbp/CCBAC03 FETCH (컬럼 15개 확인)
의약품안전나라 > 원료의약품등록(DMF) 정보 (게시판) https://nedrug.mfds.go.kr/bbs/117 FETCH (총 710건, 주/격주 공고)
공공데이터포털 — 식품의약품안전처_원료의약품등록(DMF)현황 OpenAPI https://www.data.go.kr/data/15057075/openapi.do FETCH (필드·파라미터 확인)
OpenAPI 엔드포인트 https://apis.data.go.kr/1471000/MdcDmfInfoService01/getMdcDmfList01 (문서 기재)
KHIDI — 등록대상 원료의약품(DMF) 등록 공고(7월 둘째주) https://www.khidi.or.kr/board/view?pageNum=48&rowCnt=10&menuId=MENU01872&maxIndex=00487793189998&minIndex=00487441479998&schType=0&upDown=0&no1=912&linkId=26604564 FETCH (주간 공고 컬럼 6종)
KHIDI — [지침]원료의약품 등록(DMF) 처리 절차 https://www.khidi.or.kr/board/view?pageNum=1&rowCnt=10&menuId=MENU01872&maxIndex=99999999999999&minIndex=99999999999999&schType=0&upDown=0&no1=0&linkId=26605812 FETCH (2017-03-30, HWP 223.5KB)
KHIDI — 「원료의약품 등록에 관한 규정」고시 개정 알림 https://www.khidi.or.kr/board/view?pageNum=48&rowCnt=10&no1=3447&linkId=48923038&menuId=MENU01872&maxIndex=&minIndex=&schType=0&schText=&schStartDate=&schEndDate=&boardStyle=&categoryId=&continent=&country= 🔍 검색
KPBMA — 원료의약품 등록 제도(DMF) 해설서 제5개정판 (PDF) https://www.kpbma.or.kr/api/info/law/policy/download/211364 📄 PDF (118p, 113,549자 추출 — 등록번호 체계 Q51, 변경등록/변경보고 비교표)
MFDS — 원료의약품 등록 및 변경등록 업무 (지침서 GRP-MaPP-허가업무-04, 2025.2.20 6개정, PDF) https://www.mfds.go.kr/brd/m_1059/down.do?brd_id=data0010&seq=15144&data_tp=A&file_seq=2 📄 PDF (35p, 23,925자 추출 — 처리기간·수수료·공고절차)
국가법령정보센터 — 원료의약품 등록에 관한 규정 (admRulSeq=2100000198136) https://law.go.kr/LSW/admRulLsInfoP.do?admRulSeq=2100000198136 🔍 검색
국가법령정보센터 — 원료의약품 등록에 관한 규정 (admRulSeq=2100000150569) https://www.law.go.kr/LSW/admRulLsInfoP.do?admRulSeq=2100000150569 🔍 검색
국가법령정보센터 — 원료의약품 등록에 관한 규정 (admRulSeq=2100000252602) https://law.go.kr/LSW/admRulLsInfoP.do?admRulSeq=2100000252602 🔍 검색
국가법령정보센터 — 원료의약품등록에관한규정 (행정규칙 직접 링크) https://www.law.go.kr/%ED%96%89%EC%A0%95%EA%B7%9C%EC%B9%99/%EC%9B%90%EB%A3%8C%EC%9D%98%EC%95%BD%ED%92%88%EB%93%B1%EB%A1%9D%EC%97%90%EA%B4%80%ED%95%9C%EA%B7%9C%EC%A0%95 FETCH (요약만)
국가법령정보센터 — 원료의약품 등록에 관한 규정 (조문 팝업) https://www.law.go.kr/conAdmrulByLsPop.do?lsiSeq=217283&joNo=0031&joBrNo=02&datClsCd=010102&dguBun=DEG&lnkText=%EC%8B%9D%ED%92%88%EC%9D%98%EC%95%BD%ED%92%88%EC%95%88%EC%A0%84%EC%B2%98%EC%9E%A5%EC%9D%B4+%EC%A0%95%ED%95%98%EC%97%AC+%EA%B3%A0%EC%8B%9C%ED%95%98%EB%8A%94&admRulPttninfSeq=1526 🔍 검색
MFDS — 원료의약품 등록에 관한 규정 (seq=14569) https://www.mfds.go.kr/brd/m_211/view.do?seq=14569&srchFr=&srchTo=&srchWord=&srchTp=&itm_seq_1=0&itm_seq_2=0&multi_itm_seq=0&company_cd=&company_nm=&page=1 🔍 검색
MFDS — 「원료의약품 등록에 관한 규정」고시전문 (seq=14870) https://www.mfds.go.kr/brd/m_211/view.do?seq=14870&srchFr=&srchTo=&srchWord=&srchTp=&itm_seq_1=0&itm_seq_2=0&multi_itm_seq=0&company_cd=&company_nm=&Data_stts=A&page=1 🔍 검색
KPBMA — 「원료의약품 등록에 관한 규정」(식약처 고시) 일부개정 알림 https://www.kpbma.or.kr/info/law/statute/select/210036 🔍 검색
KPBMA — 등록대상 원료의약품(DMF) 사전등록제 세부 운영방안 알림 https://www.kpbma.or.kr/inform/notice/notice/select/244214 🔍 검색
정부24 — 원료의약품(등록신청, 등록사항 변경등록신청) 민원안내 https://www.gov.kr/mw/AA020InfoCappView.do?HighCtgCD=A09006&CappBizCD=14700000458&tp_seq= FETCH (처리기간 20~90일, 콜센터 1588-2188)
KDI 경제교육·정보센터 — 원료의약품 등록 제도(DMF) 꼼꼼히 알기! https://eiec.kdi.re.kr/policy/materialView.do?num=209012&topic=&pp=20&datecount=&recommend=&pg= FETCH (2020-12-29 해설서 발간)
KDI 경제교육·정보센터 — 원료의약품 등록제도(DMF), 궁금하면 클릭하세요! https://eiec.kdi.re.kr/policy/materialView.do?num=196213&topic=L&pp=20 🔍 검색
MFDS — 원료의약품 등록제도(DMF), 궁금하면 클릭하세요! (PDF) https://www.mfds.go.kr/brd/m_99/down.do?brd_id=ntc0021&seq=43886&data_tp=A&file_seq=2 🔍 검색
MFDS — 원료등록(DMF) 기간 대폭 단축! (PDF) https://www.mfds.go.kr/brd/m_99/down.do?brd_id=ntc0021&seq=48245&data_tp=A&file_seq=2 🔍 검색
식품의약품안전평가원 — 사업소개>의약품>허가심사>KDMF https://www.nifds.go.kr/brd/m_87/list.do 🔍 검색
MFDS — 식품의약품 통계연보 목록 https://www.mfds.go.kr/brd/m_371/list.do 🔍 검색
MFDS — 2023년 식품의약품통계연보(제25호) https://www.mfds.go.kr/brd/m_371/view.do?seq=30722&itm_seq_1=0&itm_seq_2=0&multi_itm_seq=0&page=1 🔍 검색
MFDS — 2022년 식품의약품통계연보(제24호) https://www.mfds.go.kr/brd/m_371/view.do?seq=30721 🔍 검색
MFDS — 2020년 식품의약품통계연보(제22호) https://www.mfds.go.kr/brd/m_371/view.do?seq=30719&srchFr=&srchTo=&srchWord=&srchTp=&itm_seq_1=0&itm_seq_2=0&multi_itm_seq=0&company_cd=&company_nm=&page=1 🔍 검색
MFDS — 통계안내 https://www.mfds.go.kr/pgm/m_538/statInformation.do 🔍 검색
MFDS — 국가별 규제정보 (seq=40464) https://www.mfds.go.kr/brd/m_617/view.do?seq=40464&srchFr=&srchTo=&srchWord=&srchTp=&itm_seq_1=0&itm_seq_2=0&multi_itm_seq=0&company_cd=&company_nm=&page=1 🔍 검색
공공데이터포털 — 식의약 통계 정보 서비스 파일데이터 https://www.data.go.kr/data/15047676/fileData.do 🔍 검색
의약품안전나라 (홈) https://nedrug.mfds.go.kr/ 🔍 검색
의약품안전나라 — 의약품등 검색 https://nedrug.mfds.go.kr/searchDrug 🔍 검색
의약품안전나라 — 고시/공고/알림 > 변경명령 https://nedrug.mfds.go.kr/CCBAR01F012/getList/getItem?infoNo=20230089&infoClassCode=3 🔍 검색
의약품안전나라 — 고시/공고/알림 > (구)변경지시 https://nedrug.mfds.go.kr/pbp/CCBAQ03/getItem?totalPages=1257&limit=10&page=2&bbsYn=Y&orderNo=&bbscttNo=4652 🔍 검색
의약품안전나라 — 고시/공고/알림 > 품목허가현황 https://nedrug.mfds.go.kr/pbp/CCBAE01 🔍 검색
데일리팜 — 규제완화 여파…원료약 등록 1년새 237→653건 https://www.dailypharm.com/Users/News/NewsView.html?ID=324241 FETCH
데일리팜 — 인도·중국산 등록비중 70% 돌파…외면받는 국산 원료약 http://m.dailypharm.com/newsView.html?ID=307819 FETCH
데일리팜 — 필수약·공급중단 보고대상 의약품 'DMF 등록' 유예 https://m.dailypharm.com/user/news/32332 🔍 검색
데일리팜 — 의약품 해외제조소 등록 안하면 2차 위반시 허가취소 https://dailypharm.com/user/news/67688 🔍 검색
의학신문 — 식약처, 원료의약품 등록 제도(DMF) 해설서 발간 http://www.bosa.co.kr/news/articleView.html?idxno=2141146 🔍 검색
히트뉴스 — 원료의약품 품질심사, 완제약 허가신청 돼야 동시 진행 http://www.hitnews.co.kr/news/articleView.html?idxno=17459 🔍 검색
시넥스 — 원료의약품 등록 제도(DMF) https://www.synex.co.kr/popup/pop_view.asp?bbs_code=2&bbs_idx=192 🔍 검색

A.8 FDA / EMA / 국제 DMF 데이터

제목 URL 확인여부
A Guide to Drug Master Files — BDO https://www.bdo.com/insights/industries/life-sciences/a-guide-to-drug-master-files FETCH
Drug Master File — Wikipedia https://en.wikipedia.org/wiki/Drug_Master_File FETCH
FDA Drug Master Files Directory — John Snow Labs https://www.johnsnowlabs.com/marketplace/fda-drug-master-files-directory/ FETCH
Programmatic access and formatting of FDA Orange Book data — paracelsus https://manuelxduval.github.io/paracelsus/ FETCH
openFDA — Drugs@FDA API https://open.fda.gov/apis/drug/drugsfda/ FETCH
openFDA — Downloads https://open.fda.gov/data/downloads/ FETCH
Drugs@FDA Database — catalog.data.gov https://catalog.data.gov/dataset/drugsfda-database FETCH
List of Drug Master Files (DMFs) — FDA https://www.fda.gov/drugs/drug-master-files-dmfs/list-drug-master-files-dmfs ⚠️ 404
Drug Master Files (DMFs) — FDA https://www.fda.gov/drugs/forms-submission-requirements/drug-master-files-dmfs ⚠️ 404
Orange Book Data Files — FDA https://www.fda.gov/drugs/drug-approvals-and-databases/orange-book-data-files ⚠️ 404
Drugs@FDA Data Files — FDA https://www.fda.gov/drugs/drug-approvals-and-databases/drugsfda-data-files ⚠️ 404
Guideline for Drug Master Files (DMF) — FDA https://www.fda.gov/drugs/drug-master-files-dmfs/guideline-drug-master-files-dmf 🔍 검색
Types of Drug Master Files (DMFs) — FDA https://www.fda.gov/drugs/drug-master-files-dmfs/types-drug-master-files-dmfs 🔍 검색
Drug Master Files Guidance for Industry (DRAFT) — FDA https://www.fda.gov/media/131861/download 🔍 검색
FDA — Orange Book 필드 설명 (ucm129689#descriptions) https://www.fda.gov/drugs/informationondrugs/ucm129689.htm#descriptions 🔍 검색
FDA — ucm079750 (Drugs@FDA 관련) https://www.fda.gov/drugs/informationondrugs/ucm079750.htm 🔍 검색
FDA — media/103879/download (Orange Book) https://www.fda.gov/media/103879/download 🔍 검색
Frequently Asked Questions on The Orange Book — FDA https://www.fda.gov/drugs/drug-approvals-and-databases/frequently-asked-questions-orange-book 🔍 검색
Search Databases — FDA https://www.fda.gov/industry/fda-basics-industry/search-databases 🔍 검색
What's New in Drugs@FDA — FDA https://www.fda.gov/drugs/drug-approvals-and-databases/whats-new-drugsfda 🔍 검색
Orange Book (regulations.gov PDF, FDA-2019-P-1318-0003) https://downloads.regulations.gov/FDA-2019-P-1318-0003/content.pdf 🔍 검색
Orange Book: Approved Drug Products with TE (FDA-2017-P-2044-0004) https://downloads.regulations.gov/FDA-2017-P-2044-0004/content.pdf 🔍 검색
FDA Orange Book Drug Data — Kaggle https://www.kaggle.com/datasets/thedevastator/fda-orange-book-drug-data 🔍 검색
Orange Book 101 — Fish & Richardson https://www.fr.com/insights/ip-law-essentials/orange-book-101/ 🔍 검색
FDA Orange Book: Search Patents and TE Codes — Assyro AI https://www.assyro.com/blog/fda-orange-book-guide 🔍 검색
FDA Drugs Database — Databricks Marketplace (John Snow Labs) https://marketplace.databricks.com/details/cf63f2b8-da42-4ce5-a89b-9644cff0ef26/John-Snow-Labs_FDA-Drugs-Database 🔍 검색
FDA Approved Drug Products — NBER https://www.nber.org/research/data/fda-approved-drug-products 🔍 검색
Drugs@FDA Database — HealthData.gov https://healthdata.gov/FDA/Drugs-FDA-Database/8m24-qzcq 🔍 검색
Dmf List FDA Quarterly Drug Master Files Spreadsheet https://dmf-list.backgroundscheck.info/ 🔍 검색
Complete Guide to DMF Submissions — Celegence https://www.celegence.com/guide-to-drug-master-files-dmf-submission-tips-best-practices/ 🔍 검색
Excipient DMF List — Pharma Excipients https://www.pharmaexcipients.com/excipient-sources/excipient-dmf-list/ 🔍 검색
Drug Master File - US DMF FDA Guidance and Submission — FDA Pals https://fdapals.com/services/dmf-fda-guidance/ 🔍 검색
FDA's Drug Master Files — Duke Medical Center Library https://mclibrary.duke.edu/about/blog/2014-12-04/fda-drug-master-files 🔍 검색
What Does DMF Mean? Drug Master Files Explained (2026) — Tianming Pharm https://www.tianmingpharm.com/what-does-dmf-mean/ 🔍 검색
US Drug Master Files (DMF) Database — PharmaCompass https://www.pharmacompass.com/us-drug-master-files-dmfs ⚠️ 403
API Manufacturers Suppliers DMF
PDMS API Manufacturers — PharmaCompass https://www.pharmacompass.com/manufacturers-suppliers-exporters/pdms 🔍 검색
DMCTC API Manufacturers — PharmaCompass https://www.pharmacompass.com/manufacturers-suppliers-exporters/dmctc 🔍 검색
PAS API Manufacturers — PharmaCompass https://www.pharmacompass.com/manufacturers-suppliers-exporters/pas 🔍 검색
LOC API Manufacturers — PharmaCompass https://www.pharmacompass.com/manufacturers-suppliers-exporters/loc 🔍 검색
API - Uses, DMF, Dossier, Manufacturer… — PharmaCompass https://www.pharmacompass.com/apis-products 🔍 검색
Find APIs and FDF Suppliers — PharmaCompass https://www.pharmacompass.com/find-suppliers 🔍 검색
Deployment of a new EDQM CEP database feature: full download of CEP data https://www.edqm.eu/en/-/deployment-of-a-new-edqm-cep-database-feature-full-download-of-cep-data ⚠️ 403
Databases — EDQM https://www.edqm.eu/en/databases ⚠️ 403
What is the CEP 2.0? — EDQM https://www.edqm.eu/en/what-is-the-cep-2.0 🔍 검색
User guide for Certification On-Line database (PA/PH/CEP (23) 56, May 2024) — EDQM PDF https://www.edqm.eu/documents/52006/156629/User%20guide%20for%20Certification%20On-Line%20database%20(PA_PH_CEP%20(23)%2056,%20May%202024).pdf/26264db2-58b5-0cd4-7439-999bb86c4795?t=1718896649552 🔍 검색
CEP 2.0: Changes from September 2023 — EDQM https://www.edqm.eu/en/-/cep-2.0-changes-from-september-2023-to-information-published-in-certification-online-and-knowledge-databases 🔍 검색
Notifications, Revisions, Renewals and Sister Files — EDQM https://www.edqm.eu/en/notifications-revisions-renewals-and-sister-files 🔍 검색
How do I know if I have the latest valid version of a CEP? — EDQM FAQ https://faq.edqm.eu/pages/viewpage.action?pageId=1377010 🔍 검색
Certification of Suitability (CEP) — EDQM https://www.edqm.eu/en/certification 🔍 검색
Certification Policy Documents & Guidelines — EDQM https://www.edqm.eu/en/certification-policy-documents-guidelines 🔍 검색
KNOWLEDGE Database — EDQM https://www.edqm.eu/en/knowledge-database 🔍 검색

A.9 규제 인텔리전스 도구 · 뉴스레터

제목 URL 확인여부
Global Regulatory Intelligence monthly newsletter — ICON plc https://www.iconplc.com/insights/regulatory-intelligence/regulatory-intelligence-newsletter FETCH
A Comprehensive Review of Regulatory Intelligence — PDA Letter https://www.pda.org/pda-letter-portal/home/full-article/a-comprehensive-review-of-regulatory-intelligence-exploring-tools-and-program-maturities ⚠️ 403
FDA Tracker: Free Regulatory Intelligence & 483 Tracker — Leucine https://leucine.ai/fda-tracker/ ⚠️ 403
Thomson Reuters Regulatory Intelligence https://regintel-content.thomsonreuters.com/ 🔍 검색
Regulatory change tracking for compliance management — Ideagen https://www.ideagen.com/solutions/quality/regulatory-intelligence/regulatory-change-tracking 🔍 검색
RegASK Agentic AI Regulatory Intelligence Platform https://regask.com/
AI Regulatory Intelligence — IONI AI https://ioni.ai/ai-regulatory-intelligence 🔍 검색
Regulatory Intelligence Software: 5 Best Tools (2026) — IONI AI https://ioni.ai/post/best-ai-regulatory-intelligence-tool 🔍 검색
Regulatory Intelligence 101: A Beginner's Guide — IONI AI https://ioni.ai/post/regulatory-intelligence-101-a-beginners-guide-for-the-pharmaceutical-industry 🔍 검색
Regulatory Intelligence Software - Law Change Monitoring and Alerts — Visualping https://visualping.io/regulatory-intelligence 🔍 검색
Regulatory Intelligence Tools — Compliance.ai https://www.compliance.ai/solution/regulatory-intelligence/ 🔍 검색
Regulatory Intelligence Database, Software & Tools — Vistaar https://www.vistaar.ai/blog/regulatory-intelligence-database-software-tools-for-compliance/ 🔍 검색
Vistaar (홈) https://www.vistaar.ai/ 🔍 검색
Cortellis Regulatory Intelligence — Clarivate https://clarivate.com/life-sciences-healthcare/research-development/regulatory-compliance-intelligence/regulatory-intelligence-solutions/ 🔍 검색
AgencyIQ by POLITICO https://www.agencyiq.com/ 🔍 검색
Pharma and Biotech Regulatory Intelligence & Guidance Monitoring — DDi https://www.ddismart.com/pharma-biotech-regulatory-intel/ 🔍 검색
Regulatory Intelligence and Updates — PharmaRegulatory.in https://www.pharmaregulatory.in/regulatory-intelligence-and-updates/ 🔍 검색
Harnessing Regulatory Intelligence and Updates in Pharma Strategy — PharmaRegulatory.in https://www.pharmaregulatory.in/harnessing-regulatory-intelligence-and-updates-in-pharma-strategy 🔍 검색

A.10 기타 (검색 결과에 등장했으나 본 주제와 무관하거나 참고용)

제목 URL 확인여부
Dimethyl fumarate particles and pharmaceutical compositions thereof (USPTO 11291642) https://image-ppubs.uspto.gov/dirsearch-public/print/downloadPdf/11291642 🔍 검색 (동음이의 DMF — 무관)
Substance of very high concern — Wikipedia https://en.wikipedia.org/wiki/Substance_of_very_high_concern 🔍 검색 (무관)
nedrug 통합검색 (깨진 인코딩 URL) https://nedrug.mfds.go.kr/search?keyword=... 🔍 검색 (스팸성 결과)
KPI Scorecard 이미지 1 — PK: An Excel Expert https://www.pk-anexcelexpert.com/wp-content/uploads/2026/08/Flooring-Installation-KPI-Scorecard-2-1210x642.jpg (이미지)
KPI Scorecard 이미지 2 — NextGen Templates https://nextgentemplates.com/wp-content/uploads/2026/08/Flooring-Installation-KPI-Scorecard-3-scaled.jpg (이미지)
KPI Scorecard 이미지 3 — NextGen Templates https://nextgentemplates.com/wp-content/uploads/2026/08/Flooring-Installation-KPI-Scorecard-4.jpg (이미지)
KHIDI 주간 공고 — 전체 쿼리스트링 변형 https://www.khidi.or.kr/board/view?pageNum=48&rowCnt=10&menuId=MENU01872&maxIndex=00487793189998&minIndex=00487441479998&schType=0&schText=&categoryId=&continent=&country=&upDown=0&boardStyle=&no1=912&linkId=26604564 🔍 검색 (A.7 항목과 동일 문서)
KHIDI DMF 처리 절차 — 전체 쿼리스트링 변형 https://www.khidi.or.kr/board/view?pageNum=1&rowCnt=10&menuId=MENU01872&maxIndex=99999999999999&minIndex=99999999999999&schType=0&schText=&categoryId=&continent=&country=&upDown=0&boardStyle=&no1=0&linkId=26605812 🔍 검색 (A.7 항목과 동일 문서)
식약처 전자민원창구 (주간 원료의약품 등록현황 게시) http://ezdrug.mfds.go.kr (문서 기재, 미검증 ⚠️)
국가법령정보센터 (기관 홈) https://www.law.go.kr 🔍 검색
식품의약품안전처 (기관 홈) https://www.mfds.go.kr 🔍 검색
python.org (XlsxWriter 하이퍼링크 문서 예제에 등장) http://www.python.org/ (문서 예제)

부록 B. 미해결 질문 / 실측 필요 항목

B.1 소스 데이터

  • nedrug.mfds.go.kr/pbp/CCBAC03페이지네이션·정렬 파라미터를 실제 HTTP 요청으로 확정한다(WebFetch 로는 첫 페이지 렌더 결과만 확인함).
  • 같은 페이지에 엑셀/CSV 다운로드 버튼이 있는지, 있다면 요청 형식(GET/POST, 파라미터)을 확인한다. 있으면 HTML 파싱보다 우선.
  • 취소/취하구분 컬럼의 전체 값 도메인을 실측한다(관측된 값은 정상 뿐). 취소취하가 별개 값인지 확인.
  • 대상의약품 컬럼의 값 도메인 전체(별표1, 신물질 외에 별표1의2 등이 있는지).
  • 주간 공고 게시글(nedrug.mfds.go.kr/bbs/117)의 첨부파일 형식(hwp/hwpx/xlsx/pdf) 과 표 컬럼을 실측한다. 목록 뷰에서는 확인 불가였다.
  • ezdrug.mfds.go.kr 의 주간 등록현황 게시 위치가 현재도 유효한지 확인(문서 기재는 오래된 경로일 수 있음).
  • OpenAPI getMdcDmfList01갱신주기를 실측한다(문서에 명시 없음). nedrug 공고와의 반영 시차도 측정.
  • OpenAPI 응답에 totalCount 가 있는지, 있다면 메타 시트 교차검증에 쓸 수 있는지 확인.
  • OpenAPI 가 최종변경일자/취소여부를 제공하지 않는 것이 확실한지 재확인(응답 샘플 1건 확보). → 해소: design/00b-baseline-data-analysis.md 가 API 7필드만으로 만들어진 실제 운영 산출물을 전수 분석해 확인. 필드가 없는 것은 맞지만, 발급일자 이동과 레코드 소멸로 같은 정보를 간접적으로 얻을 수 있음이 실측으로 확인됐다(§8.0).
  • data.go.kr serviceKey 발급 및 일일 호출 한도 확인.

B.2 도메인 규칙

  • 재공고(re-announcement) 의 데이터상 표현을 확인한다. 해설서 통계표 주석 ( )의 숫자는 재공고된 숫자를 의미함 의 실제 판정 기준은? 등록번호가 같은가 다른가?
  • 연계 등록 원료의약품(사전등록) 이 nedrug 공고 표의 어느 컬럼에 어떻게 표기되는지 실측(대상의약품? 별도 등록구분?).
  • 총 등록 공고의 등록구분이 CCBAC03 페이지에도 있는지, 별도 페이지인지 확인.
  • 등록번호 시행군 알파벳 중 J 의 성분내일련번호 제외 규칙이 파싱에 미치는 영향 검증(정규식이 J 를 안전히 처리하는지).
  • 등록번호 괄호 표기 (1~9) 의 실제 출현 형태(범위 표기인지 단일 숫자인지) 실측.
  • 「원료의약품 등록에 관한 규정」 최신판(식약처 고시 제2024-89호, 2024.12.30)[별표1]·[별표1의2] 성분 목록을 확보해 성분일련번호 → 성분명 역매핑 테이블을 만든다.
  • 제조국가 표기 정규화 매핑표를 실데이터로 만든다(고유값 전수 조사 후 코드북 작성).

B.3 시각화 · 구현

  • 대시보드 열 폭 합계(168 문자폭)가 실제 1366×768 / 1920×1080 에서 스크롤 없이 들어가는지 스크린샷으로 검증.
  • 한글 폰트 맑은 고딕에서 28pt 숫자가 26 문자폭 타일에 넘치지 않는지 실측(5자리 + 콤마).
  • add_sparklineseries_colorwin_loss 타입에서도 적용되는지 확인(문서에는 타입별 제약 언급 없음).
  • 조건부서식 규칙 5개 + 데이터바 + 아이콘셋을 한 시트에 걸었을 때 Excel 파일 열기 성능(수만 행) 측정. 느리면 규칙을 stop_if_true 로 최적화하거나 정적 서식으로 전환.
  • 상태색 배경을 행 전체(20+열) 에 적용하는 것이 인쇄 시 잉크를 과소비하지 않는지 확인. 필요하면 인쇄용 흑백 시트를 별도 제공(Paul Tol high-contrast 팔레트 사용).
  • CVD 시뮬레이터로 실제 산출 xlsx 스크린샷을 protanopia/deuteranopia/tritanopia 3종 변환해 검증(Wilke 권고).
  • 방식 B(템플릿 피벗)를 실제로 만들어 refreshOnLoad = True 가 Excel 2016/365 양쪽에서 동작하는지 확인.
  • openpyxlws._pivots비공개 API다. openpyxl 4.x 에서 깨질 가능성 → 버전 핀 고정(openpyxl==3.1.5) 필요 여부 판단.
  • 워치리스트 시트 보호(protect())를 걸었을 때 사용자가 A:G 입력 영역만 편집 가능한지(unprotect_range 조합) 검증.
  • 파일 크기: 수만 행 × 26컬럼 + 조건부서식에서 xlsx 가 몇 MB 가 되는지 측정. 10MB 넘으면 전체 누적 을 별도 파일로 분리 검토.

B.4 접근 차단 · 재조사 필요

  • FDA 의 실제 DMF 목록 xlsx 다운로드 URL 을 찾는다(현재 안내 페이지 2개 모두 404).
  • Orange Book / Drugs@FDA 데이터 파일 페이지의 현재 유효 URL 을 찾는다(둘 다 404).
  • EDQM CEP 전체 다운로드 컬럼 목록을 원문에서 확인한다(현재 403, 검색 스니펫 기반).
  • Leucine FDA Tracker 의 실제 UI/필드를 확인한다(403). 워치리스트 UX 벤치마크에 유용.
  • PharmaCompass US DMF DB 의 컬럼·필터 구성을 확인한다(403).
  • Chandoo.org 및 Big Excel Energy 본문(403 ×3)을 다른 경로로 확인해 KPI 카드 폰트 크기 권고(2844pt)를 1차 출처로 검증한다.
  • PDA Letter 의 regulatory intelligence 성숙도 모델(403)을 확인해 스냅샷·로그 필드 설계를 보강한다.

B.5 운영

  • 06:00 실행 시 nedrug 서버가 응답하지 않을 때의 재시도·백오프 정책메타 시트 기록 형식 확정.
  • 전일 스냅샷이 없는 최초 실행(cold start)에서 오늘 변경분 시트를 어떻게 표시할지 결정(전량을 신규로 표시할지, 초기적재라는 8번째 상태를 둘지).
  • xlsx 생성 실패 시 Windows 알림 문구와 복구 안내 텍스트 확정.
  • 리포트 보관 정책(며칠치 xlsx 를 남길지)과 logs/archive/*.parquet 롤오프 임계값 확정.

이 문서와 확정 설계의 관계

이 문서는 근거 자료(research)이며, 최종 xlsx 스펙의 정본(SSOT)은 design/03-xlsx-report-spec.md 다. 절별 상태를 정리한다.

상태 비고
§0 한눈에 보기 부분 정정 시트 수·색·KPI 개수 서술을 "제안"으로 완화. 수치 자체는 §8.0 반영 후에도 유효
§2 설계 원칙(Stephen Few, 5초 규칙, KPI 배치, 색 최소화, 데이터잉크비율, 대시보드≠스토리, 차트 선택) 유효 출처 기반 일반 원칙. 00b 실측과 무관
§3 KPI 타일 해부 유효 예시 수치 일부를 00b 실측으로 갱신(§9.2 참조)
§4 규제 인텔리전스 실무 관점 유효
§5.15.6 (소스 3종, nedrug 15컬럼, 등록번호 체계, 법적 정의, 등록구분 값, 처리기간·수수료) 유효(역할 각주만 정정) nedrug 도메인 지식 자체는 정확하다. 다만 "diff 판정의 주 소스" 라는 역할 규정은 §5.1 각주로 정정
§5.7 OpenAPI 필드 — "API 는 diff 판정에 부족하다" 반증 → 정정 00b 실측으로 반증. 정정 내용은 §8.0
§5.8 시장 통계(데일리팜) 유효 외부 통계 인용. 00b 와 시점·모수가 달라 병기
§6 색 접근성 전체 유효, 단 §6.6 값은 충돌 표시 research/06 §7.1 과 상태색 배경 HEX 가 다르다. 정본은 design/03 이 확정
§7 해외 공개 데이터 벤치마크 유효
§8.0 API 필드 충분성 — 정정 신설 이번 정정의 핵심 근거
§8.1 공통 규칙 유효
§8.2 대시보드 유효(컬럼 스펙 없음, 영향 없음)
§8.3 오늘 변경분 컬럼표 정정(재작성) API 7필드 + 파생 필드 기준. 구안은 <details> 로 보존
§8.4 전체 누적 컬럼표 정정(재작성) 동일
§8.5 성분별 / §8.6 업체별 부분 유효 구조는 유효하나 신청인_표시·신청인_전체 등 컬럼명이 §8.3/8.4 의 업체명 표기와 다르다. design/03 확정 시 명칭 통일 필요
§8.7 워치리스트 유효
§8.8 스냅샷·로그 유효
§8.9 메타 유효 건수 검증 블록이 오히려 §8.0 의 756건 차이 실측과 정확히 맞아떨어진다
§8.10 탭색·링크 요약 유효
§9 와이어프레임 유효 예시 수치를 00b 실측으로 갱신
§10 피벗테이블 문제와 대안 유효
§11 구현 레시피 부분 정정 §11.4 diff 코드(DIFF_FIELDS, 취하 판정)를 §8.0 기준으로 정정. 나머지 코드의 컬럼명(업체키 등)은 design/03 확정 시 §8.3/8.4 명칭과 재정합 필요
§12 한국 제약 실무자 관점 유효
부록 A 출처 목록 유효
부록 B 미해결 질문 부분 해소 B.1 의 OpenAPI 최종변경일자/취소여부 항목 1건이 00b 로 해소됨(체크 표시). 나머지는 유지

남은 충돌(이 문서 밖에서 해결 필요):

  • 색 팔레트: research/06 §7.1 vs 본 문서 §6.6 — HEX 값 상이(§6.6 각주 참조).
  • 시트 구조: research/06 이 암시하는 시트 수(7개 내외)와 본 문서의 8시트 제안이 다르다. 최종 확정은 design/03-xlsx-report-spec.md.