Junhoee's picture
Upload 98 files
f646e0b verified
Raw
History Blame Contribute Delete
3.78 kB
"""μ½˜ν…μΈ  λ‘œλ” β€” content/ YAML λ©”λͺ¨λ¦¬ λ‘œλ“œ (MVP κ΅¬ν˜„).
μƒμœ„ 계측(services)은 이 μΈν„°νŽ˜μ΄μŠ€λ§Œ μ‚¬μš©ν•œλ‹€. 2λ‹¨κ³„μ—μ„œ GraphRAG(kb_query ν™œμ„±)
κ΅¬ν˜„μœΌλ‘œ ꡐ체해도 services μ½”λ“œκ°€ λ°”λ€Œμ§€ μ•Šμ•„μ•Ό ν•œλ‹€ (ablation μš”κ±΄).
슀포일러 μŠ€μ½”ν”„: μΉ΄λ“œμ—λŠ” μ „μ—­ μˆœμ„œ(order)κ°€ μžˆλ‹€. λͺ¨λ“  μ‘°νšŒλŠ”
`order(card) <= order(ν˜„μž¬ μΉ΄λ“œ)` ν•„ν„°λ₯Ό κ°•μ œν•œλ‹€ (beat_id ≀ ν˜„μž¬).
"""
from __future__ import annotations
import hashlib
from pathlib import Path
from engine.schemas.beatcard import BeatCard, RCard
from engine.schemas.character import CharacterSheet
from engine.schemas.chapter import Chapter
from engine.schemas.validate import check_links, load_content
class ContentError(RuntimeError):
pass
class ContentRepository:
def __init__(self, root: str | Path = "content"):
self.root = Path(root)
episodes, r_cards, chapter, characters, errors = load_content(self.root)
link_errors, _ = check_links(episodes, chapter, r_cards)
errors += link_errors
if errors:
raise ContentError("검증 μ‹€νŒ¨ μ½˜ν…μΈ λŠ” μ μž¬ν•˜μ§€ μ•ŠλŠ”λ‹€:\n" + "\n".join(errors))
if chapter is None:
raise ContentError("챕터 메타 μ—†μŒ")
self.chapter: Chapter = chapter
self.r_cards: dict[str, RCard] = r_cards
self.characters: dict[str, CharacterSheet] = {
c.id: c for c in (characters.characters if characters else [])
}
self._cards: dict[str, BeatCard] = {}
self._order: dict[str, int] = {}
self.episode_entry: dict[str, str] = {}
i = 0
for ep in episodes:
self.episode_entry[ep.episode.id] = ep.cards[0].id
for card in ep.cards:
self._cards[card.id] = card
self._order[card.id] = i
i += 1
# μ½˜ν…μΈ  버전 μŠ€νƒ¬ν”„ β€” μ„Έμ…˜ 볡원 μ‹œ 뢈일치 κ²€μ‚¬μš© (μΉ΄λ“œ ID/μˆœμ„œ κΈ°μ€€.
# 본문만 바뀐 갱신은 감지 λͺ»ν•˜μ§€λ§Œ 볡원이 κΉ¨μ§€μ§€λŠ” μ•ŠλŠ”λ‹€)
ordered_ids = sorted(self._order, key=self._order.get)
self.content_version: str = hashlib.sha256(
"|".join([self.chapter.id, *ordered_ids]).encode()).hexdigest()[:16]
# ── 쑰회 (슀포일러 ν•„ν„° κ°•μ œ) ────────────────────────────
def get_card(self, card_id: str, current_id: str | None = None) -> BeatCard:
"""μΉ΄λ“œ 쑰회. current_idλ₯Ό μ£Όλ©΄ beat_id ≀ ν˜„μž¬ ν•„ν„°λ₯Ό κ°•μ œν•œλ‹€."""
card = self._cards.get(card_id)
if card is None:
raise ContentError(f"μΉ΄λ“œ μ—†μŒ: {card_id}")
if current_id is not None and self.order(card_id) > self.order(current_id):
raise ContentError(f"슀포일러 ν•„ν„°: {card_id}λŠ” ν˜„μž¬({current_id}) 이후 μΉ΄λ“œ")
return card
def order(self, card_id: str) -> int:
return self._order[card_id]
@property
def total_cards(self) -> int:
return len(self._cards)
def has_card(self, card_id: str) -> bool:
return card_id in self._cards
def cards_upto(self, current_id: str) -> list[BeatCard]:
"""ν˜„μž¬ μ‹œμ κΉŒμ§€μ˜ μΉ΄λ“œ μ „λΆ€ (μ»¨ν…μŠ€νŠΈ μ£Όμž…μš©)."""
cut = self.order(current_id)
return [c for cid, c in self._cards.items() if self._order[cid] <= cut]
def resolve_ref(self, target: str) -> str | None:
"""μΉ΄λ“œ ID λ˜λŠ” μ—ν”Όμ†Œλ“œ μ°Έμ‘°(E02)λ₯Ό μ‹€μ œ μΉ΄λ“œ ID둜. 챕터 밖이면 None(좜ꡬ)."""
if target in self._cards:
return target
if target in self.episode_entry:
return self.episode_entry[target]
return None # E07 λ“± β€” 챕터 좜ꡬ