Base classes and orchestrators for NHRA strategic agents.
This module defines the interfaces for strategic actors in the simulation,
including heuristic models and LLM-driven agents. It also provides utilities
for generating narratives and validating strategic traces.
Classes
Agent
Bases: ABC
Abstract base class for all NHRA strategic agents.
Source code in src/nhra_gt/agents/base.py
| class Agent(ABC):
"""Abstract base class for all NHRA strategic agents."""
@abstractmethod
def decide(self, state: State, params: Params, rng: np.random.Generator) -> dict[str, Any]:
"""Choose strategies based on current system state."""
|
Functions
decide(state, params, rng)
abstractmethod
Choose strategies based on current system state.
Source code in src/nhra_gt/agents/base.py
| @abstractmethod
def decide(self, state: State, params: Params, rng: np.random.Generator) -> dict[str, Any]:
"""Choose strategies based on current system state."""
|
BriefGenerator
Converts simulation state into a textual narrative for LLM consumption.
Source code in src/nhra_gt/agents/base.py
| class BriefGenerator:
"""Converts simulation state into a textual narrative for LLM consumption."""
@staticmethod
def generate(state: State, params: Params, role: str) -> str:
"""Generate a policy brief for a specific role (Commonwealth/State/Provider)."""
mode_desc = {
"normal": "The system is currently stable.",
"stress": "The system is under significant pressure.",
"crisis": "CRITICAL: The system is in a state of failure (Code Red).",
"recovery": "The system is recovering from a crisis.",
}
# Get values from first jurisdiction if available, fallback to defaults
eff_gap = state.reported_efficiency_gap
eff_share = (
state.jurisdictions[0].effective_cth_share
if hasattr(state, "jurisdictions") and state.jurisdictions
else 0.38
)
pol_cap = (
state.jurisdictions[0].political_capital
if hasattr(state, "jurisdictions") and state.jurisdictions
else 1.0
)
eq_idx = (
state.jurisdictions[0].equity_index
if hasattr(state, "jurisdictions") and state.jurisdictions
else 1.0
)
brief = f"""
### NHRA POLICY BRIEF - {role.upper()}
**Current Year:** {state.year}, Month {state.month}
**System Mode:** {state.system_mode.value.upper()} - {mode_desc.get(state.system_mode.value, "")}
**Key Metrics:**
- System Pressure: {state.pressure:.2f} (Base: 1.0)
- ED Wait Time: {state.within4 * 100:.1f}% within 4h
- Efficiency Gap: {eff_gap * 100:.1f}% (Cost vs NEP)
- Effective Cth Share: {eff_share * 100:.1f}%
- Political Capital: {pol_cap:.2f}
- Equity Index: {eq_idx:.2f}
**Strategic Context:**
- Your goal is to maximize your utility while maintaining system stability.
- Agreement (A) restores trust but may increase bailout expectations.
- Upcoding (U) increases revenue but risks Auditor detection and penalties.
- Fragmentation (F) in Aged Care/NDIS reduces your immediate load but worsens downstream pressure.
**Decision Required:**
You must choose strategies for the current period.
"""
return brief
|
Functions
generate(state, params, role)
staticmethod
Generate a policy brief for a specific role (Commonwealth/State/Provider).
Source code in src/nhra_gt/agents/base.py
| @staticmethod
def generate(state: State, params: Params, role: str) -> str:
"""Generate a policy brief for a specific role (Commonwealth/State/Provider)."""
mode_desc = {
"normal": "The system is currently stable.",
"stress": "The system is under significant pressure.",
"crisis": "CRITICAL: The system is in a state of failure (Code Red).",
"recovery": "The system is recovering from a crisis.",
}
# Get values from first jurisdiction if available, fallback to defaults
eff_gap = state.reported_efficiency_gap
eff_share = (
state.jurisdictions[0].effective_cth_share
if hasattr(state, "jurisdictions") and state.jurisdictions
else 0.38
)
pol_cap = (
state.jurisdictions[0].political_capital
if hasattr(state, "jurisdictions") and state.jurisdictions
else 1.0
)
eq_idx = (
state.jurisdictions[0].equity_index
if hasattr(state, "jurisdictions") and state.jurisdictions
else 1.0
)
brief = f"""
### NHRA POLICY BRIEF - {role.upper()}
**Current Year:** {state.year}, Month {state.month}
**System Mode:** {state.system_mode.value.upper()} - {mode_desc.get(state.system_mode.value, "")}
**Key Metrics:**
- System Pressure: {state.pressure:.2f} (Base: 1.0)
- ED Wait Time: {state.within4 * 100:.1f}% within 4h
- Efficiency Gap: {eff_gap * 100:.1f}% (Cost vs NEP)
- Effective Cth Share: {eff_share * 100:.1f}%
- Political Capital: {pol_cap:.2f}
- Equity Index: {eq_idx:.2f}
**Strategic Context:**
- Your goal is to maximize your utility while maintaining system stability.
- Agreement (A) restores trust but may increase bailout expectations.
- Upcoding (U) increases revenue but risks Auditor detection and penalties.
- Fragmentation (F) in Aged Care/NDIS reduces your immediate load but worsens downstream pressure.
**Decision Required:**
You must choose strategies for the current period.
"""
return brief
|
AuditorValidator
Evaluates agent strategic traces from an 'Auditor' perspective.
Scores realism and detects unlikely gaming behavior.
Source code in src/nhra_gt/agents/base.py
| class AuditorValidator:
"""
Evaluates agent strategic traces from an 'Auditor' perspective.
Scores realism and detects unlikely gaming behavior.
"""
def validate(self, trace: list[dict[str, Any]]) -> dict[str, Any]:
"""Evaluates agent strategic traces from an 'Auditor' perspective.
Scores realism and detects unlikely gaming behavior.
Args:
trace: A list of step dictionaries containing state, strategy, and rationale.
Returns:
A dictionary with keys `realism_score` (float) and `findings` (list of str).
"""
# Logic would involve an LLM prompt: "Does this look like a real hospital administrator?"
# For now, return a placeholder score.
return {
"realism_score": 0.85,
"findings": ["Gaming behavior consistent with high pressure periods."],
}
|
Functions
validate(trace)
Evaluates agent strategic traces from an 'Auditor' perspective.
Scores realism and detects unlikely gaming behavior.
Parameters:
| Name |
Type |
Description |
Default |
trace
|
list[dict[str, Any]]
|
A list of step dictionaries containing state, strategy, and rationale.
|
required
|
Returns:
| Type |
Description |
dict[str, Any]
|
A dictionary with keys realism_score (float) and findings (list of str).
|
Source code in src/nhra_gt/agents/base.py
| def validate(self, trace: list[dict[str, Any]]) -> dict[str, Any]:
"""Evaluates agent strategic traces from an 'Auditor' perspective.
Scores realism and detects unlikely gaming behavior.
Args:
trace: A list of step dictionaries containing state, strategy, and rationale.
Returns:
A dictionary with keys `realism_score` (float) and `findings` (list of str).
"""
# Logic would involve an LLM prompt: "Does this look like a real hospital administrator?"
# For now, return a placeholder score.
return {
"realism_score": 0.85,
"findings": ["Gaming behavior consistent with high pressure periods."],
}
|
LLMAgent
Bases: Agent
An agent that uses a Large Language Model to make strategic decisions.
Source code in src/nhra_gt/agents/base.py
| class LLMAgent(Agent):
"""
An agent that uses a Large Language Model to make strategic decisions.
"""
def __init__(self, role: str, model_name: str = "gemini-pro"):
self.role = role
self.model_name = model_name
self.brief_gen = BriefGenerator()
def decide(self, state: State, params: Params, rng: np.random.Generator) -> dict[str, Any]:
"""
Generates a brief, calls the LLM, and parses the strategic response.
Note: Actual LLM call is stubbed/delegated to the environment.
"""
self.brief_gen.generate(state, params, self.role)
# In a real implementation, we would call the LLM here.
# For the simulation engine, we can use a callback or a mock
# that uses the HeuristicAgent as a 'smart fallback'.
# MOCK/STUB logic for now:
heuristic_response = CommonwealthAgent().decide(state, params, rng)
heuristic_response.update(JurisdictionAgent().decide(state, params, rng))
heuristic_response.update(LHNAgent().decide(state, params, rng))
# Add 'Rationale' for Cognitive Trace
heuristic_response["RATIONALE"] = (
f"Decided based on multi-agent refactor for {self.role}. Pressure is {state.pressure:.2f}."
)
return heuristic_response
|
Functions
decide(state, params, rng)
Generates a brief, calls the LLM, and parses the strategic response.
Note: Actual LLM call is stubbed/delegated to the environment.
Source code in src/nhra_gt/agents/base.py
| def decide(self, state: State, params: Params, rng: np.random.Generator) -> dict[str, Any]:
"""
Generates a brief, calls the LLM, and parses the strategic response.
Note: Actual LLM call is stubbed/delegated to the environment.
"""
self.brief_gen.generate(state, params, self.role)
# In a real implementation, we would call the LLM here.
# For the simulation engine, we can use a callback or a mock
# that uses the HeuristicAgent as a 'smart fallback'.
# MOCK/STUB logic for now:
heuristic_response = CommonwealthAgent().decide(state, params, rng)
heuristic_response.update(JurisdictionAgent().decide(state, params, rng))
heuristic_response.update(LHNAgent().decide(state, params, rng))
# Add 'Rationale' for Cognitive Trace
heuristic_response["RATIONALE"] = (
f"Decided based on multi-agent refactor for {self.role}. Pressure is {state.pressure:.2f}."
)
return heuristic_response
|
CommonwealthAgent
Bases: Agent
The Principal (Federal Govt) setting price and compliance rules.
Focuses on fiscal sustainability, compliance targets, and framing the NEP.
Source code in src/nhra_gt/agents/base.py
| class CommonwealthAgent(Agent):
"""
The Principal (Federal Govt) setting price and compliance rules.
Focuses on fiscal sustainability, compliance targets, and framing the NEP.
"""
def decide(self, state: State, params: Params, rng: np.random.Generator) -> dict[str, Any]:
"""Chooses framing (DEF) and compliance (COMP) strategies."""
obs_pressure = getattr(state, "reported_pressure", state.pressure)
obs_efficiency_gap = state.reported_efficiency_gap
# Commonwealth moves: DEF, COMP, BARG(offer)
results = {}
# 1. Compliance Strategy (COMP)
comp_prob = logistic(0.9 * params.audit_pressure - 0.7 * obs_efficiency_gap)
results["COMP"] = "T" if rng.random() < comp_prob else "L"
# 2. Framing Strategy (DEF)
def_prob = logistic(1.3 * (obs_efficiency_gap - 0.25) + 0.9 * (obs_pressure - 1.0))
results["DEF"] = "R" if rng.random() < def_prob else "E"
return results
|
Functions
decide(state, params, rng)
Chooses framing (DEF) and compliance (COMP) strategies.
Source code in src/nhra_gt/agents/base.py
| def decide(self, state: State, params: Params, rng: np.random.Generator) -> dict[str, Any]:
"""Chooses framing (DEF) and compliance (COMP) strategies."""
obs_pressure = getattr(state, "reported_pressure", state.pressure)
obs_efficiency_gap = state.reported_efficiency_gap
# Commonwealth moves: DEF, COMP, BARG(offer)
results = {}
# 1. Compliance Strategy (COMP)
comp_prob = logistic(0.9 * params.audit_pressure - 0.7 * obs_efficiency_gap)
results["COMP"] = "T" if rng.random() < comp_prob else "L"
# 2. Framing Strategy (DEF)
def_prob = logistic(1.3 * (obs_efficiency_gap - 0.25) + 0.9 * (obs_pressure - 1.0))
results["DEF"] = "R" if rng.random() < def_prob else "E"
return results
|
JurisdictionAgent
Bases: Agent
The Intermediary (State/Territory Govt) managing the Agreement.
Focuses on budget reconciliation, political capital, and cost-shifting.
Source code in src/nhra_gt/agents/base.py
| class JurisdictionAgent(Agent):
"""
The Intermediary (State/Territory Govt) managing the Agreement.
Focuses on budget reconciliation, political capital, and cost-shifting.
"""
def decide(self, state: State, params: Params, rng: np.random.Generator) -> dict[str, Any]:
"""Chooses bargaining (BARG), cost-shifting (SHIFT), and governance (GOV) strategies."""
obs_pressure = getattr(state, "reported_pressure", state.pressure)
obs_efficiency_gap = state.reported_efficiency_gap
# Get bailout expectation from first jurisdiction
bailout_exp = (
state.jurisdictions[0].bailout_expectation
if hasattr(state, "jurisdictions") and state.jurisdictions
else 0.0
)
# State moves: BARG, SHIFT, GOV
results = {}
# 1. Bargaining Strategy (BARG)
barg_prob = logistic(0.6 * (1.2 - obs_pressure) + bailout_exp)
results["BARG"] = "A" if rng.random() < barg_prob else "D"
# 2. Cost Shifting (SHIFT)
shift_prob = logistic(-1.1 * (obs_pressure - 1.0) - 1.0 * obs_efficiency_gap)
results["SHIFT"] = "I" if rng.random() < shift_prob else "S"
# 3. Governance (GOV)
gov_prob = logistic(-0.8 * (obs_pressure - 1.0) - 0.7 * params.political_salience)
results["GOV"] = "I" if rng.random() < gov_prob else "S"
return results
|
Functions
decide(state, params, rng)
Chooses bargaining (BARG), cost-shifting (SHIFT), and governance (GOV) strategies.
Source code in src/nhra_gt/agents/base.py
| def decide(self, state: State, params: Params, rng: np.random.Generator) -> dict[str, Any]:
"""Chooses bargaining (BARG), cost-shifting (SHIFT), and governance (GOV) strategies."""
obs_pressure = getattr(state, "reported_pressure", state.pressure)
obs_efficiency_gap = state.reported_efficiency_gap
# Get bailout expectation from first jurisdiction
bailout_exp = (
state.jurisdictions[0].bailout_expectation
if hasattr(state, "jurisdictions") and state.jurisdictions
else 0.0
)
# State moves: BARG, SHIFT, GOV
results = {}
# 1. Bargaining Strategy (BARG)
barg_prob = logistic(0.6 * (1.2 - obs_pressure) + bailout_exp)
results["BARG"] = "A" if rng.random() < barg_prob else "D"
# 2. Cost Shifting (SHIFT)
shift_prob = logistic(-1.1 * (obs_pressure - 1.0) - 1.0 * obs_efficiency_gap)
results["SHIFT"] = "I" if rng.random() < shift_prob else "S"
# 3. Governance (GOV)
gov_prob = logistic(-0.8 * (obs_pressure - 1.0) - 0.7 * params.political_salience)
results["GOV"] = "I" if rng.random() < gov_prob else "S"
return results
|
LHNAgent
Bases: Agent
The Operator (Hospital/LHN) managing clinical demand and revenue.
Focuses on upcoding, venue shifting, and coordination with aged care/NDIS.
Source code in src/nhra_gt/agents/base.py
| class LHNAgent(Agent):
"""
The Operator (Hospital/LHN) managing clinical demand and revenue.
Focuses on upcoding, venue shifting, and coordination with aged care/NDIS.
"""
def decide(self, state: State, params: Params, rng: np.random.Generator) -> dict[str, Any]:
"""Chooses coding, venue, and clinical coordination strategies."""
obs_pressure = getattr(state, "reported_pressure", state.pressure)
obs_efficiency_gap = state.reported_efficiency_gap
# LHN moves: CODING, VENUE_SHIFT, DISC, AGED, NDIS, COMPETITION
results = {}
# 1. Revenue Strategy (CODING)
coding_prob = logistic(1.5 * (obs_pressure - 1.1) + 1.2 * obs_efficiency_gap)
results["CODING"] = "U" if rng.random() < coding_prob else "H"
# 2. Venue Selection (VENUE_SHIFT)
venue_prob = logistic(1.2 * (obs_pressure - 1.1) + 0.8 * obs_efficiency_gap)
results["VENUE_SHIFT"] = "B" if rng.random() < venue_prob else "A"
# 3. Operations Coordination (DISC, AGED, NDIS)
results["DISC"] = "C" if rng.random() < 0.7 else "F"
results["AGED"] = "C" if rng.random() < 0.6 else "F"
results["NDIS"] = "C" if rng.random() < 0.6 else "F"
# 4. Competition Strategy
comp_prob = logistic(1.1 * (obs_pressure - 1.0) + 0.5 * params.cannibalization_beta)
results["COMPETITION"] = "A" if rng.random() < comp_prob else "M"
return results
|
Functions
decide(state, params, rng)
Chooses coding, venue, and clinical coordination strategies.
Source code in src/nhra_gt/agents/base.py
| def decide(self, state: State, params: Params, rng: np.random.Generator) -> dict[str, Any]:
"""Chooses coding, venue, and clinical coordination strategies."""
obs_pressure = getattr(state, "reported_pressure", state.pressure)
obs_efficiency_gap = state.reported_efficiency_gap
# LHN moves: CODING, VENUE_SHIFT, DISC, AGED, NDIS, COMPETITION
results = {}
# 1. Revenue Strategy (CODING)
coding_prob = logistic(1.5 * (obs_pressure - 1.1) + 1.2 * obs_efficiency_gap)
results["CODING"] = "U" if rng.random() < coding_prob else "H"
# 2. Venue Selection (VENUE_SHIFT)
venue_prob = logistic(1.2 * (obs_pressure - 1.1) + 0.8 * obs_efficiency_gap)
results["VENUE_SHIFT"] = "B" if rng.random() < venue_prob else "A"
# 3. Operations Coordination (DISC, AGED, NDIS)
results["DISC"] = "C" if rng.random() < 0.7 else "F"
results["AGED"] = "C" if rng.random() < 0.6 else "F"
results["NDIS"] = "C" if rng.random() < 0.6 else "F"
# 4. Competition Strategy
comp_prob = logistic(1.1 * (obs_pressure - 1.0) + 0.5 * params.cannibalization_beta)
results["COMPETITION"] = "A" if rng.random() < comp_prob else "M"
return results
|
HeuristicAgent
Bases: Agent
Orchestrator that delegates to distinct Commonwealth, State, and LHN agents.
Source code in src/nhra_gt/agents/base.py
| class HeuristicAgent(Agent):
"""
Orchestrator that delegates to distinct Commonwealth, State, and LHN agents.
"""
def decide(self, state: State, params: Params, rng: np.random.Generator) -> dict[str, Any]:
cw = CommonwealthAgent()
jr = JurisdictionAgent()
lhn = LHNAgent()
results = {
"SIGNAL": "L",
"SIGNAL_QUALITY": 0.9,
}
# Aggregate decisions from all levels
results.update(cw.decide(state, params, rng))
results.update(jr.decide(state, params, rng))
results.update(lhn.decide(state, params, rng))
results["RATIONALE"] = f"Multi-agent consensus reached in {params.orchestration_mode} mode."
return results
|
Functions
logistic(x)
Standard sigmoid function.
Source code in src/nhra_gt/agents/base.py
| def logistic(x: float) -> float:
"""Standard sigmoid function."""
return 1.0 / (1.0 + math.exp(-x))
|
softmax(u, tau=0.25)
Numpy implementation of tau-tempered softmax.
Source code in src/nhra_gt/agents/base.py
| def softmax(u: np.ndarray, tau: float = 0.25) -> np.ndarray:
"""Numpy implementation of tau-tempered softmax."""
u = np.asarray(u, dtype=float)
u = u - u.max()
z = np.exp(u / max(1e-9, tau))
return np.asarray(z / z.sum(), dtype=float)
|