o2o-site-AEO/solution/site/scripts/mockup/SECTIONS.md
Mina Choi ea1daae576 [fix] site/scripts: 시연본을 레포 안에서 자급자족하게 — 다른 기계에서 흰 화면이던 것
구워진 index.html 이 미러 사진 61장(`/assets/mirror/*`)과 번들 css/js 를 루트 절대경로로
가리켰다. 그 파일들은 레포가 아니라 도커 볼륨에만 있어서, 레포만 받은 사람은 흰 화면을 본다.
번들은 더 나쁘다 — 파일명이 콘텐츠 해시라 그 기계에서 구운 해시가 다르면 404 다.
실측: 구운 html 이 /assets 를 63곳 가리키고 있었다.

- img/mirror/ 61장 · vendor/ 2개를 레포로 끌어들였다
- patch_stay.py: 경로를 `/s/stay/img/mirror/` · `/s/stay/vendor/` 로 바꿔 박고,
  레포에 없는 자산이 있으면 **빌드를 멈춘다**. 끝에 `/assets` 잔재가 남아도 멈춘다
- audit-all.mjs: 히어로 사진 검사를 새 경로로
- SECTIONS.md: 설명서에서 **작업 지시서**로 다시 씀. 프롬프트를 여기 다시 적지 않고
  PROMPTS.md 의 절을 가리킨다(일정은 §3-1/3-2/3-3 세 단계다). 제품 쪽 짝 파일 표 추가
- README.md: "보고 규칙" 절 신설 — 실행하지 않은 것을 됐다고 쓰지 않는다

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

14 KiB
Raw Blame History

이 시연본을 제품으로 옮기기 — 개발자 작업 지시서

대상 https://web4ai.o2osolution.ai/s/stay (스테이,머뭄 · 전북 군산) · 작성 2026-09-11

이 문서를 읽는 사람: 이 시연본에 있는 것을 빌더·백엔드에서 자동으로 만들게 하려는 사람. 설명서가 아니라 할 일 목록이다. 프롬프트 전문은 PROMPTS.md 에 있고 여기서는 "어느 절을 쓰라"고만 가리킨다.


0. 먼저 — 화면부터 띄운다 (3분)

git clone <repo> && cd o2o-site-AEO
cp .env.example .env && cp nginx/site.conf.example nginx/site.conf
docker compose up -d

cd solution/site/scripts/mockup
python3 patch_stay.py                       # build/index.html 을 만든다

C=o2o-web4ai-solution-prerender
docker exec $C mkdir -p /app/solution/site/out/s/stay/img/people \
  /app/solution/site/out/s/stay/img/mirror /app/solution/site/out/s/stay/audio \
  /app/solution/site/out/s/stay/vendor
docker cp build/index.html $C:/app/solution/site/out/s/stay/index.html
docker cp img/.    $C:/app/solution/site/out/s/stay/img/
docker cp audio/.  $C:/app/solution/site/out/s/stay/audio/
docker cp vendor/. $C:/app/solution/site/out/s/stay/vendor/

node audit-all.mjs                          # 34개 항목 전수 점검

http://localhost/s/stay

이 시연본은 레포 안에서 자급자족한다. 번들 css/js 와 미러 사진 61장까지 vendor/ · img/mirror/ 에 들어 있고, patch_stay.py 가 경로를 /s/stay/… 로 바꿔 박는다. 그래서 그 기계에서 구운 번들 해시와 무관하게 뜬다. (2026-09-11 이전 판은 안 그랬다 — /assets/… 를 가리켜서 다른 기계에서는 흰 화면이었다.)


1. 어떤 파일을 여나

