R14-KK-G2-R1 Per-Claim Repair Entitlement + Late-Detected QA Stabilization

#59
Files changed (2) hide show
  1. core/generation_runner.py +1 -1
  2. core/pipeline_agent.py +39 -22
core/generation_runner.py CHANGED
@@ -43,7 +43,7 @@ CITATION_ROLE_RULES = {
43
  "methods": (2, 280),
44
  "discussion": (2, 220),
45
  }
46
- TRANSLATION_CONTRACT_VERSION = 'hierarchical-multilingual-v6'
47
 
48
 
49
  def _clean_trailing_references(text, keywords=("reference", "references", "reference list", "参考文献")):
 
43
  "methods": (2, 280),
44
  "discussion": (2, 220),
45
  }
46
+ TRANSLATION_CONTRACT_VERSION = 'hierarchical-multilingual-v7'
47
 
48
 
49
  def _clean_trailing_references(text, keywords=("reference", "references", "reference list", "参考文献")):
core/pipeline_agent.py CHANGED
@@ -62,7 +62,8 @@ class CitationAuditError(DeepSeekRequestError):
62
 
63
 
64
  class SSciPipelineAgent:
65
- TRANSLATION_CONTRACT_VERSION = 'hierarchical-multilingual-v6'
 
66
 
67
  def __init__(self, ds_key, s2_key=""):
68
  self.ds_key = str(ds_key or "").strip()
@@ -6330,9 +6331,11 @@ Decision must be CONFIRMED_BLOCKING or NOT_CONFIRMED. evidence_code must be one
6330
  source_blocks, translations, quality_issues, self._translation_claim_qa_evidence,
6331
  self._translation_claim_repair_meta,
6332
  )
6333
- local_round = 0
6334
- while quality_issues and local_round < 2:
6335
- local_round += 1
 
 
6336
  failed_pairs = [
6337
  (block_id, claim_id, codes)
6338
  for block_id, claim_map in sorted(quality_issues.items())
@@ -6340,7 +6343,7 @@ Decision must be CONFIRMED_BLOCKING or NOT_CONFIRMED. evidence_code must be one
6340
  ]
