Spaces:
Running
Running
R14-TQ2-PQ1-R4-R3 Final Citation Ledger English References Preservation
#68
by GuXiaopeng - opened
- core/generation_runner.py +32 -11
- core/pipeline_agent.py +151 -0
core/generation_runner.py
CHANGED
|
@@ -44,6 +44,7 @@ CITATION_ROLE_RULES = {
|
|
| 44 |
"discussion": (2, 220),
|
| 45 |
}
|
| 46 |
TRANSLATION_CONTRACT_VERSION = 'hierarchical-multilingual-v8'
|
|
|
|
| 47 |
|
| 48 |
|
| 49 |
def _clean_trailing_references(text, keywords=("reference", "references", "reference list", "参考文献")):
|
|
@@ -78,6 +79,13 @@ def _normalize_target_manuscript_typography(text: str, target_lang: str) -> str:
|
|
| 78 |
if "chinese" not in language.casefold() and "中文" not in language:
|
| 79 |
return value
|
| 80 |
value = value.replace("\r\n", "\n").replace("\r", "\n")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 81 |
lines = value.split("\n")
|
| 82 |
normalized = []
|
| 83 |
in_fence = False
|
|
@@ -93,7 +101,8 @@ def _normalize_target_manuscript_typography(text: str, target_lang: str) -> str:
|
|
| 93 |
current = re.sub(r"[ \t]+([,。!?;:])", r"\1", line)
|
| 94 |
current = re.sub(r"([,。!?;:])[ \t]+(?=\S)", r"\1", current)
|
| 95 |
normalized.append(current)
|
| 96 |
-
|
|
|
|
| 97 |
|
| 98 |
|
| 99 |
class _OrderedTranslationWorker:
|
|
@@ -1859,6 +1868,15 @@ class GenerationRunner:
|
|
| 1859 |
# Restore agent state before continuing any dependent step.
|
| 1860 |
if isinstance(state.get("agent_state"), dict):
|
| 1861 |
agent.load_state(state["agent_state"])
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1862 |
|
| 1863 |
user_lit_context = str(state.get("user_lit_context") or "")
|
| 1864 |
user_data_md = str(state.get("user_data_md") or "")
|
|
@@ -2725,15 +2743,10 @@ Output only final academic prose. Do not report the word count."""
|
|
| 2725 |
repeated_quotes = agent.find_repeated_qualitative_quotes(full_draft_en, data_metadata)
|
| 2726 |
if repeated_quotes:
|
| 2727 |
raise RuntimeError(f"Repeated qualitative quotations: {repeated_quotes[:5]}")
|
| 2728 |
-
|
| 2729 |
-
|
| 2730 |
-
|
| 2731 |
-
|
| 2732 |
-
for tag in sorted(agent.used_citations, key=agent._citation_tag_number):
|
| 2733 |
-
ref = str((agent.citation_map.get(tag) or {}).get("ref") or "").strip()
|
| 2734 |
-
if ref and ref not in seen_refs:
|
| 2735 |
-
references.append(f"- {ref}")
|
| 2736 |
-
seen_refs.add(ref)
|
| 2737 |
if not references:
|
| 2738 |
self._record_evidence_coverage_event(
|
| 2739 |
state, [("final validated references", 0, 1)],
|
|
@@ -2754,7 +2767,7 @@ Output only final academic prose. Do not report the word count."""
|
|
| 2754 |
citation_required_claim_counts_by_unit.get(unit_name, 0) or 0
|
| 2755 |
)
|
| 2756 |
method_section_id = 'methods' if mode == 'asq' else 'methodology'
|
| 2757 |
-
method_validated_tags = set(validated_by_top_level.get(method_section_id) or []) & set(
|
| 2758 |
method_authority_tags = {
|
| 2759 |
tag for tag in method_validated_tags
|
| 2760 |
if 'method_authority' in set((agent.citation_map.get(tag) or {}).get('evidence_lanes') or [])
|
|
@@ -2813,6 +2826,7 @@ Output only final academic prose. Do not report the word count."""
|
|
| 2813 |
)
|
| 2814 |
agent.validate_manuscript_structure(body_only, adaptive_structure, translated=False)
|
| 2815 |
agent.enforce_resolved_claim_specific_citations(body_only)
|
|
|
|
| 2816 |
|
| 2817 |
# Body is now frozen. Generate both Final Abstract and Final Keywords
|
| 2818 |
# from the completed manuscript/evidence, then replace the provisional
|
|
@@ -2832,6 +2846,10 @@ Output only final academic prose. Do not report the word count."""
|
|
| 2832 |
)
|
| 2833 |
agent.validate_manuscript_structure(body_only, adaptive_structure, translated=False)
|
| 2834 |
agent.enforce_resolved_claim_specific_citations(body_only)
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2835 |
self._upsert_quality_diagnostic(
|
| 2836 |
quality_diagnostics, self._validate_target_length(full_draft_en, target_words)
|
| 2837 |
)
|
|
@@ -2866,6 +2884,8 @@ Output only final academic prose. Do not report the word count."""
|
|
| 2866 |
"final_keywords": final_keywords,
|
| 2867 |
"front_matter_phase": "final",
|
| 2868 |
"final_reference_count": final_reference_count,
|
|
|
|
|
|
|
| 2869 |
"evidence_status": str(state.get("evidence_status") or "adequate"),
|
| 2870 |
"evidence_risk_detected": bool(state.get("evidence_risk_detected")),
|
| 2871 |
"evidence_coverage_events": list(state.get("evidence_coverage_events") or []),
|
|
@@ -2951,6 +2971,7 @@ Output only final academic prose. Do not report the word count."""
|
|
| 2951 |
full_draft_translated, target_lang
|
| 2952 |
)
|
| 2953 |
agent.validate_translation_alignment(full_draft_en, full_draft_translated)
|
|
|
|
| 2954 |
self._publish_target_preview(
|
| 2955 |
full_draft_translated, 'translation_complete', force=True, complete=True
|
| 2956 |
)
|
|
|
|
| 44 |
"discussion": (2, 220),
|
| 45 |
}
|
| 46 |
TRANSLATION_CONTRACT_VERSION = 'hierarchical-multilingual-v8'
|
| 47 |
+
FINAL_CITATION_LEDGER_VERSION = 'resolved-final-ledger-v1'
|
| 48 |
|
| 49 |
|
| 50 |
def _clean_trailing_references(text, keywords=("reference", "references", "reference list", "参考文献")):
|
|
|
|
| 79 |
if "chinese" not in language.casefold() and "中文" not in language:
|
| 80 |
return value
|
| 81 |
value = value.replace("\r\n", "\n").replace("\r", "\n")
|
| 82 |
+
# The English References tail is immutable data, not target-language prose.
|
| 83 |
+
# Chinese punctuation normalization therefore stops before References.
|
| 84 |
+
reference_match = re.search(r"(?m)^#\s+参考文献\s*$", value)
|
| 85 |
+
frozen_reference_tail = ""
|
| 86 |
+
if reference_match:
|
| 87 |
+
frozen_reference_tail = value[reference_match.start():]
|
| 88 |
+
value = value[:reference_match.start()]
|
| 89 |
lines = value.split("\n")
|
| 90 |
normalized = []
|
| 91 |
in_fence = False
|
|
|
|
| 101 |
current = re.sub(r"[ \t]+([,。!?;:])", r"\1", line)
|
| 102 |
current = re.sub(r"([,。!?;:])[ \t]+(?=\S)", r"\1", current)
|
| 103 |
normalized.append(current)
|
| 104 |
+
normalized_body = "\n".join(normalized)
|
| 105 |
+
return normalized_body + frozen_reference_tail
|
| 106 |
|
| 107 |
|
| 108 |
class _OrderedTranslationWorker:
|
|
|
|
| 1868 |
# Restore agent state before continuing any dependent step.
|
| 1869 |
if isinstance(state.get("agent_state"), dict):
|
| 1870 |
agent.load_state(state["agent_state"])
|
| 1871 |
+
if state.get("english_finalized") and state.get("final_citation_ledger_version") != FINAL_CITATION_LEDGER_VERSION:
|
| 1872 |
+
# R4-R3: an R4-R2 checkpoint may contain a correct resolved body but
|
| 1873 |
+
# an empty References tail caused by rescanning already-resolved CITE
|
| 1874 |
+
# tags. Re-enter only finalization/translation/package gates; completed
|
| 1875 |
+
# English units and the same task/reservation remain authoritative.
|
| 1876 |
+
logging.warning("Final citation ledger contract advanced; re-running finalization from frozen completed units.")
|
| 1877 |
+
state["english_finalized"] = False
|
| 1878 |
+
state["translation_complete"] = False
|
| 1879 |
+
state["package_validated"] = False
|
| 1880 |
|
| 1881 |
user_lit_context = str(state.get("user_lit_context") or "")
|
| 1882 |
user_data_md = str(state.get("user_data_md") or "")
|
|
|
|
| 2743 |
repeated_quotes = agent.find_repeated_qualitative_quotes(full_draft_en, data_metadata)
|
| 2744 |
if repeated_quotes:
|
| 2745 |
raise RuntimeError(f"Repeated qualitative quotations: {repeated_quotes[:5]}")
|
| 2746 |
+
# R4-R3: the body is already citation-resolved at this point. Never
|
| 2747 |
+
# rescan it for raw CITE tags; that destroys a valid citation ledger.
|
| 2748 |
+
final_citation_ledger = agent.finalize_resolved_citation_ledger(full_draft_en)
|
| 2749 |
+
references = agent.build_final_reference_lines(final_citation_ledger)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2750 |
if not references:
|
| 2751 |
self._record_evidence_coverage_event(
|
| 2752 |
state, [("final validated references", 0, 1)],
|
|
|
|
| 2767 |
citation_required_claim_counts_by_unit.get(unit_name, 0) or 0
|
| 2768 |
)
|
| 2769 |
method_section_id = 'methods' if mode == 'asq' else 'methodology'
|
| 2770 |
+
method_validated_tags = set(validated_by_top_level.get(method_section_id) or []) & set(final_citation_ledger)
|
| 2771 |
method_authority_tags = {
|
| 2772 |
tag for tag in method_validated_tags
|
| 2773 |
if 'method_authority' in set((agent.citation_map.get(tag) or {}).get('evidence_lanes') or [])
|
|
|
|
| 2826 |
)
|
| 2827 |
agent.validate_manuscript_structure(body_only, adaptive_structure, translated=False)
|
| 2828 |
agent.enforce_resolved_claim_specific_citations(body_only)
|
| 2829 |
+
agent.validate_final_citation_bijection(body_only, _reference_tail, final_citation_ledger)
|
| 2830 |
|
| 2831 |
# Body is now frozen. Generate both Final Abstract and Final Keywords
|
| 2832 |
# from the completed manuscript/evidence, then replace the provisional
|
|
|
|
| 2846 |
)
|
| 2847 |
agent.validate_manuscript_structure(body_only, adaptive_structure, translated=False)
|
| 2848 |
agent.enforce_resolved_claim_specific_citations(body_only)
|
| 2849 |
+
post_front_ledger = agent.finalize_resolved_citation_ledger(body_only)
|
| 2850 |
+
if set(post_front_ledger) != set(final_citation_ledger):
|
| 2851 |
+
raise RuntimeError("final_front_matter_changed_citation_ledger")
|
| 2852 |
+
agent.validate_final_citation_bijection(body_only, _reference_tail, final_citation_ledger)
|
| 2853 |
self._upsert_quality_diagnostic(
|
| 2854 |
quality_diagnostics, self._validate_target_length(full_draft_en, target_words)
|
| 2855 |
)
|
|
|
|
| 2884 |
"final_keywords": final_keywords,
|
| 2885 |
"front_matter_phase": "final",
|
| 2886 |
"final_reference_count": final_reference_count,
|
| 2887 |
+
"final_citation_ledger": list(final_citation_ledger),
|
| 2888 |
+
"final_citation_ledger_version": FINAL_CITATION_LEDGER_VERSION,
|
| 2889 |
"evidence_status": str(state.get("evidence_status") or "adequate"),
|
| 2890 |
"evidence_risk_detected": bool(state.get("evidence_risk_detected")),
|
| 2891 |
"evidence_coverage_events": list(state.get("evidence_coverage_events") or []),
|
|
|
|
| 2971 |
full_draft_translated, target_lang
|
| 2972 |
)
|
| 2973 |
agent.validate_translation_alignment(full_draft_en, full_draft_translated)
|
| 2974 |
+
agent.validate_reference_preservation(full_draft_en, full_draft_translated, target_lang)
|
| 2975 |
self._publish_target_preview(
|
| 2976 |
full_draft_translated, 'translation_complete', force=True, complete=True
|
| 2977 |
)
|
core/pipeline_agent.py
CHANGED
|
@@ -1794,6 +1794,151 @@ USER KEYWORD SEEDS: {keywords}
|
|
| 1794 |
)
|
| 1795 |
return body, tail
|
| 1796 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1797 |
@staticmethod
|
| 1798 |
def find_repeated_qualitative_quotes(text, data_metadata):
|
| 1799 |
violations = []
|
|
@@ -7400,6 +7545,12 @@ Decision must be CONFIRMED_BLOCKING or NOT_CONFIRMED. evidence_code must be one
|
|
| 7400 |
'Russian whole-unit language signal diagnostic | kazakh_specific=%s cyrillic=%s | nonfatal=true',
|
| 7401 |
len(kazakh_specific), len(cyrillic),
|
| 7402 |
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 7403 |
return True
|
| 7404 |
|
| 7405 |
def enforce_claim_specific_citations(self, text):
|
|
|
|
| 1794 |
)
|
| 1795 |
return body, tail
|
| 1796 |
|
| 1797 |
+
@staticmethod
|
| 1798 |
+
def _resolved_parenthetical_citations(text):
|
| 1799 |
+
"""Return resolved parenthetical author-year citation markers in prose.
|
| 1800 |
+
|
| 1801 |
+
This deliberately excludes bare years such as ``(2026)``. SmartPaper's
|
| 1802 |
+
claim-specific citation compiler emits parenthetical author-year markers,
|
| 1803 |
+
so any such marker in the frozen body must be traceable to citation_map.
|
| 1804 |
+
"""
|
| 1805 |
+
pattern = re.compile(
|
| 1806 |
+
r"\((?=[^()\n]*[A-Za-zÀ-ÖØ-öø-ÿ'’.-]+(?:\s+(?:&\s+[A-Za-zÀ-ÖØ-öø-ÿ'’.-]+|et\s+al\.))?\s*,\s*(?:19|20)\d{2}[a-z]?\b)[^()\n]+\)",
|
| 1807 |
+
re.IGNORECASE,
|
| 1808 |
+
)
|
| 1809 |
+
return [match.group(0).strip() for match in pattern.finditer(str(text or ''))]
|
| 1810 |
+
|
| 1811 |
+
def finalize_resolved_citation_ledger(self, resolved_body):
|
| 1812 |
+
"""Freeze the final citation ledger from the *resolved* manuscript body.
|
| 1813 |
+
|
| 1814 |
+
R4-R3 authority rule: once ``[CITE-XX]`` has been compiled to
|
| 1815 |
+
``(Author, year)``, raw CITE tags are no longer a valid source of truth.
|
| 1816 |
+
The final ledger is rebuilt from the resolved markers that are actually
|
| 1817 |
+
present in the frozen body and the immutable citation_map metadata.
|
| 1818 |
+
Stale pre-deletion citations are dropped; orphan/ambiguous citations fail
|
| 1819 |
+
closed rather than producing a misleading References list.
|
| 1820 |
+
"""
|
| 1821 |
+
body = str(resolved_body or '').strip()
|
| 1822 |
+
if re.search(r'CITE-\d+', body, re.I):
|
| 1823 |
+
raise DeepSeekRequestError('Final citation ledger received unresolved CITE tags.')
|
| 1824 |
+
|
| 1825 |
+
marker_index = {}
|
| 1826 |
+
for tag, metadata in (self.citation_map or {}).items():
|
| 1827 |
+
marker = str((metadata or {}).get('in_text') or '').strip()
|
| 1828 |
+
ref = str((metadata or {}).get('ref') or '').strip()
|
| 1829 |
+
if not marker or not ref:
|
| 1830 |
+
continue
|
| 1831 |
+
marker_index.setdefault(marker, []).append(str(tag))
|
| 1832 |
+
|
| 1833 |
+
observed = self._resolved_parenthetical_citations(body)
|
| 1834 |
+
orphan = sorted({marker for marker in observed if marker not in marker_index})
|
| 1835 |
+
if orphan:
|
| 1836 |
+
raise DeepSeekRequestError(
|
| 1837 |
+
'Final citation ledger found resolved citation(s) without authoritative metadata: '
|
| 1838 |
+
+ '; '.join(orphan[:5])
|
| 1839 |
+
)
|
| 1840 |
+
|
| 1841 |
+
ledger = set()
|
| 1842 |
+
ambiguous = []
|
| 1843 |
+
for marker in sorted(set(observed)):
|
| 1844 |
+
tags = marker_index.get(marker) or []
|
| 1845 |
+
if len(tags) == 1:
|
| 1846 |
+
ledger.add(tags[0])
|
| 1847 |
+
continue
|
| 1848 |
+
distinct_refs = {
|
| 1849 |
+
str((self.citation_map.get(tag) or {}).get('ref') or '').strip()
|
| 1850 |
+
for tag in tags
|
| 1851 |
+
if str((self.citation_map.get(tag) or {}).get('ref') or '').strip()
|
| 1852 |
+
}
|
| 1853 |
+
previously_used = [tag for tag in tags if tag in set(self.used_citations or set())]
|
| 1854 |
+
if len(distinct_refs) == 1:
|
| 1855 |
+
ledger.add(sorted(tags, key=self._citation_tag_number)[0])
|
| 1856 |
+
elif len(previously_used) == 1:
|
| 1857 |
+
ledger.add(previously_used[0])
|
| 1858 |
+
else:
|
| 1859 |
+
ambiguous.append((marker, sorted(tags, key=self._citation_tag_number)))
|
| 1860 |
+
if ambiguous:
|
| 1861 |
+
raise DeepSeekRequestError(
|
| 1862 |
+
'Final citation ledger found ambiguous author-year marker(s): '
|
| 1863 |
+
+ '; '.join(f"{marker} -> {tags}" for marker, tags in ambiguous[:5])
|
| 1864 |
+
)
|
| 1865 |
+
|
| 1866 |
+
self.used_citations = set(ledger)
|
| 1867 |
+
logging.info(
|
| 1868 |
+
'Final citation ledger frozen: resolved_markers=%s unique_references=%s',
|
| 1869 |
+
len(observed), len(ledger),
|
| 1870 |
+
)
|
| 1871 |
+
return sorted(ledger, key=self._citation_tag_number)
|
| 1872 |
+
|
| 1873 |
+
def build_final_reference_lines(self, ledger_tags):
|
| 1874 |
+
"""Build one English reference entry per authoritative ledger source."""
|
| 1875 |
+
references = {}
|
| 1876 |
+
for tag in ledger_tags or []:
|
| 1877 |
+
metadata = self.citation_map.get(str(tag)) or {}
|
| 1878 |
+
ref = str(metadata.get('ref') or '').strip()
|
| 1879 |
+
if not ref:
|
| 1880 |
+
raise DeepSeekRequestError(f'Final citation ledger metadata missing reference text: {tag}')
|
| 1881 |
+
references.setdefault(re.sub(r'\s+', ' ', ref).casefold(), ref)
|
| 1882 |
+
# Reader-facing References follow the conventional alphabetical order.
|
| 1883 |
+
return [f"- {ref}" for ref in sorted(references.values(), key=lambda value: value.casefold())]
|
| 1884 |
+
|
| 1885 |
+
def validate_final_citation_bijection(self, resolved_body, reference_tail, ledger_tags):
|
| 1886 |
+
"""Require exact bidirectional consistency between prose and References."""
|
| 1887 |
+
body = str(resolved_body or '').strip()
|
| 1888 |
+
tail = str(reference_tail or '').strip()
|
| 1889 |
+
ledger = list(ledger_tags or [])
|
| 1890 |
+
expected = []
|
| 1891 |
+
for tag in ledger:
|
| 1892 |
+
metadata = self.citation_map.get(str(tag)) or {}
|
| 1893 |
+
marker = str(metadata.get('in_text') or '').strip()
|
| 1894 |
+
ref = str(metadata.get('ref') or '').strip()
|
| 1895 |
+
if not marker or not ref:
|
| 1896 |
+
raise DeepSeekRequestError(f'Final citation ledger is incomplete: {tag}')
|
| 1897 |
+
if marker not in body:
|
| 1898 |
+
raise DeepSeekRequestError(f'Final citation ledger contains a citation absent from body: {tag}')
|
| 1899 |
+
expected.append(re.sub(r'\s+', ' ', ref).strip())
|
| 1900 |
+
|
| 1901 |
+
actual = [
|
| 1902 |
+
re.sub(r'\s+', ' ', line[2:]).strip()
|
| 1903 |
+
for line in tail.splitlines()
|
| 1904 |
+
if line.strip().startswith('- ')
|
| 1905 |
+
]
|
| 1906 |
+
if len(actual) != len(set(actual)):
|
| 1907 |
+
raise DeepSeekRequestError('Final References contains duplicate entries.')
|
| 1908 |
+
if set(actual) != set(expected):
|
| 1909 |
+
missing = sorted(set(expected) - set(actual))[:5]
|
| 1910 |
+
extra = sorted(set(actual) - set(expected))[:5]
|
| 1911 |
+
raise DeepSeekRequestError(
|
| 1912 |
+
f'Final citation-reference bijection failed: missing={missing}, extra={extra}.'
|
| 1913 |
+
)
|
| 1914 |
+
|
| 1915 |
+
frozen = self.finalize_resolved_citation_ledger(body)
|
| 1916 |
+
if set(frozen) != set(ledger):
|
| 1917 |
+
raise DeepSeekRequestError(
|
| 1918 |
+
'Final citation-reference bijection failed: resolved-body ledger changed after References assembly.'
|
| 1919 |
+
)
|
| 1920 |
+
return True
|
| 1921 |
+
|
| 1922 |
+
@classmethod
|
| 1923 |
+
def validate_reference_preservation(cls, english_text, translated_text, target_lang):
|
| 1924 |
+
"""References are immutable English data in ZH/RU/KK deliverables."""
|
| 1925 |
+
source = str(english_text or '').strip()
|
| 1926 |
+
target = str(translated_text or '').strip()
|
| 1927 |
+
source_parts = re.split(r'(?m)^#\s+References\s*$', source, maxsplit=1)
|
| 1928 |
+
if len(source_parts) != 2:
|
| 1929 |
+
raise DeepSeekRequestError('Reference preservation requires one English References tail.')
|
| 1930 |
+
source_tail = source_parts[1].strip()
|
| 1931 |
+
heading = re.escape(cls._localized_reference_heading(target_lang))
|
| 1932 |
+
target_parts = re.split(rf'(?m)^#\s+{heading}\s*$', target, maxsplit=1)
|
| 1933 |
+
if len(target_parts) != 2:
|
| 1934 |
+
raise DeepSeekRequestError('Target manuscript is missing the localized References heading.')
|
| 1935 |
+
target_tail = target_parts[1].strip()
|
| 1936 |
+
if source_tail != target_tail:
|
| 1937 |
+
raise DeepSeekRequestError(
|
| 1938 |
+
'Target-language References changed from the frozen English References tail.'
|
| 1939 |
+
)
|
| 1940 |
+
return True
|
| 1941 |
+
|
| 1942 |
@staticmethod
|
| 1943 |
def find_repeated_qualitative_quotes(text, data_metadata):
|
| 1944 |
violations = []
|
|
|
|
| 7545 |
'Russian whole-unit language signal diagnostic | kazakh_specific=%s cyrillic=%s | nonfatal=true',
|
| 7546 |
len(kazakh_specific), len(cyrillic),
|
| 7547 |
)
|
| 7548 |
+
if re.search(r'(?m)^#\s+References\s*$', source):
|
| 7549 |
+
cls.validate_reference_preservation(source, target, target_lang=
|
| 7550 |
+
'Russian (Русский)' if re.search(r'(?m)^#\s+Литература\s*$', target) else
|
| 7551 |
+
'Kazakh (Қазақша)' if re.search(r'(?m)^#\s+Пайдаланылған әдебиеттер\s*$', target) else
|
| 7552 |
+
'Simplified Chinese (简体中文)'
|
| 7553 |
+
)
|
| 7554 |
return True
|
| 7555 |
|
| 7556 |
def enforce_claim_specific_citations(self, text):
|