feat(curator): 4계층 picks + tier_rationale + narrative.retrospective 스키마
This commit is contained in:
@@ -17,25 +17,42 @@ class Pick(BaseModel):
|
||||
return sorted(v)
|
||||
|
||||
|
||||
class TierRationale(BaseModel):
|
||||
bonus: str = Field(max_length=40)
|
||||
extended: str = Field(max_length=40)
|
||||
pool: str = Field(max_length=40)
|
||||
|
||||
|
||||
class Narrative(BaseModel):
|
||||
headline: str
|
||||
summary_3lines: List[str] = Field(min_length=3, max_length=3)
|
||||
hot_cold_comment: str = ""
|
||||
warnings: str = ""
|
||||
retrospective: str = Field(default="", max_length=80)
|
||||
|
||||
|
||||
class CuratorOutput(BaseModel):
|
||||
picks: List[Pick]
|
||||
core_picks: List[Pick] = Field(min_length=5, max_length=5)
|
||||
bonus_picks: List[Pick] = Field(min_length=5, max_length=5)
|
||||
extended_picks: List[Pick] = Field(min_length=5, max_length=5)
|
||||
pool_picks: List[Pick] = Field(min_length=5, max_length=5)
|
||||
tier_rationale: TierRationale
|
||||
narrative: Narrative
|
||||
confidence: int = Field(ge=0, le=100)
|
||||
|
||||
|
||||
def validate_response(data: dict, candidate_numbers: List[List[int]]) -> CuratorOutput:
|
||||
out = CuratorOutput.model_validate(data)
|
||||
if len(out.picks) != 5:
|
||||
raise ValueError("picks must have exactly 5 sets")
|
||||
candidate_set = {tuple(sorted(c)) for c in candidate_numbers}
|
||||
for p in out.picks:
|
||||
all_picks = (
|
||||
out.core_picks + out.bonus_picks + out.extended_picks + out.pool_picks
|
||||
)
|
||||
# 중복 픽 검증
|
||||
pick_keys = [tuple(p.numbers) for p in all_picks]
|
||||
if len(pick_keys) != len(set(pick_keys)):
|
||||
raise ValueError("duplicate picks across tiers")
|
||||
# 후보에 없는 번호 조합 금지
|
||||
for p in all_picks:
|
||||
if tuple(p.numbers) not in candidate_set:
|
||||
raise ValueError(f"pick {p.numbers} not in candidates")
|
||||
return out
|
||||
|
||||
Reference in New Issue
Block a user