o2o-site-AEO/solution/site/scripts/mockup/PROMPTS.md
Mina Choi 43ba33dd65 [feat] site/scripts: /s/stay 시연본 조립 도구 일습 — 일정 생성기·주입분·검증
이 목업은 payload 가 없어 프리렌더 재굽기 대상이 아니다 — `index.html` 한 장이 유일본이고
자산이 지워지면 사람이 되돌려 넣어야 한다(CLAUDE.md 의 ★★ 함정). 지금까지 그 한 장을
이 맥에서만 만들고 있었다. 다른 사람이 이어받을 수 있도록 재료를 전부 올린다.
`build/index.html` 은 생성물이라 이그노어 그대로다 — `patch_stay.py` 로 다시 나온다.

- build_itinerary.py: 테마 21개 일정 생성. 좌표에서 이동시간을 계산하고(도보 4km/h,
  1.5km 초과는 차 25km/h + 주차 5분) 입·퇴실·끼니 창을 맞춘다. 규칙 14종 감사가
  **빌드 안**에 있어 하나라도 어기면 payload 를 쓰지 않고 멈춘다 — 검사가 빌드 밖에
  있던 동안 뼈대를 고칠 때마다 안 보는 규칙이 생겼다(2026-09-11 REVIEW)
- build_story.py: 노래 25곡·인물 57명. 사진은 위키백과 문서 pageimages 만 믿는다
  (이름 검색은 동명이인을 끌고 온다 — 이수현→걸그룹, 박성현→골퍼)
- patch_stay.py: 캐치프레이즈 100개·자작곡 5곡·객실 사진(A동 12/B동 10)을 넣고
  payload 를 갈아 끼운 뒤 inject.css/js 를 `</body>` 앞에 주입해 index.html 을 짠다
- inject.js/css: React 가 다시 그려도 살아남아야 하는 다섯 가지(캐치프레이즈 순환·
  헤더 미니 플레이어·지도 링크·사진 저작자 표시·카로셀 제어). `#root` 밖에 둔다
- audit-all.mjs / rails-test.mjs: 실제 브라우저로 34종 검사, 레일 13개 자동 넘김 전수
- README.md: 이어받는 사람이 먼저 읽는 문서. 배포·함정·§7 "내가 틀렸던 6가지"
- PROMPTS.md / TEXT.md / REVIEW-2026-09-11.md: 문구 생성 프롬프트 · 전체 텍스트 · 검수 보고

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-11 10:28:52 +09:00

17 KiB
Raw Blame History

/s/stay 시연본의 콘텐츠를 만드는 프롬프트

시연본에 들어간 것(히어로 문구 100개 · 여행 일정 21개 · 가요 다방 25곡 · 인물 열전 57명 · 오늘의 엽서)을 어떤 프롬프트로 뽑는지 정리한 문서다. 지금 시연본은 이 프롬프트로 뽑은 뒤 사람이 검수해 스크립트에 박아 둔 상태다.


0. 이 프롬프트들이 어디로 가야 하나

단일 출처는 solution/shared/src/lib/section-prompts.ts 다. 사장님이 [콘텐츠] 탭에서 복사해 가는 프롬프트와, 서버가 지역 단위로 돌리는 생성 잡 (services/story_service.py)이 같은 문장을 써야 한다. 두 벌로 두면 "빌더에서 뽑은 것과 서버가 채운 것의 모양이 다르다"가 조용히 생긴다.

solution/shared/src/lib/section-prompts.ts     ← 여기만 고친다
        │  npm run export:prompts
        ▼
solution/backend/services/prompts/section_prompts.json   ← 산출물, 커밋됨. 손으로 고치지 않는다

아래 프롬프트 중 2·3 은 새로 만들 것이고(지금 계약에 없다), 4·5·6 은 기존 것을 고칠 것이다. 옮길 때 maxItems 와 프롬프트 안의 숫자를 같이 바꾼다 — 어긋나면 받아 놓고 잘라 버린다.

프롬프트 지금 상태 옮길 자리
2. 히어로 캐치프레이즈 없음 — 새로 만든다 SECTION_PROMPTS.catchphrase (신규)
3. 여행 일정 · 장소 대장 있으나 규칙이 부족 canvas/dataSpec.ts itinerary 교체
4. 가요 다방 있음(8곡) SECTION_PROMPTS.songs 개선
5. 인물 열전 있음(10명) SECTION_PROMPTS.people 개선
6. 오늘의 엽서 있음(12개) SECTION_PROMPTS.postcard 개선

1. 공통 규칙 (모든 프롬프트 뒤에 붙는다)