6341
  logging.warning(
6342
  'Translation claim-local QA recovery: section=%s round=%s failed=%s',
6343
- section_name or 'section', local_round,
6344
  [
6345
  {'id': f'SP-P{block_id}-{claim_id}', 'codes': list(codes)}
6346
  for block_id, claim_id, codes in failed_pairs
@@ -6363,6 +6366,36 @@ Decision must be CONFIRMED_BLOCKING or NOT_CONFIRMED. evidence_code must be one
6363
  evidence_key = (block_id, claim_id)
6364
  repair_evidence = dict(self._translation_claim_qa_evidence.get(evidence_key) or {})
6365
  code_set = {str(code or '').strip().upper() for code in (codes or [])}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
6366
 
6367
  semantic_deltas = [
6368
  str(code or '').strip().upper() for code in (repair_evidence.get('semantic_delta_codes') or [])
@@ -6441,27 +6474,10 @@ Decision must be CONFIRMED_BLOCKING or NOT_CONFIRMED. evidence_code must be one
6441
  )
6442
  self._translation_claim_qa_evidence[evidence_key] = dict(repair_evidence)
6443
  signature = self._translation_repair_evidence_signature(codes, repair_evidence)
6444
- meta = self._translation_claim_repair_meta.setdefault(evidence_key, {
6445
- 'repair_attempt_count': 0, 'last_issue_codes': [], 'last_evidence_signature': '',
6446
- 'last_strategy': '', 'nonconvergent_state': '',
6447
- })
6448
- prior_codes = set(meta.get('last_issue_codes') or [])
6449
  same_defect = (
6450
  str(meta.get('last_evidence_signature') or '') == signature
6451
  and bool(prior_codes & code_set)
6452
  )
6453
- if (
6454
- str(meta.get('nonconvergent_state') or '').startswith('exhausted')
6455
- and bool(prior_codes & code_set)
6456
- ) or (same_defect and str(meta.get('last_strategy') or '').upper() == 'SOURCE_REGENERATE'):
6457
- meta['nonconvergent_state'] = 'exhausted_after_source_regenerate'
6458
- carried_exhausted.setdefault(block_id, {})[claim_id] = list(codes)
6459
- self._record_translation_assurance(
6460
- 'CLAIM_REPAIR_EXHAUSTED', target_lang=target_lang,
6461
- block_id=f'SP-P{block_id}-{claim_id}', severity='critical', decision='ADJUDICATE',
6462
- detail={'attempts': int(meta.get('repair_attempt_count') or 0), 'strategy': 'SOURCE_REGENERATE'},
6463
- )
6464
- continue
6465
 
6466
  force_source_regenerate = (
6467
  'UNRESOLVED_SEMANTIC_DELTA' in set(repair_evidence.get('semantic_delta_codes') or [])
@@ -6469,6 +6485,7 @@ Decision must be CONFIRMED_BLOCKING or NOT_CONFIRMED. evidence_code must be one
6469
  )
6470
  strategy = 'SOURCE_REGENERATE' if (
6471
  force_source_regenerate
 
6472
  or (same_defect and str(meta.get('last_strategy') or '').upper() == 'LOCAL_EDIT')
6473
  ) else 'LOCAL_EDIT'
6474
  if strategy == 'SOURCE_REGENERATE':
 
62
 
63
 
64
  class SSciPipelineAgent:
65
+ TRANSLATION_CONTRACT_VERSION = 'hierarchical-multilingual-v7'
66
+ MAX_TRANSLATION_CLAIM_REPAIR_ATTEMPTS = 2
67
 
68
  def __init__(self, ds_key, s2_key=""):
69
  self.ds_key = str(ds_key or "").strip()
 
6331
  source_blocks, translations, quality_issues, self._translation_claim_qa_evidence,
6332
  self._translation_claim_repair_meta,
6333
  )
6334
+ repair_cycle = 0
6335
+ total_claims = sum(len(self._translation_claim_units(source)) for source in source_blocks.values())
6336
+ max_repair_cycles = max(1, total_claims * self.MAX_TRANSLATION_CLAIM_REPAIR_ATTEMPTS)
6337
+ while quality_issues and repair_cycle < max_repair_cycles:
6338
+ repair_cycle += 1
6339
  failed_pairs = [
6340
  (block_id, claim_id, codes)
6341
  for block_id, claim_map in sorted(quality_issues.items())
 
6343
  ]
6344
  logging.warning(
6345
  'Translation claim-local QA recovery: section=%s round=%s failed=%s',
6346
+ section_name or 'section', repair_cycle,
6347
  [
6348
  {'id': f'SP-P{block_id}-{claim_id}', 'codes': list(codes)}
6349
  for block_id, claim_id, codes in failed_pairs
 
6366
  evidence_key = (block_id, claim_id)
6367
  repair_evidence = dict(self._translation_claim_qa_evidence.get(evidence_key) or {})
6368
  code_set = {str(code or '').strip().upper() for code in (codes or [])}
6369
+ meta = self._translation_claim_repair_meta.setdefault(evidence_key, {
6370
+ 'repair_attempt_count': 0, 'last_issue_codes': [], 'last_evidence_signature': '',
6371
+ 'last_strategy': '', 'nonconvergent_state': '',
6372
+ })
6373
+ prior_attempts = int(meta.get('repair_attempt_count') or 0)
6374
+ prior_codes = set(meta.get('last_issue_codes') or [])
6375
+ already_source_regenerated = str(meta.get('last_strategy') or '').upper() == 'SOURCE_REGENERATE'
6376
+ already_exhausted = str(meta.get('nonconvergent_state') or '').startswith('exhausted')
6377
+ if (
6378
+ prior_attempts >= self.MAX_TRANSLATION_CLAIM_REPAIR_ATTEMPTS
6379
+ or already_exhausted
6380
+ or (already_source_regenerated and bool(prior_codes & code_set))
6381
+ ):
6382
+ meta['nonconvergent_state'] = 'exhausted_after_source_regenerate' if already_source_regenerated else 'exhausted_after_claim_budget'
6383
+ carried_exhausted.setdefault(block_id, {})[claim_id] = list(codes)
6384
+ logging.info(
6385
+ 'Translation claim repair entitlement exhausted: section=%s id=SP-P%s-%s attempts=%s strategy=%s',
6386
+ section_name or 'section', block_id, claim_id, prior_attempts, meta.get('last_strategy') or '',
6387
+ )
6388
+ self._record_translation_assurance(
6389
+ 'CLAIM_REPAIR_EXHAUSTED', target_lang=target_lang,
6390
+ block_id=f'SP-P{block_id}-{claim_id}', severity='critical', decision='ADJUDICATE',
6391
+ detail={'attempts': prior_attempts, 'strategy': str(meta.get('last_strategy') or '')},
6392
+ )
6393
+ continue
6394
+ if prior_attempts == 0 and repair_cycle > 1:
6395
+ logging.info(
6396
+ 'Translation late-detected claim receives own repair entitlement: section=%s id=SP-P%s-%s cycle=%s',
6397
+ section_name or 'section', block_id, claim_id, repair_cycle,
6398
+ )
6399
 
6400
  semantic_deltas = [
6401
  str(code or '').strip().upper() for code in (repair_evidence.get('semantic_delta_codes') or [])
 
6474
  )
6475
  self._translation_claim_qa_evidence[evidence_key] = dict(repair_evidence)
6476
  signature = self._translation_repair_evidence_signature(codes, repair_evidence)
 
 
 
 
 
6477
  same_defect = (
6478
  str(meta.get('last_evidence_signature') or '') == signature
6479
  and bool(prior_codes & code_set)
6480
  )
 
 
 
 
 
 
 
 
 
 
 
 
6481
 
6482
  force_source_regenerate = (
6483
  'UNRESOLVED_SEMANTIC_DELTA' in set(repair_evidence.get('semantic_delta_codes') or [])
 
6485
  )
6486
  strategy = 'SOURCE_REGENERATE' if (
6487
  force_source_regenerate
6488
+ or prior_attempts >= 1
6489
  or (same_defect and str(meta.get('last_strategy') or '').upper() == 'LOCAL_EDIT')
6490
  ) else 'LOCAL_EDIT'
6491
  if strategy == 'SOURCE_REGENERATE':