Spaces:
Running
Running
R14-TQ1-R4-R2: typed defect evidence and non-convergent claim recovery
#52
by GuXiaopeng - opened
- core/generation_runner.py +2 -2
- core/pipeline_agent.py +280 -38
core/generation_runner.py
CHANGED
|
@@ -1327,7 +1327,7 @@ class GenerationRunner:
|
|
| 1327 |
'translated_text': '',
|
| 1328 |
'qa_status': 'repair_pending',
|
| 1329 |
'claim_states': dict(getattr(agent, '_last_translation_claim_states', {}) or {}),
|
| 1330 |
-
'claim_state_version': 'translation-claim-state-
|
| 1331 |
'error_code': error_code,
|
| 1332 |
'attempts': int(existing.get('attempts') or 0) + 1,
|
| 1333 |
'deferred_at': datetime.now(timezone.utc).isoformat(),
|
|
@@ -1359,7 +1359,7 @@ class GenerationRunner:
|
|
| 1359 |
'language_adapter': str(result.get('language_adapter') or ''),
|
| 1360 |
'diagnostics': list(result.get('diagnostics') or [])[-20:],
|
| 1361 |
'claim_states': dict(result.get('claim_states') or {}),
|
| 1362 |
-
'claim_state_version': str(result.get('claim_state_version') or 'translation-claim-state-
|
| 1363 |
'attempts': int(result.get('attempts') or 1),
|
| 1364 |
'completed_at': datetime.now(timezone.utc).isoformat(),
|
| 1365 |
}
|
|
|
|
| 1327 |
'translated_text': '',
|
| 1328 |
'qa_status': 'repair_pending',
|
| 1329 |
'claim_states': dict(getattr(agent, '_last_translation_claim_states', {}) or {}),
|
| 1330 |
+
'claim_state_version': 'translation-claim-state-v2',
|
| 1331 |
'error_code': error_code,
|
| 1332 |
'attempts': int(existing.get('attempts') or 0) + 1,
|
| 1333 |
'deferred_at': datetime.now(timezone.utc).isoformat(),
|
|
|
|
| 1359 |
'language_adapter': str(result.get('language_adapter') or ''),
|
| 1360 |
'diagnostics': list(result.get('diagnostics') or [])[-20:],
|
| 1361 |
'claim_states': dict(result.get('claim_states') or {}),
|
| 1362 |
+
'claim_state_version': str(result.get('claim_state_version') or 'translation-claim-state-v2'),
|
| 1363 |
'attempts': int(result.get('attempts') or 1),
|
| 1364 |
'completed_at': datetime.now(timezone.utc).isoformat(),
|
| 1365 |
}
|
core/pipeline_agent.py
CHANGED
|
@@ -11,6 +11,7 @@ import tiktoken
|
|
| 11 |
import sys
|
| 12 |
import logging
|
| 13 |
import hashlib
|
|
|
|
| 14 |
import concurrent.futures
|
| 15 |
from collections import Counter
|
| 16 |
from datetime import datetime, timezone
|
|
@@ -5021,6 +5022,10 @@ SECTION: {section_name}
|
|
| 5021 |
'CONDITION_SCOPE_CHANGED','COMPARISON_DIRECTION_CHANGED','TEMPORAL_SCOPE_CHANGED',
|
| 5022 |
'CLAIM_MISSING','CLAIM_ADDED','MEANING_CHANGED',
|
| 5023 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
| 5024 |
if not isinstance(getattr(self, '_translation_claim_qa_evidence', None), dict):
|
| 5025 |
self._translation_claim_qa_evidence = {}
|
| 5026 |
claim_rows = []
|
|
@@ -5062,7 +5067,7 @@ Review EACH claim independently. Do not rewrite and do not provide free-form exp
|
|
| 5062 |
LANGUAGE ADAPTER: {profile['adapter_id']}; morphology={profile['morphology']}. {profile['term_policy']} {profile['contamination_policy']}
|
| 5063 |
For Russian, do not flag a terminology error merely because a translated term is inflected for grammatical case/number/gender. Russian LANGUAGE_CONTAMINATION includes sustained unprotected English prose and ordinary untranslated English lexemes; never use it for protected acronyms, AI/LLM/SEM, model or tool names, variable names, citations, canonical glossary terms, or source-matched proper names/titles. For Chinese, ordinary untranslated English lexical words are contamination while protected technical objects remain legal. For Kazakh, do not flag a terminology error merely because productive suffixes attach to the correct term stem. Kazakh LANGUAGE_CONTAMINATION includes ordinary untranslated English lexemes and clearly Russian lexical/syntactic intrusions not justified by shared international terminology; Cyrillic script or absence of Kazakh-specific letters alone is never sufficient evidence.
|
| 5064 |
Return valid JSON only in this exact form: {{"items":[{{"id":"SP-P000001-C000001","ok":true,"codes":[],"evidence_codes":[]}}]}}.
|
| 5065 |
-
If ok=false, codes MUST contain only values from allowed_issue_codes. For SEMANTIC_DRIFT, NEGATION_DRIFT, CAUSALITY_STRENGTH_DRIFT, UNCERTAINTY_DRIFT, OMITTED_CLAIM, or ADDED_CLAIM, evidence_codes should identify the affected semantic dimension using only: {sorted(allowed_semantic_evidence)}. Use the narrowest supported evidence;
|
| 5066 |
response = self.call_deepseek(
|
| 5067 |
system_prompt, json.dumps(payload, ensure_ascii=False),
|
| 5068 |
max_tokens=max(1200, min(3600, 650 + 260 * len(rows))),
|
|
@@ -5083,10 +5088,12 @@ If ok=false, codes MUST contain only values from allowed_issue_codes. For SEMANT
|
|
| 5083 |
meta = row_lookup[row_id]
|
| 5084 |
raw_codes = [str(code or '').strip().upper() for code in (returned_row.get('codes') or [])]
|
| 5085 |
codes = [code for code in raw_codes if code in allowed_codes]
|
| 5086 |
-
|
| 5087 |
str(code or '').strip().upper() for code in (returned_row.get('evidence_codes') or [])
|
| 5088 |
-
if str(code or '').strip()
|
| 5089 |
]
|
|
|
|
|
|
|
| 5090 |
semantic_code_set = {
|
| 5091 |
'SEMANTIC_DRIFT','NEGATION_DRIFT','CAUSALITY_STRENGTH_DRIFT',
|
| 5092 |
'UNCERTAINTY_DRIFT','OMITTED_CLAIM','ADDED_CLAIM',
|
|
@@ -5103,9 +5110,19 @@ If ok=false, codes MUST contain only values from allowed_issue_codes. For SEMANT
|
|
| 5103 |
fallback = derived.get(issue_code)
|
| 5104 |
if fallback and fallback not in evidence_codes:
|
| 5105 |
evidence_codes.append(fallback)
|
| 5106 |
-
|
| 5107 |
-
'
|
| 5108 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 5109 |
model_contamination = 'LANGUAGE_CONTAMINATION' in codes
|
| 5110 |
lexical = self._translation_lexical_purity_evidence(
|
| 5111 |
meta['english'], meta['target'], target_lang, relevant_glossary
|
|
@@ -5497,20 +5514,78 @@ Translate headings and terminology naturally. Preserve standard variable abbrevi
|
|
| 5497 |
restored[block_id] = recovered[block_id]
|
| 5498 |
return restored
|
| 5499 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 5500 |
def _translate_single_claim_with_recovery(
|
| 5501 |
self, block_id, claim_id, source_claim, system_prompt, target_lang, issue_codes,
|
| 5502 |
-
current_target_claim=None, repair_evidence=None,
|
| 5503 |
):
|
| 5504 |
-
"""
|
| 5505 |
protected_claim, mapping = self._protect_translation_tokens(source_claim, claim_id=claim_id)
|
| 5506 |
marked_source = f'[[SP-P{block_id}]]\n[[SP-{claim_id}]]\n{protected_claim}'
|
| 5507 |
safe_manifest = self._model_safe_translation_manifest(mapping)
|
| 5508 |
codes = [str(code or '').strip().upper() for code in (issue_codes or []) if str(code or '').strip()]
|
| 5509 |
-
evidence_payload = {
|
| 5510 |
-
'issue_codes': codes[:5],
|
| 5511 |
-
'current_target_claim': str(current_target_claim or '').strip(),
|
| 5512 |
-
}
|
| 5513 |
supplied_evidence = dict(repair_evidence or {})
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 5514 |
lexical_tokens = [
|
| 5515 |
str(token or '').strip() for token in (supplied_evidence.get('detected_unprotected_english_tokens') or [])
|
| 5516 |
if str(token or '').strip()
|
|
@@ -5523,13 +5598,25 @@ Translate headings and terminology naturally. Preserve standard variable abbrevi
|
|
| 5523 |
]
|
| 5524 |
if semantic_delta_codes:
|
| 5525 |
evidence_payload['semantic_delta_codes'] = semantic_delta_codes[:6]
|
| 5526 |
-
|
| 5527 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 5528 |
+ json.dumps(codes[:5], ensure_ascii=False)
|
| 5529 |
+ '. Use the bounded detector evidence below only to repair the supplied claim; do not invent additional defects. '
|
| 5530 |
-
'
|
| 5531 |
-
'If detected_unprotected_english_tokens is non-empty, every listed token is deterministic lexical-purity evidence
|
| 5532 |
-
'If semantic_delta_codes is non-empty,
|
|
|
|
| 5533 |
'Preserve every SP-P, SP-C, and SP-T marker byte-for-byte. Do not add claims or factual content. Return only the marked translated claim.'
|
| 5534 |
+ '\n<CLAIM_REPAIR_EVIDENCE>\n'
|
| 5535 |
+ json.dumps(evidence_payload, ensure_ascii=False)
|
|
@@ -5605,10 +5692,13 @@ Translate headings and terminology naturally. Preserve standard variable abbrevi
|
|
| 5605 |
return segments[claim_id]
|
| 5606 |
|
| 5607 |
@classmethod
|
| 5608 |
-
def _snapshot_translation_claim_states(
|
| 5609 |
-
|
|
|
|
|
|
|
| 5610 |
pending = dict(pending_issues or {})
|
| 5611 |
evidence_map = dict(repair_evidence or {})
|
|
|
|
| 5612 |
states = {}
|
| 5613 |
for block_id, source_block in dict(source_blocks or {}).items():
|
| 5614 |
if block_id not in dict(translations or {}):
|
|
@@ -5625,6 +5715,7 @@ Translate headings and terminology naturally. Preserve standard variable abbrevi
|
|
| 5625 |
if not target_text:
|
| 5626 |
continue
|
| 5627 |
codes = list((pending.get(block_id) or {}).get(claim_id) or [])
|
|
|
|
| 5628 |
states[f'SP-P{block_id}-{claim_id}'] = {
|
| 5629 |
'block_id': str(block_id),
|
| 5630 |
'claim_id': str(claim_id),
|
|
@@ -5633,6 +5724,11 @@ Translate headings and terminology naturally. Preserve standard variable abbrevi
|
|
| 5633 |
'qa_status': 'repair_pending' if codes else 'passed',
|
| 5634 |
'issue_codes': [str(code or '').strip().upper() for code in codes if str(code or '').strip()][:5],
|
| 5635 |
'repair_evidence': dict(evidence_map.get((block_id, claim_id)) or {}),
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 5636 |
}
|
| 5637 |
return states
|
| 5638 |
|
|
@@ -5704,17 +5800,22 @@ Translate headings and terminology naturally. Preserve standard variable abbrevi
|
|
| 5704 |
lexical = self._translation_lexical_purity_evidence(
|
| 5705 |
source_claims[claim_id], target_claims[claim_id], target_lang, glossary
|
| 5706 |
)
|
| 5707 |
-
|
|
|
|
|
|
|
| 5708 |
if profile.get('language_code') == 'ru' and not deterministic:
|
| 5709 |
evidence = self._russian_contamination_evidence(
|
| 5710 |
source_claims[claim_id], target_claims[claim_id], glossary
|
| 5711 |
)
|
| 5712 |
deterministic = bool(evidence.get('confirmed'))
|
|
|
|
|
|
|
| 5713 |
if deterministic:
|
| 5714 |
rows.append({
|
| 5715 |
'id': f'SP-P{block_id}-{claim_id}', 'block_id': block_id,
|
| 5716 |
'claim_id': claim_id, 'code': code, 'english': source_claims[claim_id],
|
| 5717 |
'target': target_claims[claim_id], 'deterministic_confirmed': True,
|
|
|
|
| 5718 |
})
|
| 5719 |
continue
|
| 5720 |
rows.append({
|
|
@@ -5731,7 +5832,10 @@ Translate headings and terminology naturally. Preserve standard variable abbrevi
|
|
| 5731 |
self._record_translation_assurance(
|
| 5732 |
'QA_ADJUDICATION_CONFIRMED', target_lang=target_lang, block_id=row['id'],
|
| 5733 |
severity='critical', decision='HOLD_FINAL',
|
| 5734 |
-
detail={
|
|
|
|
|
|
|
|
|
|
| 5735 |
)
|
| 5736 |
|
| 5737 |
if not model_rows:
|
|
@@ -5888,6 +5992,21 @@ Decision must be CONFIRMED_BLOCKING or NOT_CONFIRMED. evidence_code must be one
|
|
| 5888 |
last_error = None
|
| 5889 |
self._last_translation_claim_states = {}
|
| 5890 |
self._translation_claim_qa_evidence = {}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 5891 |
|
| 5892 |
def emit_stage(stage):
|
| 5893 |
if not callable(stage_callback):
|
|
@@ -5931,7 +6050,8 @@ Decision must be CONFIRMED_BLOCKING or NOT_CONFIRMED. evidence_code must be one
|
|
| 5931 |
section_name=section_name or 'frozen section', max_attempts=2,
|
| 5932 |
)
|
| 5933 |
self._last_translation_claim_states = self._snapshot_translation_claim_states(
|
| 5934 |
-
source_blocks, translations, quality_issues, self._translation_claim_qa_evidence
|
|
|
|
| 5935 |
)
|
| 5936 |
local_round = 0
|
| 5937 |
while quality_issues and local_round < 2:
|
|
@@ -5951,6 +6071,7 @@ Decision must be CONFIRMED_BLOCKING or NOT_CONFIRMED. evidence_code must be one
|
|
| 5951 |
)
|
| 5952 |
emit_stage('repairing')
|
| 5953 |
touched_blocks = set()
|
|
|
|
| 5954 |
for block_id, claim_id, codes in failed_pairs:
|
| 5955 |
source_claims = {
|
| 5956 |
item['claim_id']: item['text'] for item in self._translation_claim_units(source_blocks[block_id])
|
|
@@ -5962,16 +6083,56 @@ Decision must be CONFIRMED_BLOCKING or NOT_CONFIRMED. evidence_code must be one
|
|
| 5962 |
claim_order = list(source_claims)
|
| 5963 |
_order, segments = self._parse_translation_claim_segments(translations[block_id], claim_order)
|
| 5964 |
current_target_claim = segments[claim_id]
|
| 5965 |
-
|
| 5966 |
-
|
| 5967 |
-
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 5968 |
if repair_evidence.get('semantic_delta_codes'):
|
| 5969 |
self._record_translation_assurance(
|
| 5970 |
'SEMANTIC_REPAIR_EVIDENCE', target_lang=target_lang,
|
| 5971 |
block_id=f'SP-P{block_id}-{claim_id}', severity='repair', decision='REPAIR_LOCAL',
|
| 5972 |
detail={'deltas': '|'.join(repair_evidence.get('semantic_delta_codes') or [])[:180]},
|
| 5973 |
)
|
| 5974 |
-
if '
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 5975 |
lexical = self._translation_lexical_purity_evidence(
|
| 5976 |
source_claims[claim_id], current_target_claim, target_lang, frozen_glossary
|
| 5977 |
)
|
|
@@ -5985,11 +6146,70 @@ Decision must be CONFIRMED_BLOCKING or NOT_CONFIRMED. evidence_code must be one
|
|
| 5985 |
'sample': ','.join(lexical.get('tokens') or [])[:120],
|
| 5986 |
},
|
| 5987 |
)
|
| 5988 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 5989 |
block_id, claim_id, source_claims[claim_id], system_prompt, target_lang, codes,
|
| 5990 |
-
|
| 5991 |
)
|
| 5992 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 5993 |
post_repair_lexical = self._translation_lexical_purity_evidence(
|
| 5994 |
source_claims[claim_id], repaired_claim, target_lang, frozen_glossary
|
| 5995 |
)
|
|
@@ -5997,7 +6217,7 @@ Decision must be CONFIRMED_BLOCKING or NOT_CONFIRMED. evidence_code must be one
|
|
| 5997 |
'LEXICAL_PURITY_REPAIR_RESIDUAL' if post_repair_lexical.get('confirmed') else 'LEXICAL_PURITY_REPAIR_CLEARED',
|
| 5998 |
target_lang=target_lang, block_id=f'SP-P{block_id}-{claim_id}',
|
| 5999 |
severity='repair' if post_repair_lexical.get('confirmed') else 'diagnostic',
|
| 6000 |
-
decision='REPAIR_LOCAL' if post_repair_lexical.get('confirmed') else 'PASS_WITH_WARNING',
|
| 6001 |
detail={
|
| 6002 |
'token_count': int(post_repair_lexical.get('token_count') or 0),
|
| 6003 |
'sample': ','.join(post_repair_lexical.get('tokens') or [])[:120],
|
|
@@ -6007,14 +6227,35 @@ Decision must be CONFIRMED_BLOCKING or NOT_CONFIRMED. evidence_code must be one
|
|
| 6007 |
translations[block_id] = self._compose_translation_claim_segments(claim_order, segments)
|
| 6008 |
touched_blocks.add(block_id)
|
| 6009 |
emit_stage('qa')
|
| 6010 |
-
|
| 6011 |
-
|
| 6012 |
-
|
| 6013 |
-
|
| 6014 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 6015 |
self._last_translation_claim_states = self._snapshot_translation_claim_states(
|
| 6016 |
-
source_blocks, translations, quality_issues, self._translation_claim_qa_evidence
|
|
|
|
| 6017 |
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 6018 |
if quality_issues:
|
| 6019 |
emit_stage('qa')
|
| 6020 |
quality_issues = self._adjudicate_persistent_translation_issues(
|
|
@@ -6022,7 +6263,8 @@ Decision must be CONFIRMED_BLOCKING or NOT_CONFIRMED. evidence_code must be one
|
|
| 6022 |
section_name=section_name or 'frozen section',
|
| 6023 |
)
|
| 6024 |
self._last_translation_claim_states = self._snapshot_translation_claim_states(
|
| 6025 |
-
source_blocks, translations, quality_issues, self._translation_claim_qa_evidence
|
|
|
|
| 6026 |
)
|
| 6027 |
if quality_issues:
|
| 6028 |
detail = '; '.join(
|
|
@@ -6061,7 +6303,7 @@ Decision must be CONFIRMED_BLOCKING or NOT_CONFIRMED. evidence_code must be one
|
|
| 6061 |
'language_adapter': profile['adapter_id'],
|
| 6062 |
'diagnostics': diagnostics[-20:],
|
| 6063 |
'claim_states': dict(self._last_translation_claim_states),
|
| 6064 |
-
'claim_state_version': 'translation-claim-state-
|
| 6065 |
}
|
| 6066 |
except PipelineCancelledError:
|
| 6067 |
raise
|
|
|
|
| 11 |
import sys
|
| 12 |
import logging
|
| 13 |
import hashlib
|
| 14 |
+
import inspect
|
| 15 |
import concurrent.futures
|
| 16 |
from collections import Counter
|
| 17 |
from datetime import datetime, timezone
|
|
|
|
| 5022 |
'CONDITION_SCOPE_CHANGED','COMPARISON_DIRECTION_CHANGED','TEMPORAL_SCOPE_CHANGED',
|
| 5023 |
'CLAIM_MISSING','CLAIM_ADDED','MEANING_CHANGED',
|
| 5024 |
}
|
| 5025 |
+
allowed_grammar_evidence = {
|
| 5026 |
+
'CASE_GOVERNMENT','AGREEMENT','MORPHOLOGY','VERB_FORM','WORD_ORDER',
|
| 5027 |
+
'SYNTAX_STRUCTURE','PUNCTUATION','SYNTACTIC_COMPLETENESS','GRAMMAR_UNSPECIFIED',
|
| 5028 |
+
}
|
| 5029 |
if not isinstance(getattr(self, '_translation_claim_qa_evidence', None), dict):
|
| 5030 |
self._translation_claim_qa_evidence = {}
|
| 5031 |
claim_rows = []
|
|
|
|
| 5067 |
LANGUAGE ADAPTER: {profile['adapter_id']}; morphology={profile['morphology']}. {profile['term_policy']} {profile['contamination_policy']}
|
| 5068 |
For Russian, do not flag a terminology error merely because a translated term is inflected for grammatical case/number/gender. Russian LANGUAGE_CONTAMINATION includes sustained unprotected English prose and ordinary untranslated English lexemes; never use it for protected acronyms, AI/LLM/SEM, model or tool names, variable names, citations, canonical glossary terms, or source-matched proper names/titles. For Chinese, ordinary untranslated English lexical words are contamination while protected technical objects remain legal. For Kazakh, do not flag a terminology error merely because productive suffixes attach to the correct term stem. Kazakh LANGUAGE_CONTAMINATION includes ordinary untranslated English lexemes and clearly Russian lexical/syntactic intrusions not justified by shared international terminology; Cyrillic script or absence of Kazakh-specific letters alone is never sufficient evidence.
|
| 5069 |
Return valid JSON only in this exact form: {{"items":[{{"id":"SP-P000001-C000001","ok":true,"codes":[],"evidence_codes":[]}}]}}.
|
| 5070 |
+
If ok=false, codes MUST contain only values from allowed_issue_codes. For SEMANTIC_DRIFT, NEGATION_DRIFT, CAUSALITY_STRENGTH_DRIFT, UNCERTAINTY_DRIFT, OMITTED_CLAIM, or ADDED_CLAIM, evidence_codes should identify the affected semantic dimension using only: {sorted(allowed_semantic_evidence)}. For GRAMMAR, evidence_codes should identify the narrow grammatical defect using only: {sorted(allowed_grammar_evidence)}. Use the narrowest supported evidence; use MEANING_CHANGED or GRAMMAR_UNSPECIFIED only when the narrower dimension cannot be identified from the pair. Do not invent an error dimension. Never return numbered prose, examples, quotes, or corrective wording. Never return replacement text. Return every supplied id exactly once."""
|
| 5071 |
response = self.call_deepseek(
|
| 5072 |
system_prompt, json.dumps(payload, ensure_ascii=False),
|
| 5073 |
max_tokens=max(1200, min(3600, 650 + 260 * len(rows))),
|
|
|
|
| 5088 |
meta = row_lookup[row_id]
|
| 5089 |
raw_codes = [str(code or '').strip().upper() for code in (returned_row.get('codes') or [])]
|
| 5090 |
codes = [code for code in raw_codes if code in allowed_codes]
|
| 5091 |
+
raw_evidence_codes = [
|
| 5092 |
str(code or '').strip().upper() for code in (returned_row.get('evidence_codes') or [])
|
| 5093 |
+
if str(code or '').strip()
|
| 5094 |
]
|
| 5095 |
+
evidence_codes = [code for code in raw_evidence_codes if code in allowed_semantic_evidence]
|
| 5096 |
+
grammar_evidence_codes = [code for code in raw_evidence_codes if code in allowed_grammar_evidence]
|
| 5097 |
semantic_code_set = {
|
| 5098 |
'SEMANTIC_DRIFT','NEGATION_DRIFT','CAUSALITY_STRENGTH_DRIFT',
|
| 5099 |
'UNCERTAINTY_DRIFT','OMITTED_CLAIM','ADDED_CLAIM',
|
|
|
|
| 5110 |
fallback = derived.get(issue_code)
|
| 5111 |
if fallback and fallback not in evidence_codes:
|
| 5112 |
evidence_codes.append(fallback)
|
| 5113 |
+
if 'SEMANTIC_DRIFT' in codes and not evidence_codes:
|
| 5114 |
+
evidence_codes.append('MEANING_CHANGED')
|
| 5115 |
+
evidence_row = self._translation_claim_qa_evidence.setdefault(
|
| 5116 |
+
(meta['block_id'], meta['claim_id']), {}
|
| 5117 |
+
)
|
| 5118 |
+
evidence_row['semantic_delta_codes'] = evidence_codes[:6]
|
| 5119 |
+
if 'GRAMMAR' in codes:
|
| 5120 |
+
if not grammar_evidence_codes:
|
| 5121 |
+
grammar_evidence_codes = ['GRAMMAR_UNSPECIFIED']
|
| 5122 |
+
evidence_row = self._translation_claim_qa_evidence.setdefault(
|
| 5123 |
+
(meta['block_id'], meta['claim_id']), {}
|
| 5124 |
+
)
|
| 5125 |
+
evidence_row['grammar_evidence_codes'] = grammar_evidence_codes[:4]
|
| 5126 |
model_contamination = 'LANGUAGE_CONTAMINATION' in codes
|
| 5127 |
lexical = self._translation_lexical_purity_evidence(
|
| 5128 |
meta['english'], meta['target'], target_lang, relevant_glossary
|
|
|
|
| 5514 |
restored[block_id] = recovered[block_id]
|
| 5515 |
return restored
|
| 5516 |
|
| 5517 |
+
@staticmethod
|
| 5518 |
+
def _translation_repair_evidence_signature(issue_codes, repair_evidence=None):
|
| 5519 |
+
"""Stable signature for one claim defect so repeated strategies cannot loop across Final Gate."""
|
| 5520 |
+
evidence = dict(repair_evidence or {})
|
| 5521 |
+
bounded = {
|
| 5522 |
+
'issue_codes': sorted({str(code or '').strip().upper() for code in (issue_codes or []) if str(code or '').strip()}),
|
| 5523 |
+
'semantic_delta_codes': sorted({str(code or '').strip().upper() for code in (evidence.get('semantic_delta_codes') or []) if str(code or '').strip()}),
|
| 5524 |
+
'grammar_evidence_codes': sorted({str(code or '').strip().upper() for code in (evidence.get('grammar_evidence_codes') or []) if str(code or '').strip()}),
|
| 5525 |
+
'detected_unprotected_english_tokens': sorted({str(token or '').strip().casefold() for token in (evidence.get('detected_unprotected_english_tokens') or []) if str(token or '').strip()}),
|
| 5526 |
+
}
|
| 5527 |
+
payload = json.dumps(bounded, ensure_ascii=False, sort_keys=True, separators=(',', ':'))
|
| 5528 |
+
return hashlib.sha256(payload.encode('utf-8')).hexdigest()
|
| 5529 |
+
|
| 5530 |
+
def _decompose_translation_semantic_delta(
|
| 5531 |
+
self, source_claim, current_target_claim, target_lang, issue_codes=None, section_name=''
|
| 5532 |
+
):
|
| 5533 |
+
"""Verification-only decomposition of generic MEANING_CHANGED into repairable semantic dimensions."""
|
| 5534 |
+
allowed = {
|
| 5535 |
+
'ACTOR_CHANGED','RELATION_CHANGED','OBJECT_CHANGED','POLARITY_CHANGED',
|
| 5536 |
+
'CAUSALITY_STRENGTH_CHANGED','MODALITY_UNCERTAINTY_CHANGED',
|
| 5537 |
+
'CONDITION_SCOPE_CHANGED','COMPARISON_DIRECTION_CHANGED','TEMPORAL_SCOPE_CHANGED',
|
| 5538 |
+
'CLAIM_MISSING','CLAIM_ADDED','UNRESOLVED_SEMANTIC_DELTA',
|
| 5539 |
+
}
|
| 5540 |
+
payload = {
|
| 5541 |
+
'target_language': target_lang,
|
| 5542 |
+
'section': str(section_name or 'section'),
|
| 5543 |
+
'issue_codes': [str(code or '').strip().upper() for code in (issue_codes or []) if str(code or '').strip()][:5],
|
| 5544 |
+
'english': str(source_claim or '').strip(),
|
| 5545 |
+
'target': str(current_target_claim or '').strip(),
|
| 5546 |
+
}
|
| 5547 |
+
prompt = f"""You are a bilingual semantic-delta classifier for academic translation into {target_lang}.
|
| 5548 |
+
Verification only. Do not rewrite, suggest wording, explain, or return replacement text. The pair has already been flagged with a generic semantic problem. Identify ONLY the narrow semantic dimensions that differ between the frozen English source and the current target claim.
|
| 5549 |
+
Return valid JSON only: {{"delta_codes":["RELATION_CHANGED"]}}.
|
| 5550 |
+
Each code must be one of: {sorted(allowed)}. Return one or more specific dimensions when supported. Return only UNRESOLVED_SEMANTIC_DELTA when the pair supports a meaning difference but the changed dimension cannot be narrowed safely."""
|
| 5551 |
+
try:
|
| 5552 |
+
response = self.call_deepseek(
|
| 5553 |
+
prompt, json.dumps(payload, ensure_ascii=False), max_tokens=900, temp=0.0,
|
| 5554 |
+
model_role='reasoning', thinking=False, allow_fallback=False,
|
| 5555 |
+
)
|
| 5556 |
+
parsed = self._extract_json_from_text(response)
|
| 5557 |
+
raw = parsed.get('delta_codes', []) if isinstance(parsed, dict) else []
|
| 5558 |
+
codes = [str(code or '').strip().upper() for code in raw if str(code or '').strip().upper() in allowed]
|
| 5559 |
+
codes = list(dict.fromkeys(codes))[:6]
|
| 5560 |
+
specific = [code for code in codes if code != 'UNRESOLVED_SEMANTIC_DELTA']
|
| 5561 |
+
if specific:
|
| 5562 |
+
return specific
|
| 5563 |
+
except PipelineCancelledError:
|
| 5564 |
+
raise
|
| 5565 |
+
except Exception as error:
|
| 5566 |
+
self._record_translation_assurance(
|
| 5567 |
+
'SEMANTIC_DECOMPOSITION_UNCERTAIN', target_lang=target_lang,
|
| 5568 |
+
block_id=section_name or 'claim', severity='diagnostic', decision='SOURCE_REGENERATE',
|
| 5569 |
+
detail={'error_type': type(error).__name__},
|
| 5570 |
+
)
|
| 5571 |
+
return ['UNRESOLVED_SEMANTIC_DELTA']
|
| 5572 |
+
|
| 5573 |
def _translate_single_claim_with_recovery(
|
| 5574 |
self, block_id, claim_id, source_claim, system_prompt, target_lang, issue_codes,
|
| 5575 |
+
current_target_claim=None, repair_evidence=None, repair_strategy='LOCAL_EDIT',
|
| 5576 |
):
|
| 5577 |
+
"""Repair one failed claim with bounded evidence; escalate to source-only regeneration when needed."""
|
| 5578 |
protected_claim, mapping = self._protect_translation_tokens(source_claim, claim_id=claim_id)
|
| 5579 |
marked_source = f'[[SP-P{block_id}]]\n[[SP-{claim_id}]]\n{protected_claim}'
|
| 5580 |
safe_manifest = self._model_safe_translation_manifest(mapping)
|
| 5581 |
codes = [str(code or '').strip().upper() for code in (issue_codes or []) if str(code or '').strip()]
|
|
|
|
|
|
|
|
|
|
|
|
|
| 5582 |
supplied_evidence = dict(repair_evidence or {})
|
| 5583 |
+
strategy = str(repair_strategy or 'LOCAL_EDIT').strip().upper()
|
| 5584 |
+
if strategy not in {'LOCAL_EDIT', 'SOURCE_REGENERATE'}:
|
| 5585 |
+
strategy = 'LOCAL_EDIT'
|
| 5586 |
+
evidence_payload = {'issue_codes': codes[:5], 'repair_strategy': strategy}
|
| 5587 |
+
if strategy == 'LOCAL_EDIT':
|
| 5588 |
+
evidence_payload['current_target_claim'] = str(current_target_claim or '').strip()
|
| 5589 |
lexical_tokens = [
|
| 5590 |
str(token or '').strip() for token in (supplied_evidence.get('detected_unprotected_english_tokens') or [])
|
| 5591 |
if str(token or '').strip()
|
|
|
|
| 5598 |
]
|
| 5599 |
if semantic_delta_codes:
|
| 5600 |
evidence_payload['semantic_delta_codes'] = semantic_delta_codes[:6]
|
| 5601 |
+
grammar_evidence_codes = [
|
| 5602 |
+
str(code or '').strip().upper() for code in (supplied_evidence.get('grammar_evidence_codes') or [])
|
| 5603 |
+
if str(code or '').strip()
|
| 5604 |
+
]
|
| 5605 |
+
if grammar_evidence_codes:
|
| 5606 |
+
evidence_payload['grammar_evidence_codes'] = grammar_evidence_codes[:4]
|
| 5607 |
+
retry_mode = (
|
| 5608 |
+
'\nCLAIM SOURCE REGENERATION. The previous target wording has failed bounded repair and MUST NOT be used as drafting context. Generate a fresh translation of only this one claim directly from the frozen marked English source. '
|
| 5609 |
+
if strategy == 'SOURCE_REGENERATE' else
|
| 5610 |
+
'\nCLAIM QUALITY RETRY. Repair only this one claim. '
|
| 5611 |
+
)
|
| 5612 |
+
effective_prompt = system_prompt + retry_mode + (
|
| 5613 |
+
'The only permitted repair signals are the following bounded issue codes: '
|
| 5614 |
+ json.dumps(codes[:5], ensure_ascii=False)
|
| 5615 |
+ '. Use the bounded detector evidence below only to repair the supplied claim; do not invent additional defects. '
|
| 5616 |
+
'For LOCAL_EDIT, the current target claim is context, not authority; for SOURCE_REGENERATE it is intentionally absent. The frozen English source is always authoritative. '
|
| 5617 |
+
'If detected_unprotected_english_tokens is non-empty, every listed token is deterministic lexical-purity evidence from a prior target and MUST NOT survive as ordinary untranslated target-language prose unless represented by a protected SP-T anchor. '
|
| 5618 |
+
'If semantic_delta_codes is non-empty, enforce ONLY those evidenced semantic dimensions against the frozen English source and preserve all unaffected actor, relation, object, polarity, modality, condition, comparison, temporal scope, and evidentiary strength. UNRESOLVED_SEMANTIC_DELTA is a signal to regenerate faithfully from source, not an instruction to guess a specific correction. '
|
| 5619 |
+
'If grammar_evidence_codes is non-empty, repair or avoid only those bounded grammatical defect classes; GRAMMAR_UNSPECIFIED requires a fresh natural target-language rendering rather than blind micro-editing. '
|
| 5620 |
'Preserve every SP-P, SP-C, and SP-T marker byte-for-byte. Do not add claims or factual content. Return only the marked translated claim.'
|
| 5621 |
+ '\n<CLAIM_REPAIR_EVIDENCE>\n'
|
| 5622 |
+ json.dumps(evidence_payload, ensure_ascii=False)
|
|
|
|
| 5692 |
return segments[claim_id]
|
| 5693 |
|
| 5694 |
@classmethod
|
| 5695 |
+
def _snapshot_translation_claim_states(
|
| 5696 |
+
cls, source_blocks, translations, pending_issues=None, repair_evidence=None, repair_meta=None
|
| 5697 |
+
):
|
| 5698 |
+
"""Freeze claim text plus bounded repair history so Final Gate cannot restart a failed strategy."""
|
| 5699 |
pending = dict(pending_issues or {})
|
| 5700 |
evidence_map = dict(repair_evidence or {})
|
| 5701 |
+
meta_map = dict(repair_meta or {})
|
| 5702 |
states = {}
|
| 5703 |
for block_id, source_block in dict(source_blocks or {}).items():
|
| 5704 |
if block_id not in dict(translations or {}):
|
|
|
|
| 5715 |
if not target_text:
|
| 5716 |
continue
|
| 5717 |
codes = list((pending.get(block_id) or {}).get(claim_id) or [])
|
| 5718 |
+
meta = dict(meta_map.get((block_id, claim_id)) or {})
|
| 5719 |
states[f'SP-P{block_id}-{claim_id}'] = {
|
| 5720 |
'block_id': str(block_id),
|
| 5721 |
'claim_id': str(claim_id),
|
|
|
|
| 5724 |
'qa_status': 'repair_pending' if codes else 'passed',
|
| 5725 |
'issue_codes': [str(code or '').strip().upper() for code in codes if str(code or '').strip()][:5],
|
| 5726 |
'repair_evidence': dict(evidence_map.get((block_id, claim_id)) or {}),
|
| 5727 |
+
'repair_attempt_count': int(meta.get('repair_attempt_count') or 0),
|
| 5728 |
+
'last_issue_codes': [str(code or '').strip().upper() for code in (meta.get('last_issue_codes') or []) if str(code or '').strip()][:5],
|
| 5729 |
+
'last_evidence_signature': str(meta.get('last_evidence_signature') or ''),
|
| 5730 |
+
'last_strategy': str(meta.get('last_strategy') or ''),
|
| 5731 |
+
'nonconvergent_state': str(meta.get('nonconvergent_state') or ''),
|
| 5732 |
}
|
| 5733 |
return states
|
| 5734 |
|
|
|
|
| 5800 |
lexical = self._translation_lexical_purity_evidence(
|
| 5801 |
source_claims[claim_id], target_claims[claim_id], target_lang, glossary
|
| 5802 |
)
|
| 5803 |
+
lexical_confirmed = bool(lexical.get('confirmed'))
|
| 5804 |
+
deterministic = lexical_confirmed
|
| 5805 |
+
deterministic_evidence_code = 'UNTRANSLATED_LEXEME' if lexical_confirmed else ''
|
| 5806 |
if profile.get('language_code') == 'ru' and not deterministic:
|
| 5807 |
evidence = self._russian_contamination_evidence(
|
| 5808 |
source_claims[claim_id], target_claims[claim_id], glossary
|
| 5809 |
)
|
| 5810 |
deterministic = bool(evidence.get('confirmed'))
|
| 5811 |
+
if deterministic:
|
| 5812 |
+
deterministic_evidence_code = 'UNTRANSLATED_PROSE'
|
| 5813 |
if deterministic:
|
| 5814 |
rows.append({
|
| 5815 |
'id': f'SP-P{block_id}-{claim_id}', 'block_id': block_id,
|
| 5816 |
'claim_id': claim_id, 'code': code, 'english': source_claims[claim_id],
|
| 5817 |
'target': target_claims[claim_id], 'deterministic_confirmed': True,
|
| 5818 |
+
'deterministic_evidence_code': deterministic_evidence_code,
|
| 5819 |
})
|
| 5820 |
continue
|
| 5821 |
rows.append({
|
|
|
|
| 5832 |
self._record_translation_assurance(
|
| 5833 |
'QA_ADJUDICATION_CONFIRMED', target_lang=target_lang, block_id=row['id'],
|
| 5834 |
severity='critical', decision='HOLD_FINAL',
|
| 5835 |
+
detail={
|
| 5836 |
+
'issue_code': row['code'],
|
| 5837 |
+
'evidence_code': str(row.get('deterministic_evidence_code') or 'UNTRANSLATED_PROSE'),
|
| 5838 |
+
},
|
| 5839 |
)
|
| 5840 |
|
| 5841 |
if not model_rows:
|
|
|
|
| 5992 |
last_error = None
|
| 5993 |
self._last_translation_claim_states = {}
|
| 5994 |
self._translation_claim_qa_evidence = {}
|
| 5995 |
+
self._translation_claim_repair_meta = {}
|
| 5996 |
+
for row in dict(claim_states or {}).values():
|
| 5997 |
+
if not isinstance(row, dict):
|
| 5998 |
+
continue
|
| 5999 |
+
block_key = str(row.get('block_id') or '')
|
| 6000 |
+
claim_key = str(row.get('claim_id') or '')
|
| 6001 |
+
if not block_key or not claim_key:
|
| 6002 |
+
continue
|
| 6003 |
+
self._translation_claim_repair_meta[(block_key, claim_key)] = {
|
| 6004 |
+
'repair_attempt_count': int(row.get('repair_attempt_count') or 0),
|
| 6005 |
+
'last_issue_codes': [str(code or '').strip().upper() for code in (row.get('last_issue_codes') or []) if str(code or '').strip()][:5],
|
| 6006 |
+
'last_evidence_signature': str(row.get('last_evidence_signature') or ''),
|
| 6007 |
+
'last_strategy': str(row.get('last_strategy') or '').strip().upper(),
|
| 6008 |
+
'nonconvergent_state': str(row.get('nonconvergent_state') or ''),
|
| 6009 |
+
}
|
| 6010 |
|
| 6011 |
def emit_stage(stage):
|
| 6012 |
if not callable(stage_callback):
|
|
|
|
| 6050 |
section_name=section_name or 'frozen section', max_attempts=2,
|
| 6051 |
)
|
| 6052 |
self._last_translation_claim_states = self._snapshot_translation_claim_states(
|
| 6053 |
+
source_blocks, translations, quality_issues, self._translation_claim_qa_evidence,
|
| 6054 |
+
self._translation_claim_repair_meta,
|
| 6055 |
)
|
| 6056 |
local_round = 0
|
| 6057 |
while quality_issues and local_round < 2:
|
|
|
|
| 6071 |
)
|
| 6072 |
emit_stage('repairing')
|
| 6073 |
touched_blocks = set()
|
| 6074 |
+
carried_exhausted = {}
|
| 6075 |
for block_id, claim_id, codes in failed_pairs:
|
| 6076 |
source_claims = {
|
| 6077 |
item['claim_id']: item['text'] for item in self._translation_claim_units(source_blocks[block_id])
|
|
|
|
| 6083 |
claim_order = list(source_claims)
|
| 6084 |
_order, segments = self._parse_translation_claim_segments(translations[block_id], claim_order)
|
| 6085 |
current_target_claim = segments[claim_id]
|
| 6086 |
+
evidence_key = (block_id, claim_id)
|
| 6087 |
+
repair_evidence = dict(self._translation_claim_qa_evidence.get(evidence_key) or {})
|
| 6088 |
+
code_set = {str(code or '').strip().upper() for code in (codes or [])}
|
| 6089 |
+
|
| 6090 |
+
semantic_deltas = [
|
| 6091 |
+
str(code or '').strip().upper() for code in (repair_evidence.get('semantic_delta_codes') or [])
|
| 6092 |
+
if str(code or '').strip()
|
| 6093 |
+
]
|
| 6094 |
+
semantic_issue = bool(code_set & {
|
| 6095 |
+
'SEMANTIC_DRIFT','NEGATION_DRIFT','CAUSALITY_STRENGTH_DRIFT',
|
| 6096 |
+
'UNCERTAINTY_DRIFT','OMITTED_CLAIM','ADDED_CLAIM',
|
| 6097 |
+
})
|
| 6098 |
+
derived_semantic = {
|
| 6099 |
+
'NEGATION_DRIFT': 'POLARITY_CHANGED',
|
| 6100 |
+
'CAUSALITY_STRENGTH_DRIFT': 'CAUSALITY_STRENGTH_CHANGED',
|
| 6101 |
+
'UNCERTAINTY_DRIFT': 'MODALITY_UNCERTAINTY_CHANGED',
|
| 6102 |
+
'OMITTED_CLAIM': 'CLAIM_MISSING',
|
| 6103 |
+
'ADDED_CLAIM': 'CLAIM_ADDED',
|
| 6104 |
+
}
|
| 6105 |
+
for issue_code in code_set:
|
| 6106 |
+
fallback = derived_semantic.get(issue_code)
|
| 6107 |
+
if fallback and fallback not in semantic_deltas:
|
| 6108 |
+
semantic_deltas.append(fallback)
|
| 6109 |
+
if semantic_deltas:
|
| 6110 |
+
repair_evidence['semantic_delta_codes'] = list(dict.fromkeys(semantic_deltas))[:6]
|
| 6111 |
+
specific_semantic = [code for code in semantic_deltas if code != 'MEANING_CHANGED']
|
| 6112 |
+
if semantic_issue and (not specific_semantic or 'MEANING_CHANGED' in semantic_deltas):
|
| 6113 |
+
specific_semantic = self._decompose_translation_semantic_delta(
|
| 6114 |
+
source_claims[claim_id], current_target_claim, target_lang,
|
| 6115 |
+
issue_codes=codes, section_name=f'SP-P{block_id}-{claim_id}',
|
| 6116 |
+
)
|
| 6117 |
+
repair_evidence['semantic_delta_codes'] = list(specific_semantic)[:6]
|
| 6118 |
if repair_evidence.get('semantic_delta_codes'):
|
| 6119 |
self._record_translation_assurance(
|
| 6120 |
'SEMANTIC_REPAIR_EVIDENCE', target_lang=target_lang,
|
| 6121 |
block_id=f'SP-P{block_id}-{claim_id}', severity='repair', decision='REPAIR_LOCAL',
|
| 6122 |
detail={'deltas': '|'.join(repair_evidence.get('semantic_delta_codes') or [])[:180]},
|
| 6123 |
)
|
| 6124 |
+
if 'GRAMMAR' in code_set:
|
| 6125 |
+
grammar_codes = [
|
| 6126 |
+
str(code or '').strip().upper() for code in (repair_evidence.get('grammar_evidence_codes') or [])
|
| 6127 |
+
if str(code or '').strip()
|
| 6128 |
+
] or ['GRAMMAR_UNSPECIFIED']
|
| 6129 |
+
repair_evidence['grammar_evidence_codes'] = grammar_codes[:4]
|
| 6130 |
+
self._record_translation_assurance(
|
| 6131 |
+
'GRAMMAR_REPAIR_EVIDENCE', target_lang=target_lang,
|
| 6132 |
+
block_id=f'SP-P{block_id}-{claim_id}', severity='repair', decision='REPAIR_LOCAL',
|
| 6133 |
+
detail={'classes': '|'.join(grammar_codes)[:180]},
|
| 6134 |
+
)
|
| 6135 |
+
if 'LANGUAGE_CONTAMINATION' in code_set:
|
| 6136 |
lexical = self._translation_lexical_purity_evidence(
|
| 6137 |
source_claims[claim_id], current_target_claim, target_lang, frozen_glossary
|
| 6138 |
)
|
|
|
|
| 6146 |
'sample': ','.join(lexical.get('tokens') or [])[:120],
|
| 6147 |
},
|
| 6148 |
)
|
| 6149 |
+
self._translation_claim_qa_evidence[evidence_key] = dict(repair_evidence)
|
| 6150 |
+
signature = self._translation_repair_evidence_signature(codes, repair_evidence)
|
| 6151 |
+
meta = self._translation_claim_repair_meta.setdefault(evidence_key, {
|
| 6152 |
+
'repair_attempt_count': 0, 'last_issue_codes': [], 'last_evidence_signature': '',
|
| 6153 |
+
'last_strategy': '', 'nonconvergent_state': '',
|
| 6154 |
+
})
|
| 6155 |
+
prior_codes = set(meta.get('last_issue_codes') or [])
|
| 6156 |
+
same_defect = (
|
| 6157 |
+
str(meta.get('last_evidence_signature') or '') == signature
|
| 6158 |
+
and bool(prior_codes & code_set)
|
| 6159 |
+
)
|
| 6160 |
+
if (
|
| 6161 |
+
str(meta.get('nonconvergent_state') or '').startswith('exhausted')
|
| 6162 |
+
and bool(prior_codes & code_set)
|
| 6163 |
+
) or (same_defect and str(meta.get('last_strategy') or '').upper() == 'SOURCE_REGENERATE'):
|
| 6164 |
+
meta['nonconvergent_state'] = 'exhausted_after_source_regenerate'
|
| 6165 |
+
carried_exhausted.setdefault(block_id, {})[claim_id] = list(codes)
|
| 6166 |
+
self._record_translation_assurance(
|
| 6167 |
+
'CLAIM_REPAIR_EXHAUSTED', target_lang=target_lang,
|
| 6168 |
+
block_id=f'SP-P{block_id}-{claim_id}', severity='critical', decision='ADJUDICATE',
|
| 6169 |
+
detail={'attempts': int(meta.get('repair_attempt_count') or 0), 'strategy': 'SOURCE_REGENERATE'},
|
| 6170 |
+
)
|
| 6171 |
+
continue
|
| 6172 |
+
|
| 6173 |
+
force_source_regenerate = (
|
| 6174 |
+
'UNRESOLVED_SEMANTIC_DELTA' in set(repair_evidence.get('semantic_delta_codes') or [])
|
| 6175 |
+
or 'GRAMMAR_UNSPECIFIED' in set(repair_evidence.get('grammar_evidence_codes') or [])
|
| 6176 |
+
)
|
| 6177 |
+
strategy = 'SOURCE_REGENERATE' if (
|
| 6178 |
+
force_source_regenerate
|
| 6179 |
+
or (same_defect and str(meta.get('last_strategy') or '').upper() == 'LOCAL_EDIT')
|
| 6180 |
+
) else 'LOCAL_EDIT'
|
| 6181 |
+
if strategy == 'SOURCE_REGENERATE':
|
| 6182 |
+
self._record_translation_assurance(
|
| 6183 |
+
'CLAIM_REPAIR_ESCALATED', target_lang=target_lang,
|
| 6184 |
+
block_id=f'SP-P{block_id}-{claim_id}', severity='repair', decision='SOURCE_REGENERATE',
|
| 6185 |
+
detail={'reason': 'nonconvergent_or_unresolved_evidence'},
|
| 6186 |
+
)
|
| 6187 |
+
self._translation_claim_qa_evidence[evidence_key] = dict(repair_evidence)
|
| 6188 |
+
repair_method = self._translate_single_claim_with_recovery
|
| 6189 |
+
repair_kwargs = {
|
| 6190 |
+
'current_target_claim': current_target_claim,
|
| 6191 |
+
'repair_evidence': repair_evidence,
|
| 6192 |
+
}
|
| 6193 |
+
try:
|
| 6194 |
+
repair_parameters = list(inspect.signature(repair_method).parameters.values())
|
| 6195 |
+
supports_strategy = any(
|
| 6196 |
+
item.name == 'repair_strategy' or item.kind == inspect.Parameter.VAR_KEYWORD
|
| 6197 |
+
for item in repair_parameters
|
| 6198 |
+
)
|
| 6199 |
+
except (TypeError, ValueError):
|
| 6200 |
+
supports_strategy = False
|
| 6201 |
+
if supports_strategy:
|
| 6202 |
+
repair_kwargs['repair_strategy'] = strategy
|
| 6203 |
+
repaired_claim = repair_method(
|
| 6204 |
block_id, claim_id, source_claims[claim_id], system_prompt, target_lang, codes,
|
| 6205 |
+
**repair_kwargs,
|
| 6206 |
)
|
| 6207 |
+
meta['repair_attempt_count'] = int(meta.get('repair_attempt_count') or 0) + 1
|
| 6208 |
+
meta['last_issue_codes'] = sorted(code_set)[:5]
|
| 6209 |
+
meta['last_evidence_signature'] = signature
|
| 6210 |
+
meta['last_strategy'] = strategy
|
| 6211 |
+
meta['nonconvergent_state'] = 'source_regenerated' if strategy == 'SOURCE_REGENERATE' else ''
|
| 6212 |
+
if 'LANGUAGE_CONTAMINATION' in code_set:
|
| 6213 |
post_repair_lexical = self._translation_lexical_purity_evidence(
|
| 6214 |
source_claims[claim_id], repaired_claim, target_lang, frozen_glossary
|
| 6215 |
)
|
|
|
|
| 6217 |
'LEXICAL_PURITY_REPAIR_RESIDUAL' if post_repair_lexical.get('confirmed') else 'LEXICAL_PURITY_REPAIR_CLEARED',
|
| 6218 |
target_lang=target_lang, block_id=f'SP-P{block_id}-{claim_id}',
|
| 6219 |
severity='repair' if post_repair_lexical.get('confirmed') else 'diagnostic',
|
| 6220 |
+
decision='SOURCE_REGENERATE' if post_repair_lexical.get('confirmed') and strategy == 'SOURCE_REGENERATE' else ('REPAIR_LOCAL' if post_repair_lexical.get('confirmed') else 'PASS_WITH_WARNING'),
|
| 6221 |
detail={
|
| 6222 |
'token_count': int(post_repair_lexical.get('token_count') or 0),
|
| 6223 |
'sample': ','.join(post_repair_lexical.get('tokens') or [])[:120],
|
|
|
|
| 6227 |
translations[block_id] = self._compose_translation_claim_segments(claim_order, segments)
|
| 6228 |
touched_blocks.add(block_id)
|
| 6229 |
emit_stage('qa')
|
| 6230 |
+
refreshed = {}
|
| 6231 |
+
if touched_blocks:
|
| 6232 |
+
refreshed = self._audit_translation_blocks_with_retry(
|
| 6233 |
+
{block_id: source_blocks[block_id] for block_id in sorted(touched_blocks)},
|
| 6234 |
+
{block_id: translations[block_id] for block_id in sorted(touched_blocks)},
|
| 6235 |
+
target_lang, frozen_glossary, section_name=section_name or 'frozen section', max_attempts=2,
|
| 6236 |
+
)
|
| 6237 |
+
for block_id, claim_map in carried_exhausted.items():
|
| 6238 |
+
for claim_id, codes in claim_map.items():
|
| 6239 |
+
refreshed.setdefault(block_id, {})[claim_id] = list(codes)
|
| 6240 |
+
quality_issues = refreshed
|
| 6241 |
self._last_translation_claim_states = self._snapshot_translation_claim_states(
|
| 6242 |
+
source_blocks, translations, quality_issues, self._translation_claim_qa_evidence,
|
| 6243 |
+
self._translation_claim_repair_meta,
|
| 6244 |
)
|
| 6245 |
+
if not touched_blocks and carried_exhausted:
|
| 6246 |
+
break
|
| 6247 |
+
|
| 6248 |
+
if quality_issues:
|
| 6249 |
+
for block_id, claim_map in quality_issues.items():
|
| 6250 |
+
for claim_id, codes in (claim_map or {}).items():
|
| 6251 |
+
meta = self._translation_claim_repair_meta.get((block_id, claim_id))
|
| 6252 |
+
if not isinstance(meta, dict):
|
| 6253 |
+
continue
|
| 6254 |
+
if str(meta.get('last_strategy') or '').upper() == 'SOURCE_REGENERATE':
|
| 6255 |
+
prior = set(meta.get('last_issue_codes') or [])
|
| 6256 |
+
current = {str(code or '').strip().upper() for code in (codes or [])}
|
| 6257 |
+
if prior & current:
|
| 6258 |
+
meta['nonconvergent_state'] = 'exhausted_after_source_regenerate'
|
| 6259 |
if quality_issues:
|
| 6260 |
emit_stage('qa')
|
| 6261 |
quality_issues = self._adjudicate_persistent_translation_issues(
|
|
|
|
| 6263 |
section_name=section_name or 'frozen section',
|
| 6264 |
)
|
| 6265 |
self._last_translation_claim_states = self._snapshot_translation_claim_states(
|
| 6266 |
+
source_blocks, translations, quality_issues, self._translation_claim_qa_evidence,
|
| 6267 |
+
self._translation_claim_repair_meta,
|
| 6268 |
)
|
| 6269 |
if quality_issues:
|
| 6270 |
detail = '; '.join(
|
|
|
|
| 6303 |
'language_adapter': profile['adapter_id'],
|
| 6304 |
'diagnostics': diagnostics[-20:],
|
| 6305 |
'claim_states': dict(self._last_translation_claim_states),
|
| 6306 |
+
'claim_state_version': 'translation-claim-state-v2',
|
| 6307 |
}
|
| 6308 |
except PipelineCancelledError:
|
| 6309 |
raise
|