[공통 규칙]
1. JSON 하나만 출력한다. 인사말·설명·코드펜스를 붙이지 않는다.
2. 확인되지 않은 값은 필드를 통째로 뺀다. 빈 문자열로 채우거나 "미상" 이라고 쓰지 않는다.
3. source.url 은 실제로 열리는 공식·기관·언론 페이지여야 한다. 검색 결과 주소는 쓰지 않는다.
4. 근거가 확실하면 verified 를 "확인", 애매하면 "확인필요" 로 적는다.
5. 가사·시·소설의 원문을 한 줄도 옮기지 않는다. 제목과 배경만 쓴다.
6. 설명 문장은 항목당 두 문장을 넘기지 않는다.
7. 이미지 주소는 만들지 않는다. 필요하면 imageQuery 에 검색어만 적는다.

★ 2번과 3번이 이 레포의 절대규칙을 프롬프트로 옮긴 것이다. 모델이 이걸 어기면 검증기가 뒤에서 걸러야 하는데, 걸러진 항목은 결국 화면에서 빈자리가 된다.


2. 히어로 캐치프레이즈 (신규)

상호 아래에서 7초마다 바뀌는 한 줄이다. 100개를 미리 쌓아 두고, 계절·월·날씨에 맞는 것을 섞어 순환 노출한다.

프롬프트

너는 숙소의 카피라이터다. 아래 조건에 맞는 JSON 하나만 출력한다.

[업소] {상호} ({업종})
[지역] {지명}
[업소가 가진 사실]
{fact 목록을 한 줄씩. 예: 1920년대 적산가옥 · 독채 2동 · 최대 4인 · 마당과 정원 ·
 창고형 카페 공간 · 침구 매일 세탁 · 히로쓰 가옥 옆 · 초원사진관 도보 3분}

[해야 할 일]
[업소] 의 히어로 문구를 100개 만든다. 상호 바로 아래에 한 줄로 뜨고 7초마다 바뀐다.
네 종류로 나눠 만든다.

· 일반 40개  — 언제 나와도 되는 문구
· 계절 24개  — 봄·여름·가을·겨울 각 6개 (계절은 3개월 단위: 3~5 봄 / 6~8 여름 / 9~11 가을 / 12~2 겨울)
· 월별 24개  — 1월부터 12월까지 각 2개
· 날씨 12개  — 맑음 3 · 구름많음 2 · 흐림 2 · 비 3 · 눈 2

[스키마]
{ "kind":"catchphrase", "version":1, "items":[
  { "text":"한 줄", "kind":"general|season|month|weather",
    "season":"봄|여름|가을|겨울",   // kind 가 season 일 때만
    "month":9,                      // kind 가 month 일 때만 (1~12)
    "weather":"맑음|구름많음|흐림|비|눈" }  // kind 가 weather 일 때만
] }

[이 아이템만의 규칙]
· 한 줄은 25자 안쪽이다. 두 문장으로 쓰지 않는다.
· **[업소가 가진 사실] 안에서만 쓴다.** 거기 없는 시설·거리·연도를 지어내지 않는다.
· 느낌표와 이모지를 쓰지 않는다. "최고" "완벽" "힐링" 같은 광고 단어를 쓰지 않는다.
· 상호를 문구에 넣지 않는다 — 바로 위에 이미 크게 떠 있다.
· 같은 사실을 두 번 쓰지 않는다. 100개가 다 다른 것을 말해야 한다.
· 계절 문구는 그 계절에만 맞는 말이어야 한다. "좋습니다" 처럼 아무 때나 되는 말은 일반으로 보낸다.
· 날씨 문구는 그 날씨일 때 손님이 **무엇을 할 수 있는지**를 말한다. 날씨 묘사만 하지 않는다.
· 월별 문구는 그 달의 날씨·행사·빛이 근거여야 한다. 숫자만 바꾼 문구를 12번 쓰지 않는다.
· source 와 verified 는 없다. 사실이 아니라 문구이고, 근거는 [업소가 가진 사실] 이다.

잘 나온 예 (시연본에서)

종류 문구 근거가 된 사실
일반 담 너머는 히로쓰 가옥입니다 위치
일반 침구는 매일 새것처럼 나갑니다 매일 세탁·살균
일반 네 사람까지 묶는 독채입니다 최대 4인
계절(가을) 기와 위로 가을 볕이 마릅니다 기와 지붕
월(9월) 가을이 담을 넘어오는 구월입니다 담·계절
날씨(비) 카페 공간에서 빗소리를 들으실 수 있습니다 창고형 카페

