[feat] solution: 일정을 담을 자리 — 업장·기간당 한 행
This commit is contained in:
parent
a61d9a278c
commit
18e86a0d12
@ -223,6 +223,26 @@ CREATE TABLE IF NOT EXISTS public.place_faqs (
|
|||||||
deleted BOOLEAN NOT NULL DEFAULT FALSE
|
deleted BOOLEAN NOT NULL DEFAULT FALSE
|
||||||
);
|
);
|
||||||
|
|
||||||
|
-- LLM 이 만든 여행 일정. 기간당 한 행, body 에 코스 5개가 통째로 든다.
|
||||||
|
-- ★ 키는 (place_id, duration) 이다 — 업소 이름이 프롬프트에 들어가므로 옆집이 나눠 쓸 수 없다.
|
||||||
|
-- ★ body 는 렌더러 계약(shared/lib/section-data.ts 의 ItineraryItem[]) 그대로다.
|
||||||
|
CREATE TABLE IF NOT EXISTS public.place_itineraries (
|
||||||
|
place_itinerary_id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||||
|
place_id uuid NOT NULL,
|
||||||
|
duration VARCHAR(20) NOT NULL, -- '1박 2일' · '2박 3일'
|
||||||
|
body JSONB NOT NULL, -- ItineraryItem[]
|
||||||
|
generated_by SMALLINT NOT NULL, -- SourceType: 4=llm
|
||||||
|
model VARCHAR(100) NULL, -- 'perplexity:sonar'
|
||||||
|
generated_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
||||||
|
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
||||||
|
updated_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
||||||
|
deleted BOOLEAN NOT NULL DEFAULT FALSE
|
||||||
|
);
|
||||||
|
CREATE INDEX IF NOT EXISTS ix_place_itineraries_place ON public.place_itineraries (place_id);
|
||||||
|
CREATE UNIQUE INDEX IF NOT EXISTS uq_place_itineraries
|
||||||
|
ON public.place_itineraries (place_id, duration)
|
||||||
|
WHERE deleted = FALSE;
|
||||||
|
|
||||||
-- ============================================================
|
-- ============================================================
|
||||||
-- 지역 콘텐츠 — 접두어가 없는 유일한 표. **그게 공유물이라는 표시다.**
|
-- 지역 콘텐츠 — 접두어가 없는 유일한 표. **그게 공유물이라는 표시다.**
|
||||||
-- ★ 키는 place_id 가 아니다. 축제·관광지·맛집의 유일성은 출처가 준 external_id 이고,
|
-- ★ 키는 place_id 가 아니다. 축제·관광지·맛집의 유일성은 출처가 준 external_id 이고,
|
||||||
|
|||||||
29
postgres-init/migrations/0010_place_itineraries.sql
Normal file
29
postgres-init/migrations/0010_place_itineraries.sql
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
-- 0010 · LLM 여행 일정 저장 표
|
||||||
|
--
|
||||||
|
-- 일정은 지금까지 저장하지 않고 빌드마다 즉석 계산했다(services/itinerary.py).
|
||||||
|
-- LLM 생성으로 바꾸면서 호출이 건당 20~50초·유료가 되어 매 빌드 재생성이 불가능하다 —
|
||||||
|
-- 업장·기간당 한 번 만들어 두고 그대로 쓴다.
|
||||||
|
-- ★ area_contents 에 넣지 않는다: 그 표의 유일성 근거는 지역·출처 기준이고 이 값은 업장에 붙는다.
|
||||||
|
--
|
||||||
|
-- ★ 2026-09-11 기준 dev DB 에는 아직 적용하지 않았다. 그 DB 의 schema_migrations 에는
|
||||||
|
-- 다른 브랜치의 0009_faq_catalog_columns 가 기록돼 있어, migrate.py 가 이 브랜치의
|
||||||
|
-- 0009_align_with_init_sql 까지 함께 적용하려 든다 — 번호 정리가 먼저다.
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS public.place_itineraries (
|
||||||
|
place_itinerary_id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||||
|
place_id uuid NOT NULL,
|
||||||
|
duration VARCHAR(20) NOT NULL,
|
||||||
|
body JSONB NOT NULL,
|
||||||
|
generated_by SMALLINT NOT NULL,
|
||||||
|
model VARCHAR(100) NULL,
|
||||||
|
generated_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
||||||
|
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
||||||
|
updated_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
||||||
|
deleted BOOLEAN NOT NULL DEFAULT FALSE
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE INDEX IF NOT EXISTS ix_place_itineraries_place ON public.place_itineraries (place_id);
|
||||||
|
|
||||||
|
CREATE UNIQUE INDEX IF NOT EXISTS uq_place_itineraries
|
||||||
|
ON public.place_itineraries (place_id, duration)
|
||||||
|
WHERE deleted = FALSE;
|
||||||
@ -255,6 +255,44 @@ class place_faqs(MainTableMixin, MAIN_BASE):
|
|||||||
sort_order = Column(Integer, nullable=False, server_default=text("0"), default=0)
|
sort_order = Column(Integer, nullable=False, server_default=text("0"), default=0)
|
||||||
|
|
||||||
|
|
||||||
|
class place_itineraries(MainTableMixin, MAIN_BASE):
|
||||||
|
"""LLM 이 만든 여행 일정. **기간당 한 행**이고 `body` 에 코스 5개가 통째로 든다.
|
||||||
|
|
||||||
|
★ 왜 area_contents 가 아닌가
|
||||||
|
그 표의 유일성 근거는 셋 다 지역·출처 기준이다((source, external_id) ·
|
||||||
|
(region_code, kind) · (region_code, content_type)). 이 값은 **업장 하나에 붙는다** —
|
||||||
|
업소 이름이 프롬프트에 들어가고, 같은 지역 옆집이 나눠 쓸 수 없다.
|
||||||
|
넷째 근거를 그 표에 더하면 0004·0007 에서 겪은 "제약이 겹쳐 조용히 틀리는" 사고를
|
||||||
|
다시 만든다(area_contents.__table_args__ 주석).
|
||||||
|
|
||||||
|
★ body 는 렌더러 계약 그대로다(`shared/lib/section-data.ts` 의 ItineraryItem[]).
|
||||||
|
읽는 쪽이 모양을 다시 바꾸지 않아야 사장님이 손으로 붙여넣은 것과 갈리지 않는다 —
|
||||||
|
지역 이야기가 body 에 봉투째 담는 것과 같은 이유다.
|
||||||
|
|
||||||
|
★ 코스마다 한 행으로 쪼개지 않는다. 다시 생성할 때 그 한 행을 덮어쓰면 되고,
|
||||||
|
쪼개면 "5개를 받았는데 3개만 갱신된" 상태가 생긴다.
|
||||||
|
"""
|
||||||
|
|
||||||
|
__tablename__ = "place_itineraries"
|
||||||
|
__table_args__ = (
|
||||||
|
Index(
|
||||||
|
"uq_place_itineraries",
|
||||||
|
"place_id", "duration",
|
||||||
|
unique=True,
|
||||||
|
postgresql_where=text("deleted = false"),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
place_itinerary_id = Column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4)
|
||||||
|
place_id = Column(UUID(as_uuid=True), nullable=False, index=True)
|
||||||
|
# '1박 2일' · '2박 3일'. 화면 탭이 되는 값이라 표기를 바꾸지 않는다(prompts/itinerary.DURATIONS).
|
||||||
|
duration = Column(String(20), nullable=False)
|
||||||
|
body = Column(JSONB, nullable=False) # ItineraryItem[]
|
||||||
|
generated_by = Column(SmallInteger, nullable=False) # SourceType — LLM
|
||||||
|
model = Column(String(100), nullable=True) # 'perplexity:sonar'
|
||||||
|
generated_at = Column(DateTime(timezone=True), nullable=False, server_default=_utc_now_sql())
|
||||||
|
|
||||||
|
|
||||||
# ============================================================
|
# ============================================================
|
||||||
# local : 지역 정보(행정구역 코드 단위 캐시) / 가는 길 / 주변
|
# local : 지역 정보(행정구역 코드 단위 캐시) / 가는 길 / 주변
|
||||||
# ============================================================
|
# ============================================================
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user