파일 무엇이 들어 있나 언제 연다
PROMPTS.md 프롬프트 전문 7종 — 캐치프레이즈 · 장소 대장 · 테마 · 노래 · 인물 · 엽서 생성 프롬프트를 쓸 때
README.md 목업 고치는 절차 · 배포 · 밟으면 조용히 틀리는 자리 목업 자체를 손볼 때
REVIEW-2026-09-11.md 일정이 왜 틀렸고 무엇을 고쳤나 일정 규칙을 옮기기 전에
TEXT.md 화면에 나가는 텍스트 전문 문구 톤을 볼 때
build_itinerary.py 일정 생성기 원본 — 규칙 14종이 여기 있다 일정을 서버로 옮길 때
build_story.py 노래·인물 + 위키 사진 수집 인물 사진 경로를 만들 때
patch_stay.py payload 조립 + 캐치프레이즈·자작곡·객실사진 주입 목업 데이터를 바꿀 때
inject.js / inject.css 렌더러에 없는 기능을 목업에 얹은 것 그 기능을 렌더러로 옮길 때
audit-all.mjs / rails-test.mjs 브라우저 전수 점검 언제나. 끝났다고 말하기 전에

제품 쪽 짝은 이것들이다.

이 목업 제품에서 같은 일을 하는 곳
build_itinerary.py solution/backend/services/itinerary.py
build_story.py solution/backend/services/story_service.py
PROMPTS.md solution/shared/src/lib/section-prompts.ts (→ npm run export:prompts)
payload 필드 solution/shared/src/types/site-payload.ts · shared/src/lib/section-data.ts
섹션 목록·기본 on/off solution/backend/services/site_payload.py:155 부근

2. 지금 상태 — 섹션 22개

할 일 칸이 비어 있으면 제품이 이미 만들 수 있다. 손댈 필요 없다.

id 이름 값이 오는 자리 지금 누가 만드나 할 일
hero 히어로 narrative 사장님이 적음 T1
intro 소개 narrative.about[] LLM(빌더)
rooms 객실 안내 units[] + media[] 사장님이 올림 T6
event 소식 data(kind=event) 사장님이 적음
info 기본 정보 facts[] 사장님이 적음
booking 실시간 예약 (꺼 둠)
video 영상 data(kind=video) 사장님이 적음
photos 사진 갤러리 media[] 사장님이 올림
map 오시는 길 place + routes[] 수집
festival 계절별 축제 local.festivals TourAPI 수집
local 지역 정보 local.attractions/restaurants TourAPI 수집
itinerary 추천 일정 data(kind=itinerary) services/itinerary.py T2
story 군산 이야기 아래 6탭 껍데기
songs 가요 다방 data(kind=songs) LLM section-prompts.ts:67 T3 T5
daily 오늘의 한 장 data(kind=daily) LLM :91 섹션 자리가 없다
people 인물 열전 data(kind=people) LLM :113 T3 T5
chronicle 시간의 골목 data(kind=chronicle) LLM :135
postcard 오늘의 엽서 data(kind=postcard) LLM :160
quiz 뒤집어 보는 질문 data(kind=quiz) LLM :188
faq 자주 묻는 질문 faqs[] LLM + 사장님
rules 이용 규정 facts[] 사장님이 적음
weather 날씨 local.weather open-meteo
planner 계절별 추천 하루 data(kind=planner) 없음 (이 시연본은 꺼 둠)

여기에 섹션이 아닌 것 하나가 더 있다 — 헤더 음악 플레이어(T4).


3. 할 일 여섯

T1 — 히어로 캐치프레이즈

증상 히어로 문구가 사이트마다 한 줄로 고정이다. 계절도 날씨도 안 탄다. 지금 계약(site-payload.ts:316 Narrative)에 문자열 tagline 하나뿐이고, 히어로는 tagline ?? heroSubline ?? summary 를 그대로 찍는다(HeroPension.tsx:125).

할 일

  1. Narrative 에 필드를 더한다.
    catchphrases?: { version: 1; items: {
      text: string;                              // 18자 안쪽
      kind: 'general' | 'season' | 'month' | 'weather';
      season?: '봄'|'여름'|'가을'|'겨울'; month?: number; weather?: '맑음'|'흐림'|'비'|'눈';
    }[] };
    
  2. 히어로가 지금 조건에 맞는 것만 후보로 두고 돌린다. 계절은 3개월 단위(가을 911, 겨울 122), 날씨는 local.weather.condition. 목업 구현은 inject.jsstartCatchphrase() — 7초마다 단어 단위 교체.
  3. 프롬프트는 PROMPTS.md §2 전문을 그대로 쓴다. 100개(일반 40·계절 24·월 24·날씨 12).
  4. STORY_KINDS 에 등록한다. 빠뜨리면 서버 생성 잡이 이 종류를 아예 모른다daily 가 그래서 한동안 빈칸이었다.

