Spaces:
Sleeping
Sleeping
File size: 6,140 Bytes
8244647 f0d0d63 8244647 f0d0d63 8244647 9cfcf69 8244647 e4666d9 8244647 e05d1f4 8244647 9cfcf69 8244647 9cfcf69 8244647 f0d0d63 8244647 fdbb991 8244647 3582e50 f0d0d63 3582e50 f0d0d63 3582e50 f0d0d63 3582e50 f0d0d63 3582e50 8244647 f0d0d63 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 | """OpenEnv Pydantic models for the Nation Simulator parliamentary environment."""
from __future__ import annotations
from typing import Any
from pydantic import BaseModel, Field, model_validator
from openenv.core.env_server import Action, Observation, State
# --- Supporting Models ---
class EventModel(BaseModel):
"""An event revealed during Phase 1."""
name: str
severity: int
category: str
narrative: str
affected_departments: list[str] = Field(default_factory=list)
round: int | None = None
cost: float | None = None
class ProposalModel(BaseModel):
"""A budget proposal submitted during Phase 3."""
proposal_id: str
agent_id: str
department: str
amount: float
justification: str = ""
status: str = "pending"
votes: dict[str, str] = Field(default_factory=dict)
rejection_reason: str | None = None
class VoteModel(BaseModel):
"""A single vote cast during Phase 4."""
proposal_id: str
agent_id: str
vote: str # YES / NO / ABSTAIN
class OwnDepartmentModel(BaseModel):
"""Private observation for a specific department's minister."""
name: str
allocated_budget: float | None = None
consumption: float | None = None
surplus: float | None = None
efficiency_rating: float | None = None
treasury_surplus_returned_this_round: float | None = None
baseline: float | None = None
# --- OpenEnv Action ---
class ParliamentaryAction(Action):
"""
A single agent action in the parliamentary cycle.
The `type` field determines which optional fields are required:
- DEBATE: requires `message`
- FINISH_DEBATE: requires `reason`
- PROPOSE_BUDGET: requires `department`, `amount`, `justification`
- VOTE: requires `proposal_id`, `vote`
``amount`` in PROPOSE_BUDGET is discretionary funding above the auto-funded
critical floor (see Option A in game rules).
"""
agent_id: str
type: str # DEBATE | FINISH_DEBATE | PROPOSE_BUDGET | VOTE
# DEBATE fields
message: str | None = None
# FINISH_DEBATE fields
reason: str | None = None
# PROPOSE_BUDGET fields
department: str | None = None
amount: float | None = None
justification: str | None = None
# VOTE fields
proposal_id: str | None = None
vote: str | None = None # YES / NO / ABSTAIN
def to_engine_dict(self) -> dict[str, Any]:
"""Convert to the dict format expected by core/game.py step()."""
d: dict[str, Any] = {"type": self.type, "agent_id": self.agent_id}
if self.type == "DEBATE":
d["message"] = self.message or ""
elif self.type == "PROPOSE_BUDGET":
d["department"] = self.department or ""
d["amount"] = self.amount if self.amount is not None else 0.0
d["justification"] = self.justification or ""
elif self.type == "VOTE":
d["proposal_id"] = self.proposal_id or ""
d["vote"] = str(self.vote) if self.vote else "ABSTAIN"
return d
# --- OpenEnv Observation ---
class ParliamentaryObservation(Observation):
"""
Full spec-compliant observation per 08_OBSERVATION_SPACE.md.
Includes all public information plus private `own_department` data
for the specific agent receiving the observation.
"""
round: int = 0
phase: int = 1
phase_name: str = "EVENT_REVELATION"
year: int = 1
quarter: int = 1
treasury: float = 0.0
population: int = 0
productivity: float = 1.0
# Public information
event_ledger: list[dict[str, Any]] = Field(default_factory=list)
current_events: list[EventModel] = Field(default_factory=list)
proposals: list[ProposalModel] = Field(default_factory=list)
votes: list[VoteModel] = Field(default_factory=list)
debate_messages: list[dict[str, str]] = Field(default_factory=list)
# Private to the observing agent
own_department: OwnDepartmentModel | None = None
# Phase-gated action mask
valid_actions: list[str] = Field(default_factory=list)
target_proposal_id: str | None = None
# Termination info
termination: dict[str, Any] = Field(default_factory=dict)
# Which agent should act next (set by environment)
current_agent: str | None = None
# Retry info
retry_count: int = 0
rejected_departments: list[str] = Field(default_factory=list)
# --- Legacy OpenEnv Action ---
class NationAction(Action):
"""OpenEnv action envelope for direct allocation and smoke clients."""
bids: list[float] | None = Field(
default=None,
min_length=6,
max_length=6,
description="Legacy continuous bids from each of the 6 sectors.",
)
actions: dict[str, Any] | list[dict[str, Any]] | None = Field(
default=None,
description="One phase action or a list of phase actions.",
)
direct_allocations: dict[str, float] | None = Field(
default=None,
description="Full-round direct allocation shortcut keyed by department.",
)
@model_validator(mode="after")
def require_single_action_source(self) -> "NationAction":
sources = [
self.bids is not None,
self.actions is not None,
self.direct_allocations is not None,
]
if sum(sources) != 1:
raise ValueError("Provide exactly one of bids, actions, or direct_allocations.")
return self
def to_core_action(self) -> Any:
if self.direct_allocations is not None:
return dict(self.direct_allocations)
return self.actions
class NationObservation(Observation):
"""Serializable whole-game observation for the thin OpenEnv wrapper."""
state: dict[str, Any] = Field(description="Public NationGame state snapshot.")
info: dict[str, Any] = Field(default_factory=dict)
# --- OpenEnv State ---
class NationState(State):
"""Internal state for the OpenEnv environment."""
raw_game_state: dict[str, Any] = Field(default_factory=dict)
core_state: dict[str, Any] = Field(default_factory=dict)
|