nemocity / engine /summary.py
AndresCarreon's picture
NEMOCITY v0 — mock backend, gradio 6.16.0 (pre-SSR)
d72231c verified
Raw
History Blame Contribute Delete
2.48 kB
"""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