From 54f8f83a7a432970ce75091e04cfd0a3737dd449 Mon Sep 17 00:00:00 2001 From: hbyang Date: Mon, 14 Sep 2026 10:27:37 +0900 Subject: [PATCH] =?UTF-8?q?=EB=B0=B0=ED=8F=AC=EC=9A=A9=20=EC=97=91?= =?UTF-8?q?=EC=85=80=EC=97=90=20DB=20=EC=A0=84=20=ED=85=8C=EC=9D=B4?= =?UTF-8?q?=EB=B8=94=20=EB=B0=98=EC=98=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit merchant_keyword(업체↔키워드 연결)와 generation_run(생성 감사 로그)이 빠져 있었다. 시트 2개를 추가해 DB 7개 테이블을 모두 담는다. 시트별 행수를 DB 와 대조해 전부 일치 확인: 키워드 7,093 / 지역 70 / 업종 9 / 업체 4 / 업체키워드 11 / QA 20 / 생성이력 4 Co-Authored-By: Claude Opus 5 (1M context) --- README.md | 18 +++++++++++++++++- data/배포용_키워드_DB덤프.xlsx | Bin 737096 -> 740072 bytes scripts/export-db-xlsx.py | 26 ++++++++++++++++++++++++++ 3 files changed, 43 insertions(+), 1 deletion(-) 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 0d51a7ef42e68dc756ab534d16fb1f134d121628..b4375ce340e12f58b2a401c4627a308f286250b7 100644 GIT binary patch delta 6549 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 D4~bWt 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천 행이라 엑셀에 담을 수 없고 담아도 못 읽는다.'),