Spaces:
Running
Running
S0-R1-C5.4 Contract Consistency Failure Surface Stabilization
#82
by GuXiaopeng - opened
- core/generation_runner.py +18 -0
- core/pipeline_agent.py +18 -1
- core/quantitative_engine.py +89 -76
core/generation_runner.py
CHANGED
|
@@ -944,6 +944,13 @@ class GenerationRunner:
|
|
| 944 |
continue
|
| 945 |
kind = str(item.get('type') or 'validation_issue')
|
| 946 |
detail = str(item.get('repair_instruction') or item.get('reason') or item.get('preview') or '')
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 947 |
actor = str(item.get('actor') or '')
|
| 948 |
expected = item.get('expected')
|
| 949 |
observed = item.get('observed')
|
|
@@ -3343,6 +3350,16 @@ class GenerationRunner:
|
|
| 3343 |
"\nASQ EVIDENCE-CHANNEL RULE: A sentence whose factual subject is this study's setting, participants, interviews, data sources, cases, actors, observations, events, quotations, analytic steps, or empirical findings must not carry an external [CITE-XX] tag. "
|
| 3344 |
"Place any prior-literature or methodological-rationale claim in a separate sentence and cite only that external claim."
|
| 3345 |
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 3346 |
memory_context = agent._build_memory_context(sec_title, 2500)
|
| 3347 |
memory_block = f"\n<GLOBAL_MEMORY>\n{memory_context}\n</GLOBAL_MEMORY>\n" if memory_context else ""
|
| 3348 |
snippets = rag_engine.get_relevant_context(display_name) if rag_engine is not None else ""
|
|
@@ -3458,6 +3475,7 @@ Planning length: approximately {int(adjusted_target * min_ratio)}-{int(adjusted_
|
|
| 3458 |
{citation_rule}
|
| 3459 |
{table_rule}
|
| 3460 |
AUTHOR-FACING PURITY: Write only reader-facing scholarly prose. Do not mention internal orchestration, validation, state-management, evidence-management, or execution terminology. Describe the study, method, evidence, and limitations directly.
|
|
|
|
| 3461 |
{self._hypothesis_contract_directive(hypothesis_ledger)}
|
| 3462 |
{str(sec.get('required_content') or '')}
|
| 3463 |
{qualitative_rule}
|
|
|
|
| 944 |
continue
|
| 945 |
kind = str(item.get('type') or 'validation_issue')
|
| 946 |
detail = str(item.get('repair_instruction') or item.get('reason') or item.get('preview') or '')
|
| 947 |
+
if kind == 'causal_language_violation' and not detail:
|
| 948 |
+
detail = (
|
| 949 |
+
"Rewrite only the causal-strength wording. Preserve the study fact and analytical meaning, "
|
| 950 |
+
"but state association/covariation rather than causal identification. Explicit limitation "
|
| 951 |
+
"phrases such as 'restricted to associations, not causal effects' and 'cannot establish "
|
| 952 |
+
"causality' are valid and should not be weakened further."
|
| 953 |
+
)
|
| 954 |
actor = str(item.get('actor') or '')
|
| 955 |
expected = item.get('expected')
|
| 956 |
observed = item.get('observed')
|
|
|
|
| 3350 |
"\nASQ EVIDENCE-CHANNEL RULE: A sentence whose factual subject is this study's setting, participants, interviews, data sources, cases, actors, observations, events, quotations, analytic steps, or empirical findings must not carry an external [CITE-XX] tag. "
|
| 3351 |
"Place any prior-literature or methodological-rationale claim in a separate sentence and cite only that external claim."
|
| 3352 |
)
|
| 3353 |
+
if section_id not in {"results", "findings", "appendix"}:
|
| 3354 |
+
citation_rule += (
|
| 3355 |
+
"\nCURRENT-STUDY PROVENANCE RULE: Do not attach external literature citations to this study's own research question, H1-Hn statement, sample/design fact, analytic choice, result, limitation specific to the available study evidence, or author positioning. "
|
| 3356 |
+
"If prior literature provides a rationale, state that external rationale in a separate sentence and cite only that sentence."
|
| 3357 |
+
)
|
| 3358 |
+
if bool(state.get("evidence_risk_detected")) and section_id not in {"results", "findings", "appendix"}:
|
| 3359 |
+
citation_rule += (
|
| 3360 |
+
"\nEVIDENCE-LIMITED CLAIM-DENSITY CONTRACT: Direct high-relevance literature coverage is limited. Treat assigned claim-evidence items as the ceiling for source-dependent empirical, definitional, prevalence, and field-gap claims in this unit. "
|
| 3361 |
+
"Do not invent additional literature claims merely to make the section sound comprehensive. Prefer concise study positioning, cautious conceptual reasoning, and clearly qualified interpretations when no directly validated source is assigned."
|
| 3362 |
+
)
|
| 3363 |
memory_context = agent._build_memory_context(sec_title, 2500)
|
| 3364 |
memory_block = f"\n<GLOBAL_MEMORY>\n{memory_context}\n</GLOBAL_MEMORY>\n" if memory_context else ""
|
| 3365 |
snippets = rag_engine.get_relevant_context(display_name) if rag_engine is not None else ""
|
|
|
|
| 3475 |
{citation_rule}
|
| 3476 |
{table_rule}
|
| 3477 |
AUTHOR-FACING PURITY: Write only reader-facing scholarly prose. Do not mention internal orchestration, validation, state-management, evidence-management, or execution terminology. Describe the study, method, evidence, and limitations directly.
|
| 3478 |
+
{("CAUSAL-STRENGTH CONTRACT: This cross-sectional observational design supports association/covariation language only. Do not state that one construct causes, leads to, drives, determines, produces, or causally mediates another. Explicit limitation language is valid and preferred when relevant, including 'restricted to associations, not causal effects', 'cannot establish causality', and 'the indirect association is not causal mediation identification'." if mode == "amj" else "")}
|
| 3479 |
{self._hypothesis_contract_directive(hypothesis_ledger)}
|
| 3480 |
{str(sec.get('required_content') or '')}
|
| 3481 |
{qualitative_rule}
|
core/pipeline_agent.py
CHANGED
|
@@ -2774,6 +2774,13 @@ INPUT:
|
|
| 2774 |
or section_loads.get(section, 0) >= capacities.get(section, target)
|
| 2775 |
):
|
| 2776 |
continue
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2777 |
evidence = str((self.citation_map.get(tag) or {}).get('evidence') or '')
|
| 2778 |
if len(self._citation_terms(claim) & self._citation_terms(evidence)) < 2:
|
| 2779 |
continue
|
|
@@ -3256,10 +3263,20 @@ INPUT:
|
|
| 3256 |
section = str(section_name or '').lower()
|
| 3257 |
if not text:
|
| 3258 |
return 'STRUCTURAL'
|
| 3259 |
-
if re.match(r'^(?:h\d+[a-z]?\
|
| 3260 |
return 'HYPOTHESIS_STATEMENT'
|
| 3261 |
if re.search(r'\b(?:we hypothesi[sz]e|we propose|we predict|we expect)\b', lower):
|
| 3262 |
return 'HYPOTHESIS_STATEMENT'
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 3263 |
if re.search(
|
| 3264 |
r'\b(?:the present study|this study|our study|the current study|the present research|this research)\b',
|
| 3265 |
lower,
|
|
|
|
| 2774 |
or section_loads.get(section, 0) >= capacities.get(section, target)
|
| 2775 |
):
|
| 2776 |
continue
|
| 2777 |
+
provenance = self._citation_claim_provenance(claim, section)
|
| 2778 |
+
if provenance != 'EXTERNAL_LITERATURE_CLAIM':
|
| 2779 |
+
logging.info(
|
| 2780 |
+
'Citation planner rejected non-literature claim provenance: section=%s tag=%s provenance=%s',
|
| 2781 |
+
section, tag, provenance,
|
| 2782 |
+
)
|
| 2783 |
+
continue
|
| 2784 |
evidence = str((self.citation_map.get(tag) or {}).get('evidence') or '')
|
| 2785 |
if len(self._citation_terms(claim) & self._citation_terms(evidence)) < 2:
|
| 2786 |
continue
|
|
|
|
| 3263 |
section = str(section_name or '').lower()
|
| 3264 |
if not text:
|
| 3265 |
return 'STRUCTURAL'
|
| 3266 |
+
if re.match(r'^(?:h\d+[a-z]?\b|hypothesis\s+\d+\b)', lower):
|
| 3267 |
return 'HYPOTHESIS_STATEMENT'
|
| 3268 |
if re.search(r'\b(?:we hypothesi[sz]e|we propose|we predict|we expect)\b', lower):
|
| 3269 |
return 'HYPOTHESIS_STATEMENT'
|
| 3270 |
+
if re.search(
|
| 3271 |
+
r'\b(?:the research question|our research question|research question guiding the study|the focal research question)\b',
|
| 3272 |
+
lower,
|
| 3273 |
+
):
|
| 3274 |
+
return 'STUDY_POSITIONING'
|
| 3275 |
+
if re.search(
|
| 3276 |
+
r'\b(?:h\d+[a-z]?\s+(?:states?|proposes?|predicts?|addresses?|posits?|hypothesi[sz]es?|tests?))\b',
|
| 3277 |
+
lower,
|
| 3278 |
+
):
|
| 3279 |
+
return 'HYPOTHESIS_STATEMENT'
|
| 3280 |
if re.search(
|
| 3281 |
r'\b(?:the present study|this study|our study|the current study|the present research|this research)\b',
|
| 3282 |
lower,
|
core/quantitative_engine.py
CHANGED
|
@@ -600,51 +600,77 @@ Keywords: {keywords}"""
|
|
| 600 |
] if strict else [],
|
| 601 |
}
|
| 602 |
|
| 603 |
-
@
|
| 604 |
-
def
|
| 605 |
-
"""
|
| 606 |
|
| 607 |
-
|
| 608 |
-
|
| 609 |
-
|
|
|
|
| 610 |
"""
|
| 611 |
-
value = str(
|
| 612 |
-
|
| 613 |
-
|
| 614 |
-
|
| 615 |
-
|
| 616 |
-
|
| 617 |
-
|
| 618 |
-
|
| 619 |
-
|
| 620 |
-
|
| 621 |
-
|
| 622 |
-
|
| 623 |
-
|
| 624 |
-
|
| 625 |
-
|
| 626 |
-
|
| 627 |
-
|
| 628 |
-
|
| 629 |
-
|
| 630 |
-
|
| 631 |
-
|
| 632 |
-
|
| 633 |
-
r"\b(?:should not|shouldn't|must not|cannot|can't|could not|couldn't)\s+be\s+interpreted\s+as\s*(?:(?:an?|the)\s*)?$",
|
| 634 |
-
r"\b(?:fails?|failed)\s+to\s+(?:identify|establish|demonstrate|show|infer|support|justify|warrant)\s*(?:(?:an?|the)\s*)?$",
|
| 635 |
-
r"\bno\s+(?:evidence|basis|support)\s+(?:for|of)\s*(?:(?:an?|the)\s*)?$",
|
| 636 |
-
r"\bnot\s+(?:evidence|proof)\s+(?:for|of)\s*(?:(?:an?|the)\s*)?$",
|
| 637 |
-
r"\bno\s*$",
|
| 638 |
)
|
| 639 |
-
if any(re.search(pattern,
|
| 640 |
-
return
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 641 |
|
| 642 |
-
|
| 643 |
-
|
| 644 |
-
|
| 645 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 646 |
)
|
| 647 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 648 |
|
| 649 |
@classmethod
|
| 650 |
def inspect_quantitative_study_fact_contract(cls, text, metadata, section_name=""):
|
|
@@ -668,15 +694,6 @@ Keywords: {keywords}"""
|
|
| 668 |
"mediator": r"(?:mediator|mediating\s+variable|mediating\s+construct)",
|
| 669 |
"outcome": r"(?:outcome|dependent\s+variable|outcome\s+construct)",
|
| 670 |
}
|
| 671 |
-
causal_patterns = (
|
| 672 |
-
r"(?i)\bhad\s+a\s+causal\s+effect\s+on\b",
|
| 673 |
-
r"(?i)\bexerted\s+a\s+causal\s+effect\s+on\b",
|
| 674 |
-
r"(?i)\bcausal\s+effect\s+on\b",
|
| 675 |
-
r"(?i)\b(?:an|a|the)\s+causal\s+effect\b",
|
| 676 |
-
r"(?i)\bcausal\s+effects\b",
|
| 677 |
-
r"(?i)\bcausal\s+effect\b",
|
| 678 |
-
r"(?i)\bcausal\s+mediation\b",
|
| 679 |
-
)
|
| 680 |
violations = []
|
| 681 |
|
| 682 |
def add_metric_violations(sentence, pattern, expected, *, dtype="float", label="metric"):
|
|
@@ -726,13 +743,18 @@ Keywords: {keywords}"""
|
|
| 726 |
if pattern.search(sentence):
|
| 727 |
violations.append({"type":"construct_role_mismatch","role":role,"observed":candidate,"expected":expected,"preview":sentence.strip()[:180]})
|
| 728 |
if strict:
|
| 729 |
-
|
| 730 |
-
|
| 731 |
-
|
| 732 |
-
|
| 733 |
-
|
| 734 |
-
|
| 735 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 736 |
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):
|
| 737 |
violations.append({"type":"unfrozen_p_value","preview":sentence.strip()[:180]})
|
| 738 |
if ownership == "study":
|
|
@@ -861,26 +883,17 @@ Keywords: {keywords}"""
|
|
| 861 |
diagnostics.append({"type":"construct_role_repaired","role":role,"observed":candidate,"expected":expected,"count":count})
|
| 862 |
|
| 863 |
if strict:
|
| 864 |
-
|
| 865 |
-
|
| 866 |
-
|
| 867 |
-
|
| 868 |
-
|
| 869 |
-
|
| 870 |
-
|
| 871 |
-
|
| 872 |
-
|
| 873 |
-
|
| 874 |
-
|
| 875 |
-
return replacement
|
| 876 |
-
|
| 877 |
-
revised = regex.sub(causal_repl, revised)
|
| 878 |
-
if repaired_count:
|
| 879 |
-
diagnostics.append({
|
| 880 |
-
"type":"causal_language_repaired",
|
| 881 |
-
"replacement":replacement,
|
| 882 |
-
"count":repaired_count,
|
| 883 |
-
})
|
| 884 |
if any(c in revised.casefold() for c in construct_folded) if construct_folded else False:
|
| 885 |
revised, p_count = re.subn(r"(?i)\s*[,;]?\s*p\s*(?:=|<|>|≤|≥)\s*(?:0?\.\d+|\d+(?:\.\d+)?)", "", revised)
|
| 886 |
if p_count:
|
|
|
|
| 600 |
] if strict else [],
|
| 601 |
}
|
| 602 |
|
| 603 |
+
@classmethod
|
| 604 |
+
def _classify_quantitative_causal_clause(cls, clause):
|
| 605 |
+
"""Classify study/result prose as causal assertion, explicit limitation, or non-causal.
|
| 606 |
|
| 607 |
+
The contract is semantic at the clause level rather than a blacklist of the
|
| 608 |
+
token ``causal``. Explicit statements that restrict interpretation to
|
| 609 |
+
association/covariation are permitted, while affirmative causal verbs and
|
| 610 |
+
causal-identification claims remain fail-closed.
|
| 611 |
"""
|
| 612 |
+
value = re.sub(r"\s+", " ", str(clause or "")).strip().casefold()
|
| 613 |
+
if not value:
|
| 614 |
+
return "NON_CAUSAL"
|
| 615 |
+
|
| 616 |
+
causal_nouns = r"(?:causal(?:ity|\s+(?:effect|effects|mediation|mechanism|pathway|relationship|relationships|identification))|cause(?:s|d)?|causation)"
|
| 617 |
+
limitation_patterns = (
|
| 618 |
+
rf"\b(?:cannot|can't|could not|couldn't|does not|doesn't|do not|don't|did not|didn't|fails? to|failed to|is not able to|are not able to|was not able to|were not able to|unable to|insufficient to)\s+(?:identify|establish|demonstrate|show|infer|support|imply|claim|justify|warrant|permit|allow|prove)\b[^.;]{{0,80}}\b{causal_nouns}\b",
|
| 619 |
+
rf"\b(?:is|are|was|were)\s+insufficient\s+to\s+(?:identify|establish|demonstrate|show|infer|support|prove)\b[^.;]{{0,80}}\b{causal_nouns}\b",
|
| 620 |
+
rf"\b(?:is|are|was|were)\s+not\s+(?:designed|intended)\s+to\s+(?:identify|establish|demonstrate|show|infer|support|prove)\b[^.;]{{0,80}}\b{causal_nouns}\b",
|
| 621 |
+
rf"\bwithout\s+(?:identifying|establishing|demonstrating|showing|inferring|supporting|proving)\b[^.;]{{0,80}}\b{causal_nouns}\b",
|
| 622 |
+
rf"\b(?:no|without)\s+(?:evidence|basis|support|identification|proof)\b[^.;]{{0,60}}\b{causal_nouns}\b",
|
| 623 |
+
rf"\b(?:not|never)\s+(?:evidence|proof)\s+(?:of|for)\b[^.;]{{0,50}}\b{causal_nouns}\b",
|
| 624 |
+
rf"\b(?:should not|shouldn't|must not|cannot|can't|could not|couldn't)\s+be\s+(?:interpreted|read|treated|understood)\s+as\b[^.;]{{0,60}}\b{causal_nouns}\b",
|
| 625 |
+
rf"\b(?:is|are|was|were)\s+(?:restricted|limited|confined)\s+to\b[^.;]{{0,120}}\b(?:not|rather than|instead of)\s+(?:to\s+)?\b{causal_nouns}\b",
|
| 626 |
+
rf"\b(?:restricted|limited|confined)\s+to\b[^.;]{{0,120}}\b(?:not|rather than|instead of)\s+(?:to\s+)?\b{causal_nouns}\b",
|
| 627 |
+
rf"\b(?:association|associations|covariation|covariance|correlation|correlations|indirect association|descriptive pattern|descriptive patterns)\b[^.;]{{0,80}}\b(?:not(?:\s+as)?|rather than|instead of)\s+(?:an?\s+|the\s+)?{causal_nouns}\b",
|
| 628 |
+
rf"\b{causal_nouns}\b\s+(?:is|are|was|were)\s+(?:not|never)\s+(?:identified|established|demonstrated|shown|inferred|supported|claimed|justified|warranted|proven)\b",
|
| 629 |
+
rf"\b{causal_nouns}\b\s+(?:cannot|can't|could not|couldn't)\s+be\s+(?:identified|established|demonstrated|shown|inferred|supported|claimed|justified|warranted|proven)\b",
|
| 630 |
+
rf"\b(?:inability|lack\s+of\s+(?:an?\s+)?ability)\s+to\s+(?:identify|establish|demonstrate|show|infer|support|justify|warrant|prove)\b[^.;]{{0,80}}\b{causal_nouns}\b",
|
| 631 |
+
rf"\b(?:not|no)\s+(?:an?\s+|the\s+)?{causal_nouns}\b",
|
| 632 |
+
rf"\b(?:cannot|can't|could not|couldn't)\s+be\s+used\s+to\s+(?:infer|establish|demonstrate|show|prove)\s+(?:an?\s+|the\s+)?{causal_nouns}\b",
|
| 633 |
+
rf"\bwithout\s+(?:proving|establishing|demonstrating|showing|identifying)\s+(?:an?\s+|the\s+)?{causal_nouns}\b",
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 634 |
)
|
| 635 |
+
if any(re.search(pattern, value, flags=re.I) for pattern in limitation_patterns):
|
| 636 |
+
return "EXPLICIT_CAUSAL_LIMITATION"
|
| 637 |
+
|
| 638 |
+
affirmative_patterns = (
|
| 639 |
+
r"\b(?:causes?|caused|causing)\b",
|
| 640 |
+
r"\b(?:leads?|led)\s+to\b",
|
| 641 |
+
r"\b(?:drives?|drove|driven)\b",
|
| 642 |
+
r"\b(?:determines?|determined)\b",
|
| 643 |
+
r"\b(?:produces?|produced)\b",
|
| 644 |
+
r"\b(?:results?|resulted)\s+in\b",
|
| 645 |
+
r"\b(?:transmits?|transmitted)\s+(?:an?\s+|the\s+)?causal\s+effect\b",
|
| 646 |
+
r"\b(?:has|have|had|exerts?|exerted)\s+(?:an?\s+|the\s+)?causal\s+effect\b",
|
| 647 |
+
r"\bcausal\s+(?:effect|effects|mediation|mechanism|pathway|relationship|relationships|identification)\b",
|
| 648 |
+
r"\b(?:demonstrates?|demonstrated|shows?|showed|establishes?|established|identifies?|identified|proves?|proved)\s+(?:a\s+|the\s+)?causal(?:ity|\s+(?:effect|effects|mechanism|pathway|relationship|relationships|mediation))\b",
|
| 649 |
+
)
|
| 650 |
+
if any(re.search(pattern, value, flags=re.I) for pattern in affirmative_patterns):
|
| 651 |
+
return "AFFIRMATIVE_CAUSAL"
|
| 652 |
+
return "NON_CAUSAL"
|
| 653 |
|
| 654 |
+
@classmethod
|
| 655 |
+
def classify_quantitative_causal_sentence(cls, sentence):
|
| 656 |
+
"""Three-state causal contract shared by inspection, repair, and tests."""
|
| 657 |
+
value = str(sentence or "")
|
| 658 |
+
# Contrast clauses must not let an early disclaimer shield a later assertion.
|
| 659 |
+
clauses = re.split(
|
| 660 |
+
r"(?i)(?:;|\bbut\b|\bhowever\b|\bwhereas\b|\byet\b|\bnevertheless\b|\balthough\b|\bthough\b)",
|
| 661 |
+
value,
|
| 662 |
)
|
| 663 |
+
states = [cls._classify_quantitative_causal_clause(part) for part in clauses if str(part).strip()]
|
| 664 |
+
if "AFFIRMATIVE_CAUSAL" in states:
|
| 665 |
+
return "AFFIRMATIVE_CAUSAL"
|
| 666 |
+
if "EXPLICIT_CAUSAL_LIMITATION" in states:
|
| 667 |
+
return "EXPLICIT_CAUSAL_LIMITATION"
|
| 668 |
+
return "NON_CAUSAL"
|
| 669 |
+
|
| 670 |
+
@classmethod
|
| 671 |
+
def _causal_phrase_is_explicitly_disclaimed(cls, sentence, match_start=None, match_end=None):
|
| 672 |
+
"""Compatibility wrapper for older call sites; uses the shared tri-state contract."""
|
| 673 |
+
return cls.classify_quantitative_causal_sentence(sentence) == "EXPLICIT_CAUSAL_LIMITATION"
|
| 674 |
|
| 675 |
@classmethod
|
| 676 |
def inspect_quantitative_study_fact_contract(cls, text, metadata, section_name=""):
|
|
|
|
| 694 |
"mediator": r"(?:mediator|mediating\s+variable|mediating\s+construct)",
|
| 695 |
"outcome": r"(?:outcome|dependent\s+variable|outcome\s+construct)",
|
| 696 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 697 |
violations = []
|
| 698 |
|
| 699 |
def add_metric_violations(sentence, pattern, expected, *, dtype="float", label="metric"):
|
|
|
|
| 743 |
if pattern.search(sentence):
|
| 744 |
violations.append({"type":"construct_role_mismatch","role":role,"observed":candidate,"expected":expected,"preview":sentence.strip()[:180]})
|
| 745 |
if strict:
|
| 746 |
+
causal_state = cls.classify_quantitative_causal_sentence(sentence)
|
| 747 |
+
if causal_state == "AFFIRMATIVE_CAUSAL":
|
| 748 |
+
violations.append({
|
| 749 |
+
"type":"causal_language_violation",
|
| 750 |
+
"preview":sentence.strip()[:180],
|
| 751 |
+
"reason":"The sentence makes an affirmative causal claim that exceeds the cross-sectional observational evidence contract.",
|
| 752 |
+
"repair_instruction":(
|
| 753 |
+
"Preserve the study meaning but rewrite as association/covariation only. "
|
| 754 |
+
"Explicit causal-limit language is allowed, including forms such as "
|
| 755 |
+
"'restricted to associations, not causal effects' or 'cannot establish causality'."
|
| 756 |
+
),
|
| 757 |
+
})
|
| 758 |
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):
|
| 759 |
violations.append({"type":"unfrozen_p_value","preview":sentence.strip()[:180]})
|
| 760 |
if ownership == "study":
|
|
|
|
| 883 |
diagnostics.append({"type":"construct_role_repaired","role":role,"observed":candidate,"expected":expected,"count":count})
|
| 884 |
|
| 885 |
if strict:
|
| 886 |
+
causal_state = cls.classify_quantitative_causal_sentence(revised)
|
| 887 |
+
if causal_state == "AFFIRMATIVE_CAUSAL":
|
| 888 |
+
for pattern, replacement in causal_replacements:
|
| 889 |
+
regex = re.compile(pattern)
|
| 890 |
+
revised, repaired_count = regex.subn(replacement, revised)
|
| 891 |
+
if repaired_count:
|
| 892 |
+
diagnostics.append({
|
| 893 |
+
"type":"causal_language_repaired",
|
| 894 |
+
"replacement":replacement,
|
| 895 |
+
"count":repaired_count,
|
| 896 |
+
})
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 897 |
if any(c in revised.casefold() for c in construct_folded) if construct_folded else False:
|
| 898 |
revised, p_count = re.subn(r"(?i)\s*[,;]?\s*p\s*(?:=|<|>|≤|≥)\s*(?:0?\.\d+|\d+(?:\.\d+)?)", "", revised)
|
| 899 |
if p_count:
|