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.
Files changed (30) hide show
  1. policyengine_household_api/ai_templates/__init__.py +1 -0
  2. policyengine_household_api/ai_templates/household_explainer.py +23 -0
  3. policyengine_household_api/api.py +93 -0
  4. policyengine_household_api/auth/validation.py +18 -0
  5. policyengine_household_api/constants.py +44 -0
  6. policyengine_household_api/country.py +522 -0
  7. policyengine_household_api/data/analytics_setup.py +161 -0
  8. policyengine_household_api/data/models.py +18 -0
  9. policyengine_household_api/decorators/analytics.py +92 -0
  10. policyengine_household_api/decorators/auth.py +164 -0
  11. policyengine_household_api/endpoints/__init__.py +3 -0
  12. policyengine_household_api/endpoints/home.py +32 -0
  13. policyengine_household_api/endpoints/household.py +61 -0
  14. policyengine_household_api/endpoints/household_explainer.py +192 -0
  15. policyengine_household_api/models/__init__.py +6 -0
  16. policyengine_household_api/models/computation_tree.py +33 -0
  17. policyengine_household_api/models/household.py +64 -0
  18. policyengine_household_api/openapi_spec.yaml +878 -0
  19. policyengine_household_api/tests/test_constants.py +43 -0
  20. policyengine_household_api/utils/__init__.py +5 -0
  21. policyengine_household_api/utils/computation_tree.py +234 -0
  22. policyengine_household_api/utils/config_loader.py +478 -0
  23. policyengine_household_api/utils/google_cloud.py +124 -0
  24. policyengine_household_api/utils/household.py +99 -0
  25. policyengine_household_api/utils/json.py +38 -0
  26. policyengine_household_api/utils/validate_country.py +30 -0
  27. policyengine_household_api-0.14.3.dist-info/METADATA +117 -0
  28. policyengine_household_api-0.14.3.dist-info/RECORD +30 -0
  29. policyengine_household_api-0.14.3.dist-info/WHEEL +4 -0
  30. 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