Skip to content

Commit 43e1cf1

Browse files
Add updates
1 parent a0f9944 commit 43e1cf1

5 files changed

Lines changed: 190 additions & 5 deletions

File tree

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
from policyengine_core.simulations import Simulation
2+
import pandas as pd
3+
from typing import Dict, List
4+
from pydantic import BaseModel, ConfigDict
5+
import time
6+
from ..database.models import ParameterMetadata, ParameterChangeMetadata
7+
from policyengine_core.parameters import Parameter
8+
9+
class ModelOutput(BaseModel):
10+
model_config = ConfigDict(arbitrary_types_allowed=True)
11+
12+
table_names: List[str] = []
13+
variable_whitelist: List[str] = []
14+
15+
def get_tables(self) -> Dict[str, pd.DataFrame]:
16+
return {
17+
table_name: getattr(self, table_name)
18+
for table_name in self.table_names
19+
}
20+
21+
def process_simulation(simulation: Simulation, year: int, variable_whitelist: List[str] = []) -> Dict[str, pd.DataFrame]:
22+
variables = list(simulation.tax_benefit_system.variables.values())
23+
24+
entity_tables = {}
25+
26+
for variable in variable_whitelist:
27+
simulation.calculate(variable, year)
28+
29+
known_variables = [
30+
variable for variable in variables
31+
if len(simulation.get_holder(variable.name).get_known_periods()) > 0
32+
]
33+
34+
for variable in known_variables:
35+
if variable.definition_period != "year":
36+
continue
37+
if variable.entity.key not in entity_tables:
38+
entity_tables[variable.entity.key] = pd.DataFrame()
39+
40+
try:
41+
start = time.time()
42+
result = simulation.calculate(variable.name, year)
43+
end = time.time()
44+
if end - start > 1.0:
45+
print(f"Time taken to calculate {variable.name} for {year}: {end - start} seconds")
46+
except Exception as e:
47+
print(f"Error calculating {variable.name} for {year}: {e}")
48+
continue
49+
50+
entity_tables[variable.entity.key][variable.name] = result
51+
52+
return entity_tables
53+
54+
55+
def create_default_parameters(simulation: Simulation, country: str) -> List[ParameterMetadata]:
56+
"""Create default parameters for the simulation."""
57+
parameter_tree = simulation.tax_benefit_system.parameters
58+
parameters = []
59+
60+
for parameter in parameter_tree.get_descendants():
61+
parameter_meta = ParameterMetadata(
62+
name=parameter.name,
63+
country=country,
64+
#parent somehow link to the parameter.parent Parameter object
65+
# rest of relevant attributes, look at policyengine_core.parameters.Parameter
66+
)
67+
68+
# add parameterchanges as well?

src/policyengine/countries/uk.py

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
from policyengine_uk import Simulation, Microsimulation
2+
from .general import process_simulation, ModelOutput
3+
from pydantic import BaseModel, ConfigDict
4+
import pandas as pd
5+
from typing import List
6+
from ..database.models import ParameterMetadata, ParameterChangeMetadata
7+
8+
class UKModelOutput(ModelOutput):
9+
person: pd.DataFrame
10+
benunit: pd.DataFrame
11+
household: pd.DataFrame
12+
13+
table_names: List[str] = ["person", "benunit", "household"]
14+
15+
model_config = ConfigDict(arbitrary_types_allowed=True)
16+
17+
UK_VARIABLE_WHITELIST = [
18+
"household_net_income",
19+
]
20+
21+
def process_uk_simulation(simulation: Simulation, year: int) -> UKModelOutput:
22+
entity_tables = process_simulation(simulation, year, variable_whitelist=UK_VARIABLE_WHITELIST)
23+
24+
return UKModelOutput(
25+
person=entity_tables.get("person"),
26+
benunit=entity_tables.get("benunit"),
27+
household=entity_tables.get("household"),
28+
)

src/policyengine/countries/us.py

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
from policyengine_us import Simulation, Microsimulation
2+
from .general import process_simulation, ModelOutput
3+
from pydantic import BaseModel, ConfigDict
4+
import pandas as pd
5+
from typing import List
6+
7+
class USModelOutput(ModelOutput):
8+
person: pd.DataFrame
9+
marital_unit: pd.DataFrame
10+
family: pd.DataFrame
11+
tax_unit: pd.DataFrame
12+
spm_unit: pd.DataFrame
13+
household: pd.DataFrame
14+
15+
table_names: List[str] = ["person", "marital_unit", "family", "tax_unit", "spm_unit", "household"]
16+
17+
model_config = ConfigDict(arbitrary_types_allowed=True)
18+
19+
US_VARIABLE_WHITELIST = [
20+
"household_net_income",
21+
]
22+
23+
def process_us_simulation(simulation: Simulation, year: int) -> USModelOutput:
24+
entity_tables = process_simulation(simulation, year, variable_whitelist=US_VARIABLE_WHITELIST)
25+
26+
return USModelOutput(
27+
person=entity_tables.get("person"),
28+
marital_unit=entity_tables.get("marital_unit"),
29+
family=entity_tables.get("family"),
30+
tax_unit=entity_tables.get("tax_unit"),
31+
spm_unit=entity_tables.get("spm_unit"),
32+
household=entity_tables.get("household"),
33+
)
Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,8 @@
11
"""Database abstraction for PolicyEngine with hybrid storage."""
22

