Skip to content

Commit 9ee1a2c

Browse files
authored
Merge pull request #655 from PolicyEngine/codex/mid-jct-mortgage-target
Use mortgage-specific JCT target for MID calibration
2 parents bc1a996 + afbaf7c commit 9ee1a2c

7 files changed

Lines changed: 95 additions & 6 deletions

File tree

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Use a mortgage-specific deduction variable for the JCT mortgage tax expenditure target instead of broad interest deductions.

policyengine_us_data/db/create_database_tables.py

Lines changed: 20 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
1-
import logging
21
import hashlib
2+
import logging
3+
from pathlib import Path
34
from typing import List, Optional
45

56
from sqlalchemy import event, text, UniqueConstraint
@@ -506,14 +507,29 @@ def create_database(
506507
# Create validation triggers
507508
create_validation_triggers(engine)
508509

509-
# Create SQL views
510+
create_or_replace_views(engine)
511+
512+
logger.info(f"Database and tables created successfully at {db_uri}")
513+
return engine
514+
515+
516+
def create_or_replace_views(engine) -> None:
517+
"""Refresh SQL views so existing databases pick up schema changes."""
510518
with engine.connect() as conn:
519+
conn.execute(text("DROP VIEW IF EXISTS stratum_domain"))
520+
conn.execute(text("DROP VIEW IF EXISTS target_overview"))
511521
conn.execute(text(STRATUM_DOMAIN_VIEW))
512522
conn.execute(text(TARGET_OVERVIEW_VIEW))
513523
conn.commit()
514524

515-
logger.info(f"Database and tables created successfully at {db_uri}")
516-
return engine
525+
526+
def refresh_views_for_db_path(db_path: str | Path) -> None:
527+
"""Refresh SQL views for an existing SQLite database file."""
528+
engine = create_engine(f"sqlite:///{Path(db_path)}")
529+
try:
530+
create_or_replace_views(engine)
531+
finally:
532+
engine.dispose()
517533

518534

519535
if __name__ == "__main__":

policyengine_us_data/db/etl_national_targets.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -87,7 +87,7 @@ def extract_national_targets(dataset: str = DEFAULT_DATASET):
8787
"year": HARDCODED_YEAR,
8888
},
8989
{
90-
"variable": "interest_deduction",
90+
"variable": "deductible_mortgage_interest",
9191
"value": 24.8e9,
9292
"source": "Joint Committee on Taxation",
9393
"notes": "Mortgage interest deduction tax expenditure",

policyengine_us_data/storage/download_private_prerequisites.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,10 @@
11
import os
22

3-
from policyengine_us_data.utils.huggingface import download
43
from pathlib import Path
4+
from policyengine_us_data.db.create_database_tables import (
5+
refresh_views_for_db_path,
6+
)
7+
from policyengine_us_data.utils.huggingface import download
58

69
FOLDER = Path(__file__).parent
710

@@ -41,3 +44,4 @@
4144
local_folder=FOLDER,
4245
version=None,
4346
)
47+
refresh_views_for_db_path(FOLDER / "calibration" / "policy_data.db")

policyengine_us_data/tests/test_calibration/conftest.py

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,25 @@
11
"""Shared fixtures for local area calibration tests."""
22

33
import pytest
4+
from sqlalchemy import create_engine
45

6+
from policyengine_us_data.db.create_database_tables import (
7+
create_or_replace_views,
8+
)
59
from policyengine_us_data.storage import STORAGE_FOLDER
610

711

12+
@pytest.fixture(scope="session", autouse=True)
13+
def refresh_policy_db_views():
14+
db_path = STORAGE_FOLDER / "calibration" / "policy_data.db"
15+
if db_path.exists():
16+
engine = create_engine(f"sqlite:///{db_path}")
17+
try:
18+
create_or_replace_views(engine)
19+
finally:
20+
engine.dispose()
21+
22+
823
@pytest.fixture(scope="module")
924
def db_uri():
1025
db_path = STORAGE_FOLDER / "calibration" / "policy_data.db"

policyengine_us_data/tests/test_database.py

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import hashlib
2+
from sqlalchemy import text
23

34
import pytest
45
from sqlalchemy.exc import IntegrityError
@@ -8,6 +9,7 @@
89
Stratum,
910
StratumConstraint,
1011
Target,
12+
create_or_replace_views,
1113
create_database,
1214
)
1315

@@ -226,6 +228,38 @@ def test_target_with_null_source(engine):
226228
assert retrieved.source is None
227229

228230

231+
def test_create_database_refreshes_existing_views(tmp_path):
232+
db_uri = f"sqlite:///{tmp_path / 'test.db'}"
233+
engine = create_database(db_uri)
234+
235+
with engine.connect() as conn:
236+
conn.execute(text("DROP VIEW target_overview"))
237+
conn.execute(
238+
text(
239+
"""
240+
CREATE VIEW target_overview AS
241+
SELECT
242+
t.target_id,
243+
t.stratum_id,
244+
t.variable,
245+
t.value,
246+
t.period,
247+
t.active
248+
FROM targets t
249+
"""
250+
)
251+
)
252+
conn.commit()
253+
254+
create_or_replace_views(engine)
255+
256+
with engine.connect() as conn:
257+
cursor = conn.execute(text("SELECT * FROM target_overview LIMIT 0"))
258+
columns = [desc[0] for desc in cursor.cursor.description]
259+
260+
assert "reform_id" in columns
261+
262+
229263
def test_valid_geographic_hierarchy(engine):
230264
"""CD under its correct state should succeed."""
231265
with Session(engine) as session:

policyengine_us_data/tests/test_database_build.py

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -125,6 +125,25 @@ def test_national_targets_loaded(built_db):
125125
)
126126

127127

128+
def test_jct_mortgage_tax_expenditure_uses_mortgage_specific_variable(built_db):
129+
"""The mortgage JCT target should point at a mortgage-specific variable."""
130+
conn = sqlite3.connect(str(built_db))
131+
rows = conn.execute("""
132+
SELECT DISTINCT t.variable, t.source, t.notes
133+
FROM targets t
134+
WHERE t.variable = 'deductible_mortgage_interest'
135+
""").fetchall()
136+
conn.close()
137+
138+
assert rows == [
139+
(
140+
"deductible_mortgage_interest",
141+
"PolicyEngine",
142+
"Mortgage interest deduction tax expenditure | Modeled as repeal-based income tax expenditure target | Source: Joint Committee on Taxation",
143+
)
144+
]
145+
146+
128147
def test_state_income_tax_targets(built_db):
129148
"""State income tax targets should cover all income-tax states."""
130149
conn = sqlite3.connect(str(built_db))

0 commit comments

Comments
 (0)