diff --git a/README.md b/README.md index 2853fe9..8e3273e 100644 --- a/README.md +++ b/README.md @@ -71,7 +71,7 @@ DB 가 기준이다. 데이터셋 JSON 은 생성 원본일 뿐 적재분과 완 | 산출물 | 명령 | 용도 | |---|---|---| -| `data/배포용_키워드_DB덤프.xlsx` | `npm run db:export-xlsx` | 사람이 읽고 검수하는 용도. 6시트 | +| `data/배포용_키워드_DB덤프.xlsx` | `npm run db:export-xlsx` | **DB 7개 테이블 전부**. 8시트 | | `data/ontology-dump.sql.gz` | `npm run db:dump` | **임베딩 포함 그대로 복원**. 13MB, git 제외 | ### A. pg_dump 복원 (권장) @@ -94,6 +94,22 @@ npm run dataset:ingest && npm run dataset:ingest-nationwide 텍스트에서 임베딩을 다시 만든다. 최초 1회 모델 다운로드(약 50초) + 임베딩 약 15초. 같은 모델이면 값이 동일하게 나오므로 A 와 결과가 같다. +### 엑셀 시트 (DB 테이블과 1:1) + +| 시트 | 테이블 | 행 | +|---|---|---:| +| 키워드 | `keyword` | 7,093 | +| 지역 | `region` | 70 | +| 업종 | `industry` | 9 | +| 업체 | `merchant` | 4 | +| 업체키워드 | `merchant_keyword` | 11 | +| QA(AEO) | `qa_pair` | 20 | +| 생성이력 | `generation_run` | 4 | +| 배포가이드 | — | 25 | + +임베딩만 담지 않는다 (384 float × 7천 행). `[임베딩]` 열에 보유 여부만 표시하며, +같은 모델로 재생성하면 동일하게 복원된다. + ### 현재 적재 내용 | 출처 | 건수 | 내용 | diff --git a/data/배포용_키워드_DB덤프.xlsx b/data/배포용_키워드_DB덤프.xlsx index 0d51a7e..b4375ce 100644 Binary files a/data/배포용_키워드_DB덤프.xlsx and b/data/배포용_키워드_DB덤프.xlsx differ diff --git a/scripts/export-db-xlsx.py b/scripts/export-db-xlsx.py index 4bd65b9..589c197 100644 --- a/scripts/export-db-xlsx.py +++ b/scripts/export-db-xlsx.py @@ -92,6 +92,30 @@ sheet(wb, 'QA(AEO)', ['업체', '질문', '답변', '상태'], FROM qa_pair q JOIN merchant m ON m.id=q.merchant_id ORDER BY m.name, q.created_at"""), [16, 44, 70, 10]) +# 5-b) 업체↔키워드 연결 +sheet(wb, '업체키워드', + ['업체', '키워드', '관련도', '상태', '출처', '노출수', '클릭수', 'CTR', '근거'], + query("""SELECT m.name, k.canonical, round(mk.relevance::numeric,2), mk.status::text, + mk.source, mk.impressions, mk.clicks, round(mk.ctr::numeric,4), + COALESCE(mk.rationale,'') + FROM merchant_keyword mk + JOIN merchant m ON m.id=mk.merchant_id + JOIN keyword k ON k.id=mk.keyword_id + ORDER BY m.name, mk.relevance DESC"""), + [16, 30, 9, 10, 10, 10, 9, 9, 28]) + +# 5-c) 생성 이력 (감사 로그) +sheet(wb, '생성이력', + ['업체', 'provider', 'model', '프롬프트버전', '트리거', '상태', '통계', '시작', '종료'], + query("""SELECT COALESCE(m.name,''), g.provider, g.model, g.prompt_version, + g.trigger, g.status, g.stats::text, + to_char(g.started_at,'YYYY-MM-DD HH24:MI'), + COALESCE(to_char(g.finished_at,'YYYY-MM-DD HH24:MI'),'') + FROM generation_run g + LEFT JOIN merchant m ON m.id=g.merchant_id + ORDER BY g.started_at DESC"""), + [16, 10, 20, 14, 12, 10, 60, 17, 17]) + # 6) 배포 가이드 counts = collections.Counter(r[8] for r in kw) guide = [ @@ -99,6 +123,8 @@ guide = [ ('', f"벡터 DB(keyword 테이블)에 실제 적재된 {len(kw):,}건 전부. 데이터셋 JSON 이 아니라 DB 가 기준이다."), ('', '출처별: ' + ' · '.join(f'{k} {v:,}' for k, v in counts.most_common())), ('', 'dataset = 군산 상세(매칭 엔진 개발용) / nationwide = 전국 54개 지역'), + ('', 'DB 의 7개 테이블을 모두 담았다: keyword / region / industry / merchant /'), + ('', 'merchant_keyword / qa_pair / generation_run.'), ('', ''), ('임베딩은 왜 없나', ''), ('', '384개 float × 7천 행이라 엑셀에 담을 수 없고 담아도 못 읽는다.'),