LS · Local Showcase — 기획 패키지 동작 데모

닫힌 가게와 열린 가게를 한눈에 가르는 것부터 시작합니다

기획 패키지 9개 문서에 적힌 규격을 실제로 돌아가는 코드로 옮긴 데모입니다. 추천 점수 산식, AI 문구 검증 파이프라인, 영업 중 판정, 30초 등록 측정이 문서의 숫자 그대로 동작합니다. 화면을 누르면 내부에서 무엇이 계산됐는지 같이 보여줍니다.

승인 매장 44 소식 90 행정동 7 심사 대기 4 30초 등록 최근 5건 평균 24.5초

이 순서로 보시면 됩니다

전부 둘러보지 않아도 됩니다. 아래 네 가지가 기획 문서에서 가장 많은 분량을 쓴 지점이고, 데모에서 직접 눌러 확인할 수 있게 만든 부분입니다.

30초 등록을 직접 재 보기 사장님 모드에서 소식 올리기를 누르면 스톱워치가 시작됩니다. 사진을 고르는 순간 업로드와 AI 문구 생성이 동시에 출발하고, 후보 3개 중 하나를 골라 게시하면 측정값이 기록됩니다. 완료 기준 1번(테스트 매장 5곳 평균 30초)이 관리자 화면에 누적됩니다.
추천이 왜 이 순서인지 열어 보기 손님 앱 추천 탭에서 카드의 점수 버튼을 누르면 D·O·F·T·A·N 여섯 항목의 원점수와 가중치 기여분이 그대로 펼쳐집니다. LLM 호출은 0회입니다.
가중치를 바꿔 저장 전에 결과 비교하기 관리자 → 추천 가중치에서 슬라이더를 움직이면 현재값과 변경안의 상위 20건이 좌우로 나란히 나오고 순위 변동이 표시됩니다. 시각과 기준 동네를 바꿔 "점심시간 역삼", "밤 11시 논현"을 각각 확인할 수 있습니다.
AI가 고장났을 때를 재현하기 관리자 → AI 운영의 고장 주입 스위치로 스키마 위반·후보 전량 금칙어·10초 타임아웃·API 5xx를 각각 켤 수 있습니다. 어느 경우에도 사장님 등록 흐름이 멈추지 않고 "직접 쓰기"로 넘어가는지 확인해 주세요.

문서의 규격이 코드에서 어디에 있는지

데모는 기획서를 그림으로 옮긴 것이 아니라 규격을 실행 가능한 형태로 옮긴 것입니다.

영업 중 판정

영업시간·브레이크타임·임시휴무·수동 토글이 겹칠 때의 우선순위를 한 함수로만 구현했습니다. 목록·지도·추천 세 곳이 이 함수를 호출합니다. 자정 넘김(18:00~02:00)과 경계 시각(시작 포함·종료 미포함)도 그대로입니다.

04번 6장 → OpenStateResolver.php

추천 점수 산식

S = w_d·D + w_o·O + w_f·F + w_t·T + w_a·A + w_n·N − P. 가중치는 전부 설정 테이블 값이고, 다양성 재배치는 점수를 바꾸지 않고 순서만 조정합니다. 정렬에 무작위가 없어 같은 입력이면 같은 결과가 나옵니다.

06번 2~4장 → FeedRanker.php

AI 출력 검증 7단계

JSON 파싱 → 길이 3 → 40~180자 → 금칙어 → 과장·효능 → 중복 → 남은 후보 0개면 폴백. 각 단계가 어디서 걸렸는지 사장님 화면과 관리자 로그 양쪽에 남습니다.

05번 5장 → AiCopywriter::validate()

위치 조회

바운딩 박스로 후보를 줄이고 하버사인으로 모서리를 쳐냅니다. 행정동 조회 결과와 합집합으로 모아 중복을 제거합니다. 목록 화면에 실제 후보 수와 소요 시간이 표시됩니다.

07번 4장 → GeoQuery.php

