P4-P7 페르소나와 Tailnet 허용 보강
This commit is contained in:
parent
17b114f647
commit
7f4a2d90c7
12 changed files with 121 additions and 30 deletions
|
|
@ -10,6 +10,7 @@ from __future__ import annotations
|
|||
import json
|
||||
import uuid
|
||||
from dataclasses import dataclass
|
||||
from pathlib import Path
|
||||
from typing import Any, Iterable, Literal, cast
|
||||
|
||||
from .config import settings
|
||||
|
|
@ -18,6 +19,7 @@ from .services.persona import PersonaCard, SEED_PERSONAS, get_seed_persona
|
|||
|
||||
|
||||
SEED_VERSION = 1
|
||||
REPO_PERSONA_DIR = Path(__file__).resolve().parents[3] / "data" / "personas"
|
||||
PersonaStatus = Literal["draft", "review", "approved", "archived"]
|
||||
|
||||
_CARD_COLUMNS = """
|
||||
|
|
@ -70,6 +72,46 @@ def seed_persona_id(code: str) -> str:
|
|||
return str(uuid.uuid5(uuid.NAMESPACE_URL, f"vignette:persona:{code.upper()}"))
|
||||
|
||||
|
||||
def _card_from_payload(payload: dict[str, Any]) -> PersonaCard:
|
||||
return PersonaCard(
|
||||
code=str(payload["code"]).strip().upper(),
|
||||
display_name=str(payload["display_name"]),
|
||||
difficulty=str(payload["difficulty"]),
|
||||
theory_target=_string_list(payload.get("theory_target")),
|
||||
demographics=_json_dict(payload.get("demographics")),
|
||||
presenting=_json_dict(payload.get("presenting")),
|
||||
history=_json_dict(payload.get("history")),
|
||||
big5=_json_dict(payload.get("big5")),
|
||||
resistance=_json_dict(payload.get("resistance")),
|
||||
speech_style=_json_dict(payload.get("speech_style")),
|
||||
affect_baseline=_json_dict(payload.get("affect_baseline")),
|
||||
ccd=_json_dict(payload.get("ccd")),
|
||||
dsm5_dimensional=_json_dict(payload.get("dsm5_dimensional")),
|
||||
source_provenance=str(payload.get("source_provenance") or ""),
|
||||
is_synthetic=bool(payload.get("is_synthetic", True)),
|
||||
)
|
||||
|
||||
|
||||
def load_file_personas(persona_dir: Path = REPO_PERSONA_DIR) -> list[PersonaCard]:
|
||||
"""Load repository-managed persona cards that are not hard-coded in persona.py."""
|
||||
if not persona_dir.exists():
|
||||
return []
|
||||
cards: list[PersonaCard] = []
|
||||
for path in sorted(persona_dir.glob("P*.json")):
|
||||
payload = json.loads(path.read_text(encoding="utf-8"))
|
||||
if isinstance(payload, dict):
|
||||
cards.append(_card_from_payload(payload))
|
||||
return cards
|
||||
|
||||
|
||||
def built_in_personas() -> list[PersonaCard]:
|
||||
"""Return deterministic built-in catalog cards from code seeds plus repo JSON cards."""
|
||||
cards: dict[str, PersonaCard] = {card.code.upper(): card for card in SEED_PERSONAS.values()}
|
||||
for card in load_file_personas():
|
||||
cards.setdefault(card.code.upper(), card)
|
||||
return [cards[code] for code in sorted(cards)]
|
||||
|
||||
|
||||
def _json_dict(value: Any) -> dict[str, Any]:
|
||||
if value is None:
|
||||
return {}
|
||||
|
|
@ -157,7 +199,10 @@ def persona_draft_record_from_row(row: Any) -> PersonaDraftRecord:
|
|||
|
||||
|
||||
def seed_fallback_persona(code: str) -> CatalogPersona | None:
|
||||
card = get_seed_persona(code)
|
||||
normalized = code.strip().upper()
|
||||
card = get_seed_persona(normalized)
|
||||
if card is None:
|
||||
card = next((entry for entry in load_file_personas() if entry.code.upper() == normalized), None)
|
||||
if card is None:
|
||||
return None
|
||||
return CatalogPersona(
|
||||
|
|
@ -178,7 +223,7 @@ def seed_fallback_personas() -> list[CatalogPersona]:
|
|||
source="seed_fallback",
|
||||
degraded=True,
|
||||
)
|
||||
for card in SEED_PERSONAS.values()
|
||||
for card in built_in_personas()
|
||||
]
|
||||
|
||||
|
||||
|
|
@ -187,7 +232,7 @@ async def materialize_seed_personas() -> int:
|
|||
get_pool()
|
||||
count = 0
|
||||
async with acquire(role="admin") as conn:
|
||||
for card in SEED_PERSONAS.values():
|
||||
for card in built_in_personas():
|
||||
await conn.execute(
|
||||
"""
|
||||
INSERT INTO app.persona_card (
|
||||
|
|
@ -574,6 +619,7 @@ __all__ = [
|
|||
"PersonaReviewAction",
|
||||
"PersonaStatus",
|
||||
"SEED_VERSION",
|
||||
"built_in_personas",
|
||||
"card_from_row",
|
||||
"catalog_persona_from_row",
|
||||
"create_persona_draft",
|
||||
|
|
@ -583,6 +629,7 @@ __all__ = [
|
|||
"list_approved_personas",
|
||||
"list_catalog_personas",
|
||||
"list_persona_review_queue",
|
||||
"load_file_personas",
|
||||
"materialize_seed_personas",
|
||||
"persona_draft_record_from_row",
|
||||
"persona_review_item_from_row",
|
||||
|
|
|
|||
|
|
@ -208,6 +208,36 @@ def _draft_payload(
|
|||
|
||||
|
||||
class PersonaApprovalBoundaryTest(unittest.IsolatedAsyncioTestCase):
|
||||
async def test_repository_loads_repo_persona_files_for_p4_to_p7(self) -> None:
|
||||
cards = persona_repository.load_file_personas()
|
||||
|
||||
self.assertEqual([card.code for card in cards], ["P4", "P5", "P6", "P7"])
|
||||
self.assertEqual(cards[0].display_name, "하늘(가명) · 고2 · 학업/시험 불안")
|
||||
self.assertEqual(cards[0].difficulty, "easy")
|
||||
self.assertIn("cbt", cards[0].theory_target)
|
||||
|
||||
async def test_seed_fallback_includes_repo_persona_files(self) -> None:
|
||||
entry = persona_repository.seed_fallback_persona("p4")
|
||||
|
||||
self.assertIsNotNone(entry)
|
||||
assert entry is not None
|
||||
self.assertEqual(entry.card.code, "P4")
|
||||
self.assertEqual(entry.source, "seed_fallback")
|
||||
self.assertTrue(entry.degraded)
|
||||
|
||||
async def test_materialize_seed_personas_upserts_code_and_repo_file_cards(self) -> None:
|
||||
conn = _PersonaCardConn([])
|
||||
|
||||
with (
|
||||
patch.object(persona_repository, "get_pool", return_value=object()),
|
||||
patch.object(persona_repository, "acquire", lambda **_: _Acquire(conn)),
|
||||
):
|
||||
count = await persona_repository.materialize_seed_personas()
|
||||
|
||||
self.assertEqual(count, 7)
|
||||
inserted_codes = [args[1] for _, args in conn.execute_calls]
|
||||
self.assertEqual(inserted_codes, ["P1", "P2", "P3", "P4", "P5", "P6", "P7"])
|
||||
|
||||
async def test_catalog_repository_lists_only_approved_personas(self) -> None:
|
||||
conn = _PersonaCardConn(
|
||||
[
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue