Spaces:
Running
Running
Case Zero - initial public release (fully local: Qwen2.5-1.5B via llama.cpp + Supertonic, custom pixel-noir SPA via gradio.Server)
414dc55 | """casefile_to_public: a generated CaseFile projects to the PUBLIC view without leaking | |
| the sealed solution, and conforms to the same wire contract the client expects. | |
| """ | |
| from __future__ import annotations | |
| import json | |
| from case_zero.api.case_adapter import casefile_to_public | |
| from case_zero.api.public_view import PublicCase | |
| from case_zero.generator.assemble import assemble_case | |
| from case_zero.generator.stages import GenSuspect, MysteryOut, WorldCastOut | |
| from case_zero.schemas.case import GenerationKnobs | |
| from case_zero.schemas.enums import MotiveCategory | |
| from case_zero.schemas.timeline import TimeWindow | |
| def _casefile(): | |
| world = WorldCastOut( | |
| title="THE LANTERN ROOM", | |
| briefing="A bookbinder is found dead in a locked study during a winter salon.", | |
| setting_name="Ashgrove House", | |
| locations=["The Study", "The Parlor", "The Conservatory", "The Cellar"], | |
| victim_name="Edmund Crane", | |
| victim_role="Master Bookbinder", | |
| cause_of_death="Struck from behind with a heavy press", | |
| found_at_index=0, | |
| weapon_name="bookbinding press", | |
| weapon_kind="blunt", | |
| suspects=[ | |
| GenSuspect(name="Cora Vale", role="Apprentice", persona_summary="A quiet apprentice with ink-stained hands.", secret="She forged a signature once.", cover_story="I was cataloguing in the parlor.", evidence_name="ink bottle", evidence_reveal="A spilled bottle of rare ink.", gender="female"), | |
| GenSuspect(name="Miles Ardent", role="Patron", persona_summary="A wealthy patron with debts.", secret="He owes the victim money.", cover_story="I never left the salon.", evidence_name="promissory note", evidence_reveal="A note for a large sum, unpaid.", gender="male"), | |
| GenSuspect(name="Greta Holt", role="Housekeeper", persona_summary="The long-serving housekeeper.", secret="She skims the household accounts.", cover_story="I was in the cellar fetching wine.", evidence_name="ledger", evidence_reveal="A ledger with altered figures.", gender="female"), | |
| GenSuspect(name="Felix Crane", role="Nephew", persona_summary="The restless nephew and heir.", secret="He gambles in secret.", cover_story="I stepped out to the conservatory for air.", evidence_name="card", evidence_reveal="A gambling marker from a city den.", gender="male"), | |
| ], | |
| ) | |
| mystery = MysteryOut( | |
| motive_category=MotiveCategory.GREED, | |
| motive_summary="To erase a debt that would have ruined him.", | |
| method_narrative="He waited until the study emptied, then struck.", | |
| alibi_claim="I was in the parlor with the others all evening.", | |
| alibi_claimed_loc_index=1, | |
| breaker_one_name="thread", | |
| breaker_one_reveal="A torn thread of dark cloth on the study latch.", | |
| breaker_two_name="footprint", | |
| breaker_two_reveal="A damp footprint by the press, fresh at the hour.", | |
| deduction_chain=["The study was locked from inside.", "Only one route stayed open.", "The thread matches the patron's coat."], | |
| ) | |
| return assemble_case( | |
| case_id="gen-000042", seed=42, knobs=GenerationKnobs(), world=world, mystery=mystery, | |
| window=TimeWindow(start_min=21 * 60, end_min=22 * 60), | |
| tod=TimeWindow(start_min=21 * 60 + 20, end_min=21 * 60 + 50), | |
| culprit_idx=1, crime_idx=0, claimed_idx=1, | |
| ) | |
| def test_adapter_conforms_to_public_contract(): | |
| pub = casefile_to_public(_casefile()) | |
| assert isinstance(pub, PublicCase) | |
| assert len(pub.suspects) == 4 | |
| assert len(pub.evidence) >= 1 | |
| assert len(pub.motives) == 4 | |
| assert any(m.id == "M1" for m in pub.motives) # the true motive is option M1 | |
| assert pub.timeline and pub.flashback.title | |
| for s in pub.suspects: | |
| assert len(s.suggested_questions) == 5 | |
| def test_adapter_does_not_leak_culprit(): | |
| case = _casefile() | |
| pub = casefile_to_public(case) | |
| blob = json.dumps(pub.model_dump(by_alias=True)) | |
| # the sealed culprit is S2 (culprit_idx=1); nothing must mark who it is | |
| for token in ('"isCulprit"', '"is_culprit"', '"secret"', '"breaksOn"', '"correctMotive"', '"killer"'): | |
| assert token not in blob | |
| # the true motive text appears (as one option) but is not flagged correct | |
| assert case.culprit.true_motive.summary in blob | |