푸시 멱등

post:{id}:user:{id} 유니크 키로 중복 발송을 구조적으로 막습니다. 야간(22~08시) 게시분은 보류 상태로 들어갑니다. 게시 직후 발송 결과가 화면에 표시됩니다.

04번 8장 · 07번 6장 → OwnerApi::dispatchPush()

비용 3겹

캐시(같은 사진·키워드·톤·프롬프트 버전) → 매장별 일일 쿼터 → 서비스 전체 상한. 캐시 적중 시 과금 건수에서 빠지는 것을 관리자 패널에서 확인할 수 있습니다.

05번 8장 → AiCopywriter::generate()

데모에서 실물로 대체한 것

없는 것을 있는 척하지 않았습니다. 아래 네 가지는 대체물이고, 나머지는 실제 구현입니다.

항목데모에서는실제 구현 시
LLM 모델 호출 모의 어댑터가 응답을 만듭니다. 검증·필터·캐시·쿼터·폴백은 전부 실제입니다. AI_TEXT_PROVIDER=gemini 한 줄로 교체. 호출부 코드는 바뀌지 않습니다 (AI-02). GeminiAdapter 구현이 저장소에 들어 있습니다.
지도 SDK 좌표로 그린 벡터 맵. 마커·클러스터·반경 원·현재 위치는 실제 좌표 계산입니다. ⚠️ Q5 미확정. 발주사 명의 키 발급 후 네이버·카카오·구글 중 선택 (07번 7장).
매장 사진 업종별 일러스트를 생성해 채웠습니다. 목록 1:1 / 상세 비율 규칙은 그대로 적용됩니다. 테스트 매장 실사진 4주차 수신 (01번 5-5장 확인사항 1번).
본인인증 · 국세청 조회 심사 화면에 저장된 조회 결과를 표시합니다. 폐업 사업자 건이 한 건 섞여 있습니다. 본인확인 서비스 계약(3주차)·공공데이터포털 신청(4주차) 후 연동 (07번 7장).

데이터 모델에서 고친 네 가지

04번 문서를 실제 스키마로 옮기면서 그대로는 동작하지 않는 지점을 발견해 교정했습니다. 데모 DB는 교정본으로 돌아가고 있습니다.

대상원안의 문제교정
view_log
feed_event
파티션을 쓰면서 PRIMARY KEY (id) 단독. 파티션 키가 모든 유니크 키에 포함되어야 하므로 테이블 생성 자체가 ERROR 1503으로 실패합니다. PRIMARY KEY (id, viewed_at) 복합으로 변경. 일 단위 파티션은 그대로 유지.
store UNIQUE (biz_no, status)는 의도("승인 건 중복 방지")와 달리 "같은 상태 조합 금지"로 동작해 동일 번호의 pending 2건과 closed 2건까지 막습니다. 승인 상태에서만 값을 갖는 컬럼에 유니크. MySQL 8은 생성 컬럼 한 줄, MariaDB는 트리거로 동일 보장.
store_hour OW-14가 요구하는 격주 정기휴무를 담을 자리가 없습니다. 요일 단위 휴무까지만 표현됩니다. week_parity 추가. 판정 함수가 해당 월의 몇째 주인지로 가릅니다.
user role ENUM('customer','owner')은 CM-14의 모드 전환(한 사람이 손님이면서 사장님)을 표현하지 못합니다. is_owner 플래그로 변경. 사장님도 손님 화면을 그대로 씁니다.
데모 범위 안내. 로그인·약관 동의·계정 삭제·본인인증은 흐름만 정의하고 화면을 만들지 않았습니다. 결제·주문은 1차 범위 밖이지만 테이블은 생성되어 있습니다 (04번 10장). 데모 손님은 동네탐험가 계정 하나로 고정되어 있고, 사장님 모드는 소담 반찬 매장에 연결되어 있습니다. 여러 사람이 동시에 들어오면 같은 데이터를 보게 됩니다.