Skip to content
Merged
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/report-run-timestamps.fixed.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Expose report output run timestamps for report responses.
35 changes: 17 additions & 18 deletions policyengine_api/endpoints/household.py
Original file line number Diff line number Diff line change
@@ -1,24 +1,23 @@
from policyengine_api.country import (
COUNTRIES,
)
from policyengine_api.data import database, local_database
import json
from flask import Response, request
from policyengine_api.utils import hash_object
from policyengine_api.constants import COUNTRY_PACKAGE_VERSIONS
import sqlalchemy.exc
from policyengine_api.country import COUNTRIES
import json
import logging
from datetime import date
from policyengine_api.utils.payload_validators import validate_country


def add_yearly_variables(household, country_id):
def get_countries():
from policyengine_api.country import COUNTRIES

return COUNTRIES


def add_yearly_variables(household, country_id, countries=None):
"""
Add yearly variables to a household dict before enqueueing calculation
"""
metadata = COUNTRIES.get(country_id).metadata
metadata = (countries or get_countries()).get(country_id).metadata

variables = metadata["variables"]
entities = metadata["entities"]
Expand All @@ -35,8 +34,8 @@ def add_yearly_variables(household, country_id):
possible_entities = household[entity_plural].keys()
for entity in possible_entities:
if (
not variables[variable]["name"]
in household[entity_plural][entity]
variables[variable]["name"]
not in household[entity_plural][entity]
):
if variables[variable]["isInputVariable"]:
household[entity_plural][entity][
Expand Down Expand Up @@ -85,7 +84,7 @@ def get_household_under_policy(country_id: str, household_id: str, policy_id: st
# Look in computed_households to see if already computed

row = local_database.query(
f"SELECT * FROM computed_household WHERE household_id = ? AND policy_id = ? AND api_version = ?",
"SELECT * FROM computed_household WHERE household_id = ? AND policy_id = ? AND api_version = ?",
(household_id, policy_id, api_version),
).fetchone()

Expand All @@ -109,7 +108,7 @@ def get_household_under_policy(country_id: str, household_id: str, policy_id: st
# Retrieve from the household table

row = database.query(
f"SELECT * FROM household WHERE id = ? AND country_id = ?",
"SELECT * FROM household WHERE id = ? AND country_id = ?",
(household_id, country_id),
).fetchone()

Expand All @@ -135,7 +134,7 @@ def get_household_under_policy(country_id: str, household_id: str, policy_id: st
# Retrieve from the policy table

row = database.query(
f"SELECT * FROM policy WHERE id = ? AND country_id = ?",
"SELECT * FROM policy WHERE id = ? AND country_id = ?",
(policy_id, country_id),
).fetchone()

Expand All @@ -153,7 +152,7 @@ def get_household_under_policy(country_id: str, household_id: str, policy_id: st
mimetype="application/json",
)

country = COUNTRIES.get(country_id)
country = get_countries().get(country_id)

try:
result = country.calculate(
Expand All @@ -178,7 +177,7 @@ def get_household_under_policy(country_id: str, household_id: str, policy_id: st

try:
local_database.query(
f"INSERT INTO computed_household (country_id, household_id, policy_id, computed_household_json, api_version) VALUES (?, ?, ?, ?, ?)",
"INSERT INTO computed_household (country_id, household_id, policy_id, computed_household_json, api_version) VALUES (?, ?, ?, ?, ?)",
(
country_id,
household_id,
Expand All @@ -190,7 +189,7 @@ def get_household_under_policy(country_id: str, household_id: str, policy_id: st
except Exception:
# Update the result if it already exists
local_database.query(
f"UPDATE computed_household SET computed_household_json = ? WHERE country_id = ? AND household_id = ? AND policy_id = ?",
"UPDATE computed_household SET computed_household_json = ? WHERE country_id = ? AND household_id = ? AND policy_id = ?",
(json.dumps(result), country_id, household_id, policy_id),
)

Expand All @@ -217,7 +216,7 @@ def get_calculate(country_id: str, add_missing: bool = False) -> dict:
# Add in any missing yearly variables to household_json
household_json = add_yearly_variables(household_json, country_id)

country = COUNTRIES.get(country_id)
country = get_countries().get(country_id)

try:
result = country.calculate(household_json, policy_json)
Expand Down
177 changes: 174 additions & 3 deletions policyengine_api/openapi_spec.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,7 @@ paths:
data:
type: object
responses:
201:
"201":
description: OK
content:
application/json:
Expand Down Expand Up @@ -151,7 +151,7 @@ paths:
schema:
type: integer
responses:
200:
"200":
description: The policy record.
content:
application/json:
Expand Down Expand Up @@ -219,7 +219,7 @@ paths:
schema:
type: string
responses:
200:
"200":
description: The search results.
content:
application/json:
Expand Down Expand Up @@ -560,6 +560,177 @@ paths:
type: string
message:
type: string
/{country_id}/report:
post:
summary: Create a report output
operationId: create_report_output
description: Create or retrieve a report output for the provided simulations and year.
parameters:
- name: country_id
in: path
description: The country ID.
required: true
schema:
type: string
requestBody:
required: true
content:
application/json:
schema:
type: object
properties:
simulation_1_id:
type: integer
simulation_2_id:
type: integer
nullable: true
year:
type: string
responses:
"200":
description: Existing report output.
content:
application/json:
schema:
type: object
properties:
status:
type: string
message:
type: string
nullable: true
result:
type: object
properties:
requested_at:
type: string
nullable: true
started_at:
type: string
nullable: true
finished_at:
type: string
nullable: true
"201":
description: Created report output.
content:
application/json:
schema:
type: object
properties:
status:
type: string
message:
type: string
nullable: true
result:
type: object
properties:
requested_at:
type: string
nullable: true
started_at:
type: string
nullable: true
finished_at:
type: string
nullable: true
patch:
summary: Update a report output
operationId: update_report_output
description: Update a report output status, result, or error message.
parameters:
- name: country_id
in: path
description: The country ID.
required: true
schema:
type: string
requestBody:
required: true
content:
application/json:
schema:
type: object
properties:
id:
type: integer
status:
type: string
output:
type: object
nullable: true
error_message:
type: string
nullable: true
responses:
"200":
description: Updated report output.
content:
application/json:
schema:
type: object
properties:
status:
type: string
message:
type: string
nullable: true
result:
type: object
properties:
requested_at:
type: string
nullable: true
started_at:
type: string
nullable: true
finished_at:
type: string
nullable: true
/{country_id}/report/{report_id}:
get:
summary: Get a report output
operationId: get_report_output
description: Get a report output by ID. Timestamp fields are projected from the selected base report run.
parameters:
- name: country_id
in: path
description: The country ID.
required: true
schema:
type: string
- name: report_id
in: path
description: The report output ID.
required: true
schema:
type: integer
responses:
"200":
description: Report output.
content:
application/json:
schema:
type: object
properties:
status:
type: string
message:
type: string
nullable: true
result:
type: object
properties:
requested_at:
type: string
nullable: true
started_at:
type: string
nullable: true
finished_at:
type: string
nullable: true
/{country_id}/economy/{policy_id}/over/{baseline_policy_id}:
get:
summary: Calculate the economic impact of a policy
Expand Down
25 changes: 17 additions & 8 deletions policyengine_api/routes/report_output_routes.py
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,11 @@ def get_report_output(country_id: str, report_id: int) -> Response:
"""
Get a report output record by ID.

The response result may include requested_at, started_at, and finished_at
values projected from the selected report_output_runs row. Those fields are
base report execution metadata, not user-specific user-report association
last-run metadata.

Args:
country_id (str): The country ID.
report_id (int): The report output ID.
Expand Down Expand Up @@ -155,7 +160,7 @@ def update_report_output(country_id: str) -> Response:

Request body can contain:
- id (int): The report output ID.
- status (str): The new status ('complete' or 'error')
- status (str): The new status ('pending', 'running', 'complete', or 'error')
- output (dict): The result output (for complete status)
- api_version (str): The API version of the report
- error_message (str): The error message (for error status)
Expand All @@ -173,19 +178,23 @@ def update_report_output(country_id: str) -> Response:
print(f"Updating report #{report_id} for country {country_id}")

# Validate status if provided
if status is not None and status not in ["pending", "complete", "error"]:
raise BadRequest("status must be 'pending', 'complete', or 'error'")
if status is not None and status not in [
"pending",
"running",
"complete",
"error",
]:
raise BadRequest("status must be 'pending', 'running', 'complete', or 'error'")

# Validate that complete status has output
if status == "complete" and output is None:
raise BadRequest("output is required when status is 'complete'")

try:
# First check if the report output exists
existing_report = report_output_service.get_stored_report_output(
country_id, report_id
)
if existing_report is None:
# First check if the report output exists without running pointer sync:
# syncing a completed parent before this mutation can clear an active
# pending rerun that this PATCH is about to mark as running.
if not report_output_service.report_output_exists(country_id, report_id):
raise NotFound(f"Report #{report_id} not found.")

# Update the report output
Expand Down
Loading
Loading