Skip to content

Commit 5a6ac1a

Browse files
Make one config handler for Jurisdictionso
YAML parsing of the jurisdiction configs was originally handled in multiple places, with duplicate configs all around. This PR puts all of the config info for each jurisdiction in its YAML file, and reads it everywhere that jurisdiction configs are used. No more multiple parsing functions[^1]. [^1]: Okay, one parsing function: left in `get_case_type_config`, since it's unclear to me if case_types in jurisdictions should contain all of the base case types or just the ones they've defined / overriden. Need more understanding to make that choice, so I left the code as is there.
1 parent 75a535c commit 5a6ac1a

12 files changed

Lines changed: 270 additions & 527 deletions

‎efile_app/efile/api/case_form_views.py‎

Lines changed: 3 additions & 184 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@
1414
import yaml
1515
from django.views.decorators.http import require_http_methods
1616

17+
from ..utils.config_loader import config_loader
18+
1719
from .base import APIResponseMixin
1820

1921
logger = logging.getLogger(__name__)
@@ -22,117 +24,6 @@
2224
class CaseFormAPIViews(APIResponseMixin):
2325
"""API views for dynamic case form configuration"""
2426

25-
@staticmethod
26-
@lru_cache(maxsize=32)
27-
def _load_base_configuration():
28-
"""Load base case type configuration from base-case-types.yaml"""
29-
try:
30-
config_path = os.path.join(
31-
os.path.dirname(os.path.dirname(__file__)), "static", "config", "base-case-types.yaml"
32-
)
33-
34-
with open(config_path, encoding="utf-8") as file:
35-
return yaml.safe_load(file) or {}
36-
except Exception as e:
37-
logger.exception(f"Error loading base-case-types.yaml: {e}")
38-
return {}
39-
40-
@staticmethod
41-
@lru_cache(maxsize=32)
42-
def _load_jurisdiction_configuration(jurisdiction="illinois"):
43-
"""Load jurisdiction-specific configuration and merge with base"""
44-
try:
45-
# Load base configuration first
46-
base_config = CaseFormAPIViews._load_base_configuration()
47-
48-
# Load jurisdiction-specific configuration
49-
config_path = os.path.join(
50-
os.path.dirname(os.path.dirname(__file__)), "static", "config", "states", f"{jurisdiction}.yaml"
51-
)
52-
53-
jurisdiction_config = {}
54-
if os.path.exists(config_path):
55-
with open(config_path, encoding="utf-8") as file:
56-
jurisdiction_config = yaml.safe_load(file) or {}
57-
58-
# Merge configurations - jurisdiction overrides base
59-
return CaseFormAPIViews._deep_merge_configs(base_config, jurisdiction_config)
60-
61-
except Exception as e:
62-
logger.exception(f"Error loading jurisdiction configuration for {jurisdiction}: {e}")
63-
return CaseFormAPIViews._load_base_configuration()
64-
65-
@staticmethod
66-
def _deep_merge_configs(base, overlay):
67-
"""Deep merge two configuration dictionaries"""
68-
merged = base.copy()
69-
70-
for key, value in overlay.items():
71-
if key in merged and isinstance(merged[key], dict) and isinstance(value, dict):
72-
merged[key] = CaseFormAPIViews._deep_merge_configs(merged[key], value)
73-
else:
74-
merged[key] = value
75-
76-
return merged
77-
78-
@staticmethod
79-
def _find_case_type_config(case_type_name, jurisdiction="illinois"):
80-
"""Find case type configuration by matching keywords in merged config"""
81-
config = CaseFormAPIViews._load_jurisdiction_configuration(jurisdiction)
82-
83-
# Check both case_types and base_case_types sections
84-
case_types_sources = [config.get("case_types", {}), config.get("base_case_types", {})]
85-
86-
case_type_name_lower = case_type_name.lower()
87-
found_config = None
88-
89-
print(f"� Looking for case type: '{case_type_name}' (lower: '{case_type_name_lower}')")
90-
91-
for case_types in case_types_sources:
92-
# Try exact match first
93-
if case_type_name_lower in case_types:
94-
found_config = case_types[case_type_name_lower]
95-
break
96-
97-
# Try keyword matching
98-
for _case_type_key, case_type_config in case_types.items():
99-
if "keywords" in case_type_config:
100-
for keyword in case_type_config["keywords"]:
101-
# Check both substring matching and exact matching
102-
keyword_lower = keyword.lower()
103-
if (
104-
keyword_lower in case_type_name_lower
105-
or case_type_name_lower in keyword_lower
106-
or keyword_lower == case_type_name_lower
107-
):
108-
found_config = case_type_config
109-
break
110-
if found_config:
111-
break
112-
if found_config:
113-
break
114-
115-
if not found_config:
116-
return None
117-
118-
# Resolve inheritance if extends is present
119-
if "extends" in found_config:
120-
extends_ref = found_config["extends"]
121-
base_config = None
122-
123-
# Parse the extends reference (e.g., "base_case_types.name_change")
124-
if "." in extends_ref:
125-
section, key = extends_ref.split(".", 1)
126-
if section in config and key in config[section]:
127-
base_config = config[section][key]
128-
129-
if base_config:
130-
# Deep merge base config with the current config
131-
merged_config = CaseFormAPIViews._deep_merge_configs(base_config.copy(), found_config)
132-
return merged_config
133-
134-
return found_config
135-
13627
@staticmethod
13728
@require_http_methods(["GET"])
13829
def get_case_form_config(request):
@@ -159,7 +50,7 @@ def get_case_form_config(request):
15950
# Try to find case type configuration using jurisdiction-aware lookup
16051
case_config = None
16152
if case_type_id:
162-
case_config = CaseFormAPIViews._find_case_type_config(case_type_id, jurisdiction)
53+
case_config = config_loader.get_case_type_config(jurisdiction, case_type_id, court_code)
16354

