ipin-vr/ipin_vr/generator.py
Oskar Kapala 691c8ea360 Implement Stage 1: command generator, CLI, TTS and LLM clients
- 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>
2026-06-09 15:03:50 +02:00

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}")