AIP로 생성된 디자인을 CSV로 올려, 전체 페이지를 맥락과 함께 봅니다.
이름이 part- 로 시작하는 파일 하나만 체크해
Download. (Size 가 MB 로 표시된 그 파일입니다 — 이름이 길고
매번 바뀌지만 상관없습니다. _ 로 시작하는 0 B 파일들은 표시용)
지난달이 필요하면 위쪽 경로에서 aip-design-eval-gz 를
눌러 다른 달을 고르세요.
사내망 또는 VPN 연결 상태여야 열립니다.
압축을 풀지 마세요. .csv.gz 그대로 놓으면 앱이 알아서 풉니다.
여러 달을 함께 보려면 파일 여러 개를 한꺼번에 놓으면 됩니다.
데이터브릭스 CLI가 설치돼 있으면 한 줄로 받습니다.
코딩은 필요 없습니다. 쿼리를 복사해 붙여넣고, 날짜만 바꾸고, 실행하면 끝입니다.
SQL Editor 에 붙여넣습니다
오른쪽 위에서 COMMON_USE_SQL_CLUSTER 를 고르세요. 그게 실행할 컴퓨터입니다.
그 아래는 손대지 않아도 됩니다.
| 이 줄 | 이렇게 바꾸면 |
|---|---|
start_d · end_d |
보고 싶은 기간. 그대로 두면 7월 14일부터 어제까지 |
min_pages |
3 으로 하면 3장 미만인 디자인은 빠집니다 |
only_with_feedback |
true 로 하면 NPS·VoC 를 남긴 것만 — 결과가 훨씬 작아집니다 |
only_with_comment |
true 로 하면 VoC(코멘트)를 쓴 것만 (위보다 더 적음) |
only_country |
'KR' 로 하면 한국 사용자만. 'JP' 는 일본 |
prompt_maxlen |
1500 처럼 적으면 긴 요청문을 잘라 파일을 줄입니다 |
실행은 ⌘↵. 결과 표 위쪽의 내려받기 버튼으로 CSV 를 받으세요.
위 쿼리를 그대로 쓰면 이미 맞습니다.
design_id · page_url 을
맨 앞(1·2번째 컬럼)에 두세요.아래 예시 표처럼 만들면 됩니다. 한 행 = 한 페이지.
결과 CSV 예시| design_id | page_url | 페이지순서 | 생성일 | 생성방식 |
|---|---|---|---|---|
| D-1001 | …/D-1001/p1.webp | 1 | 2026-07-23 | 컴포넌트 |
| D-1001 | …/D-1001/p2.webp | 2 | 2026-07-23 | 컴포넌트 |
| D-1002 | …/D-1002/p1.webp | 1 | 2026-07-22 | 템플릿 |
필수 (design_id · page_url)
선택 — 넣으면 검색 필터.
한 행 = 한 페이지 (D-1001은 2장이라 두 행).
내려받기가 안 되거나 파일이 잘려요
결과가 너무 큰 경우입니다. 먼저 3번에서 only_with_feedback 을
true 로 바꿔 보세요. 대부분 이걸로 해결됩니다.
그래도 전량이 필요하면 아래 두 줄을 쿼리 맨 앞에 붙여 실행하세요.
결과가 화면이 아니라 파일로 저장됩니다. 저장된 파일은 ① 방법으로 받습니다.
<본인ID> 는 본인 사번/아이디로 바꾸세요.
INSERT OVERWRITE DIRECTORY '/Volumes/temp/<본인ID>_temp/exports/my-aip-eval'
USING CSV OPTIONS ('header'='true', 'escape'='"')
파일이 여러 개로 쪼개지면, 쿼리의 첫 SELECT 바로 뒤에
/*+ COALESCE(1) */ 를 넣으면 하나로 합쳐집니다.
컬럼을 더 넣고 싶어요
쿼리 아래쪽 ⑧ ⑨ ⑩ 표시가 있는 자리에
예시가 주석으로 달려 있습니다. 붙일 수 있는 데이터 목록은 쿼리 맨 위에 있습니다.
알아둘 것
NPS·VoC 는 디자인을 완성한 시점에 남긴 것만 붙습니다.
요청문(프롬프트)은 파일을 줄이려고 디자인의 첫 페이지 줄에만 들어 있습니다.
-- ─────────────────────────────────────────────────────────────────────────────
-- AIP 디자인 평가 뷰어용 CSV 추출
--
-- 사용법: 아래 ▼ 파라미터만 고쳐서 Databricks SQL 에디터에서 실행 → CSV 다운로드 → 앱에 업로드
--
-- 출력: 한 행 = 한 페이지. 1·2번째 컬럼이 design_id · page_url (앱 필수 계약)
-- 나머지 컬럼은 앱에서 자동으로 검색 필터가 된다
--
-- ⚠️ 긴 텍스트(프롬프트)는 디자인의 첫 페이지 행에만 채우고 나머지는 빈칸이다.
-- 모든 행에 넣으면 페이지 수만큼(평균 12.8배) 중복돼 CSV가 7.6GB로 폭발한다.
--
-- ⚠️ NPS·코멘트는 완성후(completed) 피드백만 쓴다.
-- 생성중(generating/T1)은 대기 경험에 대한 말이라 결과물 평가와 대응하지 않는다.
--
-- ── 컬럼 추가하기 ───────────────────────────────────────────────────────────
-- 아래 세 자리만 채우면 된다. 값 종류가 40개 이하면 앱이 필터 드롭다운을 자동 생성한다.
-- ⑧ CTE 추가 ⑨ SELECT 컬럼 추가 ⑩ JOIN 추가
--
-- 붙일 수 있는 것 (이번 조사에서 확인한 것만)
-- design_stat_info 다운로드수·좋아요·조회수·댓글수 bronze...design.design_stat_info
-- 디자인 제목 title silver...design_version_union.title
-- 프롬프트 슬라이드 수 $.slideCount OUTLINE_DOCUMENT.request
-- AIP가 해석한 브리프 $.outline.presentationBrief CONTENT.request
-- .subject / .purpose / .audience / .contentLanguage
-- 레이아웃 템플릿 idx layout_template_idx ai_presentation_design_statistics
-- 생성 소요시간 CONTENT.created - OUTLINE.created
--
-- ⚠️ 붙이기 전에 알아야 할 함정
-- · 페이지 순서는 design_atlas.design.page_ids 배열 인덱스가 원본이다.
-- page_sequence.sequence / template_page_no 는 AIP 디자인에서 0·NULL 이다.
-- · design_idx 는 2026-07-14 에 적재가 끊겼다. 신규 디자인은 UUID(design_id)만 있다.
-- · 피드백·프롬프트는 디자인과 직접 키가 없어 account_id + 시각 근접으로 붙인다.
-- · 긴 텍스트를 추가하면 반드시 페이지순서=1 행에만 넣어라(아래 프롬프트 방식 참고).
-- 전 행에 넣으면 평균 12.8배 중복된다.
-- ─────────────────────────────────────────────────────────────────────────────
WITH params AS (
SELECT
-- ══════════════════════════════════════════════════════════════════════
-- ▼▼▼ 설정 — 이 블록만 고치면 됩니다 ▼▼▼
-- ══════════════════════════════════════════════════════════════════════
-- 1) 기간 ─────────────────────────────────────────────────────────────
-- 디자인이 "생성된" 날짜 기준입니다.
DATE'2026-07-14' AS start_d, -- 시작일 (이 날 포함)
-- 종료일: 기본은 '데이터가 있는 마지막 날'을 자동으로 씁니다.
-- ⚠️ 오늘 날짜를 쓰면 최근 1~2일 디자인이 통째로 빠집니다
-- (페이지 썸네일 적재가 그만큼 늦습니다).
-- 특정 날짜로 자르려면 아래 줄을 지우고 DATE'2026-07-20' AS end_d 로 바꾸세요.
(SELECT max(p_created_date) FROM silver.miricanvas_design.design_version_union) AS end_d,
-- 2) 최소 페이지 수 ───────────────────────────────────────────────────
-- 1 = 전부. 표지 한 장짜리를 걸러내려면 3~5 정도로 올리세요.
1 AS min_pages,
-- 3) 피드백이 있는 디자인만? ──────────────────────────────────────────
-- false = 전체 디자인 (기본)
-- true = NPS·코멘트가 달린 디자인만 → 파일이 훨씬 작아집니다
false AS only_with_feedback,
-- 4) 코멘트가 있는 디자인만? ──────────────────────────────────────────
-- true 로 하면 유저가 글로 남긴 것만 봅니다 (3번보다 더 좁음)
false AS only_with_comment,
-- 5) 국가 ─────────────────────────────────────────────────────────────
-- NULL = 전체. 특정 국가만 보려면 'KR' 또는 'JP' 처럼 넣으세요.
CAST(NULL AS STRING) AS only_country,
-- 6) 프롬프트 길이 상한 ───────────────────────────────────────────────
-- 프롬프트는 최대 2만자입니다. 파일이 너무 크면 1500 정도로 줄이세요.
-- 0 = 자르지 않음
0 AS prompt_maxlen
-- ══════════════════════════════════════════════════════════════════════
-- ▲▲▲ 여기까지 ▲▲▲
--
-- 파일이 너무 크면? → 기간을 좁히거나(1), only_with_feedback=true(3),
-- 프롬프트를 자르세요(6). 실측: 14일 전량 = 480MB
-- 컬럼을 더 넣고 싶으면? → 파일 아래쪽 ⑧ ⑨ ⑩ 주석을 보세요.
-- ══════════════════════════════════════════════════════════════════════
),
b AS (
SELECT CAST(start_d AS STRING) AS s,
CAST(end_d AS STRING) AS e,
CAST(date_add(end_d, 1) AS STRING) AS nxt,
CAST(date_sub(start_d, 1) AS STRING) AS s_m1,
min_pages, only_with_feedback, only_with_comment, only_country, prompt_maxlen
FROM params
),
-- ① AIP 생성 디자인 (전체. 피드백 유무와 무관)
aip AS (
SELECT s.design_id,
acg.account_id,
s.request_type,
s.device_type,
acg.usage AS aip_mode, -- COMPONENT_BASED / TEMPLATE_BASED / LAYOUT_BASED
s.layout_template_key,
-- CONTENT.request 의 브리프는 OUTLINE_DOCUMENT.response 의 산출물이 그대로 들어온 것.
-- subject 문자열이 두 단계를 잇는 키가 된다(시각과 무관).
get_json_object(acg.request,'$.outline.presentationBrief.subject') AS brief_subject,
from_utc_timestamp(CAST(s.created_date_tz AS timestamp),'Asia/Seoul') AS gen_ts,
acg.idx AS cg_idx
FROM bronze.miridih_miricanvas.ai_presentation_design_statistics s
JOIN bronze.miridih_miricanvas.ai_async_content_generation acg
ON acg.idx = s.ai_async_content_generation_idx
CROSS JOIN b
WHERE s.created_date_tz >= b.s AND s.created_date_tz < b.nxt
AND s.request_type IS NOT NULL
AND s.design_id IS NOT NULL AND s.design_id <> ''
),
-- ② 페이지 순서 = 디자인 2.0 도큐먼트의 page_ids 배열 인덱스
-- download_page.page_number(다운로드분의 확정 순서)와 653/653 일치 검증됨
atlas AS (
SELECT design_id, pos, pid
FROM (SELECT design_id, page_ids,
row_number() OVER (PARTITION BY design_id ORDER BY updated_date DESC) rn
FROM bronze.miridih_miricanvas_design_atlas.design
WHERE NOT coalesce(__deleted, false)) t
LATERAL VIEW posexplode(page_ids) e AS pos, pid
WHERE rn = 1
),
pages AS (
SELECT v.design_id, v.page_id,
replace(v.thumbnail_abs_path,'http://','https://') AS page_url,
v.width, v.height,
a.pos
FROM silver.miricanvas_design.design_version_union v
CROSS JOIN b
LEFT JOIN atlas a ON a.design_id = v.design_id AND a.pid = v.page_id
WHERE v.p_created_date >= b.s_m1
AND v.thumbnail_abs_path IS NOT NULL AND v.thumbnail_abs_path <> ''
),
-- ③ 유저 정보
user_info AS (
SELECT CAST(u.account_id AS BIGINT) AS account_id,
COALESCE(u.main_user_type,'설문미제출') AS user_type,
COALESCE(c.country_code, u.country, 'KR') AS country
FROM (SELECT account_id, main_user_type, country
FROM gold.miridih_analytics.mican_user_info_hst
WHERE p_date = (SELECT MAX(p_date) FROM gold.miridih_analytics.mican_user_info_hst)) u
LEFT JOIN gold.miridih_analytics.mican_sign_up_session_ga_hst g ON u.account_id = g.account_id
LEFT JOIN gold.miridih_analytics.country_code_mapping_ref c ON g.country = c.country_name
),
-- ④ 다운로드된 확장자
dl AS (
SELECT d.design_id, concat_ws(',', array_sort(collect_set(d.type))) AS dl_types
FROM bronze.miridih_miricanvas_design.download_event d CROSS JOIN b
WHERE d.p_created_date >= b.s_m1
GROUP BY d.design_id
),
-- ⑤ NPS·코멘트 — 완성후(completed)만
-- 직접 키가 없어 account_id + 시각 근접으로 붙인다.
-- 방향 강제: 완성된 덱을 보고 남기므로 디자인이 먼저다(자명한 매칭 실측 96.5%).
-- gap ∈ [-2분, +60분] 중 gap 최소 = 피드백 직전에 만든 가장 최근 디자인.
-- -2분은 dynamo/DB 시계 오차 유예 (자명한 매칭의 99.5% 보존).
fb_raw AS (
SELECT CAST(f.accountId AS BIGINT) AS account_id,
CAST(f.score AS INT) AS score,
f.comment,
from_utc_timestamp(COALESCE(
try_to_timestamp(f.feedbackTime,"yyyy.MM.dd HH:mm:ss"),
try_to_timestamp(f.feedbackTime,"yyyy-MM-dd'T'HH:mm:ss.SSS'Z'"),
try_to_timestamp(f.feedbackTime)),'Asia/Seoul') AS fb_ts
FROM bronze.miridih_miricanvas_user_feedback.mc_user_feedback f CROSS JOIN b
WHERE f.option.preset_key.S = 'AI_PRESENTATION'
AND f.accountId RLIKE '^[0-9]+$'
AND f.option.phase.S = 'completed' -- ★ completed only
AND f.score IS NOT NULL
AND f.feedbackTime >= b.s_m1
),
fb AS (
-- ⚠️ 디자인당 1행으로 줄여야 한다. (account_id, fb_ts) 기준으로만 줄이면
-- 한 계정이 남긴 여러 피드백이 같은 디자인에 배정될 수 있어 최종 조인에서 행이 배로 늘어난다.
-- (실제로 14일 전량에서 88만 → 335만 행으로 폭발) → 디자인별 최신 피드백 1건만 남긴다.
SELECT design_id, score, comment, fb_ts, gap_min, rivals
FROM (
SELECT *, row_number() OVER (PARTITION BY design_id ORDER BY fb_ts DESC) AS rk2
FROM (
SELECT a.design_id, f.score, f.comment, f.fb_ts,
round((unix_timestamp(f.fb_ts) - unix_timestamp(a.gen_ts))/60.0, 1) AS gap_min,
count(*) OVER (PARTITION BY f.account_id, f.fb_ts) - 1 AS rivals,
row_number() OVER (PARTITION BY f.account_id, f.fb_ts
ORDER BY CASE WHEN unix_timestamp(f.fb_ts) >= unix_timestamp(a.gen_ts)
THEN 0 ELSE 1 END,
abs(unix_timestamp(f.fb_ts) - unix_timestamp(a.gen_ts))) AS rk
FROM fb_raw f
JOIN aip a ON a.account_id = f.account_id
AND unix_timestamp(f.fb_ts) - unix_timestamp(a.gen_ts) BETWEEN -120 AND 3600
) WHERE rk = 1
) WHERE rk2 = 1
),
-- ⑥ 최초 프롬프트 — PRESENTATION_OUTLINE_DOCUMENT
-- ⚠️ CONTENT 행의 $.userInput 은 항상 NULL이다($.outline은 개요 단계의 산출물).
--
-- 조인은 2단 우선순위다. 실측 커버리지(7/15~27, 디자인 51,216개):
-- subject 일치 단독 86.7%
-- 시각 근접 30분 단독 90.7%
-- 시각 근접 6시간 단독 93.3%
-- subject OR 6시간 93.6% ← 채택
-- 시각 창을 30분으로 뒀을 때 5%가 빠졌다. 실제 gap 중앙값이 97분이다
-- (유저가 개요를 받고 한참 뒤에 생성한다). 창만 넓히면 오귀속이 늘어나므로
-- subject 일치를 1순위로 두고, 없을 때만 시각으로 고른다.
--
-- ⚠️ userInput 이 없는 건도 가져온다(NULL 조건 없음).
-- 파일만 올리고 프롬프트를 안 쓴 경우가 있다 — 7/15~27 기준 57,455건 중 2,992건.
-- 이건 매칭 실패가 아니라 프롬프트가 원래 없는 정상 케이스라서,
-- `입력방식` 컬럼으로 구분해 화면에서 데이터 누락과 헷갈리지 않게 한다.
od AS (
SELECT o.account_id,
CAST(o.created_date_tz AS timestamp) AS od_ts,
get_json_object(o.request,'$.userInput') AS user_input,
get_json_object(o.request,'$.fileKey') AS file_key,
get_json_object(o.response,'$.presentationBrief.subject') AS od_subject
FROM bronze.miridih_miricanvas.ai_async_content_generation o CROSS JOIN b
WHERE o.type = 'PRESENTATION_OUTLINE_DOCUMENT'
AND o.created_date_tz >= b.s_m1
),
prompt AS (
SELECT design_id, user_input, file_key
FROM (
SELECT a.design_id, o.user_input, o.file_key,
row_number() OVER (PARTITION BY a.design_id ORDER BY
-- 1순위: 브리프 subject 일치 (시각 무관, 확정에 가깝다)
CASE WHEN a.brief_subject IS NOT NULL AND o.od_subject = a.brief_subject THEN 0 ELSE 1 END,
-- 2순위: 개요가 생성보다 먼저인 것 중 가장 가까운 것
abs(unix_timestamp(a.gen_ts) - unix_timestamp(from_utc_timestamp(o.od_ts,'Asia/Seoul')))) rk
FROM aip a
JOIN od o ON o.account_id = a.account_id
AND ( (a.brief_subject IS NOT NULL AND o.od_subject = a.brief_subject)
OR unix_timestamp(a.gen_ts) - unix_timestamp(from_utc_timestamp(o.od_ts,'Asia/Seoul'))
BETWEEN -300 AND 21600 ) -- 6시간 창 (실측 gap 중앙 97분)
) WHERE rk = 1
),
-- ⑧ ▼ 여기에 CTE 추가 (예시)
-- my_stat AS (
-- SELECT design_id, download_cnt, like_cnt, view_cnt
-- FROM bronze.miridih_miricanvas_design.design_stat_info
-- ),
-- ⑦ 디자인 단위 집계
dsn AS (
SELECT a.design_id, a.account_id, a.request_type, a.device_type, a.gen_ts,
a.aip_mode, a.layout_template_key, a.brief_subject,
count(p.page_id) AS page_cnt,
max(p.width) AS w, max(p.height) AS h,
sum(CASE WHEN p.pos IS NULL THEN 1 ELSE 0 END) AS no_order_cnt
FROM aip a JOIN pages p ON p.design_id = a.design_id
GROUP BY a.design_id, a.account_id, a.request_type, a.device_type, a.gen_ts,
a.aip_mode, a.layout_template_key, a.brief_subject
)
SELECT
-- ★ 필수 2개 (순서 고정)
d.design_id AS design_id,
p.page_url AS page_url,
-- 페이지
row_number() OVER (PARTITION BY d.design_id
ORDER BY coalesce(p.pos, 999999), p.page_id) AS `페이지순서`,
d.page_cnt AS `페이지수`,
CASE WHEN d.no_order_cnt = 0 THEN '확정' ELSE '일부미상' END AS `순서정확도`,
-- 디자인
date_format(d.gen_ts,'yyyy-MM-dd') AS `생성일`,
date_format(d.gen_ts,'yyyy-MM-dd HH:mm') AS `생성시각`,
-- AIP 생성 모드 (usage). 컴포넌트 94% / 템플릿 6% / 레이아웃 0.2%
CASE WHEN d.aip_mode LIKE '%TEMPLATE%' THEN '템플릿'
WHEN d.aip_mode LIKE '%LAYOUT%' THEN '레이아웃'
WHEN d.aip_mode LIKE '%COMPONENT%' THEN '컴포넌트'
ELSE COALESCE(d.aip_mode,'-') END AS `AIP모드`,
COALESCE(d.aip_mode,'') AS `AIP모드원본`,
COALESCE(d.layout_template_key,'') AS `레이아웃키`,
d.request_type AS `입력유형`,
d.device_type AS `디바이스`,
concat(CAST(d.w AS STRING),'x',CAST(d.h AS STRING)) AS `캔버스`,
-- 유저
CAST(d.account_id AS STRING) AS account_id,
COALESCE(u.country,'-') AS `국가`,
COALESCE(u.user_type,'-') AS `유저유형`,
-- 행동
COALESCE(dl.dl_types,'') AS `다운로드`,
CASE WHEN dl.dl_types IS NULL THEN '다운로드X' ELSE '다운로드O' END AS `다운로드여부`,
-- 피드백 (완성후만)
CASE WHEN f.score IS NULL THEN '' ELSE CAST(f.score AS STRING) END AS NPS,
CASE WHEN f.score IS NULL THEN '피드백없음'
WHEN f.score >= 9 THEN '프로모터'
WHEN f.score >= 7 THEN '패시브'
ELSE '디트랙터' END AS `NPS구간`,
CASE WHEN f.comment IS NULL OR trim(f.comment) = '' THEN '없음' ELSE '있음' END AS `코멘트여부`,
-- 코멘트는 짧아(평균 24자) 전 행에 둬도 부담 없다 → 필터·검색에 유용
COALESCE(f.comment,'') AS `코멘트`,
-- 입력방식 — 프롬프트 빈칸이 '데이터 누락'인지 '원래 없음'인지 구분한다
CASE WHEN pr.design_id IS NULL THEN '매칭안됨'
WHEN trim(coalesce(pr.user_input,'')) <> '' AND coalesce(pr.file_key,'') <> '' THEN '파일+프롬프트'
WHEN trim(coalesce(pr.user_input,'')) <> '' THEN '프롬프트'
WHEN coalesce(pr.file_key,'') <> '' THEN '파일만'
ELSE '입력없음' END AS `입력방식`,
COALESCE(d.brief_subject,'') AS `주제`,
-- ⑨ ▼ 여기에 컬럼 추가 (예시)
-- COALESCE(CAST(m.like_cnt AS STRING),'0') AS `좋아요`,
-- 프롬프트 — 첫 페이지 행에만 (중복 방지)
CASE WHEN row_number() OVER (PARTITION BY d.design_id
ORDER BY coalesce(p.pos, 999999), p.page_id) = 1
THEN CASE WHEN (SELECT prompt_maxlen FROM b) > 0
THEN substr(COALESCE(pr.user_input,''), 1, (SELECT prompt_maxlen FROM b))
ELSE COALESCE(pr.user_input,'') END
ELSE '' END AS `프롬프트`
FROM dsn d
JOIN pages p ON p.design_id = d.design_id
LEFT JOIN user_info u ON u.account_id = d.account_id
LEFT JOIN dl dl ON dl.design_id = d.design_id
LEFT JOIN fb f ON f.design_id = d.design_id
LEFT JOIN prompt pr ON pr.design_id = d.design_id
-- ⑩ ▼ 여기에 조인 추가 (예시)
-- LEFT JOIN my_stat m ON m.design_id = d.design_id
CROSS JOIN b bb
WHERE d.page_cnt >= bb.min_pages
AND (NOT bb.only_with_feedback OR f.score IS NOT NULL)
AND (NOT bb.only_with_comment OR (f.comment IS NOT NULL AND trim(f.comment) <> ''))
AND (bb.only_country IS NULL OR u.country = bb.only_country)
ORDER BY d.gen_ts DESC, d.design_id, `페이지순서`
.csv.gz도 그대로