Spaces:
Running
Running
S1-R3A Final Abstract Resilience + RU-KK Title Fallback
#124
by GuXiaopeng - opened
- core/generation_runner.py +289 -13
- core/quantitative_engine.py +1 -1
core/generation_runner.py
CHANGED
|
@@ -1053,7 +1053,8 @@ class GenerationRunner:
|
|
| 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 = []
|
|
@@ -1065,7 +1066,8 @@ class GenerationRunner:
|
|
| 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
|
|
|
|
| 1069 |
)
|
| 1070 |
for sentence in sentences:
|
| 1071 |
if not finding_pattern.search(sentence):
|
|
@@ -1100,8 +1102,10 @@ class GenerationRunner:
|
|
| 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|
|
| 1104 |
-
r"
|
|
|
|
|
|
|
| 1105 |
value,
|
| 1106 |
))
|
| 1107 |
if status == "supported":
|
|
@@ -1113,6 +1117,232 @@ class GenerationRunner:
|
|
| 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
|
|
@@ -1679,6 +1909,27 @@ class GenerationRunner:
|
|
| 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, []
|
|
@@ -2249,25 +2500,27 @@ class GenerationRunner:
|
|
| 2249 |
if 'moderation' in missing:
|
| 2250 |
label = role_label.get('moderator') or ''
|
| 2251 |
if code.startswith('zh'):
|
| 2252 |
-
suffixes.append(f'
|
| 2253 |
elif code.startswith('ru'):
|
| 2254 |
-
suffixes.append(f'модера
|
| 2255 |
elif code.startswith('kk') or code.startswith('kz'):
|
| 2256 |
-
suffixes.append(f'модератор
|
| 2257 |
else:
|
| 2258 |
-
suffixes.append(f'
|
| 2259 |
if missing.intersection({'mediation', 'serial_mediation', 'parallel_mediation'}):
|
| 2260 |
label = role_label.get('mediator') or ''
|
| 2261 |
if code.startswith('zh'):
|
| 2262 |
-
suffixes.append(f'
|
| 2263 |
elif code.startswith('ru'):
|
| 2264 |
-
suffixes.append(f'медиа
|
| 2265 |
elif code.startswith('kk') or code.startswith('kz'):
|
| 2266 |
-
suffixes.append(f'медиатор
|
| 2267 |
else:
|
| 2268 |
-
suffixes.append(f'
|
| 2269 |
if suffixes:
|
| 2270 |
-
|
|
|
|
|
|
|
| 2271 |
value = value[:title_match.start(1)] + repaired_title + value[title_match.end(1):]
|
| 2272 |
diagnostics.append({'type':'target_user_relation_title_rebound','missing':sorted(missing)})
|
| 2273 |
return value, diagnostics
|
|
@@ -5663,6 +5916,29 @@ The previous attempt for this same manuscript unit was rejected by validation. R
|
|
| 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,
|
|
|
|
| 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"\bdid\s+not\s+(?:significantly\s+)?(?:moderate|mediate|change|alter)\b"
|
| 1058 |
r")"
|
| 1059 |
)
|
| 1060 |
result_sentences = []
|
|
|
|
| 1066 |
retrospective = re.compile(
|
| 1067 |
r"(?i)\b(?:results?|findings?\s+(?:show|indicate|reveal|identify|suggest)|"
|
| 1068 |
r"analysis\s+(?:show|indicate|reveal|identify)|found|revealed|indicated|"
|
| 1069 |
+
r"supported|unsupported|non[- ]?significant|not\s+significant|nonzero|received\s+no\s+support|"
|
| 1070 |
+
r"did\s+not\s+(?:significantly\s+)?(?:moderate|mediate|change|alter))\b"
|
| 1071 |
)
|
| 1072 |
for sentence in sentences:
|
| 1073 |
if not finding_pattern.search(sentence):
|
|
|
|
| 1102 |
if status == "unsupported":
|
| 1103 |
return bool(re.search(
|
| 1104 |
r"(?i)\b(?:not\s+supported|unsupported|no\s+support|did\s+not\s+support|"
|
| 1105 |
+
r"non[- ]?significant|not\s+significant|not\s+significantly\s+(?:associated|related)|"
|
| 1106 |
+
r"no\s+(?:meaningful\s+)?(?:association|relationship|interaction|moderation|effect|evidence)|"
|
| 1107 |
+
r"did\s+not\s+(?:significantly\s+)?(?:moderate|mediate|differ|vary|change|alter)|"
|
| 1108 |
+
r"confidence\s+interval[^.!?]{0,80}(?:included|crossed)\s+zero)\b",
|
| 1109 |
value,
|
| 1110 |
))
|
| 1111 |
if status == "supported":
|
|
|
|
| 1117 |
))
|
| 1118 |
return True
|
| 1119 |
|
| 1120 |
+
@staticmethod
|
| 1121 |
+
def _final_abstract_metric_light(text: str) -> tuple[str, list[dict]]:
|
| 1122 |
+
"""Remove nonessential inferential statistics from a Final Abstract.
|
| 1123 |
+
|
| 1124 |
+
This is a completion-resilience transform, not a correctness waiver.
|
| 1125 |
+
It is used only after a frozen Final Abstract has already been generated
|
| 1126 |
+
and a strict study-fact validator reports a metric-specific defect. The
|
| 1127 |
+
substantive finding sentence is preserved while beta/CI/p-value detail
|
| 1128 |
+
that is optional in an abstract is removed. Sample-size/design facts are
|
| 1129 |
+
intentionally untouched.
|
| 1130 |
+
"""
|
| 1131 |
+
value = str(text or "")
|
| 1132 |
+
original = value
|
| 1133 |
+
|
| 1134 |
+
# Parenthetical inferential bundles are the common case:
|
| 1135 |
+
# (β = .21, 95% CI [...], p = .03)
|
| 1136 |
+
metric_bundle = re.compile(
|
| 1137 |
+
r"\s*\((?=[^()]{0,220}(?:β|standardized\s+estimate|(?:\d{2}%\s*)?CI\b|confidence\s+interval|\bp\s*[=<>]))[^()]*\)",
|
| 1138 |
+
flags=re.I,
|
| 1139 |
+
)
|
| 1140 |
+
value = metric_bundle.sub("", value)
|
| 1141 |
+
|
| 1142 |
+
# Bare inline statistics are also optional in the abstract. Keep the
|
| 1143 |
+
# surrounding finding language and remove only the metric surface.
|
| 1144 |
+
value = re.sub(
|
| 1145 |
+
r"(?i)(?:,\s*)?(?:β|standardized\s+estimate)\s*(?:=|:)?\s*[-+]?\d+(?:\.\d+)?",
|
| 1146 |
+
"",
|
| 1147 |
+
value,
|
| 1148 |
+
)
|
| 1149 |
+
value = re.sub(
|
| 1150 |
+
r"(?i)(?:,\s*)?(?:\d{2}%\s*)?(?:CI|confidence\s+interval)\s*[:=]?\s*[\[(][^\])]{1,80}[\])]?",
|
| 1151 |
+
"",
|
| 1152 |
+
value,
|
| 1153 |
+
)
|
| 1154 |
+
value = re.sub(r"(?i)(?:,\s*)?\bp\s*(?:=|<|>)\s*\.?\d+(?:\.\d+)?", "", value)
|
| 1155 |
+
|
| 1156 |
+
# Typography cleanup only; never alter construct identities or finding
|
| 1157 |
+
# polarity/support language.
|
| 1158 |
+
value = re.sub(r"\s+,", ",", value)
|
| 1159 |
+
value = re.sub(r",\s*,+", ",", value)
|
| 1160 |
+
value = re.sub(r"\(\s*\)", "", value)
|
| 1161 |
+
value = re.sub(r"\s+([.;:])", r"\1", value)
|
| 1162 |
+
value = re.sub(r" {2,}", " ", value)
|
| 1163 |
+
value = re.sub(r"\n{3,}", "\n\n", value).strip()
|
| 1164 |
+
|
| 1165 |
+
diagnostics = []
|
| 1166 |
+
if value != original.strip():
|
| 1167 |
+
diagnostics.append({
|
| 1168 |
+
"type": "final_abstract_metric_light_recovery",
|
| 1169 |
+
"reason": (
|
| 1170 |
+
"Optional inferential statistics were removed from the Final Abstract after strict metric validation could not safely bind them."
|
| 1171 |
+
),
|
| 1172 |
+
})
|
| 1173 |
+
return value, diagnostics
|
| 1174 |
+
|
| 1175 |
+
@classmethod
|
| 1176 |
+
def _canonical_final_abstract_findings(
|
| 1177 |
+
cls, mode: str, data_metadata: dict, *, hypothesis_ledger: dict | None = None,
|
| 1178 |
+
violation_types: set[str] | None = None,
|
| 1179 |
+
) -> list[str]:
|
| 1180 |
+
"""Return fact-safe, metric-light finding sentences from frozen ledgers.
|
| 1181 |
+
|
| 1182 |
+
These sentences are a last-resort completion fallback after DeepSeek has
|
| 1183 |
+
already received bounded local-repair opportunities. They use only
|
| 1184 |
+
frozen relation/theme identities and support status; no new statistic,
|
| 1185 |
+
method, participant, or causal claim is invented.
|
| 1186 |
+
"""
|
| 1187 |
+
meta = dict(data_metadata or {})
|
| 1188 |
+
wanted = set(violation_types or set())
|
| 1189 |
+
out: list[str] = []
|
| 1190 |
+
|
| 1191 |
+
if str(mode or "").strip().lower() == "amj":
|
| 1192 |
+
ledger = dict(hypothesis_ledger or meta.get("hypothesis_ledger") or {})
|
| 1193 |
+
evidence = dict(meta.get("association_results") or {})
|
| 1194 |
+
if not evidence:
|
| 1195 |
+
evidence = dict((meta.get("quantitative_evidence_ledger") or {}).get("association_results") or {})
|
| 1196 |
+
relation_results = dict(evidence.get("relation_results") or {})
|
| 1197 |
+
indirect_results = dict(evidence.get("indirect_results") or {})
|
| 1198 |
+
items = [dict(x) for x in (ledger.get("items") or []) if isinstance(x, dict)]
|
| 1199 |
+
|
| 1200 |
+
def relation_sentence(item: dict) -> str:
|
| 1201 |
+
predictor = str(item.get("predictor") or "").strip()
|
| 1202 |
+
outcome = str(item.get("outcome") or "").strip()
|
| 1203 |
+
moderator = str(item.get("moderator") or "").strip()
|
| 1204 |
+
relation = str(item.get("relation") or "").strip().lower()
|
| 1205 |
+
row = dict(relation_results.get(str(item.get("relation_id") or "")) or {})
|
| 1206 |
+
if not predictor or not outcome or not row:
|
| 1207 |
+
return ""
|
| 1208 |
+
status = cls._final_abstract_ci_status(row)
|
| 1209 |
+
estimate = row.get("estimate")
|
| 1210 |
+
try:
|
| 1211 |
+
estimate_value = float(estimate)
|
| 1212 |
+
except (TypeError, ValueError):
|
| 1213 |
+
estimate_value = 0.0
|
| 1214 |
+
if relation == "moderation" and moderator:
|
| 1215 |
+
if status == "unsupported":
|
| 1216 |
+
return f"{moderator} did not significantly moderate the {predictor}–{outcome} association."
|
| 1217 |
+
if status == "supported":
|
| 1218 |
+
return f"{moderator} significantly moderated the {predictor}–{outcome} association."
|
| 1219 |
+
return f"The analysis assessed whether {moderator} moderated the {predictor}–{outcome} association."
|
| 1220 |
+
if status == "unsupported":
|
| 1221 |
+
return f"{predictor} was not significantly associated with {outcome}."
|
| 1222 |
+
direction = "positively" if estimate_value >= 0 else "negatively"
|
| 1223 |
+
return f"{predictor} was {direction} associated with {outcome}."
|
| 1224 |
+
|
| 1225 |
+
if wanted.intersection({
|
| 1226 |
+
"final_abstract_missing_unsupported_finding",
|
| 1227 |
+
"final_abstract_missing_moderation_finding",
|
| 1228 |
+
"final_abstract_missing_empirical_finding",
|
| 1229 |
+
}):
|
| 1230 |
+
unsupported = []
|
| 1231 |
+
moderation = []
|
| 1232 |
+
for item in items:
|
| 1233 |
+
row = dict(relation_results.get(str(item.get("relation_id") or "")) or {})
|
| 1234 |
+
status = cls._final_abstract_ci_status(row)
|
| 1235 |
+
if status == "unsupported":
|
| 1236 |
+
unsupported.append(item)
|
| 1237 |
+
if str(item.get("relation") or "").strip().lower() == "moderation":
|
| 1238 |
+
moderation.append(item)
|
| 1239 |
+
if "final_abstract_missing_unsupported_finding" in wanted:
|
| 1240 |
+
out.extend(filter(None, (relation_sentence(item) for item in unsupported)))
|
| 1241 |
+
if "final_abstract_missing_moderation_finding" in wanted and moderation:
|
| 1242 |
+
sentence = relation_sentence(moderation[0])
|
| 1243 |
+
if sentence:
|
| 1244 |
+
out.append(sentence)
|
| 1245 |
+
if "final_abstract_missing_empirical_finding" in wanted and not out:
|
| 1246 |
+
candidate = next((item for item in items if relation_results.get(str(item.get("relation_id") or ""))), None)
|
| 1247 |
+
sentence = relation_sentence(candidate or {})
|
| 1248 |
+
if sentence:
|
| 1249 |
+
out.append(sentence)
|
| 1250 |
+
|
| 1251 |
+
if "final_abstract_missing_indirect_finding" in wanted:
|
| 1252 |
+
indirect_rows = [dict(x) for x in (ledger.get("indirect_analyses") or []) if isinstance(x, dict)]
|
| 1253 |
+
selected = next((x for x in indirect_rows if x.get("numbered_hypothesis")), indirect_rows[0] if indirect_rows else None)
|
| 1254 |
+
if selected:
|
| 1255 |
+
path = [str(x or "").strip() for x in (selected.get("path") or []) if str(x or "").strip()]
|
| 1256 |
+
effect = dict(indirect_results.get(str(selected.get("effect_id") or "")) or {})
|
| 1257 |
+
if len(path) >= 3 and effect:
|
| 1258 |
+
status = cls._final_abstract_ci_status(effect)
|
| 1259 |
+
try:
|
| 1260 |
+
estimate_value = float(effect.get("estimate"))
|
| 1261 |
+
except (TypeError, ValueError):
|
| 1262 |
+
estimate_value = 0.0
|
| 1263 |
+
if status == "unsupported":
|
| 1264 |
+
out.append(
|
| 1265 |
+
f"The indirect association from {path[0]} to {path[-1]} through {path[1]} was not significant."
|
| 1266 |
+
)
|
| 1267 |
+
else:
|
| 1268 |
+
direction = "positive" if estimate_value >= 0 else "negative"
|
| 1269 |
+
out.append(
|
| 1270 |
+
f"The indirect association from {path[0]} to {path[-1]} through {path[1]} was {direction} and significant."
|
| 1271 |
+
)
|
| 1272 |
+
|
| 1273 |
+
elif str(mode or "").strip().lower() == "asq":
|
| 1274 |
+
if wanted.intersection({
|
| 1275 |
+
"final_abstract_missing_qualitative_finding",
|
| 1276 |
+
"final_abstract_missing_qualitative_theme_findings",
|
| 1277 |
+
}):
|
| 1278 |
+
themes = [dict(x) for x in (meta.get("themes") or []) if isinstance(x, dict)]
|
| 1279 |
+
labels = []
|
| 1280 |
+
for theme in themes:
|
| 1281 |
+
label = str(theme.get("title") or "").strip()
|
| 1282 |
+
if not label:
|
| 1283 |
+
concepts = [str(x or "").strip() for x in (theme.get("concepts") or []) if str(x or "").strip()]
|
| 1284 |
+
label = concepts[0] if concepts else ""
|
| 1285 |
+
if label and label not in labels:
|
| 1286 |
+
labels.append(label)
|
| 1287 |
+
if len(labels) >= 2:
|
| 1288 |
+
break
|
| 1289 |
+
if len(labels) >= 2:
|
| 1290 |
+
out.append(f"The analysis identified {labels[0]} and {labels[1]} as central themes.")
|
| 1291 |
+
elif len(labels) == 1:
|
| 1292 |
+
out.append(f"The analysis identified {labels[0]} as a central theme.")
|
| 1293 |
+
|
| 1294 |
+
deduped = []
|
| 1295 |
+
seen = set()
|
| 1296 |
+
for sentence in out:
|
| 1297 |
+
key = cls._final_abstract_normalized_text(sentence)
|
| 1298 |
+
if key and key not in seen:
|
| 1299 |
+
deduped.append(sentence)
|
| 1300 |
+
seen.add(key)
|
| 1301 |
+
return deduped
|
| 1302 |
+
|
| 1303 |
+
@classmethod
|
| 1304 |
+
def _final_abstract_resilience_repair(
|
| 1305 |
+
cls, mode: str, text: str, violations, data_metadata: dict, *, hypothesis_ledger: dict | None = None,
|
| 1306 |
+
) -> tuple[str, list[dict]]:
|
| 1307 |
+
"""Provider-neutral last-mile repair for recoverable Final-Abstract defects."""
|
| 1308 |
+
current = str(text or "").strip()
|
| 1309 |
+
rows = [dict(x) for x in (violations or []) if isinstance(x, dict)]
|
| 1310 |
+
kinds = {str(x.get("type") or "") for x in rows}
|
| 1311 |
+
diagnostics: list[dict] = []
|
| 1312 |
+
|
| 1313 |
+
metric_kinds = {"quantitative_metric_owner_unresolved", "quantitative_metric_mismatch", "unfrozen_p_value"}
|
| 1314 |
+
if str(mode or "").strip().lower() == "amj" and kinds.intersection(metric_kinds):
|
| 1315 |
+
current, metric_diagnostics = cls._final_abstract_metric_light(current)
|
| 1316 |
+
diagnostics.extend(metric_diagnostics)
|
| 1317 |
+
|
| 1318 |
+
finding_kinds = {
|
| 1319 |
+
"final_abstract_missing_empirical_finding",
|
| 1320 |
+
"final_abstract_missing_unsupported_finding",
|
| 1321 |
+
"final_abstract_missing_moderation_finding",
|
| 1322 |
+
"final_abstract_missing_indirect_finding",
|
| 1323 |
+
"final_abstract_missing_qualitative_finding",
|
| 1324 |
+
"final_abstract_missing_qualitative_theme_findings",
|
| 1325 |
+
}
|
| 1326 |
+
if kinds.intersection(finding_kinds):
|
| 1327 |
+
sentences = cls._canonical_final_abstract_findings(
|
| 1328 |
+
mode, data_metadata, hypothesis_ledger=hypothesis_ledger,
|
| 1329 |
+
violation_types=kinds.intersection(finding_kinds),
|
| 1330 |
+
)
|
| 1331 |
+
haystack = cls._final_abstract_normalized_text(current)
|
| 1332 |
+
added = []
|
| 1333 |
+
for sentence in sentences:
|
| 1334 |
+
key = cls._final_abstract_normalized_text(sentence)
|
| 1335 |
+
if key and key not in haystack:
|
| 1336 |
+
added.append(sentence)
|
| 1337 |
+
if added:
|
| 1338 |
+
current = (current.rstrip() + " " + " ".join(added)).strip()
|
| 1339 |
+
diagnostics.append({
|
| 1340 |
+
"type": "final_abstract_frozen_finding_fallback",
|
| 1341 |
+
"count": len(added),
|
| 1342 |
+
"reason": "Frozen finding identities were added after bounded model repair left only abstract-coverage defects.",
|
| 1343 |
+
})
|
| 1344 |
+
return current, diagnostics
|
| 1345 |
+
|
| 1346 |
@classmethod
|
| 1347 |
def _final_abstract_finding_violations(
|
| 1348 |
cls, mode: str, text: str, data_metadata: dict, *, hypothesis_ledger: dict | None = None
|
|
|
|
| 1909 |
rows.extend(self._final_abstract_finding_violations(
|
| 1910 |
mode, repaired, data_metadata, hypothesis_ledger=hypothesis_ledger
|
| 1911 |
))
|
| 1912 |
+
if rows:
|
| 1913 |
+
resilient, resilience_notes = self._final_abstract_resilience_repair(
|
| 1914 |
+
mode, repaired, rows, data_metadata, hypothesis_ledger=hypothesis_ledger
|
| 1915 |
+
)
|
| 1916 |
+
if resilient != repaired:
|
| 1917 |
+
repaired = resilient
|
| 1918 |
+
rows = self._study_fact_violations(
|
| 1919 |
+
mode, agent, quant_engine, repaired, data_metadata, section_name=label
|
| 1920 |
+
)
|
| 1921 |
+
if hypothesis_ledger:
|
| 1922 |
+
rows.extend(self._hypothesis_contract_violations(
|
| 1923 |
+
repaired, hypothesis_ledger, section_name=label
|
| 1924 |
+
))
|
| 1925 |
+
rows.extend(self._final_abstract_finding_violations(
|
| 1926 |
+
mode, repaired, data_metadata, hypothesis_ledger=hypothesis_ledger
|
| 1927 |
+
))
|
| 1928 |
+
if resilience_notes:
|
| 1929 |
+
self._record_adaptive_completion_residual(
|
| 1930 |
+
quality_notes, resilience_notes, section_name=label,
|
| 1931 |
+
stage="front_matter_resilience_recovery"
|
| 1932 |
+
)
|
| 1933 |
current, remaining = repaired, rows
|
| 1934 |
if not remaining:
|
| 1935 |
return current, []
|
|
|
|
| 2500 |
if 'moderation' in missing:
|
| 2501 |
label = role_label.get('moderator') or ''
|
| 2502 |
if code.startswith('zh'):
|
| 2503 |
+
suffixes.append(f'{label}的调节作用' if label else '调节作用')
|
| 2504 |
elif code.startswith('ru'):
|
| 2505 |
+
suffixes.append(f'модерирующая роль переменной «{label}»' if label else 'модерирующая роль')
|
| 2506 |
elif code.startswith('kk') or code.startswith('kz'):
|
| 2507 |
+
suffixes.append(f'«{label}» айнымалысының модераторлық рөлі' if label else 'модераторлық рөл')
|
| 2508 |
else:
|
| 2509 |
+
suffixes.append(f'the moderating role of {label}' if label else 'moderation')
|
| 2510 |
if missing.intersection({'mediation', 'serial_mediation', 'parallel_mediation'}):
|
| 2511 |
label = role_label.get('mediator') or ''
|
| 2512 |
if code.startswith('zh'):
|
| 2513 |
+
suffixes.append(f'{label}的中介作用' if label else '中介作用')
|
| 2514 |
elif code.startswith('ru'):
|
| 2515 |
+
suffixes.append(f'медиирующая роль переменной «{label}»' if label else 'медиирующая роль')
|
| 2516 |
elif code.startswith('kk') or code.startswith('kz'):
|
| 2517 |
+
suffixes.append(f'«{label}» айнымалысының медиаторлық рөлі' if label else 'медиаторлық рөл')
|
| 2518 |
else:
|
| 2519 |
+
suffixes.append(f'the mediating role of {label}' if label else 'mediation')
|
| 2520 |
if suffixes:
|
| 2521 |
+
separator = ':' if code.startswith('zh') else ': '
|
| 2522 |
+
joiner = ';' if code.startswith('zh') else '; '
|
| 2523 |
+
repaired_title = title.rstrip(' .;::') + separator + joiner.join(suffixes)
|
| 2524 |
value = value[:title_match.start(1)] + repaired_title + value[title_match.end(1):]
|
| 2525 |
diagnostics.append({'type':'target_user_relation_title_rebound','missing':sorted(missing)})
|
| 2526 |
return value, diagnostics
|
|
|
|
| 5916 |
remaining_final_fact_rows.extend(self._final_abstract_finding_violations(
|
| 5917 |
mode, final_abstract, data_metadata, hypothesis_ledger=hypothesis_ledger
|
| 5918 |
))
|
| 5919 |
+
if remaining_final_fact_rows:
|
| 5920 |
+
resilient_abstract, resilience_notes = self._final_abstract_resilience_repair(
|
| 5921 |
+
mode, final_abstract, remaining_final_fact_rows, data_metadata,
|
| 5922 |
+
hypothesis_ledger=hypothesis_ledger
|
| 5923 |
+
)
|
| 5924 |
+
if resilient_abstract != final_abstract:
|
| 5925 |
+
final_abstract = resilient_abstract
|
| 5926 |
+
remaining_final_fact_rows = self._study_fact_violations(
|
| 5927 |
+
mode, agent, quant_engine, final_abstract, data_metadata,
|
| 5928 |
+
section_name="Final Abstract",
|
| 5929 |
+
)
|
| 5930 |
+
if hypothesis_ledger:
|
| 5931 |
+
remaining_final_fact_rows.extend(self._hypothesis_contract_violations(
|
| 5932 |
+
final_abstract, hypothesis_ledger, section_name="Final Abstract"
|
| 5933 |
+
))
|
| 5934 |
+
remaining_final_fact_rows.extend(self._final_abstract_finding_violations(
|
| 5935 |
+
mode, final_abstract, data_metadata, hypothesis_ledger=hypothesis_ledger
|
| 5936 |
+
))
|
| 5937 |
+
if resilience_notes:
|
| 5938 |
+
self._record_adaptive_completion_residual(
|
| 5939 |
+
quality_notes, resilience_notes, section_name="Final Abstract",
|
| 5940 |
+
stage="final_abstract_resilience_recovery"
|
| 5941 |
+
)
|
| 5942 |
if remaining_final_fact_rows:
|
| 5943 |
final_abstract, residual_final_rows = self._adaptive_repair_front_matter(
|
| 5944 |
agent=agent, quant_engine=quant_engine, abstract_text=final_abstract,
|
core/quantitative_engine.py
CHANGED
|
@@ -1861,7 +1861,7 @@ Keywords: {keywords}"""
|
|
| 1861 |
continue
|
| 1862 |
if row.get("estimate") is not None:
|
| 1863 |
candidates.append((f"relation:{rid}", row.get("estimate")))
|
| 1864 |
-
if re.search(r"(?i)
|
| 1865 |
for eid, row in indirect_results.items():
|
| 1866 |
if not isinstance(row, dict):
|
| 1867 |
continue
|
|
|
|
| 1861 |
continue
|
| 1862 |
if row.get("estimate") is not None:
|
| 1863 |
candidates.append((f"relation:{rid}", row.get("estimate")))
|
| 1864 |
+
if re.search(r"(?i)\bindirect\b", clause):
|
| 1865 |
for eid, row in indirect_results.items():
|
| 1866 |
if not isinstance(row, dict):
|
| 1867 |
continue
|