33
from .database import Database, DatabaseConfig
4-
from .simulation import save_simulation, load_simulation, list_simulations
54

65
__all__ = [
76
"Database",
87
"DatabaseConfig",
9-
"save_simulation",
10-
"load_simulation",
11-
"list_simulations",
128
]

src/policyengine/database/models.py

Lines changed: 61 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ class SimulationMetadata(Base):
4040
# Scenario information
4141
dataset = Column(String, nullable=False) # e.g., "frs_2023_24"
4242
scenario = Column(String, nullable=False) # e.g., "baseline"
43+
model_version = Column(String, nullable=True) # e.g., "0.5.2"
4344

4445
# Processing metadata
4546
status = Column(Enum(SimulationStatus), default=SimulationStatus.PENDING, nullable=False)
@@ -65,24 +66,83 @@ class DatasetMetadata(Base):
6566
# Dataset characteristics
6667
source = Column(String, nullable=True) # "FRS", "CPS", etc.
6768
version = Column(String, nullable=True)
69+
model_version = Column(String, nullable=True) # e.g., "0.5.2"
6870

6971
# Metadata
7072
description = Column(Text, nullable=True)
7173
created_at = Column(DateTime, default=datetime.now)
7274
updated_at = Column(DateTime, default=datetime.now, onupdate=datetime.now)
7375

7476

75-
class Scenario(Base):
77+
class ScenarioMetadata(Base):
7678
"""Modifications made to baseline simulation behaviour."""
7779
__tablename__ = "scenarios"
7880

7981
id = Column(String, primary_key=True, default=generate_uuid)
8082
name = Column(String, nullable=False, unique=True, index=True)
8183
country = Column(String, nullable=False, index=True)
84+
model_version = Column(String, nullable=True) # e.g., "0.5.2"
8285

8386
# Metadata
8487
description = Column(Text, nullable=True)
8588

8689
created_at = Column(DateTime, default=datetime.now)
8790
updated_at = Column(DateTime, default=datetime.now, onupdate=datetime.now)
8891
created_by = Column(String, nullable=True)
92+
93+
# Relationships
94+
parameter_changes = relationship("ParameterChange", back_populates="scenario", cascade="all, delete-orphan")
95+
96+
97+
class ParameterMetadata(Base):
98+
"""Registry of all parameters that can be modified."""
99+
__tablename__ = "parameters"
100+
101+
id = Column(String, primary_key=True, default=generate_uuid)
102+
name = Column(String, nullable=False, unique=True, index=True) # e.g., "gov.basic_rate"
103+
country = Column(String, nullable=False, index=True)
104+
parent_id = Column(String, ForeignKey("parameters.id"), nullable=True)
105+
106+
# Parameter metadata
107+
label = Column(String, nullable=True) # Human-readable name
108+
description = Column(Text, nullable=True)
109+
unit = Column(String, nullable=True) # e.g., "GBP", "percent", "boolean"
110+
data_type = Column(String, nullable=False) # "float", "int", "bool", "string"
111+
112+
# Metadata
113+
created_at = Column(DateTime, default=datetime.now)
114+
updated_at = Column(DateTime, default=datetime.now, onupdate=datetime.now)
115+
116+
# Relationships
117+
changes = relationship("ParameterChange", back_populates="parameter", cascade="all, delete-orphan")
118+
119+
120+
class ParameterChangeMetadata(Base):
121+
"""Individual parameter change within a scenario."""
122+
__tablename__ = "parameter_changes"
123+
124+
id = Column(String, primary_key=True, default=generate_uuid)
125+
126+
# Foreign keys
127+
scenario_id = Column(String, ForeignKey("scenarios.id"), nullable=False, index=True)
128+
parameter_id = Column(String, ForeignKey("parameters.id"), nullable=False, index=True)
129+
130+
# Time period for this change
131+
start_date = Column(DateTime, nullable=False, index=True) # When this change takes effect
132+
end_date = Column(DateTime, nullable=True, index=True) # When this change expires (null = indefinite)
133+
134+
# The actual change
135+
value = Column(JSON, nullable=False) # JSON to handle different data types
136+
137+
# Ordering within scenario (for applying changes in sequence)
138+
order_index = Column(Integer, nullable=False, default=0)
139+
140+
# Metadata
141+
model_version = Column(String, nullable=True) # e.g., "0.5.2"
142+
description = Column(Text, nullable=True) # Optional description of this specific change
143+
created_at = Column(DateTime, default=datetime.now)
144+
updated_at = Column(DateTime, default=datetime.now, onupdate=datetime.now)
145+
146+
# Relationships
147+
scenario = relationship("Scenario", back_populates="parameter_changes")
148+
parameter = relationship("Parameter", back_populates="changes")

0 commit comments

Comments
 (0)