받은 뒤 검사할 것

  • 100개인가. 종류별 개수가 40/24/24/12 인가
  • 25자를 넘는 것이 있는가
  • [업소가 가진 사실] 에 없는 말이 있는가 ← 이게 제일 많이 틀린다
  • 계절 문구가 그 계절에만 맞는가
  • 같은 말이 두 번 나오는가

3. 여행 일정 (신규 — 두 단계로 나눈다)

한 프롬프트로 일정을 통째로 받지 않는다. 시연본 첫 판이 그랬고, 나온 것이 "둘째 날 해산물축제 → 근대건축관" 으로 끝나는 하루였다. 모델은 시각 계산을 못 한다 — 이동시간을 지어내고, 점심을 09:40 에 넣고, 21시를 넘겨 화면에서 잘려 나가는 칸을 만든다.

그래서 3-1 로 장소 대장만 받고, 3-2 는 코드가 계산한다.

3-1. 장소 대장 (LLM 이 만든다)

너는 지역 여행 코디네이터다. 아래 조건에 맞는 JSON 하나만 출력한다.

[업소] {상호} ({업종})
[지역] {지명}
[좌표] {위도},{경도}

[해야 할 일]
[업소] 에서 출발해 당일로 다녀올 수 있는 장소를 30곳까지 찾는다.
관광지·박물관·골목·바다·절 같은 볼거리 20곳과, 끼니를 해결할 곳 10곳을 섞는다.
[업소] 에서 30km 안쪽만 고른다.

[스키마]
{ "kind":"places", "version":1, "items":[
  { "name":"장소 이름",
    "searchQuery":"지도에서 검색할 말",
    "category":"볼거리|식당|카페",
    "stayMinutes":{"min":20,"base":30,"max":45},
    "todo":"거기서 무엇을 하는지 한 문장",
    "indoor":true,
    "needsCar":false,
    "bestTime":"오전|오후|저녁|밤|상관없음",
    "verified":"확인|확인필요",
    "source":{"name":"출처명","url":"https://..."} } ] }

[이 아이템만의 규칙]
· **todo 는 장소 설명이 아니라 할 일이다.**
  ✗ "1899년에 지어진 근대 건축물입니다"
  ✓ "옛 조선은행 금고와 은행 창구를 봅니다. 2층 전시까지 보면 45분입니다."
  손님이 거기서 무엇을 하는지 모르면 그 줄은 쓸모가 없다.
· stayMinutes 는 **실제로 걸리는 시간**이다. min 은 빨리 보고 나오는 사람, max 는 천천히 보는 사람.
  박물관 60~120분, 골목 15~40분, 식당 40~70분이 기준이다. 무엇이든 90분으로 적지 않는다.
· 식당은 **무엇을 먹는지**까지 적는다. "맛집입니다" 로는 무엇을 시킬지 모른다.
· indoor 는 비 오는 날 갈 수 있는 곳만 true 다. 처마 밑·실내 전시가 여기 들어간다.
· needsCar 는 [업소] 에서 걸어서 못 가는 곳(1.5km 초과)이면 true 다.
· 좌표는 적지 않는다. 코드가 카카오 로컬에서 받는다 — 지어낸 좌표는 지도에 엉뚱한 핀을 찍는다.
· 영업시간·휴무일이 확인되면 source 에 그 페이지를 단다.

3-2. 일정 조립 (코드가 한다 — LLM 아님)

solution/site/scripts/mockup/build_itinerary.py 가 하는 일이고, 제품에서는 solution/backend/services/itinerary.py 가 같은 규칙을 가져야 한다.

LLM 에게 맡기지 않는 것

이동시간 좌표에서 계산한다. 도보 4km/h, 1.5km 초과는 차 25km/h + 주차 5분
도착·출발 시각 시작 시각부터 누적
끼니 시각 맞추기 창을 벗어나면 앞 칸 체류를 범위 안에서 늘이고 줄인다
하루 구성 아래 뼈대

하루 뼈대@ 는 숙소

첫날      체크인@ · 오후 · 오후 · 저녁 · 밤산책 · 복귀@
가운데날   아침@ · 오전 · 점심 · 오후 · 오후 · 쉼@ · 저녁 · 복귀@
쉬는날     숙소에서 쉬는 날@ · 점심 · 쉼@ · 저녁 · 복귀@
마지막날   아침(퇴실)@ · 오전 · 점심 · 오후 · 마무리@

