counsel-env / server /case_generator.py
heavycoderhh's picture
Upload folder using huggingface_hub
96a73f4 verified
Raw
History Blame Contribute Delete
31.3 kB
"""
Cross-Examination Arena — Case generator.
Three fully-fleshed templates (alibi, knowledge denial, possession denial),
six slot-fill pools, and the public `generate_case()` factory.
Add 2 more templates on Day 1 if time allows. Each new template just needs
to return a dict with the same shape as `_make_case_dict` produces.
"""
import random
from dataclasses import dataclass
from typing import Dict, List
# ---------------------------------------------------------------------------
# Contradiction dataclass (mirror of witness.Contradiction so the case file
# stays self-contained; in your real codebase, import from witness.py).
# ---------------------------------------------------------------------------
@dataclass
class Contradiction:
cid: str
trigger_keywords: List[str]
sealed_claim: str
disprover_evidence_id: str
triggered: bool = False
surfaced: bool = False
# ---------------------------------------------------------------------------
# Slot-fill pools. Extend these freely — each entry adds case variety.
# ---------------------------------------------------------------------------
NAMES = [
"Sara Patel", "Mark Chen", "Alex Reyes", "Priya Sharma", "Jordan Kim",
"Diana Okafor", "Tomas Ruiz", "Yelena Volkov", "Hassan Ali", "Mei Tanaka",
"Carlos Mendes", "Ingrid Lindqvist", "Dev Krishnan", "Naomi Brown",
"Samir Haque", "Lila Andersson", "Theo Morales", "Reza Farahani",
"Kavita Iyer", "Owen Nakamura", "Esme Dubois", "Ravi Joshi",
"Hana Lee", "Felix Bauer", "Aaliyah Singh", "Mateo Silva",
"Zoe Whitfield", "Daichi Sato", "Nadine Mahfouz", "Pavel Novak",
]
PLACES = [
"the Riverside Cafe on Fifth Avenue",
"the parking garage at 22 Elm Street",
"the Marriott hotel lobby downtown",
"the office of Henderson & Co. on Pine",
"the warehouse on Industrial Drive",
"the gym on Lexington",
"Brennan's Bar near Union Square",
"the apartment at 480 Oak Lane",
"the boardwalk at Crescent Beach",
"the Greyhound bus station on Central",
"the parking lot behind the Westgate Mall",
"the rooftop bar at the Stratton Hotel",
"the public library on Walnut Street",
"the diner at the corner of 9th and Broad",
]
TIMES = [
"8:15 PM", "9:00 PM", "9:45 PM", "10:30 PM", "11:00 PM", "11:45 PM",
"12:15 AM", "1:00 AM", "7:30 PM", "6:45 PM",
]
DATES = [
"March 14th", "April 3rd", "January 22nd", "October 17th", "August 9th",
"December 5th", "May 28th", "June 14th",
]
OBJECTS = [
"the .38 revolver", "the briefcase", "the Rolex watch", "the burner phone",
"the laptop", "the security badge", "the diamond pendant",
"the warehouse keys", "the master keycard",
]
MOTIVES = [
"a $40,000 unpaid debt",
"a contested inheritance",
"a botched business partnership",
"an extramarital affair that had been discovered",
"a longstanding professional rivalry",
"the dismissal from a senior position six months earlier",
]
# ---------------------------------------------------------------------------
# Helper to build the case dict in a consistent shape.
# ---------------------------------------------------------------------------
def _make_case_dict(case_id, brief, ground_truth, story, evidence, contradictions):
return {
"case_id": case_id,
"case_brief": brief,
"ground_truth": ground_truth,
"witness_story": story,
"evidence": evidence,
"contradictions": contradictions,
}
def _case_suffix() -> str:
"""Seed-respecting six-character case suffix."""
return f"{random.randint(0, 16**6 - 1):06x}"
# ---------------------------------------------------------------------------
# Helper functions for time manipulation
# ---------------------------------------------------------------------------
def _minus_minutes(time_str, minutes):
# Simple helper for time arithmetic
hour, minute = map(int, time_str.replace(" PM", "").replace(" AM", "").split(":"))
if "PM" in time_str and hour != 12:
hour += 12
total_minutes = hour * 60 + minute - minutes
new_hour = total_minutes // 60
new_minute = total_minutes % 60
return f"{new_hour % 12 or 12}:{new_minute:02d} {'PM' if new_hour >= 12 else 'AM'}"
def _plus_minutes(time_str, minutes):
hour, minute = map(int, time_str.replace(" PM", "").replace(" AM", "").split(":"))
if "PM" in time_str and hour != 12:
hour += 12
total_minutes = hour * 60 + minute + minutes
new_hour = total_minutes // 60
new_minute = total_minutes % 60
return f"{new_hour % 12 or 12}:{new_minute:02d} {'PM' if new_hour >= 12 else 'AM'}"
# ---------------------------------------------------------------------------
# Template 1 — Alibi conflict
# ---------------------------------------------------------------------------
def generate_alibi_case():
"""Witness claims they were at place A; phone/transit data places them at B."""
perp = random.choice(NAMES)
fake_place = random.choice(PLACES)
real_place = random.choice([p for p in PLACES if p != fake_place])
crime_place = random.choice([p for p in PLACES if p not in (fake_place, real_place)])
t = random.choice(TIMES)
date = random.choice(DATES)
motive = random.choice(MOTIVES)
case_id = f"alibi_{_case_suffix()}"
brief = (
f"{perp} is the prime suspect in the burglary at {crime_place} on the night of {date}. "
f"The state alleges the burglary occurred at approximately {t}. "
f"The defendant has taken the stand."
)
ground_truth = (
f"{perp} was at {real_place} at {t} on {date}, then traveled to {crime_place} "
f"and committed the burglary. Motive: {motive}."
)
story = {
"alibi": f"I was at {fake_place} that entire evening with friends.",
"where": f"I told you, {fake_place}. I never left.",
"evening": f"I had dinner and drinks at {fake_place} until close.",
"warehouse": f"I have never been to {crime_place}. Not once.",
"crime": f"I had nothing to do with what happened at {crime_place}.",
"motive": f"I had no reason to do anything to anyone.",
"victim": f"We had a professional disagreement, that's all. Nothing serious.",
"phone": f"My phone was with me the whole night.",
}
evidence = {
"phone_tower_log":
f"Cellular records: {perp}'s phone connected to the tower nearest "
f"{real_place} at {t}, then to the tower covering {crime_place} 22 minutes later.",
"ride_receipt":
f"Rideshare receipt in {perp}'s account: dropoff at {real_place} at "
f"{_minus_minutes(t, 30)}; second pickup at {real_place}, dropoff at "
f"{crime_place} at {_plus_minutes(t, 18)}.",
"victim_letter":
f"Letter from the victim's attorney to {perp}, dated three weeks before the "
f"burglary, formally demanding payment related to {motive}.",
"doormat_print":
f"A partial shoe print recovered from the doormat at {crime_place}, "
f"matching size and tread of footwear later seized from {perp}'s residence.",
}
contradictions = [
Contradiction(
cid="c1_alibi_location",
trigger_keywords=[
"where were you", "alibi", "that night", "evening", "your location",
t.lower(),
],
sealed_claim=f"I was at {fake_place} the entire evening — I never left.",
disprover_evidence_id="phone_tower_log",
),
Contradiction(
cid="c2_warehouse_familiarity",
trigger_keywords=[
"ever been to", "familiar with", "the warehouse", "the scene",
"crime scene", crime_place.split()[1].lower() if len(crime_place.split()) > 1 else "warehouse",
],
sealed_claim=f"I have never set foot at {crime_place}. I wouldn't even know how to get there.",
disprover_evidence_id="ride_receipt",
),
Contradiction(
cid="c3_motive_denial",
trigger_keywords=[
"motive", "reason", "grievance", "dispute", "the victim",
"any conflict", "disagreement",
],
sealed_claim=f"I had no real grievance with the victim. We had a polite professional relationship.",
disprover_evidence_id="victim_letter",
),
]
return _make_case_dict(case_id, brief, ground_truth, story, evidence, contradictions)
# ---------------------------------------------------------------------------
# Template 2 — Knowledge denial
# ---------------------------------------------------------------------------
def generate_knowledge_denial_case():
"""Witness denies knowing victim/place/object; documentary evidence shows otherwise."""
perp = random.choice(NAMES)
victim = random.choice([n for n in NAMES if n != perp])
place = random.choice(PLACES)
date = random.choice(DATES)
case_id = f"knowledge_{_case_suffix()}"
brief = (
f"{perp} is charged with embezzlement from {place}. The state alleges {perp} "
f"systematically diverted funds in coordination with the manager, {victim}, "
f"between {date} and the present. The defendant denies knowing {victim}."
)
ground_truth = (
f"{perp} and {victim} have been business associates for over two years. "
f"They met at {place} and collaborated on the embezzlement scheme."
)
story = {
"know": f"I don't know {victim} at all. Never met them.",
"victim": f"Who is {victim}? I've never heard of them.",
"place": f"I've never been to {place}.",
"embezzlement": f"I had nothing to do with any embezzlement.",
"work": f"I work in a completely different field.",
"meeting": f"I've never met with anyone about this.",
}
evidence = {
"email_chain":
f"Email chain between {perp} and {victim} discussing the embezzlement plan, "
f"dated {date}, sent from {perp}'s work email.",
"meeting_notes":
f"Meeting notes from {place} conference room, dated two weeks ago, "
f"with {perp} and {victim}'s signatures.",
"bank_records":
f"Bank records showing transfers from {place}'s accounts to {perp}'s "
f"personal account, totaling $150,000 over six months.",
"visitor_log":
f"Security log from {place} showing {perp}'s entry and exit times "
f"on multiple dates, including meetings with {victim}.",
}
contradictions = [
Contradiction(
cid="c1_know_victim",
trigger_keywords=[
"know", "met", "familiar", victim.split()[0].lower(),
"relationship", "acquaintance",
],
sealed_claim=f"I have never met {victim} in my life.",
disprover_evidence_id="email_chain",
),
Contradiction(
cid="c2_place_visit",
trigger_keywords=[
"been to", "visited", place.split()[1].lower() if len(place.split()) > 1 else "place",
"location", "office",
],
sealed_claim=f"I've never set foot in {place}.",
disprover_evidence_id="visitor_log",
),
Contradiction(
cid="c3_collaboration",
trigger_keywords=[
"worked with", "collaborated", "together", "scheme",
"embezzlement", "plan",
],
sealed_claim=f"I worked alone. No one else was involved.",
disprover_evidence_id="meeting_notes",
),
]
return _make_case_dict(case_id, brief, ground_truth, story, evidence, contradictions)
# ---------------------------------------------------------------------------
# Template 3 — Possession denial
# ---------------------------------------------------------------------------
def generate_possession_denial_case():
"""Witness denies possessing object; evidence shows they did."""
perp = random.choice(NAMES)
obj = random.choice(OBJECTS)
place = random.choice(PLACES)
date = random.choice(DATES)
motive = random.choice(MOTIVES)
case_id = f"possession_{_case_suffix()}"
brief = (
f"{perp} is charged with theft of {obj} from {place} on {date}. "
f"The state alleges {perp} stole {obj} and used it in a subsequent crime. "
f"The defendant denies ever possessing {obj}."
)
ground_truth = (
f"{perp} stole {obj} from {place} on {date}. They used it later that night "
f"in a robbery. Motive for the theft: {motive}."
)
story = {
"possession": f"I've never had {obj}. Never seen it.",
"object": f"What is {obj}? I've never heard of it.",
"theft": f"I didn't steal anything.",
"crime": f"I had nothing to do with any crime.",
"place": f"I've never been to {place}.",
"motive": f"I had no reason to steal anything.",
}
evidence = {
"surveillance_footage":
f"Surveillance footage from {place} showing {perp} entering at 8:00 PM "
f"on {date}, taking {obj}, and leaving at 8:15 PM.",
"fingerprint_analysis":
f"Fingerprint analysis of {obj} recovered from the crime scene, "
f"matching {perp}'s fingerprints on file.",
"witness_statement":
f"Statement from an employee at {place} identifying {perp} as the person "
f"who took {obj} on {date}.",
"pawn_receipt":
f"Pawn shop receipt in {perp}'s name, dated the day after {date}, "
f"for selling an item matching the description of {obj}.",
}
contradictions = [
Contradiction(
cid="c1_possession_object",
trigger_keywords=[
"have", "possessed", "owned", obj.split()[1].lower() if len(obj.split()) > 1 else "object",
"in your possession",
],
sealed_claim=f"I've never possessed {obj} in my life.",
disprover_evidence_id="fingerprint_analysis",
),
Contradiction(
cid="c2_theft_admission",
trigger_keywords=[
"stole", "took", "theft", "place", place.split()[1].lower() if len(place.split()) > 1 else "place",
],
sealed_claim=f"I didn't steal anything from {place}.",
disprover_evidence_id="surveillance_footage",
),
Contradiction(
cid="c3_disposal",
trigger_keywords=[
"sold", "pawned", "got rid of", "disposed", "object",
],
sealed_claim=f"I don't know what happened to {obj} after it was stolen.",
disprover_evidence_id="pawn_receipt",
),
]
return _make_case_dict(case_id, brief, ground_truth, story, evidence, contradictions)
# ---------------------------------------------------------------------------
# Template 4 — Timeline shift (new template)
# ---------------------------------------------------------------------------
def generate_timeline_shift_case():
"""Witness claims event happened at time A; evidence shows time B."""
perp = random.choice(NAMES)
place = random.choice(PLACES)
fake_time = random.choice(TIMES)
real_time = random.choice([t for t in TIMES if t != fake_time])
date = random.choice(DATES)
motive = random.choice(MOTIVES)
case_id = f"timeline_{_case_suffix()}"
brief = (
f"{perp} is accused of assault at {place} on {date}. The victim claims the assault "
f"occurred at {real_time}. The defendant claims they were elsewhere at that time."
)
ground_truth = (
f"{perp} assaulted the victim at {place} at {real_time} on {date}. "
f"They falsely claimed to be at another location at {fake_time}. Motive: {motive}."
)
story = {
"time": f"The assault happened at {fake_time}, not {real_time}.",
"when": f"It was around {fake_time}. I remember clearly.",
"alibi": f"I was at home at {fake_time}. I have witnesses.",
"assault": f"I didn't assault anyone.",
"place": f"I've never been to {place}.",
"motive": f"I had no reason to hurt anyone.",
}
evidence = {
"victim_statement":
f"Victim's statement: The assault occurred at {real_time} on {date} at {place}.",
"surveillance_timestamp":
f"Surveillance footage timestamped at {real_time} showing {perp} at {place}.",
"phone_records":
f"Phone records showing {perp}'s location at {place} at {real_time}.",
"witness_alibi":
f"Alibi witness statement claiming {perp} was with them at {fake_time}, "
f"but the witness later recanted under pressure.",
"motive_email":
f"Email from {perp} sent the week before {date} describing {motive} "
f"and threatening to confront the victim at {place}.",
}
contradictions = [
Contradiction(
cid="c1_time_of_assault",
trigger_keywords=[
"time", "when", "occurred", "happened", real_time.lower(),
],
sealed_claim=f"The assault happened at {fake_time}, not {real_time}.",
disprover_evidence_id="surveillance_timestamp",
),
Contradiction(
cid="c2_location_during_assault",
trigger_keywords=[
"where", "location", "place", place.split()[1].lower() if len(place.split()) > 1 else "place",
],
sealed_claim=f"I was not at {place} during the assault.",
disprover_evidence_id="phone_records",
),
Contradiction(
cid="c3_motive_denial",
trigger_keywords=[
"motive", "reason", "why", "conflict", "grievance",
],
sealed_claim="I had no conflict with the victim and no reason to confront anyone.",
disprover_evidence_id="motive_email",
),
]
return _make_case_dict(case_id, brief, ground_truth, story, evidence, contradictions)
# ---------------------------------------------------------------------------
# Template 5 — Motive coverup (new template)
# ---------------------------------------------------------------------------
def generate_motive_coverup_case():
"""Witness denies motive; evidence reveals hidden relationship."""
perp = random.choice(NAMES)
victim = random.choice([n for n in NAMES if n != perp])
place = random.choice(PLACES)
date = random.choice(DATES)
motive = random.choice(MOTIVES)
case_id = f"motive_{_case_suffix()}"
brief = (
f"{perp} is charged with harassment of {victim}. The state alleges {perp} "
f"stalked {victim} at {place} on {date}. The defendant denies any motive."
)
ground_truth = (
f"{perp} harassed {victim} due to {motive}. They had a hidden relationship "
f"that went sour, leading to the stalking at {place} on {date}."
)
story = {
"motive": f"I had no reason to harass {victim}.",
"relationship": f"I barely knew {victim}. Just an acquaintance.",
"harassment": f"I didn't harass anyone.",
"place": f"I've never been to {place}.",
"victim": f"{victim} and I had no issues.",
}
evidence = {
"text_messages":
f"Text messages between {perp} and {victim} showing romantic relationship "
f"that ended badly due to {motive}.",
"surveillance_stalking":
f"Surveillance footage of {perp} following {victim} at {place} on {date}.",
"police_reports":
f"Previous police reports of {perp} contacting {victim} inappropriately.",
"witness_statement":
f"Witness statement from a mutual friend confirming the relationship and motive.",
"parking_receipt":
f"Parking receipt in {perp}'s name placing their car beside {place} "
f"on {date}, fifteen minutes before {victim} arrived.",
}
contradictions = [
Contradiction(
cid="c1_relationship_denial",
trigger_keywords=[
"relationship", "knew", "acquaintance", victim.split()[0].lower(),
],
sealed_claim=f"I barely knew {victim}. We were just casual acquaintances.",
disprover_evidence_id="text_messages",
),
Contradiction(
cid="c2_motive_denial",
trigger_keywords=[
"motive", "reason", "why", "harass",
],
sealed_claim=f"I had absolutely no motive to harass {victim}.",
disprover_evidence_id="witness_statement",
),
Contradiction(
cid="c3_place_denial",
trigger_keywords=[
"where", "place", "visited", "been to", place.split()[1].lower() if len(place.split()) > 1 else "place",
],
sealed_claim=f"I was nowhere near {place} on {date}.",
disprover_evidence_id="parking_receipt",
),
]
return _make_case_dict(case_id, brief, ground_truth, story, evidence, contradictions)
# ---------------------------------------------------------------------------
# Template 6 - Corporate fraud deposition
# ---------------------------------------------------------------------------
def generate_corporate_fraud_case():
"""Executive denies knowledge, approval, and motive in an internal fraud inquiry."""
executive = random.choice(NAMES)
analyst = random.choice([n for n in NAMES if n != executive])
company = random.choice(["Northstar Biologics", "Meridian Freight", "Aster Cloud", "HelioGrid Energy"])
t = random.choice(TIMES)
date = random.choice(DATES)
motive = random.choice(MOTIVES)
case_id = f"corporate_{_case_suffix()}"
brief = (
f"{executive}, a senior executive at {company}, is questioned about revenue manipulation "
f"recorded on {date}. The company claims the irregular entries were a clerical mistake."
)
ground_truth = (
f"{executive} approved backdated revenue entries at {t} on {date}, after {analyst} "
f"warned that the quarter would miss guidance. Motive: {motive}."
)
story = {
"approval": "I never approved any backdated revenue entries.",
"analyst": f"{analyst} handled routine reporting. I did not discuss adjustments with them.",
"forecast": "I was not worried about the quarterly forecast.",
"revenue": "Any revenue issue was a clerical mistake, not my decision.",
"meeting": "There was no late meeting about accounting entries.",
}
evidence = {
"approval_log":
f"Accounting system log: {executive} approved backdated entries at {t} on {date}.",
"analyst_chat":
f"Chat from {analyst} to {executive}: 'Without the adjustment, we miss guidance by 8%.'",
"board_deck":
f"Draft board deck listing the missed forecast and {motive} as a risk to compensation.",
"calendar_invite":
f"Calendar invite: emergency revenue recognition meeting with {executive} and {analyst} at {t}.",
"printer_log":
"Printer maintenance log from the same floor, unrelated to the accounting entries.",
}
contradictions = [
Contradiction(
cid="c1_approval_denial",
trigger_keywords=["approved", "approval", "signed off", "authorized", "revenue"],
sealed_claim="I never approved or authorized any backdated revenue entries.",
disprover_evidence_id="approval_log",
),
Contradiction(
cid="c2_analyst_discussion",
trigger_keywords=["analyst", analyst.split()[0].lower(), "discuss", "guidance", "forecast"],
sealed_claim=f"I did not discuss any forecast problem with {analyst}.",
disprover_evidence_id="analyst_chat",
),
Contradiction(
cid="c3_late_meeting",
trigger_keywords=["meeting", "calendar", "late", t.lower(), "emergency"],
sealed_claim="There was no late meeting about accounting entries.",
disprover_evidence_id="calendar_invite",
),
]
return _make_case_dict(case_id, brief, ground_truth, story, evidence, contradictions)
# ---------------------------------------------------------------------------
# Template 7 - Workplace investigation
# ---------------------------------------------------------------------------
def generate_workplace_investigation_case():
"""Manager denies a message, location, and prior warning in an HR inquiry."""
manager = random.choice(NAMES)
employee = random.choice([n for n in NAMES if n != manager])
place = random.choice(["Conference Room B", "the sixth-floor break room", "the loading dock", "the HR office"])
date = random.choice(DATES)
t = random.choice(TIMES)
case_id = f"workplace_{_case_suffix()}"
brief = (
f"{manager} is questioned in an HR investigation after {employee} reported retaliation "
f"at {place} on {date}. The manager denies sending threatening messages or being nearby."
)
ground_truth = (
f"{manager} sent a threatening message before meeting {employee} at {place} at {t}. "
f"HR had warned {manager} one week earlier."
)
story = {
"message": f"I never sent {employee} any threatening message.",
"where": f"I was not near {place} that day.",
"warning": "HR never warned me about retaliation.",
"employee": f"{employee} and I barely interacted.",
"retaliation": "I did not retaliate against anyone.",
}
evidence = {
"message_export":
f"Company chat export: {manager} wrote to {employee}, 'Drop the complaint or regret it,' on {date}.",
"badge_scan":
f"Badge access log: {manager} entered {place} at {t} on {date}.",
"hr_warning_memo":
f"HR memo issued one week before {date}, warning {manager} not to contact {employee}.",
"security_clip":
f"Security clip still: {manager} standing outside {place} minutes before {employee} arrived.",
"cafeteria_menu":
"Cafeteria menu from that week, unrelated to the investigation.",
}
contradictions = [
Contradiction(
cid="c1_message_denial",
trigger_keywords=["message", "text", "chat", "threat", employee.split()[0].lower()],
sealed_claim=f"I never sent {employee} any threatening message.",
disprover_evidence_id="message_export",
),
Contradiction(
cid="c2_location_denial",
trigger_keywords=["where", "location", "near", "badge", place.split()[0].lower()],
sealed_claim=f"I was nowhere near {place} on {date}.",
disprover_evidence_id="badge_scan",
),
Contradiction(
cid="c3_warning_denial",
trigger_keywords=["warning", "hr", "memo", "retaliation", "complaint"],
sealed_claim="HR never warned me about contacting the employee.",
disprover_evidence_id="hr_warning_memo",
),
]
return _make_case_dict(case_id, brief, ground_truth, story, evidence, contradictions)
# ---------------------------------------------------------------------------
# Public factory function
# ---------------------------------------------------------------------------
CURRICULUM_DISTRIBUTIONS = {
"easy": {"easy": 1.0},
"warmup": {"easy": 1.0},
"medium": {"easy": 0.25, "medium": 0.75},
"main": {"easy": 0.2, "medium": 0.8},
"hard": {"easy": 0.1, "medium": 0.3, "hard": 0.6},
"challenge": {"medium": 0.25, "hard": 0.75},
"mixed": {"easy": 0.3, "medium": 0.45, "hard": 0.25},
}
def _sample_difficulty(curriculum_stage: str, distribution: Dict[str, float] | None = None) -> str:
choices = distribution or CURRICULUM_DISTRIBUTIONS.get(curriculum_stage, {curriculum_stage: 1.0})
labels = list(choices.keys())
weights = list(choices.values())
return random.choices(labels, weights=weights, k=1)[0]
def _apply_difficulty(case: Dict, difficulty: str) -> Dict:
contradictions = list(case["contradictions"])
if difficulty == "easy":
case["contradictions"] = contradictions[:1]
elif difficulty == "medium":
case["contradictions"] = contradictions[:2]
for contradiction in case["contradictions"]:
if "where" not in contradiction.trigger_keywords:
contradiction.trigger_keywords.append("where")
elif difficulty == "hard":
case["contradictions"] = contradictions[: max(3, len(contradictions))]
case["evidence"]["irrelevant_weather_report"] = (
"Weather report: light rain near the courthouse. It does not address any witness claim."
)
case["evidence"]["unrelated_receipt"] = (
"Receipt for coffee purchased by an unrelated bystander earlier that afternoon."
)
for contradiction in case["contradictions"]:
if "why" not in contradiction.trigger_keywords:
contradiction.trigger_keywords.append("why")
else:
raise ValueError(f"Unknown difficulty: {difficulty}")
case["difficulty"] = difficulty
return case
def generate_case(
difficulty: str | None = None,
curriculum_stage: str = "medium",
distribution: Dict[str, float] | None = None,
) -> Dict:
"""
Generate a random cross-examination case.
Args:
difficulty: "easy", "medium", or "hard" — controls number of contradictions.
Returns:
Case dict with brief, story, evidence, contradictions.
"""
templates = [
generate_alibi_case,
generate_knowledge_denial_case,
generate_possession_denial_case,
generate_timeline_shift_case,
generate_motive_coverup_case,
generate_corporate_fraud_case,
generate_workplace_investigation_case,
]
selected_difficulty = difficulty or _sample_difficulty(curriculum_stage, distribution)
return _apply_difficulty(random.choice(templates)(), selected_difficulty)
if __name__ == "__main__":
# Test the generator
case = generate_case()
print(f"Case ID: {case['case_id']}")
print(f"Brief: {case['case_brief']}")
print(f"Contradictions: {len(case['contradictions'])}")
for c in case["contradictions"]:
print(f" - {c.cid}: {c.trigger_keywords}")