16455
# If no specific configuration found, return minimal structure
16556
if not case_config:
@@ -178,13 +69,6 @@ def get_case_form_config(request):
17869
}
17970
)
18071

181-
# Apply court-specific customizations if they exist
182-
sections = case_config.get("sections", {})
183-
if court_code:
184-
sections = CaseFormAPIViews._apply_court_specific_config(
185-
sections, court_code, case_type_id, jurisdiction
186-
)
187-
18872
# Structure the response based on the new configuration format
18973
form_config = {
19074
"case_info": {
@@ -205,68 +89,3 @@ def get_case_form_config(request):
20589

20690
except Exception as e:
20791
return CaseFormAPIViews.error_response(f"Error retrieving case form configuration: {str(e)}")
208-
209-
@staticmethod
210-
def _apply_court_specific_config(sections, court_code, case_type_id, jurisdiction):
211-
"""Apply court-specific configurations and conditional requirements"""
212-
try:
213-
config = CaseFormAPIViews._load_jurisdiction_configuration(jurisdiction)
214-
court_requirements = config.get("court_specific_requirements", {}).get(court_code, {})
215-
216-
if not court_requirements:
217-
return sections
218-
219-
case_specific = court_requirements.get("case_types", {}).get(case_type_id, {})
220-
if not case_specific:
221-
return sections
222-
223-
# Apply field modifications first
224-
field_modifications = case_specific.get("field_modifications", [])
225-
for modification in field_modifications:
226-
field_group_name = modification.get("field_group")
227-
modifications = modification.get("modifications", {})
228-
229-
# Apply modifications to the matching field group
230-
if "parties" in sections and "fields" in sections["parties"]:
231-
# Use a copy of the list to avoid modification during iteration
232-
fields_list = sections["parties"]["fields"][:]
233-
for field_group in fields_list:
234-
if field_group.get("section_title") == field_group_name:
235-
# Apply modifications to this field group
236-
for key, value in modifications.items():
237-
if key == "hidden" and value:
238-
# Remove this field group entirely if hidden
239-
if field_group in sections["parties"]["fields"]:
240-
sections["parties"]["fields"].remove(field_group)
241-
break
242-
elif key == "required":
243-
field_group["required"] = value
244-
elif key == "conditional_requirements":
245-
field_group["conditional_requirements"] = value
246-
break
247-
248-
# Apply additional fields
249-
additional_fields = case_specific.get("additional_fields", [])
250-
if additional_fields and "parties" in sections and "fields" in sections["parties"]:
251-
# Add additional fields to the first section of parties
252-
if sections["parties"]["fields"]:
253-
first_section = sections["parties"]["fields"][0]
254-
if "fields" in first_section:
255-
# Check for existing fields to prevent duplicates
256-
existing_field_names = {field.get("name") for field in first_section["fields"]}
257-
258-
# Only add fields that don't already exist
259-
new_fields = []
260-
for field in additional_fields:
261-
field_name = field.get("name")
262-
if field_name and field_name not in existing_field_names:
263-
new_fields.append(field)
264-
265-
if new_fields:
266-
first_section["fields"].extend(new_fields)
267-
268-
return sections
269-
270-
except Exception as e:
271-
logger.exception(f"Error applying court-specific config: {e}")
272-
return sections

‎efile_app/efile/api/case_type_config.py‎

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
from django.http import JsonResponse
22

33
from .case_form_views import CaseFormAPIViews
4+
from ..utils.config_loader import JurisdictionConfigLoader, config_loader
45

56

67
def get_case_type_config(request):
@@ -11,11 +12,13 @@ def get_case_type_config(request):
1112
jurisdiction = request.GET.get("jurisdiction") or request.session.get("jurisdiction", "illinois")
1213

1314
# Use the new jurisdiction-aware configuration loader
14-
config_data = CaseFormAPIViews._load_jurisdiction_configuration(jurisdiction)
15+
config_data = config_loader.load_jurisdiction_config(jurisdiction)
1516

1617
# Process case types to ensure proper inheritance from base_case_types
1718
processed_case_types = {}
1819

20+
# TODO(brycew): decide if jurisdictions get all from base_case_types, or
21+
# just the listed ones. Remove this if it's the latter.
1922
# First, add all base case types
2023
if "base_case_types" in config_data:
2124
for key, value in config_data["base_case_types"].items():
@@ -30,7 +33,7 @@ def get_case_type_config(request):
3033
jurisdiction_config = value.copy()
3134

3235
# Deep merge the configurations
33-
merged_config = CaseFormAPIViews._deep_merge_configs(base_config, jurisdiction_config)
36+
merged_config = JurisdictionConfigLoader._deep_merge(base_config, jurisdiction_config)
3437
processed_case_types[key] = merged_config
3538
else:
3639
# New case type not in base

‎efile_app/efile/api/config_views.py‎

Lines changed: 2 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -9,37 +9,14 @@
99

1010
from .base import APIResponseMixin
1111
from .case_form_views import CaseFormAPIViews
12+
from ..utils.config_loader import config_loader
1213

1314
logger = logging.getLogger(__name__)
1415

1516

1617
class ConfigAPIViews(APIResponseMixin):
1718
"""API views for form configuration - delegates to CaseFormAPIViews for jurisdiction-aware configs"""
1819

19-
@staticmethod
20-
def _find_config_key_for_case_type(case_type_id, jurisdiction="illinois"):
21-
"""Find the config key (like 'name_change') that corresponds to a case type ID (like '78346')"""
22-
try:
23-
config = CaseFormAPIViews._load_jurisdiction_configuration(jurisdiction)
24-
case_types_sources = [config.get("case_types", {}), config.get("base_case_types", {})]
25-
26-
case_type_id_lower = case_type_id.lower()
27-
28-
for case_types in case_types_sources:
29-
for case_type_key, case_type_config in case_types.items():
30-
if "keywords" in case_type_config:
31-
for keyword in case_type_config["keywords"]:
32-
keyword_lower = keyword.lower()
33-
if (
34-
keyword_lower in case_type_id_lower
35-
or case_type_id_lower in keyword_lower
36-
or keyword_lower == case_type_id_lower
37-
):
38-
return case_type_key
39-
return None
40-
except Exception:
41-
return None
42-
4320
@staticmethod
4421
@require_http_methods(["GET"])
4522
def get_form_config(request):
@@ -62,7 +39,7 @@ def get_form_config(request):
6239
return ConfigAPIViews.error_response("Missing required parameter: case_type")
6340

6441
# Use the new jurisdiction-aware configuration system
65-
case_config = CaseFormAPIViews._find_case_type_config(case_type_id, jurisdiction)
42+
case_config = config_loader.get_case_type_config(jurisdiction, case_type_id, court=court_code)
6643

6744
# If no specific configuration found, return minimal structure
6845
if not case_config:
@@ -76,15 +53,7 @@ def get_form_config(request):
7653
}
7754
)
7855