완료 기준 새 업장을 발행했을 때 히어로 문구가 100개 돌고, 9월에 겨울 문구가 안 나온다.


T2 — 추천 일정 ★ 가장 큰 일

증상 서버 생성기(services/itinerary.py)는 하루를 관광지 2 + 맛집 2 로 짠다. 체류시간·이동시간·입퇴실·끼니 시각 개념이 없다. 그래서 나온 하루가 "둘째 날 해산물축제 → 근대건축관" 으로 끝난다.

★ 프롬프트 하나로 일정을 받지 않는다. 모델은 시각 계산을 못 한다 — 이동시간을 지어내고, 점심을 09:40 에 넣고, 21시를 넘겨 렌더러가 조용히 버리는 칸을 만든다 (shared/src/lib/section-data.ts:535). 시연본 첫 판이 정확히 그랬다.

세 단계로 나눈다. 전문은 PROMPTS.md §3.

단계 누가 무엇을 내놓나 전문
3-1 장소 대장 LLM 장소 30곳 × {stayMinutes:{min,base,max}, todo, indoor, needsCar, bestTime, searchQuery} PROMPTS.md §3-1
좌표 코드 카카오 로컬로 searchQuery → 위경도. LLM 이 적은 좌표는 쓰지 않는다
3-2 조립 코드 이동시간·도착시각·끼니 창 맞추기·규칙 14종 감사 PROMPTS.md §3-2 · build_itinerary.py
3-3 테마 LLM 테마 20개 × {name, duration, audience, why, days[{kind, places[]}]} PROMPTS.md §3-3

코드가 하는 일(3-2)에서 반드시 옮겨야 하는 것 — 근거는 build_itinerary.py 줄번호

무엇 어디
이동시간 좌표 계산. 도보 4km/h(≤1.5km), 그 위는 차 25km/h + 주차 5분 move_minutes()
숙소 고정 모든 일정이 숙소에서 출발해 숙소로 복귀. 첫 칸·마지막 칸이 숙소 :108 :182
시각 창 입실 15:00+ · 퇴실 11:00 · 점심 11:3013:30 · 늦은 점심 13:0015:00 · 저녁 17:3020:00 · 밤산책 18:3020:00 :156 :166
창 맞추기 어긋나면 앞 칸 체류를 범위(min~max) 안에서 늘리고 줄인다. 이동시간을 고치지 않는다 plan_day()
테마별 완급 시작 시각·체류 배율이 테마마다 다르다 → 끝나는 시각이 13:35~21:00 로 퍼진다 :470
감사 14종 하나라도 어기면 payload 를 쓰지 않고 멈춘다 :526

감사는 생성기 안에 둔다. 밖에 두면 뼈대를 고칠 때마다 "이번에 안 본 규칙"이 생긴다 — 실제로 그렇게 33건이 통과했다(REVIEW-2026-09-11.md 2장).

완료 기준 새 업장에서 테마 20개가 나오고 audit() 위반 0건. 하루가 숙소로 시작해 숙소로 끝나고, 끼니가 제 시각에 있고, 21시를 안 넘는다.


T3 — 인물·노래 사진

증상 프롬프트가 사진 URL 을 금지하고 imageQuery 만 받는다(section-prompts.ts:128). 초상권·저작권 때문이고 그 판단은 맞다. 그런데 다음 단계가 없어서 인물 열전이 활자만으로 선다.

