- lexicon.py: exact forms from docs/LEXICON.md (Polish diacritics preserved) - generator.py: Clause/Command dataclasses, render(), random_command(level 1-3) - llm.py: OpenAI-compatible client via urllib, JSON-only contract, fallback to random - tts.py: Piper via subprocess, defensive (prints [TTS off: reason] on any failure) - cli.py: argparse interface per SPEC §9 - tests/test_generator.py: 23 tests covering all genders, locations, level-3 joining, capitalisation; all pass DoD verified: pytest 23/23, python -m ipin_vr --level 3 --count 6 --no-audio OK. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
74 lines
1.9 KiB
Python
74 lines
1.9 KiB
Python
import random
|
|
from dataclasses import dataclass
|
|
from typing import Optional
|
|
|
|
from .lexicon import COLORS, LOCATIONS, OBJECTS, VERBS
|
|
|
|
|
|
@dataclass
|
|
class Clause:
|
|
verb_id: str
|
|
object_id: str
|
|
location_id: str
|
|
color_id: Optional[str]
|
|
|
|
|
|
@dataclass
|
|
class Command:
|
|
clauses: list[Clause]
|
|
level: int
|
|
|
|
|
|
def _render_clause(clause: Clause, first: bool) -> str:
|
|
verb = VERBS[clause.verb_id]
|
|
if not first:
|
|
verb = verb.lower()
|
|
|
|
obj = OBJECTS[clause.object_id]
|
|
parts = [verb]
|
|
if clause.color_id is not None:
|
|
parts.append(COLORS[clause.color_id][obj["gender"]])
|
|
parts.append(obj["acc"])
|
|
parts.append(LOCATIONS[clause.location_id])
|
|
return " ".join(parts)
|
|
|
|
|
|
def render(command: Command) -> str:
|
|
rendered = [
|
|
_render_clause(clause, i == 0)
|
|
for i, clause in enumerate(command.clauses)
|
|
]
|
|
return " i ".join(rendered) + "."
|
|
|
|
|
|
def random_command(level: int) -> Command:
|
|
verb_ids = list(VERBS)
|
|
object_ids = list(OBJECTS)
|
|
location_ids = list(LOCATIONS)
|
|
color_ids = list(COLORS)
|
|
|
|
def _clause(object_id: str, use_color: bool) -> Clause:
|
|
return Clause(
|
|
verb_id=random.choice(verb_ids),
|
|
object_id=object_id,
|
|
location_id=random.choice(location_ids),
|
|
color_id=random.choice(color_ids) if use_color else None,
|
|
)
|
|
|
|
if level == 1:
|
|
return Command(clauses=[_clause(random.choice(object_ids), False)], level=1)
|
|
|
|
if level == 2:
|
|
return Command(clauses=[_clause(random.choice(object_ids), True)], level=2)
|
|
|
|
if level == 3:
|
|
obj1 = random.choice(object_ids)
|
|
obj2 = random.choice([o for o in object_ids if o != obj1])
|
|
clauses = [
|
|
_clause(obj1, random.choice([True, False])),
|
|
_clause(obj2, random.choice([True, False])),
|
|
]
|
|
return Command(clauses=clauses, level=3)
|
|
|
|
raise ValueError(f"Nieznany poziom: {level}")
|