79-
# Apply court-specific customizations if they exist
8056
sections = case_config.get("sections", {})
81-
if court_code:
82-
# For court-specific modifications, we need to use the config key (like "name_change")
83-
# not the case type ID (like "78346"). Find the config key by reverse lookup.
84-
case_config_key = ConfigAPIViews._find_config_key_for_case_type(case_type_id, jurisdiction)
85-
sections = CaseFormAPIViews._apply_court_specific_config(
86-
sections, court_code, case_config_key or case_type_id, jurisdiction
87-
)
8857

8958
# Structure the response in the expected format
9059
config = {

‎efile_app/efile/context_processors.py‎

Lines changed: 5 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -2,32 +2,17 @@
22
Context processors for jurisdiction-aware templates
33
"""
44

5+
from .utils.config_loader import config_loader
56

67
def jurisdiction_context(request):
78
"""Add current jurisdiction to all template contexts"""
89
current_jurisdiction = request.session.get("jurisdiction", "illinois")
910

10-
jurisdiction_configs = {
11-
"illinois": {
12-
"name": "Illinois eFile",
13-
"code": "illinois",
14-
"display_name": "Illinois",
15-
"icon": "fas fa-balance-scale",
16-
"api_endpoint": "/api/illinois/",
17-
"config_file": "illinois.yaml",
18-
},
19-
"massachusetts": {
20-
"name": "Massachusetts eFile",
21-
"code": "massachusetts",
22-
"display_name": "Massachusetts",
23-
"icon": "fas fa-balance-scale",
24-
"api_endpoint": "/api/massachusetts/",
25-
"config_file": "massachusetts.yaml",
26-
},
27-
}
11+
config = config_loader.get_short_jurisdiction_config(current_jurisdiction)
2812

2913
return {
3014
"current_jurisdiction": current_jurisdiction,
31-
"jurisdiction_config": jurisdiction_configs.get(current_jurisdiction, jurisdiction_configs["illinois"]),
32-
"available_jurisdictions": jurisdiction_configs,
15+
# Returns just a subset of the keys in the config
16+
"jurisdiction_config": config,
17+
"available_jurisdictions": {code: config_loader.get_short_jurisdiction_config(code) for code in config_loader.get_available_jurisdictions()},
3318
}

‎efile_app/efile/static/config/states/illinois.yaml‎

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,15 @@
11
# Illinois-specific Case Type Configuration
22
# This file extends the base configuration with Illinois-specific settings
33

4-
# State metadata
4+
# High level metadata
5+
jurisdiction:
6+
name: "Illinois eFile"
7+
code: "illinois"
8+
display_name: "Illinois"
9+
icon: "fas fa-balance-scale"
10+
api_endpoint: "/api/illinois"
11+
12+
# State metadata, for the address
513
state:
614
code: "IL"
715
name: "Illinois"

‎efile_app/efile/static/config/states/massachusetts.yaml‎

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,13 @@
33
# Massachusetts-specific Case Type Configuration
44
# This file extends the base configuration with Massachusetts-specific settings
55

6+
jurisdiction:
7+
name: "Massachusetts eFile"
8+
code: "massachusetts"
9+
display_name: "Massachusetts"
10+
icon: "fas fa-balance-scale"
11+
api_endpoint: "/api/massachusetts"
12+
613
# State metadata
714
state:
815
code: "MA"

0 commit comments

Comments
 (0)