할 일

  1. services/external/wikimedia.py문서 → pageimagesimageinfo.extmetadata 경로를 붙인다.
  2. 이름으로 커먼즈를 검색하지 않는다. 동명이인이 온다 — 실측: 이수현→걸그룹, 은성수→축구선수, 박성현→골퍼, 이길여→건물. 위키백과 문서에 붙은 대표사진만 믿는다. 구현은 build_story.py.
  3. 저작자·라이선스를 imageCredit 으로 payload 에 넣는다. ★ 문자열이다(site/src/sections/items/common.tsx:206). 객체로 넣으면 렌더러가 조용히 아무것도 안 그린다 — CC BY-SA 는 출처 표시가 라이선스 조건이라 그건 침해다.
  4. 사진 있는 사람을 앞으로 정렬한다. 빈 카드가 먼저 오면 목록이 비어 보인다.

완료 기준 인물 카드에 사진과 저작자 줄이 같이 뜬다. 목업 실적은 57명 중 18명.


T4 — 음악 플레이어

증상 media 에 오디오 종류가 없다. 목업은 mp3 를 /s/stay/audio/직접 넣었다.

할 일 MediaItemkind:'audio'(+title artist durationSec)를 넣고 발행 때 같이 올린다. 헤더에 붙는 작은 플레이어를 만든다 — 아이콘 · 현재 곡명 · 재생/멈춤 · 목록. 자동재생은 넣지 않는다(2026-09-11 지시). 순서는 랜덤 시작 후 순차. 목업 구현은 inject.jsstartPlayer(), 모양은 inject.css#w4d-mini · #w4d-panel.


T5 — 상한(maxItems)

종류 지금 목업
songs 8 25 8곡이면 '다방'이 아니라 목록이다
people 10 57 위키백과 '군산시 출신' 분류만으로 57명이 확인된다

한 번에 다 받지 않는다. 25곡을 한 번에 시키면 뒤쪽이 급격히 부실해진다. 8개씩 나눠 받고 제목으로 중복을 지운다 — 나누는 방법은 PROMPTS.md §4.


T6 — 객실 사진 출처

목업은 야놀자 등록본(nol.yanolja.com/stay/domestic/10068088)의 Next.js 플라이트 데이터에서 roomTypes[].photos 를 파싱해 A동 12·B동 10 을 가져왔다(rooms.json). 손으로 한 것이고 제품 경로가 아니다.

수집 어댑터를 만들려면 docs/DECISIONS.md 1-1 과 docs/DATA_SOURCE_RESEARCH.md 를 먼저 읽는다 — 봇 탐지 우회는 결론과 무관하게 영구 금지다. 현실적인 경로는 사장님이 올리는 것이거나 업소가 준 채널의 공개 API 다.


4. 프롬프트를 새로 쓸 때 지킬 것

SECTION_PROMPT_RULES(section-prompts.ts:55)가 공통 7줄이고 종류별 규칙을 덧붙인다.

  1. STORY_KINDS 에 등록한다. 빠지면 서버 생성 잡이 그 종류를 모른다.
  2. maxItems 와 프롬프트 안의 숫자를 같게 둔다. 어긋나면 받아 놓고 잘라 버린다.
  3. 빈 값을 지어내지 않는다. 확실한 게 12개면 12개만 낸다.
  4. 출처는 실제로 열리는 페이지여야 한다. 검색 결과 주소는 안 된다.
  5. 가사·시·소설 원문을 한 줄도 옮기지 않는다.
  6. 고친 뒤 npm run export:prompts 를 돌린다 — 백엔드는 그 산출물을 읽는다. backend/services/prompts/section_prompts.json 을 손으로 고치지 않는다.

5. 끝났다고 말하기 전에

무엇 어떻게
생성 단계 규칙 위반이면 결과물을 쓰지 않고 멈춘다 감사를 생성기 에 (build_itinerary.py:526)
렌더 단계 브라우저로 열어 숫자를 센다 node audit-all.mjs — 34/34
카로셀 레일 13개 × 데스크톱·모바일 node rails-test.mjs
다른 기계 레포만 받아 띄워 본다 §0 을 그대로 실행

★ 여기 있는 것 중 "코드에서 확인했다"로 끝낸 것은 하나도 없다. 전부 화면에서 숫자를 세서 붙였다. 그렇게 안 해서 틀린 기록이 README.md §7REVIEW-2026-09-11.md 에 있다.