policyengine-household-api 0.14.3__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- policyengine_household_api/ai_templates/__init__.py +1 -0
- policyengine_household_api/ai_templates/household_explainer.py +23 -0
- policyengine_household_api/api.py +93 -0
- policyengine_household_api/auth/validation.py +18 -0
- policyengine_household_api/constants.py +44 -0
- policyengine_household_api/country.py +522 -0
- policyengine_household_api/data/analytics_setup.py +161 -0
- policyengine_household_api/data/models.py +18 -0
- policyengine_household_api/decorators/analytics.py +92 -0
- policyengine_household_api/decorators/auth.py +164 -0
- policyengine_household_api/endpoints/__init__.py +3 -0
- policyengine_household_api/endpoints/home.py +32 -0
- policyengine_household_api/endpoints/household.py +61 -0
- policyengine_household_api/endpoints/household_explainer.py +192 -0
- policyengine_household_api/models/__init__.py +6 -0
- policyengine_household_api/models/computation_tree.py +33 -0
- policyengine_household_api/models/household.py +64 -0
- policyengine_household_api/openapi_spec.yaml +878 -0
- policyengine_household_api/tests/test_constants.py +43 -0
- policyengine_household_api/utils/__init__.py +5 -0
- policyengine_household_api/utils/computation_tree.py +234 -0
- policyengine_household_api/utils/config_loader.py +478 -0
- policyengine_household_api/utils/google_cloud.py +124 -0
- policyengine_household_api/utils/household.py +99 -0
- policyengine_household_api/utils/json.py +38 -0
- policyengine_household_api/utils/validate_country.py +30 -0
- policyengine_household_api-0.14.3.dist-info/METADATA +117 -0
- policyengine_household_api-0.14.3.dist-info/RECORD +30 -0
- policyengine_household_api-0.14.3.dist-info/WHEEL +4 -0
- policyengine_household_api-0.14.3.dist-info/licenses/LICENSE +661 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
from .household_explainer import household_explainer_template
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import anthropic
|
|
2
|
+
|
|
3
|
+
household_explainer_template = f"""{anthropic.HUMAN_PROMPT} You are an AI assistant explaining US policy calculations.
|
|
4
|
+
The user has run a simulation for the variable '{{variable}}'.
|
|
5
|
+
The below is a computation tree segment for the variable '{{variable}}'
|
|
6
|
+
describing the variable and all its sub-variables, as well as each
|
|
7
|
+
variable's entity group.
|
|
8
|
+
{{computation_tree_segment}}
|
|
9
|
+
Here's an ordered list of the entity groups in the simulation and their
|
|
10
|
+
individual entities:
|
|
11
|
+
{{entity_description}}
|
|
12
|
+
Note that the user is interested in the value of '{{variable}}' associated
|
|
13
|
+
with entity '{{entity}}'.
|
|
14
|
+
|
|
15
|
+
Please explain this result in simple terms. Your explanation should:
|
|
16
|
+
1. Briefly describe what {{variable}} is.
|
|
17
|
+
2. Explain the main factors that led to this result.
|
|
18
|
+
3. Mention any key thresholds or rules that affected the calculation.
|
|
19
|
+
4. If relevant, suggest how changes in input might affect this result.
|
|
20
|
+
|
|
21
|
+
Keep your explanation concise but informative, suitable for a general audience. Do not start with phrases like "Certainly!" or "Here's an explanation. It will be rendered as markdown, so preface $ with \.
|
|
22
|
+
|
|
23
|
+
{anthropic.AI_PROMPT}"""
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
"""
|
|
2
|
+
This is the main Flask app for the PolicyEngine API.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
# Python imports
|
|
6
|
+
import os
|
|
7
|
+
from pathlib import Path
|
|
8
|
+
|
|
9
|
+
# External imports
|
|
10
|
+
from flask_cors import CORS
|
|
11
|
+
import flask
|
|
12
|
+
from flask_sqlalchemy import SQLAlchemy
|
|
13
|
+
from sqlalchemy.orm import DeclarativeBase
|
|
14
|
+
from dotenv import load_dotenv
|
|
15
|
+
from flask_limiter import Limiter
|
|
16
|
+
from flask_limiter.util import get_remote_address
|
|
17
|
+
from policyengine_household_api.data.analytics_setup import (
|
|
18
|
+
initialize_analytics_db_if_enabled,
|
|
19
|
+
)
|
|
20
|
+
|
|
21
|
+
# Internal imports
|
|
22
|
+
from .decorators.auth import create_auth_decorator
|
|
23
|
+
from .constants import VERSION, REPO
|
|
24
|
+
from policyengine_household_api.decorators.analytics import (
|
|
25
|
+
log_analytics_if_enabled,
|
|
26
|
+
)
|
|
27
|
+
|
|
28
|
+
# Endpoints
|
|
29
|
+
from .endpoints import (
|
|
30
|
+
get_home,
|
|
31
|
+
get_calculate,
|
|
32
|
+
generate_ai_explainer,
|
|
33
|
+
)
|
|
34
|
+
|
|
35
|
+
# Create the authentication decorator (will be either Auth0 or no-op based on config)
|
|
36
|
+
require_auth_if_enabled = create_auth_decorator()
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
print("Initialising API...")
|
|
40
|
+
|
|
41
|
+
app = application = flask.Flask(__name__)
|
|
42
|
+
|
|
43
|
+
CORS(app)
|
|
44
|
+
|
|
45
|
+
# Use in-memory storage for rate limiting
|
|
46
|
+
# Note that this provides limits per-instance;
|
|
47
|
+
# rate limits not shared if scaling more than 1 instance.
|
|
48
|
+
limiter = Limiter(
|
|
49
|
+
app=app,
|
|
50
|
+
key_func=get_remote_address,
|
|
51
|
+
default_limits=[],
|
|
52
|
+
storage_uri="memory://",
|
|
53
|
+
)
|
|
54
|
+
|
|
55
|
+
initialize_analytics_db_if_enabled(app)
|
|
56
|
+
|
|
57
|
+
app.route("/", methods=["GET"])(get_home)
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
@app.route("/<country_id>/calculate", methods=["POST"])
|
|
61
|
+
@require_auth_if_enabled()
|
|
62
|
+
@log_analytics_if_enabled
|
|
63
|
+
def calculate(country_id):
|
|
64
|
+
return get_calculate(country_id)
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
@app.route("/<country_id>/ai-analysis", methods=["POST"])
|
|
68
|
+
@require_auth_if_enabled()
|
|
69
|
+
def ai_analysis(country_id: str):
|
|
70
|
+
return generate_ai_explainer(country_id)
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
@app.route("/liveness_check", methods=["GET"])
|
|
74
|
+
def liveness_check():
|
|
75
|
+
return flask.Response(
|
|
76
|
+
"OK", status=200, headers={"Content-Type": "text/plain"}
|
|
77
|
+
)
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
@app.route("/readiness_check", methods=["GET"])
|
|
81
|
+
def readiness_check():
|
|
82
|
+
return flask.Response(
|
|
83
|
+
"OK", status=200, headers={"Content-Type": "text/plain"}
|
|
84
|
+
)
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
@app.route("/<country_id>/calculate_demo", methods=["POST"])
|
|
88
|
+
@limiter.limit("1 per second")
|
|
89
|
+
def calculate_demo(country_id):
|
|
90
|
+
return get_calculate(country_id)
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
print("API initialised.")
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import json
|
|
2
|
+
from urllib.request import urlopen
|
|
3
|
+
|
|
4
|
+
from authlib.oauth2.rfc7523 import JWTBearerTokenValidator
|
|
5
|
+
from authlib.jose.rfc7517.jwk import JsonWebKey
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class Auth0JWTBearerTokenValidator(JWTBearerTokenValidator):
|
|
9
|
+
def __init__(self, domain, audience):
|
|
10
|
+
issuer = f"https://{domain}/"
|
|
11
|
+
jsonurl = urlopen(f"{issuer}.well-known/jwks.json")
|
|
12
|
+
public_key = JsonWebKey.import_key_set(json.loads(jsonurl.read()))
|
|
13
|
+
super(Auth0JWTBearerTokenValidator, self).__init__(public_key)
|
|
14
|
+
self.claims_options = {
|
|
15
|
+
"exp": {"essential": True},
|
|
16
|
+
"aud": {"essential": True, "value": audience},
|
|
17
|
+
"iss": {"essential": True, "value": issuer},
|
|
18
|
+
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import tomllib
|
|
2
|
+
from pathlib import Path
|
|
3
|
+
from importlib.metadata import PackageNotFoundError, version
|
|
4
|
+
|
|
5
|
+
REPO = Path(__file__).parents[1]
|
|
6
|
+
GET = "GET"
|
|
7
|
+
POST = "POST"
|
|
8
|
+
UPDATE = "UPDATE"
|
|
9
|
+
LIST = "LIST"
|
|
10
|
+
COUNTRIES = ("uk", "us", "ca", "ng", "il")
|
|
11
|
+
COUNTRY_PACKAGE_NAMES = (
|
|
12
|
+
"policyengine_uk",
|
|
13
|
+
"policyengine_us",
|
|
14
|
+
"policyengine_canada",
|
|
15
|
+
"policyengine_ng",
|
|
16
|
+
"policyengine_il",
|
|
17
|
+
)
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def get_package_version(package_name: str) -> str:
|
|
21
|
+
try:
|
|
22
|
+
return version(package_name)
|
|
23
|
+
except PackageNotFoundError:
|
|
24
|
+
return "0.0.0"
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def get_repo_version() -> str:
|
|
28
|
+
pyproject = REPO / "pyproject.toml"
|
|
29
|
+
try:
|
|
30
|
+
project = tomllib.loads(pyproject.read_text())["project"]
|
|
31
|
+
return project["version"]
|
|
32
|
+
except (FileNotFoundError, KeyError, tomllib.TOMLDecodeError):
|
|
33
|
+
try:
|
|
34
|
+
return version("policyengine-household-api")
|
|
35
|
+
except PackageNotFoundError:
|
|
36
|
+
return "0.0.0"
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
VERSION = get_repo_version()
|
|
40
|
+
COUNTRY_PACKAGE_VERSIONS = {
|
|
41
|
+
country: get_package_version(package_name)
|
|
42
|
+
for country, package_name in zip(COUNTRIES, COUNTRY_PACKAGE_NAMES)
|
|
43
|
+
}
|
|
44
|
+
__version__ = VERSION
|