__ __ _ ____ _ _ _ _ _
\ \ / /_ _ _ _ __ | | | _ \(_) | | / _|___ ___ _ __| |_
\ \/\/ / _` | '_/ _` | | |_) | | | | | (__/ _ / -_) '_ | / /
\_/\_/\__,_|_| \__,_| | __/|_|_|_| \___\___\___| .__|_\_\
|_| 💊 |_|
ward-pillcheck — 병동 지참약 식별 PWA
병동 간호사가 환자가 가져온 약(지참약) 을 현장에서 빠르게 식별·정리하는 모바일 PWA. 약 이름을 몰라도 색 + 모양 + 각인 + 마크로 거꾸로 찾고, 주사제·수기 약도 더해 환자별 복약 리스트를 만들어 정렬·금기점검·인계 복사까지. 데이터는 기기에만.
🔗 앱: https://yuangunn.github.io/ward-pillcheck/
병동에서 입원 환자의 지참약(가져온 약) 을 식별하고 복약 리스트로 정리해 인계까지 돕는 모바일 PWA입니다. 지참약은 보통 이름표 없이 낱알만 오므로, 식약처 공공데이터를 활용해 색·모양·각인·마크(그림) 같은 겉모습으로 거꾸로 찾는 데 초점을 맞췄습니다.
- 🔍 식별 방법: ① 실물검색(색·모양·각인·마크 갤러리) ② 이름검색 ③ 주사제(이름) ④ 직접입력(수기)
- 🗂 데이터: 식약처 낱알식별·허가정보·e약은요·DUR. 낱알·주사제·마크는 기기에 번들로 내려받아 오프라인·즉시 검색(매일 자동 갱신)
- 🛡 안전: 환자 리스트에 대해 DUR 금기·중복 점검(병용금기·임부·노인·연령·효능군중복)
- 🔒 개인정보: 실명·주민번호·등록번호 입력란 없음(익명 라벨만), 모든 기록은 기기 localStorage 에만 — 외부 서버 전송 없음
- 📱 UX: 모바일 세로 우선, 한 손 조작. 홈(환자) → 환자(복약 리스트) → 검색 흐름, Toss 스타일 디자인, 라이트/다크(기기 설정 자동), 설치형 PWA(오프라인 셸)
- ⚖️ 주의: 처방·복약지도 자동화가 아닌 식별 보조 도구이며 최종 확인은 의료진 책임
| 기능 | 설명 |
|---|---|
| 실물 검색 | 색 스와치 + 모양 칩 + 앞면 각인으로 거꾸로 찾기(기본 탭). 각인은 실제 인쇄 각인만(마크는 별도) |
| 마크로 찾기(골라서) | 약에 새겨진 그림(마크)을 이미지 갤러리에서 골라 검색(번들 마크 이미지 431종) |
| 마크로 찾기(그려서) | 캔버스에 마크를 그리면 번들 마크와 온디바이스 형상 유사도로 닮은 후보 제시(서버·API 0) |
| 이름 검색 | 품목명으로 검색 |
| 외용·주사제 검색 | 흡입제(벤토린)·좌약(둘코락스)·연고·점안 등 외용약과 주사제(인슐린)를 이름으로 검색(허가정보 번들). 낱알 모양 없는 약 대응 |
| 직접 입력 | 목록에 없는 약·주사제를 수기로(이름 + 용량 단위 T/U/mL/회분/포 + 용법·시점) |
| 결과 카드 | 품목명·업체·색/모양/각인(앞·뒤, 검색어 강조)·분할선·제형. 약 글리프 탭 → 확대(실사진 원본 링크) |
| 상세 정보 | 실물사진 + 외형요약(성분·분류·구분·제형·색·모양·각인·분할선) + 효능·용법·주의·상호작용·이상반응·보관(e약은요→허가정보 폴백, 출처 표시) + 사진 원본 링크 |
| 환자 리스트 추가 | 정제수(0.5 단위)·용법·투약시점. 색/모양/각인 자동채움(수정 가능) |
| 투약시점 多 | 용법에 맞춰 시점 칸 자동 — QD=1, BID=2, TID=3, QID=4 + 직접입력 |
| 환자 관리 | 익명 라벨 추가/전환, 제목 옆 > 또는 ⋯ 로 이름변경·삭제 |
| 정렬 | 기본순 / 용법순 / 시점순 (환자별 저장) |
| 인계 복사/공유 | 복약 리스트를 한 줄 포맷 텍스트로 클립보드 복사(지원 시 시스템 공유) |
| 금기·중복 점검(DUR) | 리스트에 대해 병용금기·임부·노인·연령 금기·효능군 중복 자동 점검 |
| DB 정보·업데이트 | 홈에서 약품 데이터 건수·최근 업데이트일 표시 + 수동 업데이트 |
| 의약품 검색(독립) | 홈 상단 [지참약 식별 / 의약품 검색] 탭. 환자와 무관하게 경구약·주사제·외용약을 이름으로 찾아 상세만 열람(약 누르면 상세 시트, 사진 탭 시 확대) |
| 약 상세 보기 | 환자 리스트의 약 그림 탭 또는 의약품 검색 결과 탭 → 상세 시트. 사진 탭하면 인앱 확대 |
| 다크 모드 | 헤더 🌙/☀️ 토글(제목 탭과 같은 줄 정렬), 기기 설정 자동 감지 |
| 자동 업데이트 | 새 배포 감지 시 서비스워커가 자동 적용·새로고침(설치형 PWA도 수동 강제새로고침 불필요) |
저장 항목 한 줄 표시 포맷:
아스피린장용정100mg 1T QD 아침식후 (흰/원형/Bayer)
메트포르민500mg 1T BID 아침식후,저녁식후 (흰/원형/MF500)
란투스주솔로스타펜 10U BID 아침식전,저녁식전
Vite + React + TypeScript · vite-plugin-pwa · @dnd-kit · 상태는 Context + useReducer
- localStorage. 데이터 접근은 추상화 계층(
src/api) 뒤에 두고, 검색은 기기 번들 데이터셋(IndexedDB/정적파일), 상세·이미지는 Cloudflare Worker 프록시로 분리.
src/
api/ types · workerClient · bundledClient · mockClient · index(팩토리)
dataset(번들 낱알/마크 IndexedDB) · dur(금기점검) · permit(주사제·허가)
marksim(그려서 마크 찾기 — 온디바이스 형상 유사도)
domain/ models · format(한 줄 포맷) · sort(정렬)
constants/ frequency(용법·시점칸) · timing · appearance(색/모양)
state/ store(Context+reducer) · persist(localStorage) · useDataset
design/ theme(토큰·라이트/다크) · Icon(+PillGlyph/MarkGlyph) · ui · screens · sheets
worker/ Cloudflare Worker(인증키 보관 + CORS + 이미지 프록시)
scripts/ build-dataset.mjs(낱알·주사제·마크 번들 생성)
npm install
npm run dev # http://localhost:5173 (VITE_API_BASE 없으면 데모/목 모드)
npm run build # 타입체크 + 프로덕션 빌드
npm test # Vitest 단위/통합 (60+ 케이스)
npm run e2e # Playwright 실브라우저 E2E (최초 1회 npx playwright install chromium)VITE_API_BASE 가 비어 있으면 목(mock) 모드 — 인증키 없이 샘플 데이터로 UI/흐름 확인.
CI(.github/workflows/ci.yml): PR/푸시마다 typecheck + Vitest + 빌드 + E2E.
공공데이터포털에서 아래를 활용신청(개발계정·자동승인) 후, 마이페이지의 일반 인증키(Decoding) 를 사용합니다(코드/저장소엔 절대 넣지 않음).
| 데이터 | ID | 용도 |
|---|---|---|
| 의약품 낱알식별 정보 | 15057639 | 색·모양·각인·마크 검색 |
| 의약품개요정보(e약은요) | 15075057 | 효능/용법/주의 상세 |
| 의약품 제품 허가정보 | 15095677 | 주사제 이름검색 · 상세 폴백 |
| 의약품안전사용서비스(DUR) 품목정보 | 15059486 | 금기·중복 점검 |
| (선택) DUR 성분정보 | 15056780 | 성분 기준 보조 |
엔드포인트 버전 suffix 는 갱신될 수 있어 Worker 의
*_ENDPOINT환경변수로 덮어쓸 수 있습니다.
cd worker
npm install
npx wrangler login
npx wrangler secret put SERVICE_KEY # data.go.kr Decoding 키
npm run deploy출력된 https://<name>.<subdomain>.workers.dev 가 프론트의 VITE_API_BASE 값입니다.
운영 시 wrangler.toml 의 ALLOW_ORIGIN 을 Pages 도메인으로 제한 권장.
Worker 엔드포인트
| 경로 | 식약처 API / 역할 |
|---|---|
GET /api/pills |
낱알식별 getMdcinGrnIdntfcInfoList03 |
GET /api/detail?itemSeq= |
e약은요 getDrbEasyDrugList |
GET /api/permit?itemSeq=&item_name= |
제품 허가정보 — 효능/용법/주의 폴백 + 주성분(허가목록 ITEM_SEQ 일치 매칭) |
GET /api/drugsearch?item_name=&inj=1 |
허가정보 이름검색(주사제) |
GET /api/dur?itemSeq= |
DUR 품목정보(병용금기·임부·노인·연령·효능군중복) |
GET /api/img?u= |
nedrug 이미지 프록시(헤더 정리 + CORS) |
main push 시 .github/workflows/deploy.yml 가 데이터셋 빌드 → 빌드 → 배포.
- Settings → Pages → Source: GitHub Actions
- Settings → … → Variables:
VITE_API_BASE= Worker URL - Settings → … → Secrets:
SERVICE_KEY= data.go.kr Decoding 키(데이터셋 빌드용) - base 경로는 저장소명 기준
/ward-pillcheck/(다르면VITE_BASE)
식약처 API는 색/모양/각인 검색 파라미터를 서버에서 무시하는 경우가 있어, 전체 데이터를
빌드 때 받아 기기에서 직접 검색합니다. scripts/build-dataset.mjs 가 생성:
| 파일 | 내용 | 규모(예시) |
|---|---|---|
pills.json |
낱알식별 전체(색/모양/각인/마크코드/분할선…) + 주성분(허가정보 결합) | ~25,000건 |
injections.json |
허가정보에서 추린 외용약·주사제(흡입제/좌약/연고/점안/주사) | ~수천건 |
marks.json + marks/*.gif |
고유 마크 코드 + 마크 이미지 번들(SW 프리캐시) | ~431종, ~수MB |
details.json.gz |
전 품목 허가사항(효능·효과/용법·용량/주의) — 전문약 포함. gzip | 수십 MB(gz) |
dur.json.gz |
DUR 전체 룰셋(병용금기·임부·노인·연령·효능군중복) — 로컬 매칭 | gzip |
- 검색 번들은 첫 실행 시 IndexedDB 에 캐시(메타 바뀌면 자동 갱신).
details/dur는 용량이 커서 설정에서 받을 때만 다운로드(gzip →DecompressionStream해제 → IndexedDB). 단건 조회로 자동 다운로드하지 않음.- 매일 cron(
0 18 * * *UTC) 으로 재생성·재배포. - 허가목록은
totalCount기준 전수 페이징(누락 방지).
상세(효능/용법/주의)·성분·DUR을 허가사항 문서(전수) 로 번들화해, 워커(공용 인터넷) 없이도 검색·상세·금기점검이 됩니다. 상세 소스는 e약은요(일부 전문약 누락) 대신 허가정보 허가사항.
- 설정(⚙️) → "전체 의약품 데이터 받기": 검색+상세+DUR을 기기에 적재 → 이후 폐쇄망/오프라인 동작.
- getDetail: 받아둔 허가사항(전수) 우선 → 온라인이면 e약은요로 상호작용/부작용/보관 보강.
- 실물사진(선택): 본 사진은 SW(
pill-photos, CacheFirst)로 자동 보관. 설정에서 전부 미리 받기(대용량 ~수 GB, 중단 가능)도 가능 — 식약처에서 폰으로 직접(레포/Pages 호스팅 없음). - 사용 흐름: 인터넷 되는 곳에서 1회 받아두면 병동 인트라넷/오프라인에서 그대로 사용.
- 처방·복약 지도 자동화 아님 — 식별 보조 도구, 최종 확인은 의료진 책임
- 환자 식별정보(실명·등록번호 등) 수집/전송 없음