지켜야 하는 규칙 14종 (어기면 그 일정을 만들지 않는다)

  1. 하루의 첫 칸·마지막 칸은 숙소 — 출발지와 복귀지
  2. 같은 자리가 연달아 서지 않는다 (숙소 칸 사이에는 반드시 바깥 정거장이 있다)
  3. 가운데 숙소 칸은 '쉼'·'쉬는 날' 뿐
  4. 이동시간 = 좌표 계산값
  5. 입실 15:00+ · 점심 11:30~13:30 · 늦은 점심 13:00~15:00 · 저녁 17:30~20:00 · 밤산책 18:30~20:00
  6. 퇴실 11:00 전
  7. 21:00 초과 금지 — 넘는 칸은 렌더러가 조용히 버린다
  8. 끼니 자리에는 먹는 곳만
  9. 같은 날 같은 식당 두 번 금지
  10. 한 테마 안에서 같은 곳 반복 금지
  11. "비가 와도 되는" 테마는 indoor: true
  12. "걸어서만/차 없이" 테마에 needsCar: true 금지
  13. 1박 2일 = 2일, 2박 3일 = 3일
  14. 아이 동반 테마에 술집 금지

테마 이름·대상·이유는 LLM 이 만들어도 된다 (아래 3-3).

3-3. 테마 짓기 (LLM)

[해야 할 일]
[장소 대장] 을 재료로 1박 2일 10개, 2박 3일 10개의 테마를 만든다.
테마는 **손님의 성격**으로 가른다 — 차가 있는지, 걷기를 좋아하는지, 비가 오는지,
아이가 있는지, 사진을 찍는지, 쉬러 오는지.

[스키마]
{ "kind":"themes", "version":1, "items":[
  { "name":"테마 이름", "duration":"1박 2일|2박 3일",
    "audience":"누구에게", "why":"왜 이 순서인지 한두 문장",
    "days":[ { "kind":"first|mid|mid_rest|mid_island|last", "places":["장소명","장소명"] } ] } ] }

[이 아이템만의 규칙]
· places 는 [장소 대장] 에 있는 이름만 쓴다. 없는 곳을 적으면 그 테마는 버려진다.
· 뼈대의 자리 수와 places 개수가 같아야 한다.
· 끼니 자리에는 category 가 식당·카페인 곳만 넣는다.
· **한 테마 안에서 같은 곳을 두 번 넣지 않는다.**
· 테마 이름에 "최고" "완벽" 을 쓰지 않는다. 무엇을 하는 일정인지가 이름이다.
· 20개 중 하나는 **숙소에서 거의 나가지 않는 날**을 포함한다.

4. 가요 다방 (기존 개선)

지금 계약은 8곡이다. 50곡으로 올린다 — 다만 한 번에 못 받으므로 나눠 부른다.

[해야 할 일]
[지역]을 노래한 대중가요를 찾아 아래 JSON 으로 정리한다.
이번에는 {N}번째부터 {N+14}번째까지, 15곡을 낸다.
이미 받은 곡은 아래에 있다 — 같은 곡을 다시 내지 않는다.
[이미 받은 곡] {곡명 목록}

지명·항구·강·다리가 제목이나 배경에 나오는 곡을 고른다.
1960~80년대 곡을 먼저 찾고, 다 떨어지면 지자체가 만든 곡·지역 가수의 곡으로 넓힌다.

[스키마]
{ "kind":"songs", "version":1, "title":"가요 다방", "items":[
  { "title":"곡명", "artist":"가수", "lyricist":"작사", "composer":"작곡",
    "year":1966, "label":"음반사", "labelColor":"#d4551f",
    "story":"곡의 배경 (두 문장 이내, 가사 없이)",
    "verified":"확인|확인필요",
    "source":{"name":"출처명","url":"https://..."} } ] }

[이 아이템만의 규칙]
· lyrics 필드는 스키마에 없다. 어떤 이유로도 만들지 마라. 가사 한 소절도 안 된다.
· **곡이 실재하는지 출처로 확인한다.** 지역 노래 목록·언론 기사·지자체 음반 안내가 근거다.
  검색으로 못 찾으면 그 곡을 내지 않는다 — 15곡을 억지로 채우지 않는다.
· labelColor 는 레코드 라벨 색이다. 곡의 분위기에 맞춰 진한 색 하나를 hex 로 고른다.
· 작사·작곡·발표연도를 모르면 그 필드를 뺀다. "미상" 이라고 쓰지 않는다.
· 유튜브 영상 ID 를 적지 않는다 — 코드가 검색 주소를 만든다. 틀린 영상이 붙는 것보다 낫다.
· 숙소가 직접 만든 곡은 여기 넣지 않는다. 이 섹션은 **도시의 노래**를 모으는 자리다.

