Spaces:
Running
Running
S1-R2 final abstract frozen finding coverage
#123
by GuXiaopeng - opened
- core/generation_runner.py +281 -0
core/generation_runner.py
CHANGED
|
@@ -1022,6 +1022,267 @@ class GenerationRunner:
|
|
| 1022 |
rows.extend(scope_inspector(text, data_metadata, section_name=section_name) or [])
|
| 1023 |
return list(rows)
|
| 1024 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1025 |
@staticmethod
|
| 1026 |
def _section_retry_feedback(violations, *, limit=8) -> str:
|
| 1027 |
rows = []
|
|
@@ -1090,6 +1351,12 @@ class GenerationRunner:
|
|
| 1090 |
"unsupported_moderation_claim", "unsupported_mediation_claim", "unsupported_temporal_scope",
|
| 1091 |
"unsupported_design_tradition", "unsupported_unit_of_analysis", "unsupported_data_source_claim",
|
| 1092 |
"unsupported_process_claim",
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1093 |
}
|
| 1094 |
if kind in always_fatal:
|
| 1095 |
return "FATAL"
|
|
@@ -1408,6 +1675,10 @@ class GenerationRunner:
|
|
| 1408 |
rows.extend(self._hypothesis_contract_violations(
|
| 1409 |
repaired, hypothesis_ledger, section_name=label
|
| 1410 |
))
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1411 |
current, remaining = repaired, rows
|
| 1412 |
if not remaining:
|
| 1413 |
return current, []
|
|
@@ -5354,6 +5625,9 @@ The previous attempt for this same manuscript unit was rejected by validation. R
|
|
| 5354 |
final_front_fact_rows.extend(self._hypothesis_contract_violations(
|
| 5355 |
final_abstract, hypothesis_ledger, section_name="Final Abstract"
|
| 5356 |
))
|
|
|
|
|
|
|
|
|
|
| 5357 |
if final_front_fact_rows:
|
| 5358 |
feedback = self._section_retry_feedback(final_front_fact_rows)
|
| 5359 |
logging.warning(
|
|
@@ -5386,6 +5660,9 @@ The previous attempt for this same manuscript unit was rejected by validation. R
|
|
| 5386 |
remaining_final_fact_rows.extend(self._hypothesis_contract_violations(
|
| 5387 |
final_abstract, hypothesis_ledger, section_name="Final Abstract"
|
| 5388 |
))
|
|
|
|
|
|
|
|
|
|
| 5389 |
if remaining_final_fact_rows:
|
| 5390 |
final_abstract, residual_final_rows = self._adaptive_repair_front_matter(
|
| 5391 |
agent=agent, quant_engine=quant_engine, abstract_text=final_abstract,
|
|
@@ -5394,6 +5671,10 @@ The previous attempt for this same manuscript unit was rejected by validation. R
|
|
| 5394 |
quality_notes=quality_notes, label="Final Abstract",
|
| 5395 |
)
|
| 5396 |
if residual_final_rows:
|
|
|
|
|
|
|
|
|
|
|
|
|
| 5397 |
logging.warning("Adaptive Final Abstract accepted with residual diagnostics | count=%s", len(residual_final_rows))
|
| 5398 |
quality_diagnostics.append({
|
| 5399 |
"scope":"final_abstract_study_fact_integrity", "name":"Final Abstract",
|
|
|
|
| 1022 |
rows.extend(scope_inspector(text, data_metadata, section_name=section_name) or [])
|
| 1023 |
return list(rows)
|
| 1024 |
|
| 1025 |
+
@staticmethod
|
| 1026 |
+
def _final_abstract_normalized_text(value: str) -> str:
|
| 1027 |
+
"""Normalize reader-facing English prose for deterministic finding coverage checks."""
|
| 1028 |
+
return re.sub(r"[^a-z0-9]+", " ", str(value or "").casefold()).strip()
|
| 1029 |
+
|
| 1030 |
+
@classmethod
|
| 1031 |
+
def _final_abstract_identity_present(cls, sentence: str, label: str) -> bool:
|
| 1032 |
+
needle = cls._final_abstract_normalized_text(label)
|
| 1033 |
+
haystack = cls._final_abstract_normalized_text(sentence)
|
| 1034 |
+
return bool(needle and haystack and needle in haystack)
|
| 1035 |
+
|
| 1036 |
+
@staticmethod
|
| 1037 |
+
def _final_abstract_result_sentences(text: str) -> list[str]:
|
| 1038 |
+
"""Return sentences that actually assert a frozen empirical finding.
|
| 1039 |
+
|
| 1040 |
+
Deliberately exclude vague contribution language such as "our findings
|
| 1041 |
+
contribute". A Final Abstract must report what the study found, not
|
| 1042 |
+
merely say that findings exist.
|
| 1043 |
+
"""
|
| 1044 |
+
sentences = [
|
| 1045 |
+
part.strip()
|
| 1046 |
+
for part in re.split(r"(?<=[.!?])\s+|\n+", str(text or ""))
|
| 1047 |
+
if str(part or "").strip()
|
| 1048 |
+
]
|
| 1049 |
+
finding_pattern = re.compile(
|
| 1050 |
+
r"(?i)(?:"
|
| 1051 |
+
r"\b(?:we|the\s+study|the\s+analysis|the\s+results?|our\s+analysis)\s+"
|
| 1052 |
+
r"(?:find|finds|found|show|shows|showed|indicate|indicates|indicated|reveal|reveals|revealed|identify|identifies|identified|support|supports|supported)\b|"
|
| 1053 |
+
r"\bfindings?\s+(?:show|shows|showed|indicate|indicates|indicated|reveal|reveals|revealed|identify|identifies|identified|suggest|suggests|suggested)\b|"
|
| 1054 |
+
r"\b(?:was|were|is|are)\s+(?:positively|negatively|significantly|non[- ]?significantly|not\s+significantly)\s+(?:associated|related)\b|"
|
| 1055 |
+
r"\b(?:association|relationship|interaction|indirect\s+(?:association|effect)|theme|themes|pattern|patterns|mechanism|mechanisms)\b[^.!?]{0,140}\b(?:was|were|is|are|emerged|identified|supported|unsupported|significant|non[- ]?significant|nonzero)\b|"
|
| 1056 |
+
r"\bH\d+\s+(?:was|were|is|are)\s+(?:supported|unsupported|not\s+supported|rejected)\b"
|
| 1057 |
+
r")"
|
| 1058 |
+
)
|
| 1059 |
+
result_sentences = []
|
| 1060 |
+
prospective = re.compile(
|
| 1061 |
+
r"(?i)\b(?:we\s+(?:propose|hypothesize|hypothesise|predict|expect|test)|"
|
| 1062 |
+
r"hypothes(?:is|es|ized|ised)|this\s+study\s+(?:examines|investigates|tests)|"
|
| 1063 |
+
r"we\s+further\s+hypothesize)\b"
|
| 1064 |
+
)
|
| 1065 |
+
retrospective = re.compile(
|
| 1066 |
+
r"(?i)\b(?:results?|findings?\s+(?:show|indicate|reveal|identify|suggest)|"
|
| 1067 |
+
r"analysis\s+(?:show|indicate|reveal|identify)|found|revealed|indicated|"
|
| 1068 |
+
r"supported|unsupported|non[- ]?significant|not\s+significant|nonzero|received\s+no\s+support)\b"
|
| 1069 |
+
)
|
| 1070 |
+
for sentence in sentences:
|
| 1071 |
+
if not finding_pattern.search(sentence):
|
| 1072 |
+
# Explicit frozen-status language can be result-bearing even
|
| 1073 |
+
# without a finite 'was/were' construction.
|
| 1074 |
+
if not re.search(
|
| 1075 |
+
r"(?i)\b(?:nonzero\s+indirect\s+(?:association|effect)|"
|
| 1076 |
+
r"unsupported\s+(?:interaction|moderation|association)|"
|
| 1077 |
+
r"supported\s+(?:interaction|moderation|association))\b",
|
| 1078 |
+
sentence,
|
| 1079 |
+
):
|
| 1080 |
+
continue
|
| 1081 |
+
if prospective.search(sentence) and not retrospective.search(sentence):
|
| 1082 |
+
continue
|
| 1083 |
+
result_sentences.append(sentence)
|
| 1084 |
+
return result_sentences
|
| 1085 |
+
|
| 1086 |
+
@staticmethod
|
| 1087 |
+
def _final_abstract_ci_status(row: dict) -> str:
|
| 1088 |
+
ci = list((row or {}).get("ci") or [])
|
| 1089 |
+
if len(ci) != 2:
|
| 1090 |
+
return ""
|
| 1091 |
+
try:
|
| 1092 |
+
low, high = float(ci[0]), float(ci[1])
|
| 1093 |
+
except (TypeError, ValueError):
|
| 1094 |
+
return ""
|
| 1095 |
+
return "supported" if (low > 0.0 or high < 0.0) else "unsupported"
|
| 1096 |
+
|
| 1097 |
+
@staticmethod
|
| 1098 |
+
def _final_abstract_support_language(sentence: str, status: str) -> bool:
|
| 1099 |
+
value = str(sentence or "")
|
| 1100 |
+
if status == "unsupported":
|
| 1101 |
+
return bool(re.search(
|
| 1102 |
+
r"(?i)\b(?:not\s+supported|unsupported|no\s+support|did\s+not\s+support|"
|
| 1103 |
+
r"non[- ]?significant|not\s+significant|no\s+(?:meaningful\s+)?(?:association|relationship|interaction|moderation|effect|evidence)|"
|
| 1104 |
+
r"did\s+not\s+(?:moderate|differ|vary)|confidence\s+interval[^.!?]{0,80}(?:included|crossed)\s+zero)\b",
|
| 1105 |
+
value,
|
| 1106 |
+
))
|
| 1107 |
+
if status == "supported":
|
| 1108 |
+
return bool(re.search(
|
| 1109 |
+
r"(?i)\b(?:supported|significant|nonzero|positive(?:ly)?\s+(?:association|associated|relationship|related)|"
|
| 1110 |
+
r"negative(?:ly)?\s+(?:association|associated|relationship|related)|"
|
| 1111 |
+
r"was\s+associated|were\s+associated|was\s+related|were\s+related)\b",
|
| 1112 |
+
value,
|
| 1113 |
+
))
|
| 1114 |
+
return True
|
| 1115 |
+
|
| 1116 |
+
@classmethod
|
| 1117 |
+
def _final_abstract_finding_violations(
|
| 1118 |
+
cls, mode: str, text: str, data_metadata: dict, *, hypothesis_ledger: dict | None = None
|
| 1119 |
+
) -> list[dict]:
|
| 1120 |
+
"""S1-R2 deterministic Final-Abstract frozen-finding coverage gate.
|
| 1121 |
+
|
| 1122 |
+
The gate is language-neutral at the manuscript-authority layer: English
|
| 1123 |
+
Final Front Matter is frozen once, then ZH/RU/KK are semantic
|
| 1124 |
+
translations of that exact source. Product tier is intentionally not an
|
| 1125 |
+
input because Standard and Flagship share this correctness contract.
|
| 1126 |
+
"""
|
| 1127 |
+
value = str(text or "").strip()
|
| 1128 |
+
meta = dict(data_metadata or {})
|
| 1129 |
+
result_sentences = cls._final_abstract_result_sentences(value)
|
| 1130 |
+
rows: list[dict] = []
|
| 1131 |
+
|
| 1132 |
+
if str(mode or "").strip().lower() == "amj":
|
| 1133 |
+
ledger = dict(hypothesis_ledger or meta.get("hypothesis_ledger") or {})
|
| 1134 |
+
evidence = dict(meta.get("association_results") or {})
|
| 1135 |
+
if not evidence:
|
| 1136 |
+
evidence = dict((meta.get("quantitative_evidence_ledger") or {}).get("association_results") or {})
|
| 1137 |
+
relation_results = dict(evidence.get("relation_results") or {})
|
| 1138 |
+
indirect_results = dict(evidence.get("indirect_results") or {})
|
| 1139 |
+
|
| 1140 |
+
def relation_sentence(item: dict, *, require_status: bool = True) -> bool:
|
| 1141 |
+
relation_id = str(item.get("relation_id") or "")
|
| 1142 |
+
frozen = dict(relation_results.get(relation_id) or {})
|
| 1143 |
+
status = cls._final_abstract_ci_status(frozen)
|
| 1144 |
+
identities = [item.get("predictor"), item.get("outcome")]
|
| 1145 |
+
if str(item.get("relation") or "") == "moderation":
|
| 1146 |
+
identities.append(item.get("moderator"))
|
| 1147 |
+
for sentence in result_sentences:
|
| 1148 |
+
if not all(cls._final_abstract_identity_present(sentence, label) for label in identities if label):
|
| 1149 |
+
continue
|
| 1150 |
+
if str(item.get("relation") or "") == "moderation" and not re.search(r"(?i)\b(?:moderat|interaction)\w*\b", sentence):
|
| 1151 |
+
continue
|
| 1152 |
+
if require_status and status and not cls._final_abstract_support_language(sentence, status):
|
| 1153 |
+
continue
|
| 1154 |
+
return True
|
| 1155 |
+
return False
|
| 1156 |
+
|
| 1157 |
+
numbered = [dict(item) for item in (ledger.get("items") or []) if isinstance(item, dict)]
|
| 1158 |
+
unsupported = []
|
| 1159 |
+
moderation = []
|
| 1160 |
+
for item in numbered:
|
| 1161 |
+
frozen = dict(relation_results.get(str(item.get("relation_id") or "")) or {})
|
| 1162 |
+
status = cls._final_abstract_ci_status(frozen)
|
| 1163 |
+
if status == "unsupported":
|
| 1164 |
+
unsupported.append(item)
|
| 1165 |
+
if str(item.get("relation") or "") == "moderation":
|
| 1166 |
+
moderation.append(item)
|
| 1167 |
+
|
| 1168 |
+
# Unsupported numbered hypotheses are interpretation-critical and
|
| 1169 |
+
# must never disappear from the Final Abstract.
|
| 1170 |
+
for item in unsupported:
|
| 1171 |
+
if relation_sentence(item, require_status=True):
|
| 1172 |
+
continue
|
| 1173 |
+
rows.append({
|
| 1174 |
+
"type": "final_abstract_missing_unsupported_finding",
|
| 1175 |
+
"hypothesis_id": str(item.get("hypothesis_id") or ""),
|
| 1176 |
+
"reason": (
|
| 1177 |
+
"The Final Abstract omits the frozen unsupported/non-significant status of a numbered hypothesis."
|
| 1178 |
+
),
|
| 1179 |
+
"repair_instruction": (
|
| 1180 |
+
"State the unsupported finding concisely in ordinary results language, preserving the exact construct identities. "
|
| 1181 |
+
"Do not add coefficients unless indispensable."
|
| 1182 |
+
),
|
| 1183 |
+
})
|
| 1184 |
+
|
| 1185 |
+
# A focal moderation result is a main boundary-condition finding even
|
| 1186 |
+
# when supported; require one moderation result sentence when present.
|
| 1187 |
+
if moderation and not any(relation_sentence(item, require_status=True) for item in moderation):
|
| 1188 |
+
item = moderation[0]
|
| 1189 |
+
rows.append({
|
| 1190 |
+
"type": "final_abstract_missing_moderation_finding",
|
| 1191 |
+
"hypothesis_id": str(item.get("hypothesis_id") or ""),
|
| 1192 |
+
"reason": "The Final Abstract does not report the frozen moderation/interaction finding.",
|
| 1193 |
+
"repair_instruction": (
|
| 1194 |
+
"Report whether the frozen moderator changed the focal association, using the exact predictor, moderator, and outcome identities. "
|
| 1195 |
+
"Keep the statement concise and evidence-bounded."
|
| 1196 |
+
),
|
| 1197 |
+
})
|
| 1198 |
+
|
| 1199 |
+
# Preserve the main mechanism finding when an indirect analysis was
|
| 1200 |
+
# actually compiled. If several unnumbered paths exist, one main
|
| 1201 |
+
# frozen path is sufficient; numbered indirect hypotheses remain
|
| 1202 |
+
# independently governed by the hypothesis contract.
|
| 1203 |
+
indirect_rows = [dict(item) for item in (ledger.get("indirect_analyses") or []) if isinstance(item, dict)]
|
| 1204 |
+
if indirect_rows:
|
| 1205 |
+
selected = next((item for item in indirect_rows if item.get("numbered_hypothesis")), indirect_rows[0])
|
| 1206 |
+
effect = dict(indirect_results.get(str(selected.get("effect_id") or "")) or {})
|
| 1207 |
+
status = cls._final_abstract_ci_status(effect)
|
| 1208 |
+
path = list(selected.get("path") or [])
|
| 1209 |
+
indirect_found = False
|
| 1210 |
+
for sentence in result_sentences:
|
| 1211 |
+
if not re.search(r"(?i)\b(?:indirect|mediat)\w*\b", sentence):
|
| 1212 |
+
continue
|
| 1213 |
+
required_labels = []
|
| 1214 |
+
if path:
|
| 1215 |
+
required_labels.extend([path[0], path[-1]])
|
| 1216 |
+
if len(path) > 2:
|
| 1217 |
+
required_labels.append(path[1])
|
| 1218 |
+
if required_labels and not all(cls._final_abstract_identity_present(sentence, x) for x in required_labels if x):
|
| 1219 |
+
continue
|
| 1220 |
+
if status and not cls._final_abstract_support_language(sentence, status):
|
| 1221 |
+
continue
|
| 1222 |
+
indirect_found = True
|
| 1223 |
+
break
|
| 1224 |
+
if not indirect_found:
|
| 1225 |
+
rows.append({
|
| 1226 |
+
"type": "final_abstract_missing_indirect_finding",
|
| 1227 |
+
"reason": "The Final Abstract omits the main frozen indirect/mediation finding.",
|
| 1228 |
+
"repair_instruction": (
|
| 1229 |
+
"Summarize the frozen indirect association through the identified mediator in one concise findings sentence. "
|
| 1230 |
+
"Do not claim causal mediation for observational data and do not add unnecessary statistics."
|
| 1231 |
+
),
|
| 1232 |
+
})
|
| 1233 |
+
|
| 1234 |
+
# Every empirical Final Abstract needs at least one actual result
|
| 1235 |
+
# sentence. Design/hypothesis prose plus 'our findings contribute'
|
| 1236 |
+
# is not sufficient.
|
| 1237 |
+
if relation_results and not result_sentences:
|
| 1238 |
+
rows.append({
|
| 1239 |
+
"type": "final_abstract_missing_empirical_finding",
|
| 1240 |
+
"reason": "The Final Abstract describes the design/hypotheses but states no frozen empirical result.",
|
| 1241 |
+
"repair_instruction": (
|
| 1242 |
+
"Add one or two concise sentences stating the main frozen empirical findings before the contribution. "
|
| 1243 |
+
"Prefer support/non-support and substantive direction over a list of coefficients."
|
| 1244 |
+
),
|
| 1245 |
+
})
|
| 1246 |
+
return rows
|
| 1247 |
+
|
| 1248 |
+
if str(mode or "").strip().lower() == "asq":
|
| 1249 |
+
themes = [dict(item) for item in (meta.get("themes") or []) if isinstance(item, dict)]
|
| 1250 |
+
if not themes:
|
| 1251 |
+
if not result_sentences:
|
| 1252 |
+
rows.append({
|
| 1253 |
+
"type": "final_abstract_missing_qualitative_finding",
|
| 1254 |
+
"reason": "The Final Abstract states no empirical qualitative finding.",
|
| 1255 |
+
"repair_instruction": "Add a concise findings sentence grounded in the frozen qualitative evidence; do not invent a theme or process claim.",
|
| 1256 |
+
})
|
| 1257 |
+
return rows
|
| 1258 |
+
|
| 1259 |
+
covered = set()
|
| 1260 |
+
for index, theme in enumerate(themes):
|
| 1261 |
+
anchors = [str(theme.get("title") or "").strip()]
|
| 1262 |
+
anchors.extend(str(x or "").strip() for x in (theme.get("concepts") or []))
|
| 1263 |
+
anchors = [x for x in anchors if x]
|
| 1264 |
+
for sentence in result_sentences:
|
| 1265 |
+
if any(cls._final_abstract_identity_present(sentence, anchor) for anchor in anchors):
|
| 1266 |
+
covered.add(index)
|
| 1267 |
+
break
|
| 1268 |
+
required = min(2, len(themes))
|
| 1269 |
+
if len(covered) < required:
|
| 1270 |
+
rows.append({
|
| 1271 |
+
"type": "final_abstract_missing_qualitative_theme_findings",
|
| 1272 |
+
"observed": len(covered),
|
| 1273 |
+
"expected": required,
|
| 1274 |
+
"reason": (
|
| 1275 |
+
"The Final Abstract does not report enough frozen qualitative theme/concept findings to function as a results-bearing final abstract."
|
| 1276 |
+
),
|
| 1277 |
+
"repair_instruction": (
|
| 1278 |
+
f"State the main qualitative findings in one or two concise sentences grounded in at least {required} frozen theme/concept identities. "
|
| 1279 |
+
"Do not merely say that findings contribute, and do not invent themes, quotations, participants, or procedures."
|
| 1280 |
+
),
|
| 1281 |
+
})
|
| 1282 |
+
return rows
|
| 1283 |
+
|
| 1284 |
+
return rows
|
| 1285 |
+
|
| 1286 |
@staticmethod
|
| 1287 |
def _section_retry_feedback(violations, *, limit=8) -> str:
|
| 1288 |
rows = []
|
|
|
|
| 1351 |
"unsupported_moderation_claim", "unsupported_mediation_claim", "unsupported_temporal_scope",
|
| 1352 |
"unsupported_design_tradition", "unsupported_unit_of_analysis", "unsupported_data_source_claim",
|
| 1353 |
"unsupported_process_claim",
|
| 1354 |
+
"final_abstract_missing_empirical_finding",
|
| 1355 |
+
"final_abstract_missing_unsupported_finding",
|
| 1356 |
+
"final_abstract_missing_moderation_finding",
|
| 1357 |
+
"final_abstract_missing_indirect_finding",
|
| 1358 |
+
"final_abstract_missing_qualitative_finding",
|
| 1359 |
+
"final_abstract_missing_qualitative_theme_findings",
|
| 1360 |
}
|
| 1361 |
if kind in always_fatal:
|
| 1362 |
return "FATAL"
|
|
|
|
| 1675 |
rows.extend(self._hypothesis_contract_violations(
|
| 1676 |
repaired, hypothesis_ledger, section_name=label
|
| 1677 |
))
|
| 1678 |
+
if str(label or "").strip().casefold() == "final abstract":
|
| 1679 |
+
rows.extend(self._final_abstract_finding_violations(
|
| 1680 |
+
mode, repaired, data_metadata, hypothesis_ledger=hypothesis_ledger
|
| 1681 |
+
))
|
| 1682 |
current, remaining = repaired, rows
|
| 1683 |
if not remaining:
|
| 1684 |
return current, []
|
|
|
|
| 5625 |
final_front_fact_rows.extend(self._hypothesis_contract_violations(
|
| 5626 |
final_abstract, hypothesis_ledger, section_name="Final Abstract"
|
| 5627 |
))
|
| 5628 |
+
final_front_fact_rows.extend(self._final_abstract_finding_violations(
|
| 5629 |
+
mode, final_abstract, data_metadata, hypothesis_ledger=hypothesis_ledger
|
| 5630 |
+
))
|
| 5631 |
if final_front_fact_rows:
|
| 5632 |
feedback = self._section_retry_feedback(final_front_fact_rows)
|
| 5633 |
logging.warning(
|
|
|
|
| 5660 |
remaining_final_fact_rows.extend(self._hypothesis_contract_violations(
|
| 5661 |
final_abstract, hypothesis_ledger, section_name="Final Abstract"
|
| 5662 |
))
|
| 5663 |
+
remaining_final_fact_rows.extend(self._final_abstract_finding_violations(
|
| 5664 |
+
mode, final_abstract, data_metadata, hypothesis_ledger=hypothesis_ledger
|
| 5665 |
+
))
|
| 5666 |
if remaining_final_fact_rows:
|
| 5667 |
final_abstract, residual_final_rows = self._adaptive_repair_front_matter(
|
| 5668 |
agent=agent, quant_engine=quant_engine, abstract_text=final_abstract,
|
|
|
|
| 5671 |
quality_notes=quality_notes, label="Final Abstract",
|
| 5672 |
)
|
| 5673 |
if residual_final_rows:
|
| 5674 |
+
self._raise_if_fatal_completion_residual(
|
| 5675 |
+
residual_final_rows, section_name="Final Abstract", mode=mode,
|
| 5676 |
+
error_code="final_abstract_finding_contract_unresolved"
|
| 5677 |
+
)
|
| 5678 |
logging.warning("Adaptive Final Abstract accepted with residual diagnostics | count=%s", len(residual_final_rows))
|
| 5679 |
quality_diagnostics.append({
|
| 5680 |
"scope":"final_abstract_study_fact_integrity", "name":"Final Abstract",
|