Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions changelog.d/budget-window-batch.fixed.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Added a budget-window economy endpoint that batches yearly impact calculations with bounded server-side concurrency and returns aggregated progress plus totals.
1 change: 1 addition & 0 deletions changelog.d/fix-silent-exception-swallowing.fixed.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Log exceptions instead of silently swallowing them during household calculations.
8 changes: 6 additions & 2 deletions policyengine_api/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

import time
import sys
import os

start_time = time.time()

Expand Down Expand Up @@ -157,8 +158,11 @@ def log_timing(message):
app.register_blueprint(user_profile_bp)
log_timing("User profile routes registered")

app.route("/simulations", methods=["GET"])(get_simulations)
log_timing("Simulations endpoint registered")
if os.environ.get("FLASK_DEBUG") == "1":
app.route("/simulations", methods=["GET"])(get_simulations)
log_timing("Simulations endpoint registered")
else:
log_timing("Simulations endpoint skipped outside debug mode")

app.register_blueprint(tracer_analysis_bp)
log_timing("Tracer analysis routes registered")
Expand Down
7 changes: 3 additions & 4 deletions policyengine_api/country.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import importlib
import logging
from flask import Response
import json
from policyengine_core.taxbenefitsystems import TaxBenefitSystem
Expand Down Expand Up @@ -445,11 +446,9 @@ def calculate(
entity_result
)
except Exception as e:
if "axes" in household:
pass
else:
logging.exception(f"Error computing {variable_name} for {entity_id}")
if "axes" not in household:
household[entity_plural][entity_id][variable_name][period] = None
print(f"Error computing {variable_name} for {entity_id}: {e}")

tracer_output = simulation.tracer.computation_log
log_lines = tracer_output.lines(aggregate=False, max_depth=10)
Expand Down
7 changes: 6 additions & 1 deletion policyengine_api/data/__init__.py
Original file line number Diff line number Diff line change
@@ -1 +1,6 @@
from .data import PolicyEngineDatabase, database, local_database
from .data import (
PolicyEngineDatabase,
database,
get_remote_database,
local_database,
)
29 changes: 21 additions & 8 deletions policyengine_api/data/data.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ class _ResultProxy:
Provides fetchone()/fetchall() with dict-like row access."""

def __init__(self, cursor_result):
self.rowcount = getattr(cursor_result, "rowcount", -1)
try:
# Use .mappings() so rows behave like dicts
self._rows = list(cursor_result.mappings())
Expand Down Expand Up @@ -75,16 +76,20 @@ def _create_pool(self):
with open(".dbpw") as f:
db_pass = f.read().strip()
db_name = "policyengine"
conn = self.connector.connect(
instance_connection_string=instance_connection_name,
driver="pymysql",
db=db_name,
user=db_user,
password=db_pass,
)

def get_connection():
return self.connector.connect(
instance_connection_string=instance_connection_name,
driver="pymysql",
db=db_name,
user=db_user,
password=db_pass,
)

self.pool = sqlalchemy.create_engine(
"mysql+pymysql://",
creator=lambda: conn,
creator=get_connection,
pool_pre_ping=True,
)

def _close_pool(self):
Expand Down Expand Up @@ -194,3 +199,11 @@ def initialize(self):
database = PolicyEngineDatabase(local=False, initialize=False)

local_database = PolicyEngineDatabase(local=True, initialize=False)
remote_database = None


def get_remote_database() -> PolicyEngineDatabase:
global remote_database
if remote_database is None:
remote_database = PolicyEngineDatabase(local=False, initialize=False)
return remote_database
12 changes: 8 additions & 4 deletions policyengine_api/endpoints/simulation.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
from policyengine_api.data import local_database
from policyengine_api.data import get_remote_database

"""

Expand Down Expand Up @@ -28,9 +28,13 @@ def get_simulations(

desc_limit = f"DESC LIMIT {max_results}" if max_results is not None else ""

result = local_database.query(
f"SELECT * FROM reform_impact ORDER BY start_time {desc_limit}",
).fetchall()
result = (
get_remote_database()
.query(
f"SELECT * FROM reform_impact ORDER BY start_time {desc_limit}",
)
.fetchall()
)

# Format into [{}]

Expand Down
132 changes: 132 additions & 0 deletions policyengine_api/openapi_spec.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -660,6 +660,138 @@ paths:
type: string
message:
type: string
/{country_id}/economy/{policy_id}/over/{baseline_policy_id}/budget-window:
get:
summary: Calculate budget-window economic impacts
operationId: get_budget_window_economic_impact
description: Calculate annual and total budget impacts for a policy over a multi-year budget window.
parameters:
- name: country_id
in: path
description: The country ID.
required: true
schema:
type: string
- name: policy_id
in: path
description: The reform policy ID.
required: true
schema:
type: string
- name: baseline_policy_id
in: path
description: The baseline policy ID.
required: true
schema:
type: string
- name: region
in: query
description: The sub-national region.
required: true
schema:
type: string
- name: start_year
in: query
description: First year in the budget window.
required: true
schema:
type: string
- name: window_size
in: query
description: Number of years to include in the budget window.
required: true
schema:
type: integer
- name: dataset
in: query
description: Dataset selection.
required: false
schema:
type: string
default: default
- name: version
in: query
description: API version number.
required: false
schema:
type: string
- name: include_district_breakdowns
in: query
description: Whether to include congressional district breakdowns for US national simulations.
required: false
schema:
type: boolean
default: false
- name: target
in: query
description: Impact target. Budget-window calculations only support general impacts.
required: false
schema:
type: string
default: general
responses:
200:
description: Budget-window economic impact, progress, or error state.
content:
application/json:
schema:
type: object
properties:
status:
type: string
enum:
- ok
- computing
- error
message:
type: string
nullable: true
result:
type: object
nullable: true
progress:
type: integer
nullable: true
completed_years:
type: array
items:
type: string
computing_years:
type: array
items:
type: string
queued_years:
type: array
items:
type: string
error:
type: string
nullable: true
400:
description: Invalid budget-window request.
content:
application/json:
schema:
type: object
properties:
status:
type: string
message:
type: string
result:
type: object
nullable: true
404:
description: Invalid country ID.
content:
text/html:
schema:
type: object
properties:
status:
type: string
message:
type: string
/{country_id}/analysis:
post:
summary: Get or trigger policy analysis
Expand Down
Loading
Loading