Spaces:
Running on Zero
Running on Zero
Update hudanet_core/evidence_selection.py
Browse files- hudanet_core/evidence_selection.py +164 -36
hudanet_core/evidence_selection.py
CHANGED
|
@@ -302,14 +302,19 @@ class UnifiedEvidenceSelector:
|
|
| 302 |
return ""
|
| 303 |
|
| 304 |
def _explicit_condition_member_bridge(self, query: QueryFrame, item: ScoredEvidence) -> bool:
|
| 305 |
-
"""Recognize
|
| 306 |
-
|
| 307 |
-
|
| 308 |
-
|
| 309 |
-
|
| 310 |
-
|
| 311 |
-
|
| 312 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 313 |
"""
|
| 314 |
if query.primary_request_type != "conditions" or self.text is None:
|
| 315 |
return False
|
|
@@ -317,43 +322,130 @@ class UnifiedEvidenceSelector:
|
|
| 317 |
ruling = self._localized_source_value(item, "ruling", query.language)
|
| 318 |
if not ruling:
|
| 319 |
return False
|
| 320 |
-
|
| 321 |
-
|
|
|
|
| 322 |
return False
|
| 323 |
|
|
|
|
|
|
|
|
|
|
|
|
|
| 324 |
if query.language == "ar":
|
| 325 |
-
|
| 326 |
-
|
| 327 |
-
|
|
|
|
| 328 |
return False
|
| 329 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 330 |
if query.facet_dimension == "obligation":
|
| 331 |
-
facet_marker = bool(
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 332 |
else:
|
| 333 |
-
facet_marker =
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 334 |
else:
|
| 335 |
-
if re.search(
|
|
|
|
|
|
|
|
|
|
|
|
|
| 336 |
return False
|
| 337 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 338 |
if query.facet_dimension == "obligation":
|
| 339 |
-
facet_marker = bool(
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 340 |
else:
|
| 341 |
-
facet_marker =
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 342 |
|
| 343 |
-
if not condition_marker
|
|
|
|
|
|
|
| 344 |
return False
|
| 345 |
|
| 346 |
-
|
| 347 |
-
|
| 348 |
-
|
| 349 |
-
|
| 350 |
-
|
| 351 |
-
|
| 352 |
-
|
| 353 |
-
|
| 354 |
-
|
| 355 |
-
|
| 356 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 357 |
|
| 358 |
@staticmethod
|
| 359 |
def _clip(value: object, default: float = 0.0) -> float:
|
|
@@ -454,6 +546,8 @@ class UnifiedEvidenceSelector:
|
|
| 454 |
facet_conflict = self._clip(m.get("facet_conflicting_support", 0.0))
|
| 455 |
if query.facet_locked:
|
| 456 |
facet = self._clip(m.get("facet_alignment", 0.0)) * (1.0 - 0.72 * facet_conflict)
|
|
|
|
|
|
|
| 457 |
|
| 458 |
# The requested operation, subject, constraints and text integrity are
|
| 459 |
# bottleneck features. Neural retrieval is deliberately only a tie-breaker.
|
|
@@ -643,10 +737,20 @@ class UnifiedEvidenceSelector:
|
|
| 643 |
all_rules = tuple(item.evidence.semantic_slots.get("condition_rule", ()))
|
| 644 |
valid_rules = tuple(item.source.get("generic_valid_condition_rules", ()))
|
| 645 |
if all_rules and not valid_rules:
|
| 646 |
-
|
| 647 |
-
|
| 648 |
-
|
| 649 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 650 |
|
| 651 |
direct = bool(
|
| 652 |
f["direct_score"] >= direct_floor
|
|
@@ -744,6 +848,26 @@ class UnifiedEvidenceSelector:
|
|
| 744 |
return "exception", reasons
|
| 745 |
return "direct", reasons
|
| 746 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 747 |
supporting = bool(
|
| 748 |
(f["support_score"] >= self.cfg["support_floor"] or f["entailment_operator"] >= self.cfg["entailment_support_floor"])
|
| 749 |
and f["subject"] >= self.cfg["minimum_subject"]
|
|
@@ -935,6 +1059,10 @@ class UnifiedEvidenceSelector:
|
|
| 935 |
"generic_explicit_condition_member_bridge": bool(
|
| 936 |
self._explicit_condition_member_bridge(query, item)
|
| 937 |
),
|
|
|
|
|
|
|
|
|
|
|
|
|
| 938 |
"generic_training_candidate": bool(
|
| 939 |
shadow_disagreement
|
| 940 |
or (role in {"adjacent", "irrelevant"} and f["neural"] >= self.cfg["hard_negative_neural_floor"])
|
|
|
|
| 302 |
return ""
|
| 303 |
|
| 304 |
def _explicit_condition_member_bridge(self, query: QueryFrame, item: ScoredEvidence) -> bool:
|
| 305 |
+
"""Recognize one explicit positive member of a requested conditions set.
|
| 306 |
+
|
| 307 |
+
Distributed condition sets are often stored as sibling records whose linked
|
| 308 |
+
questions are phrased as rulings, definitions, or edge cases. A record may
|
| 309 |
+
still contribute when its own source framing explicitly marks it as a
|
| 310 |
+
positive condition of the requested facet.
|
| 311 |
+
|
| 312 |
+
Safety rules:
|
| 313 |
+
- negative labels such as "not a condition" are rejected;
|
| 314 |
+
- the condition marker must come from the ruling itself;
|
| 315 |
+
- the requested facet must be locally expressed in ruling/question/answer;
|
| 316 |
+
- the central subject must be bound structurally, unless retrieval already
|
| 317 |
+
marked the record as a bounded source-local sibling of a qualified set seed.
|
| 318 |
"""
|
| 319 |
if query.primary_request_type != "conditions" or self.text is None:
|
| 320 |
return False
|
|
|
|
| 322 |
ruling = self._localized_source_value(item, "ruling", query.language)
|
| 323 |
if not ruling:
|
| 324 |
return False
|
| 325 |
+
|
| 326 |
+
normalized_ruling = self.text.normalize(ruling, query.language)
|
| 327 |
+
if not normalized_ruling:
|
| 328 |
return False
|
| 329 |
|
| 330 |
+
question = self._localized_source_value(item, "question", query.language)
|
| 331 |
+
title = self._localized_source_value(item, "title", query.language)
|
| 332 |
+
answer_text = str(item.evidence.answer_text or "").strip()
|
| 333 |
+
|
| 334 |
if query.language == "ar":
|
| 335 |
+
if re.search(
|
| 336 |
+
r"(?:ليس|ليست|غير|لا)\s+.{0,24}(?:شرط|شروط)",
|
| 337 |
+
normalized_ruling,
|
| 338 |
+
):
|
| 339 |
return False
|
| 340 |
+
|
| 341 |
+
condition_marker = bool(
|
| 342 |
+
re.search(
|
| 343 |
+
r"(?:^|\s)(?:شرط|شروط)(?:\s|$|\s*\()",
|
| 344 |
+
normalized_ruling,
|
| 345 |
+
)
|
| 346 |
+
)
|
| 347 |
+
|
| 348 |
+
facet_frame = self.text.normalize(
|
| 349 |
+
" ".join(filter(None, (ruling, question, answer_text))),
|
| 350 |
+
query.language,
|
| 351 |
+
)
|
| 352 |
if query.facet_dimension == "obligation":
|
| 353 |
+
facet_marker = bool(
|
| 354 |
+
re.search(
|
| 355 |
+
r"(?:وجوب|واجب|يجب|يلزم|فرض)",
|
| 356 |
+
facet_frame,
|
| 357 |
+
)
|
| 358 |
+
)
|
| 359 |
else:
|
| 360 |
+
facet_marker = (
|
| 361 |
+
self._term_overlap(
|
| 362 |
+
query.facet_terms,
|
| 363 |
+
facet_frame,
|
| 364 |
+
query.language,
|
| 365 |
+
)
|
| 366 |
+
>= 0.44
|
| 367 |
+
)
|
| 368 |
else:
|
| 369 |
+
if re.search(
|
| 370 |
+
r"\b(?:not|isn't|isnt)\b.{0,32}\bconditions?\b",
|
| 371 |
+
normalized_ruling,
|
| 372 |
+
re.I,
|
| 373 |
+
):
|
| 374 |
return False
|
| 375 |
+
|
| 376 |
+
condition_marker = bool(
|
| 377 |
+
re.search(
|
| 378 |
+
r"\bconditions?\b",
|
| 379 |
+
normalized_ruling,
|
| 380 |
+
re.I,
|
| 381 |
+
)
|
| 382 |
+
)
|
| 383 |
+
|
| 384 |
+
facet_frame = " ".join(
|
| 385 |
+
filter(None, (ruling, question, answer_text))
|
| 386 |
+
)
|
| 387 |
if query.facet_dimension == "obligation":
|
| 388 |
+
facet_marker = bool(
|
| 389 |
+
re.search(
|
| 390 |
+
r"\b(?:obligation|obligatory|required|must)\b",
|
| 391 |
+
facet_frame,
|
| 392 |
+
re.I,
|
| 393 |
+
)
|
| 394 |
+
)
|
| 395 |
else:
|
| 396 |
+
facet_marker = (
|
| 397 |
+
self._term_overlap(
|
| 398 |
+
query.facet_terms,
|
| 399 |
+
facet_frame,
|
| 400 |
+
query.language,
|
| 401 |
+
)
|
| 402 |
+
>= 0.44
|
| 403 |
+
)
|
| 404 |
|
| 405 |
+
if not condition_marker:
|
| 406 |
+
return False
|
| 407 |
+
if query.facet_locked and not facet_marker:
|
| 408 |
return False
|
| 409 |
|
| 410 |
+
target_terms = tuple(
|
| 411 |
+
dict.fromkeys(
|
| 412 |
+
(
|
| 413 |
+
*query.subject_terms,
|
| 414 |
+
*query.anchor_terms,
|
| 415 |
+
)
|
| 416 |
+
)
|
| 417 |
+
)
|
| 418 |
+
|
| 419 |
+
structural_frame = " ".join(
|
| 420 |
+
filter(
|
| 421 |
+
None,
|
| 422 |
+
(
|
| 423 |
+
title,
|
| 424 |
+
question,
|
| 425 |
+
ruling,
|
| 426 |
+
),
|
| 427 |
+
)
|
| 428 |
+
)
|
| 429 |
+
|
| 430 |
+
target_overlap = (
|
| 431 |
+
self._term_overlap(
|
| 432 |
+
target_terms,
|
| 433 |
+
structural_frame,
|
| 434 |
+
query.language,
|
| 435 |
+
)
|
| 436 |
+
if target_terms
|
| 437 |
+
else 1.0
|
| 438 |
+
)
|
| 439 |
+
|
| 440 |
+
source_local_sibling = bool(
|
| 441 |
+
item.source.get("set_local_sibling", False)
|
| 442 |
+
or item.source.get("generic_set_local_sibling", False)
|
| 443 |
+
)
|
| 444 |
+
|
| 445 |
+
if target_terms and target_overlap < 0.34 and not source_local_sibling:
|
| 446 |
+
return False
|
| 447 |
+
|
| 448 |
+
return True
|
| 449 |
|
| 450 |
@staticmethod
|
| 451 |
def _clip(value: object, default: float = 0.0) -> float:
|
|
|
|
| 546 |
facet_conflict = self._clip(m.get("facet_conflicting_support", 0.0))
|
| 547 |
if query.facet_locked:
|
| 548 |
facet = self._clip(m.get("facet_alignment", 0.0)) * (1.0 - 0.72 * facet_conflict)
|
| 549 |
+
if explicit_condition_bridge:
|
| 550 |
+
facet = max(facet, 0.78)
|
| 551 |
|
| 552 |
# The requested operation, subject, constraints and text integrity are
|
| 553 |
# bottleneck features. Neural retrieval is deliberately only a tie-breaker.
|
|
|
|
| 737 |
all_rules = tuple(item.evidence.semantic_slots.get("condition_rule", ()))
|
| 738 |
valid_rules = tuple(item.source.get("generic_valid_condition_rules", ()))
|
| 739 |
if all_rules and not valid_rules:
|
| 740 |
+
if explicit_condition_bridge:
|
| 741 |
+
reasons.append(
|
| 742 |
+
"explicit_condition_member_overrides_rule_locality"
|
| 743 |
+
)
|
| 744 |
+
else:
|
| 745 |
+
reasons.append(
|
| 746 |
+
"condition_rule_not_locally_bound_to_query_contract"
|
| 747 |
+
)
|
| 748 |
+
if (
|
| 749 |
+
f["subject"] >= self.cfg["adjacent_subject_floor"]
|
| 750 |
+
or f["neural"] >= 0.76
|
| 751 |
+
):
|
| 752 |
+
return "adjacent", reasons
|
| 753 |
+
return "irrelevant", reasons
|
| 754 |
|
| 755 |
direct = bool(
|
| 756 |
f["direct_score"] >= direct_floor
|
|
|
|
| 848 |
return "exception", reasons
|
| 849 |
return "direct", reasons
|
| 850 |
|
| 851 |
+
explicit_condition_member_support = bool(
|
| 852 |
+
query.primary_request_type == "conditions"
|
| 853 |
+
and explicit_condition_bridge
|
| 854 |
+
and f["integrity"] >= self.cfg["minimum_integrity"]
|
| 855 |
+
and f["facet"] >= self.cfg["minimum_facet"]
|
| 856 |
+
and f["operator"] >= 0.70
|
| 857 |
+
and (
|
| 858 |
+
f["subject"] >= 0.34
|
| 859 |
+
or bool(
|
| 860 |
+
item.source.get("set_local_sibling", False)
|
| 861 |
+
or item.source.get("generic_set_local_sibling", False)
|
| 862 |
+
)
|
| 863 |
+
)
|
| 864 |
+
)
|
| 865 |
+
if explicit_condition_member_support:
|
| 866 |
+
reasons.append(
|
| 867 |
+
"explicit_condition_member_supporting_anchor"
|
| 868 |
+
)
|
| 869 |
+
return "supporting", reasons
|
| 870 |
+
|
| 871 |
supporting = bool(
|
| 872 |
(f["support_score"] >= self.cfg["support_floor"] or f["entailment_operator"] >= self.cfg["entailment_support_floor"])
|
| 873 |
and f["subject"] >= self.cfg["minimum_subject"]
|
|
|
|
| 1059 |
"generic_explicit_condition_member_bridge": bool(
|
| 1060 |
self._explicit_condition_member_bridge(query, item)
|
| 1061 |
),
|
| 1062 |
+
"generic_set_local_sibling": bool(
|
| 1063 |
+
item.source.get("set_local_sibling", False)
|
| 1064 |
+
or item.source.get("generic_set_local_sibling", False)
|
| 1065 |
+
),
|
| 1066 |
"generic_training_candidate": bool(
|
| 1067 |
shadow_disagreement
|
| 1068 |
or (role in {"adjacent", "irrelevant"} and f["neural"] >= self.cfg["hard_negative_neural_floor"])
|