★ 시연본은 25곡에서 멈췄다. 출처에서 확인되는 군산 곡이 거기까지였다. 모자라면 모자란 채로 내는 것이 맞다 — 지어낸 곡은 아는 사람이 바로 알아본다.


5. 인물 열전 (기존 개선)

지금 계약은 10명이다. 50명 이상으로 올린다.

[해야 할 일]
[지역] 출신이거나 [지역]과 깊이 얽힌 인물을 50명까지 찾는다.
문학·음악·미술·연기·체육·정치·학문·기업을 고루 섞는다. 한 분야가 절반을 넘지 않게 한다.
생존 인물은 공개된 활동 사실만 쓴다.

[스키마]
{ "kind":"people", "version":1, "title":"인물 열전", "items":[
  { "name":"이름", "aka":"호·예명", "years":"19021950", "role":"소설가",
    "oneLine":"한 문장 소개", "imageQuery":"사진 검색어",
    "verified":"확인|확인필요",
    "source":{"name":"출처명","url":"https://..."} } ] }

[이 아이템만의 규칙]
· **source.url 은 그 사람의 문서**여야 한다(위키백과 문서, 기관 소개 페이지).
  "OO시 출신 인물 목록" 같은 목록 페이지는 근거가 아니다.
· oneLine 은 **그 사람이 무엇을 한 사람인지**다. "군산 출신입니다" 는 이름 아래 이미 있다.
· "~ 출신으로 알려진" 처럼 근거가 전언뿐이면 verified 를 "확인필요" 로 한다.
· 생존 인물의 가족·거주지·건강·재산 같은 사생활은 쓰지 않는다.
· 사진 URL 을 넣지 않는다. imageQuery 만 넣는다 — 초상권과 저작권은 사람이 확인한다.
· years 를 모르면 뺀다.

사진은 프롬프트로 받지 않는다. 시연본은 위키백과 API 로 문서 사진을 받고, 라이선스가 확인된 것만(CC BY / CC BY-SA / KOGL / 공용) 썼다 — 57명 중 17명. CC BY-SA 는 저작자 표시가 라이선스 조건이라 화면에 그대로 찍는다.


6. 오늘의 엽서 (기존 개선 — 링크가 붙게)

[해야 할 일]
[지역]에 대해 손님이 자기 SNS 에 그대로 붙여 쓸 만한 한 문장을 12개 쓴다.
사실 하나가 반드시 들어가되, 설명하지 말고 툭 던지는 문장으로 쓴다.

[스키마]
{ "kind":"postcard", "version":1, "title":"오늘의 엽서", "items":[
  { "line":"한 문장", "hashtags":["#태그"],
    "place":"그 문장이 가리키는 장소 이름",
    "searchQuery":"지도에서 검색할 말",
    "postmark":"소인에 찍을 짧은 지명",
    "verified":"확인|확인필요",
    "source":{"name":"출처명","url":"https://..."} } ] }

[이 아이템만의 규칙]
· 한 문장은 40자 안쪽이다. 두 문장으로 쓰지 않는다.
· 느낌표와 이모지를 쓰지 않는다. 광고 문구처럼 들리면 실패다.
· 해시태그는 3개까지. 지역명 하나는 반드시 넣는다.
· **place 와 searchQuery 를 반드시 채운다.** 화면이 그 값으로 지도 링크를 건다.
  postmark 는 도장 문구라 지명이 아닐 수 있다 — 그걸로 지도를 열면 엉뚱한 데가 나온다.

★ 시연본에서 실제로 그랬다. 소인 글자(군산 內港)로 링크를 만들었더니 군산 군산 같은 검색어가 나왔다. place 를 따로 받는 이유다.


7. 프롬프트를 고칠 때 지킬 것

  1. maxItems 와 프롬프트 안의 숫자를 같이 바꾼다. 어긋나면 받아 놓고 잘라 버린다.
  2. 개수를 억지로 채우게 하지 않는다. "확실한 것이 12개면 12개만 낸다" 를 규칙에 넣는다. 채우라고 하면 채운다 — 지어내서.
  3. 계산은 시키지 않는다. 시각·거리·요금은 코드가 한다. 모델은 자신 있게 틀린 숫자를 준다.
  4. "설명" 이 아니라 "할 일" 을 시킨다. 손님이 그 자리에서 무엇을 하는지가 콘텐츠다.
  5. 출처의 종류를 지정한다. "출처를 달아라" 로는 목록 페이지가 온다.
  6. 고친 뒤 npm run export:prompts 를 돌린다. 안 돌리면 서버는 옛 프롬프트로 돈다.