@@ -78,6 +78,71 @@ def _hints(value, path):
7878 _string (hint , field )
7979
8080
81+ def _scope_name (value , scope ):
82+ name = normalize_name (value )
83+ if scope == "counties" :
84+ name = name .removesuffix (" county" )
85+ return name
86+
87+
88+ def _validate_filing_hint_overrides (payload , document_ids ):
89+ overrides = _object (payload .get ("filing_hint_overrides" , {}), "filing_hint_overrides" )
90+ for scope in ("counties" , "courts" ):
91+ choices = _object (overrides .get (scope , {}), f"filing_hint_overrides.{ scope } " )
92+ if len (choices ) > 100 :
93+ raise HandoffError (f"filing_hint_overrides.{ scope } supports up to 100 names." )
94+ normalized = set ()
95+ for name , values in choices .items ():
96+ _string (name , f"filing_hint_overrides.{ scope } name" )
97+ key = _scope_name (name , scope )
98+ if not key or key in normalized :
99+ raise HandoffError (f"filing_hint_overrides.{ scope } names must be unique." )
100+ normalized .add (key )
101+ values = _object (values , f"filing_hint_overrides.{ scope } .{ name } " )
102+ _hints (values , f"filing_hint_overrides.{ scope } .{ name } " )
103+ documents = _object (values .get ("documents" , {}), f"filing_hint_overrides.{ scope } .{ name } .documents" )
104+ if not set (documents ).issubset (document_ids ):
105+ raise HandoffError ("Scoped document hints must use a declared document id." )
106+ for document_id , document_hints in documents .items ():
107+ _hints (
108+ _object (
109+ document_hints ,
110+ f"filing_hint_overrides.{ scope } .{ name } .documents.{ document_id } " ,
111+ ),
112+ f"filing_hint_overrides.{ scope } .{ name } .documents.{ document_id } " ,
113+ )
114+
115+
116+ def _matching_hint_override (payload , scope , candidates ):
117+ choices = payload .get ("filing_hint_overrides" , {}).get (scope , {})
118+ wanted = {_scope_name (candidate , scope ) for candidate in candidates if candidate }
119+ return next (
120+ (values for name , values in choices .items () if _scope_name (name , scope ) in wanted ),
121+ {},
122+ )
123+
124+
125+ def effective_hints (payload , * , court_name = "" , document = None ):
126+ """Apply general, county, then court-specific semantic hint overrides."""
127+ base = document if document is not None else payload
128+ result = {field : list (value ) for field in HINT_FIELDS if isinstance ((value := base .get (field )), list )}
129+ case = payload .get ("case" , {})
130+ scopes = (
131+ _matching_hint_override (payload , "counties" , [case .get ("county" , "" )]),
132+ _matching_hint_override (
133+ payload ,
134+ "courts" ,
135+ [case .get ("court_name" , "" ), court_name ],
136+ ),
137+ )
138+ for override in scopes :
139+ selected = override .get ("documents" , {}).get (document .get ("id" , "" ), {}) if document else override
140+ for field in HINT_FIELDS :
141+ if field in selected :
142+ result [field ] = list (selected [field ])
143+ return result
144+
145+
81146def validate_payload (payload , source_config , files ):
82147 _object (payload , "payload" )
83148 if type (payload .get ("schema_version" )) is not int or payload ["schema_version" ] != 1 :
@@ -98,6 +163,7 @@ def validate_payload(payload, source_config, files):
98163 raise HandoffError ("case.existing_case must be true or false when known." )
99164 for field in CASE_FIELDS :
100165 _string (case .get (field , "" ), f"case.{ field } " , FilingDraft ._meta .get_field (field ).max_length )
166+ _string (case .get ("county" , "" ), "case.county" )
101167 _object (payload .get ("known_filing_facts" , {}), "known_filing_facts" )
102168 parties = payload .get ("parties" , [])
103169 if not isinstance (parties , list ) or len (parties ) > 100 :
@@ -167,6 +233,7 @@ def validate_payload(payload, source_config, files):
167233 raise HandoffError ("A document bundle needs exactly one lead PDF." )
168234 if set (files ) != ids or any (len (files .getlist (key )) != 1 for key in files ):
169235 raise HandoffError ("Upload each declared document exactly once." )
236+ _validate_filing_hint_overrides (payload , ids )
170237 return payload
171238
172239
@@ -296,12 +363,13 @@ def choose(obj, field, options, hints):
296363 choose (draft , "court_code" , _codes (draft .jurisdiction , "" , with_names = True ), court_hints (draft , payload ))
297364 if not draft .court_code :
298365 return
366+ payload_hints = effective_hints (payload , court_name = draft .court_name )
299367 timing = "Subsequent" if draft .existing_case == ExistingCase .EXISTING else "Initial"
300368 choose (
301369 draft ,
302370 "case_category_code" ,
303371 _codes (draft .jurisdiction , f"{ draft .court_code } /categories" , timing = timing , fileable_only = True ),
304- intent .get ("case_category_name_aliases" , []) + payload .get ("case_category_name_hints" , []),
372+ intent .get ("case_category_name_aliases" , []) + payload_hints .get ("case_category_name_hints" , []),
305373 )
306374 if not draft .case_category_code :
307375 return
@@ -311,7 +379,7 @@ def choose(obj, field, options, hints):
311379 _codes (
312380 draft .jurisdiction , f"{ draft .court_code } /case_types/" , category_id = draft .case_category_code , timing = timing
313381 ),
314- intent .get ("case_type_name_aliases" , []) + payload .get ("case_type_name_hints" , []),
382+ intent .get ("case_type_name_aliases" , []) + payload_hints .get ("case_type_name_hints" , []),
315383 )
316384 if not draft .case_type_code :
317385 return
@@ -325,16 +393,17 @@ def choose(obj, field, options, hints):
325393 for document in draft .documents .all ():
326394 suggestion = draft .metadata_events .filter (path = f"documents.{ document .pk } " , kind = "source_suggestion" ).first ()
327395 hints = suggestion .value if suggestion else {}
396+ document_hints = effective_hints (payload , court_name = draft .court_name , document = hints )
328397 curated = intent .get ("documents" , {}).get (hints .get ("id" , "" ), {})
329398 choose (
330399 document ,
331400 "filing_type_code" ,
332401 options ,
333402 curated .get ("filing_type_name_aliases" , [])
334403 + (
335- hints .get ("filing_type_name_hints" , [])
404+ document_hints .get ("filing_type_name_hints" , [])
336405 or (
337- intent .get ("filing_type_name_aliases" , []) + payload .get ("filing_type_name_hints" , [])
406+ intent .get ("filing_type_name_aliases" , []) + payload_hints .get ("filing_type_name_hints" , [])
338407 if document .role == "lead"
339408 else [hints .get ("form_name" , "" )]
340409 )
@@ -348,7 +417,12 @@ def choose(obj, field, options, hints):
348417 choices = _codes (
349418 draft .jurisdiction , f"{ draft .court_code } /filing_types/{ document .filing_type_code } /{ endpoint } "
350419 )
351- choose (document , field , choices , hints .get (field .replace ("_code" , "_name_hints" ), []))
420+ choose (
421+ document ,
422+ field ,
423+ choices ,
424+ document_hints .get (field .replace ("_code" , "_name_hints" ), []),
425+ )
352426 party_options = _codes (draft .jurisdiction , f"{ draft .court_code } /case_types/{ draft .case_type_code } /party_types" )
353427 for party in draft .parties .all ():
354428 if party .role == "filer" and not (party .is_self or party .is_filing_party ):
0 commit comments