Spaces:
Running
Running
S0-R1-C3 Quantitative Causal-Negation Semantic Guard
#76
by GuXiaopeng - opened
- core/quantitative_engine.py +64 -4
core/quantitative_engine.py
CHANGED
|
@@ -493,6 +493,47 @@ Keywords: {keywords}"""
|
|
| 493 |
] if strict else [],
|
| 494 |
}
|
| 495 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 496 |
@classmethod
|
| 497 |
def inspect_quantitative_study_fact_contract(cls, text, metadata, section_name=""):
|
| 498 |
"""Pure AMJ quantitative Evidence Ledger inspection; never rewrites prose."""
|
|
@@ -574,7 +615,10 @@ Keywords: {keywords}"""
|
|
| 574 |
violations.append({"type":"construct_role_mismatch","role":role,"observed":candidate,"expected":expected,"preview":sentence.strip()[:180]})
|
| 575 |
if strict:
|
| 576 |
for pattern in causal_patterns:
|
| 577 |
-
|
|
|
|
|
|
|
|
|
|
| 578 |
violations.append({"type":"causal_language_violation","preview":sentence.strip()[:180]})
|
| 579 |
break
|
| 580 |
if (any(c in sentence.casefold() for c in construct_folded) if construct_folded else False) and re.search(r"(?i)\bp\s*(?:=|<|>|≤|≥)\s*(?:0?\.\d+|\d+(?:\.\d+)?)", sentence):
|
|
@@ -706,9 +750,25 @@ Keywords: {keywords}"""
|
|
| 706 |
|
| 707 |
if strict:
|
| 708 |
for pattern, replacement in causal_replacements:
|
| 709 |
-
|
| 710 |
-
|
| 711 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 712 |
if any(c in revised.casefold() for c in construct_folded) if construct_folded else False:
|
| 713 |
revised, p_count = re.subn(r"(?i)\s*[,;]?\s*p\s*(?:=|<|>|≤|≥)\s*(?:0?\.\d+|\d+(?:\.\d+)?)", "", revised)
|
| 714 |
if p_count:
|
|
|
|
| 493 |
] if strict else [],
|
| 494 |
}
|
| 495 |
|
| 496 |
+
@staticmethod
|
| 497 |
+
def _causal_phrase_is_explicitly_disclaimed(sentence, match_start, match_end):
|
| 498 |
+
"""Return True only when a causal phrase is explicitly scoped by a limitation.
|
| 499 |
+
|
| 500 |
+
This is intentionally local to the matched phrase rather than sentence-wide so a
|
| 501 |
+
sentence can safely say that causal identification is unavailable while still
|
| 502 |
+
failing closed if a separate clause makes an affirmative causal claim.
|
| 503 |
+
"""
|
| 504 |
+
value = str(sentence or "")
|
| 505 |
+
left_floor = max(0, int(match_start) - 180)
|
| 506 |
+
left = value[left_floor:int(match_start)]
|
| 507 |
+
# Do not let a disclaimer in an earlier independent/contrast clause shield a later
|
| 508 |
+
# affirmative causal assertion. Keep the closest clause-like segment only.
|
| 509 |
+
boundaries = list(re.finditer(
|
| 510 |
+
r"(?i)(?:[;:,]|\bbut\b|\bhowever\b|\bwhereas\b|\byet\b|\bnevertheless\b)",
|
| 511 |
+
left,
|
| 512 |
+
))
|
| 513 |
+
if boundaries:
|
| 514 |
+
left = left[boundaries[-1].end():]
|
| 515 |
+
left_folded = re.sub(r"\s+", " ", left.casefold()).strip()
|
| 516 |
+
right = value[int(match_end):min(len(value), int(match_end) + 140)]
|
| 517 |
+
right_folded = re.sub(r"\s+", " ", right.casefold()).strip()
|
| 518 |
+
|
| 519 |
+
pre_disclaimer_patterns = (
|
| 520 |
+
r"\b(?:cannot|can't|could not|couldn't|does not|doesn't|do not|don't|did not|didn't)\s+(?:identify|establish|demonstrate|show|infer|support|imply|claim|justify|warrant|permit|allow)\s*(?:(?:an?|the)\s*)?$",
|
| 521 |
+
r"\b(?:is not|isn't|are not|aren't|was not|wasn't|were not|weren't)\s+(?:able\s+to\s+)?(?:identify|establish|demonstrate|show|infer|support|justify|warrant)\s*(?:(?:an?|the)\s*)?$",
|
| 522 |
+
r"\b(?:unable|insufficient)\s+to\s+(?:identify|establish|demonstrate|show|infer|support|justify|warrant)\s*(?:(?:an?|the)\s*)?$",
|
| 523 |
+
r"\b(?:fails?|failed)\s+to\s+(?:identify|establish|demonstrate|show|infer|support|justify|warrant)\s*(?:(?:an?|the)\s*)?$",
|
| 524 |
+
r"\bno\s+(?:evidence|basis|support)\s+(?:for|of)\s*(?:(?:an?|the)\s*)?$",
|
| 525 |
+
r"\bnot\s+(?:evidence|proof)\s+(?:for|of)\s*(?:(?:an?|the)\s*)?$",
|
| 526 |
+
r"\bno\s*$",
|
| 527 |
+
)
|
| 528 |
+
if any(re.search(pattern, left_folded) for pattern in pre_disclaimer_patterns):
|
| 529 |
+
return True
|
| 530 |
+
|
| 531 |
+
post_disclaimer_patterns = (
|
| 532 |
+
r"^(?:\s*(?:is|are|was|were|has|have|had))?\s*(?:not|never)\s+(?:identified|established|demonstrated|shown|inferred|supported|claimed|justified|warranted)\b",
|
| 533 |
+
r"^\s*(?:cannot|can't|could not|couldn't|should not|shouldn't|must not|does not|doesn't|do not|don't)\s+(?:be\s+)?(?:identified|established|demonstrated|shown|inferred|supported|claimed|interpreted|justified|warranted)\b",
|
| 534 |
+
)
|
| 535 |
+
return any(re.search(pattern, right_folded) for pattern in post_disclaimer_patterns)
|
| 536 |
+
|
| 537 |
@classmethod
|
| 538 |
def inspect_quantitative_study_fact_contract(cls, text, metadata, section_name=""):
|
| 539 |
"""Pure AMJ quantitative Evidence Ledger inspection; never rewrites prose."""
|
|
|
|
| 615 |
violations.append({"type":"construct_role_mismatch","role":role,"observed":candidate,"expected":expected,"preview":sentence.strip()[:180]})
|
| 616 |
if strict:
|
| 617 |
for pattern in causal_patterns:
|
| 618 |
+
match = re.search(pattern, sentence)
|
| 619 |
+
if match and not cls._causal_phrase_is_explicitly_disclaimed(
|
| 620 |
+
sentence, match.start(), match.end()
|
| 621 |
+
):
|
| 622 |
violations.append({"type":"causal_language_violation","preview":sentence.strip()[:180]})
|
| 623 |
break
|
| 624 |
if (any(c in sentence.casefold() for c in construct_folded) if construct_folded else False) and re.search(r"(?i)\bp\s*(?:=|<|>|≤|≥)\s*(?:0?\.\d+|\d+(?:\.\d+)?)", sentence):
|
|
|
|
| 750 |
|
| 751 |
if strict:
|
| 752 |
for pattern, replacement in causal_replacements:
|
| 753 |
+
regex = re.compile(pattern)
|
| 754 |
+
repaired_count = 0
|
| 755 |
+
|
| 756 |
+
def causal_repl(match):
|
| 757 |
+
nonlocal repaired_count
|
| 758 |
+
if cls._causal_phrase_is_explicitly_disclaimed(
|
| 759 |
+
revised, match.start(), match.end()
|
| 760 |
+
):
|
| 761 |
+
return match.group(0)
|
| 762 |
+
repaired_count += 1
|
| 763 |
+
return replacement
|
| 764 |
+
|
| 765 |
+
revised = regex.sub(causal_repl, revised)
|
| 766 |
+
if repaired_count:
|
| 767 |
+
diagnostics.append({
|
| 768 |
+
"type":"causal_language_repaired",
|
| 769 |
+
"replacement":replacement,
|
| 770 |
+
"count":repaired_count,
|
| 771 |
+
})
|
| 772 |
if any(c in revised.casefold() for c in construct_folded) if construct_folded else False:
|
| 773 |
revised, p_count = re.subn(r"(?i)\s*[,;]?\s*p\s*(?:=|<|>|≤|≥)\s*(?:0?\.\d+|\d+(?:\.\d+)?)", "", revised)
|
| 774 |
if p_count:
|