pharmacy-pos-qr-system/docs/ANIMAL_DRUG_APC_MAPPING.html
thug0bin 29648e3a7d feat: yakkok.com 제품 이미지 크롤러 + 어드민 페이지
크롤러 (utils/yakkok_crawler.py):
- yakkok.com에서 제품 검색 및 이미지 추출
- MSSQL 오늘 판매 품목 자동 조회
- base64 변환 후 SQLite 저장
- CLI 지원 (--today, --product)

DB (product_images.db):
- 바코드, 제품명, 이미지(base64), 상태 저장
- 크롤링 로그 테이블

어드민 페이지 (/admin/product-images):
- 이미지 목록/검색/필터
- 통계 (성공/실패/대기)
- 상세 보기/삭제
- 오늘 판매 제품 일괄 크롤링

API:
- GET /api/admin/product-images
- GET /api/admin/product-images/<barcode>
- POST /api/admin/product-images/crawl-today
- DELETE /api/admin/product-images/<barcode>
2026-03-02 23:19:52 +09:00

515 lines
12 KiB
HTML

<!DOCTYPE html>
<html lang="ko">
<head>
<meta charset="UTF-8">
<title>스마트헬스케어 사업제안서</title>
<style>
@import url('https://fonts.googleapis.com/css2?family=Noto+Sans+KR:wght@400;500;600;700&display=swap');
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: 'Noto Sans KR', -apple-system, BlinkMacSystemFont, sans-serif;
line-height: 1.8;
color: #1e293b;
max-width: 210mm;
margin: 0 auto;
padding: 20mm;
background: #fff;
}
h1 {
font-size: 28px;
font-weight: 700;
color: #6366f1;
margin: 40px 0 20px;
padding-bottom: 10px;
border-bottom: 3px solid #6366f1;
}
h2 {
font-size: 22px;
font-weight: 700;
color: #334155;
margin: 35px 0 15px;
padding-bottom: 8px;
border-bottom: 2px solid #e2e8f0;
}
h3 {
font-size: 18px;
font-weight: 600;
color: #475569;
margin: 25px 0 12px;
}
h4 {
font-size: 16px;
font-weight: 600;
color: #64748b;
margin: 20px 0 10px;
}
p {
margin: 12px 0;
text-align: justify;
}
blockquote {
background: linear-gradient(135deg, #f8fafc 0%, #f1f5f9 100%);
border-left: 4px solid #6366f1;
padding: 16px 20px;
margin: 20px 0;
border-radius: 0 8px 8px 0;
font-style: italic;
color: #475569;
}
code {
background: #f1f5f9;
padding: 2px 6px;
border-radius: 4px;
font-family: 'Consolas', 'Monaco', monospace;
font-size: 13px;
color: #dc2626;
}
pre {
background: #1e293b;
color: #e2e8f0;
padding: 20px;
border-radius: 12px;
overflow-x: auto;
margin: 20px 0;
font-size: 12px;
line-height: 1.6;
}
pre code {
background: none;
color: inherit;
padding: 0;
}
table {
width: 100%;
border-collapse: collapse;
margin: 20px 0;
font-size: 14px;
}
th {
background: linear-gradient(135deg, #6366f1 0%, #8b5cf6 100%);
color: #fff;
padding: 12px 16px;
text-align: left;
font-weight: 600;
}
td {
padding: 12px 16px;
border-bottom: 1px solid #e2e8f0;
}
tr:nth-child(even) {
background: #f8fafc;
}
ul, ol {
margin: 15px 0;
padding-left: 25px;
}
li {
margin: 8px 0;
}
hr {
border: none;
height: 2px;
background: linear-gradient(90deg, #6366f1, #8b5cf6, #ec4899);
margin: 40px 0;
border-radius: 2px;
}
strong {
color: #334155;
font-weight: 600;
}
em {
color: #64748b;
}
/* 첫 페이지 타이틀 */
h1:first-of-type {
font-size: 32px;
text-align: center;
border-bottom: none;
margin-top: 60px;
margin-bottom: 10px;
}
h1:first-of-type + blockquote {
text-align: center;
border-left: none;
background: none;
font-size: 18px;
margin-bottom: 60px;
}
/* 프린트 스타일 */
@media print {
body {
padding: 15mm;
}
pre {
white-space: pre-wrap;
word-wrap: break-word;
}
h1, h2, h3 {
page-break-after: avoid;
}
table, pre, blockquote {
page-break-inside: avoid;
}
}
/* 페이지 구분 */
.page-break {
page-break-before: always;
}
</style>
</head>
<body>
<h1 id="apc">동물약 APC 매핑 가이드</h1>
<blockquote>
<p>최종 업데이트: 2026-03-02</p>
</blockquote>
<h2 id="_1">개요</h2>
<p>POS(PIT3000)의 동물약 제품을 APDB의 APC 코드와 매핑하여 제품 정보(용법, 용량, 주의사항) 및 이미지를 표시하기 위한 작업 가이드.</p>
<hr />
<h2 id="_2">현재 상태</h2>
<h3 id="_3">매핑 현황</h3>
<table>
<thead>
<tr>
<th>구분</th>
<th>개수</th>
<th>비율</th>
</tr>
</thead>
<tbody>
<tr>
<td>동물약 총</td>
<td>39개</td>
<td>100%</td>
</tr>
<tr>
<td>APC 매핑됨</td>
<td>7개</td>
<td>18%</td>
</tr>
<tr>
<td><strong>APC 미매핑</strong></td>
<td><strong>32개</strong></td>
<td><strong>82%</strong></td>
</tr>
</tbody>
</table>
<h3 id="_4">매핑 완료 제품</h3>
<table>
<thead>
<tr>
<th>POS 제품명</th>
<th>DrugCode</th>
<th>APC</th>
</tr>
</thead>
<tbody>
<tr>
<td>(판)복합개시딘</td>
<td>LB000003140</td>
<td>0231093520106</td>
</tr>
<tr>
<td>안텔민킹(5kg이상)</td>
<td>LB000003158</td>
<td>0230237810109</td>
</tr>
<tr>
<td>안텔민뽀삐(5kg이하)</td>
<td>LB000003157</td>
<td>0230237010107</td>
</tr>
<tr>
<td>파라캅L(5kg이상)</td>
<td>LB000003159</td>
<td>0230338510101</td>
</tr>
<tr>
<td>파라캅S(5kg이하)</td>
<td>LB000003160</td>
<td>0230347110106</td>
</tr>
<tr>
<td>세레니아정16mg(개멀미약)</td>
<td>LB000003353</td>
<td>0231884610109</td>
</tr>
<tr>
<td>세레니아정24mg(개멀미약)</td>
<td>LB000003354</td>
<td>0231884620107</td>
</tr>
</tbody>
</table>
<hr />
<h2 id="_5">매핑 구조</h2>
<h3 id="_6">데이터베이스 연결</h3>
<pre><code>MSSQL (192.168.0.4\PM2014) PostgreSQL (192.168.0.87:5432)
┌─────────────────────────┐ ┌─────────────────────────┐
│ PM_DRUG.CD_GOODS │ │ apdb_master.apc │
│ - DrugCode │ │ - apc (PK) │
│ - GoodsName │ │ - product_name │
│ - BARCODE │ │ - image_url1 │
│ │ │ - llm_pharm (JSONB) │
├─────────────────────────┤ └─────────────────────────┘
│ PM_DRUG.CD_ITEM_UNIT_ │
│ MEMBER │
│ - DRUGCODE (FK) │
│ - CD_CD_BARCODE ◀───────┼── APC 코드 저장 (023%로 시작)
│ - CHANGE_DATE │
└─────────────────────────┘
</code></pre>
<h3 id="apc_1">APC 매핑 방식</h3>
<ol>
<li><code>CD_ITEM_UNIT_MEMBER</code> 테이블에 <strong>추가 바코드</strong>로 APC 등록</li>
<li>기존 바코드는 유지, APC를 별도 레코드로 INSERT</li>
<li>APC 코드는 <code>023%</code>로 시작 (식별자)</li>
</ol>
<hr />
<h2 id="11">1:1 매핑 가능 후보</h2>
<h3 id="1">✅ 확실한 매핑 (1개)</h3>
<table>
<thead>
<tr>
<th>POS 제품명</th>
<th>DrugCode</th>
<th>APC</th>
<th>APDB 제품명</th>
<th>이미지</th>
</tr>
</thead>
<tbody>
<tr>
<td><strong>제스타제(10정)</strong></td>
<td>LB000003146</td>
<td>8809720800455</td>
<td>제스타제</td>
<td>✅ 있음</td>
</tr>
</tbody>
</table>
<h3 id="1_1">⚠️ 검토 필요 (1개)</h3>
<table>
<thead>
<tr>
<th>POS 제품명</th>
<th>DrugCode</th>
<th>APC 후보</th>
<th>비고</th>
</tr>
</thead>
<tbody>
<tr>
<td>안텔민</td>
<td>S0000001</td>
<td>0230237800003</td>
<td>"안텔민킹"과 "안텔민뽀삐"는 이미 별도 매핑됨. 이 제품이 무엇인지 확인 필요</td>
</tr>
</tbody>
</table>
<h3 id="apdb-3">❌ APDB에 없음 (3개)</h3>
<table>
<thead>
<tr>
<th>POS 제품명</th>
<th>사유</th>
</tr>
</thead>
<tbody>
<tr>
<td>(판)클라펫정50(100정)</td>
<td>APDB엔 "클라펫 정"만 있음 (함량 불일치)</td>
</tr>
<tr>
<td>넥스가드xs(2~3.5kg)</td>
<td>사이즈별 APC 없음</td>
</tr>
<tr>
<td>캐치원캣(2.5~7.5kg)/고양이</td>
<td>APDB에 캐치원 자체가 없음</td>
</tr>
</tbody>
</table>
<hr />
<h2 id="1n-27">1:N 매핑 필요 제품 (27개)</h2>
<p>사이즈별로 세분화된 제품들. 하나의 APDB APC에 여러 POS 제품을 매핑해야 함.</p>
<h3 id="_7">브랜드별 현황</h3>
<table>
<thead>
<tr>
<th>브랜드</th>
<th>POS 제품 수</th>
<th>APDB 존재</th>
<th>비고</th>
</tr>
</thead>
<tbody>
<tr>
<td>다이로하트</td>
<td>3개 (SS/S/M)</td>
<td></td>
<td>다이로하트 츄어블 정</td>
</tr>
<tr>
<td>하트세이버</td>
<td>4개 (mini/S/M/L)</td>
<td></td>
<td>하트세이버 플러스 츄어블</td>
</tr>
<tr>
<td>하트웜솔루션</td>
<td>2개 (S/M)</td>
<td></td>
<td>APDB에 없음</td>
</tr>
<tr>
<td>리펠로</td>
<td>2개 (S/M)</td>
<td></td>
<td>리펠로액 (이미지 있음!)</td>
</tr>
<tr>
<td>캐치원</td>
<td>5개 (SS/S/M/L/캣)</td>
<td></td>
<td>APDB에 없음</td>
</tr>
<tr>
<td>셀라이트</td>
<td>5개 (SS/S/M/L/XL)</td>
<td></td>
<td>셀라이트 액</td>
</tr>
<tr>
<td>넥스가드</td>
<td>2개 (xs/L)</td>
<td></td>
<td>넥스가드 스펙트라</td>
</tr>
<tr>
<td>가드닐</td>
<td>3개 (S/M/L)</td>
<td></td>
<td>가드닐 액</td>
</tr>
<tr>
<td>심피드</td>
<td>2개 (M/L)</td>
<td></td>
<td>APDB에 없음</td>
</tr>
<tr>
<td>하트캅</td>
<td>1개</td>
<td></td>
<td>하트캅-츄어블 정</td>
</tr>
</tbody>
</table>
<hr />
<h2 id="apdb">APDB 통계</h2>
<table>
<thead>
<tr>
<th>항목</th>
<th>수치</th>
</tr>
</thead>
<tbody>
<tr>
<td>전체 APC</td>
<td>16,326개</td>
</tr>
<tr>
<td>이미지 있음</td>
<td>73개 (0.4%)</td>
</tr>
<tr>
<td>LLM 정보 있음</td>
<td>81개 (0.5%)</td>
</tr>
<tr>
<td>동물 관련 키워드</td>
<td>~200개</td>
</tr>
</tbody>
</table>
<p>⚠️ <strong>주의:</strong> APDB에 이미지가 거의 없음. 이미지 표시가 목적이라면 다른 소스 필요.</p>
<hr />
<h2 id="_8">매핑 스크립트</h2>
<h3 id="_9">매핑 후보 찾기</h3>
<pre><code class="language-bash">python backend/scripts/batch_apc_matching.py
</code></pre>
<h3 id="11_1">1:1 매핑 가능 후보 추출</h3>
<pre><code class="language-bash">python backend/scripts/find_1to1_candidates.py
</code></pre>
<h3 id="_10">매핑 실행 (수동)</h3>
<pre><code class="language-python"># backend/scripts/batch_insert_apc.py 참고
MAPPINGS = [
('제스타제(10정)', 'LB000003146', '8809720800455'),
]
</code></pre>
<h3 id="insert">INSERT 쿼리 예시</h3>
<pre><code class="language-sql">INSERT INTO CD_ITEM_UNIT_MEMBER (
DRUGCODE, CD_CD_UNIT, CD_NM_UNIT, CD_MY_UNIT, CD_IN_UNIT,
CD_CD_BARCODE, CD_CD_POS, CHANGE_DATE
) VALUES (
'LB000003146', -- DrugCode
'015', -- 단위코드
1.0, -- 단위명
&lt;기존값&gt;, -- CD_MY_UNIT (기존 레코드에서 복사)
&lt;기존값&gt;, -- CD_IN_UNIT (기존 레코드에서 복사)
'8809720800455', -- APC 바코드
'',
'20260302' -- 변경일자
)
</code></pre>
<hr />
<h2 id="_11">다음 단계</h2>
<ol>
<li><strong>제스타제</strong> 1:1 매핑 실행</li>
<li><strong>안텔민(S0000001)</strong> 제품 확인 후 결정</li>
<li>1:N 매핑 정책 결정 (사이즈별 제품 → 동일 APC?)</li>
<li>이미지 소스 대안 검토 (필요시)</li>
</ol>
<hr />
<h2 id="_12">관련 파일</h2>
<ul>
<li><code>backend/db/dbsetup.py</code> - DB 연결 설정</li>
<li><code>backend/scripts/batch_apc_matching.py</code> - 매칭 후보 찾기</li>
<li><code>backend/scripts/batch_insert_apc.py</code> - 매핑 실행</li>
<li><code>backend/scripts/find_1to1_candidates.py</code> - 1:1 후보 추출</li>
<li><code>backend/app.py</code> - <code>_get_animal_drugs()</code>, <code>_get_animal_drug_rag()</code></li>
</ul>
</body>
</html>