"""Compact city description for the LLM prompt (<600 chars, always). Covers: NEMOCITY, population, building counts by kind, named landmarks and streets, traffic index + worst street, growth ring. Duck-types over World (no engine.world import — no cycles). """ from __future__ import annotations import time from typing import Any, Optional from . import constants as C from . import traffic from .city import CityState MAX_LEN = 599 # contract: strictly under 600 chars _MAX_TALLY_KINDS = 6 _MAX_LANDMARKS = 4 _MAX_STREETS = 5 _A_OR_AN_VOWEL = "aeiou" def _count_phrase(noun: str, n: int) -> str: noun = noun.replace("_", " ") if n == 1: article = "an" if noun[:1] in _A_OR_AN_VOWEL else "a" return f"{article} {noun}" plural = noun + ("es" if noun.endswith(("s", "x", "z", "ch", "sh")) else "s") return f"{n} {plural}" def summarize(world: Any, now_s: Optional[float] = None) -> str: if now_s is None: now_s = time.time() city = CityState.from_events(getattr(world, "features", ()) or ()) parts: list[str] = [ f"NEMOCITY, pop {city.population(now_s)}, epoch {getattr(world, 'epoch', 0)}" ] counts = city.counts_by_kind() if counts: bits = [ _count_phrase(kind, counts[kind]) for kind in list(counts)[:_MAX_TALLY_KINDS] ] if len(counts) > _MAX_TALLY_KINDS: bits.append("more") parts.append("buildings: " + ", ".join(bits)) parts.append(f"jobs {city.jobs}, homes for {city.housing_capacity}") landmarks = [ b.name for b in reversed(city.buildings) if b.name and C.BUILDINGS[b.kind]["attract"] >= 3 ][:_MAX_LANDMARKS] if landmarks: parts.append("landmarks: " + ", ".join(landmarks)) streets = list(city.street_cells) if streets: tail = f" +{len(streets) - _MAX_STREETS}" if len(streets) > _MAX_STREETS else "" parts.append("streets: " + ", ".join(streets[:_MAX_STREETS]) + tail) assignment = traffic.assign(city, now_s) line = f"traffic index {assignment.traffic_index}" if assignment.max_ratio > 0: worst = assignment.top_cells(1) if worst: line += f" (worst: {assignment.net.name(worst[0])} {assignment.ratio(worst[0]):.1f}x)" parts.append(line) parts.append(f"growth ring {city.growth_radius}") text = " | ".join(parts) if len(text) > MAX_LEN: text = text[: MAX_LEN - 3] + "..." return text