Files
ss-tools/backend/src/plugins/translate/preview_response_parser.py
busya f872e610a9 refactor: decompose oversized contracts to satisfy INV_7 fractal limit
Break monolithic modules >400 lines into focused sub-modules while
preserving backward-compatible imports and all test coverage:

Backend (Python):
- TranslationExecutor: 1974→241 lines, split into 9 sub-modules
- Translate plugin: orchestrator (1137→148), preview (1303→244),
  service (1052→275), dictionary (1007→68)
- ProfileService: 857→172 with 4 extracted sub-modules
- TaskManager: 708→322 with graph/event_bus/lifecycle extracted
- Test dictionary: 1199→split into 6 focused test files

Frontend (Svelte):
- SettingsPage: 1451→291 with 6 extracted tab components
- GitManager: 1220→228 with 5 extracted panels
- DatasetReviewWorkspace: 1202→314
- translate.js API: 664→28 barrel with 6 domain modules

Protocol:
- Remove single-contract 150-line limit from INV_7 (keep CC≤10)
- Fix unclosed #endregion tags across 11 files
- Fix 19 test regressions from stale mock paths
- All 294 tests passing
2026-05-17 19:18:32 +03:00

138 lines
5.4 KiB
Python

# #region preview_response_parser [C:3] [TYPE Module] [SEMANTICS llm, parse, json, superset, hash]
# @BRIEF Parse LLM JSON responses and Superset data; compute config/dict hashes for preview.
# @RELATION DEPENDS_ON -> [TranslationJob]
# @RELATION DEPENDS_ON -> [TranslationJobDictionary]
import hashlib
import json
import re
from typing import Any
from sqlalchemy.orm import Session
from ...models.translate import TranslationJob, TranslationJobDictionary
# #region parse_llm_response [C:3] [TYPE Function] [SEMANTICS llm, parse, json, translate]
# @BRIEF Parse LLM JSON response into structured translations dict with per-language support.
# @PRE response_text is a valid JSON string (possibly wrapped in markdown code block).
# @POST Returns dict mapping row_id -> {detected_source_language, lang_code: translation, ...}.
def parse_llm_response(
response_text: str,
expected_count: int,
target_languages: list[str] | None = None,
finish_reason: str | None = None,
) -> dict[str, dict[str, str]]:
"""Parse LLM JSON response into structured translations dict."""
try:
data = json.loads(response_text)
except json.JSONDecodeError:
data = None
match = re.search(r'```(?:json)?\s*\n?(.*?)\n?```', response_text, re.DOTALL)
if match:
try:
data = json.loads(match.group(1))
except json.JSONDecodeError:
pass
if data is None:
rows_match = re.findall(r'\{\s*"row_id"\s*:\s*(?:\d+|"\d+").*?\}\s*', response_text, re.DOTALL)
if rows_match:
partial_rows = []
for row_text in rows_match:
try:
partial_rows.append(json.loads(row_text))
except json.JSONDecodeError:
continue
if partial_rows:
data = {"rows": partial_rows}
if data is None:
raise ValueError("LLM response was not valid JSON")
rows = data.get("rows", [])
if not isinstance(rows, list):
raise ValueError("LLM response missing 'rows' array")
translations: dict[str, dict[str, str]] = {}
for item in rows:
row_id = str(item.get("row_id", ""))
if not row_id:
continue
detected_lang = str(item.get("detected_source_language", "und")) if item.get("detected_source_language") else "und"
result: dict[str, str] = {"detected_source_language": detected_lang}
has_language_data = False
if target_languages:
for lang_code in target_languages:
lang_val = item.get(lang_code)
if lang_val is not None and str(lang_val).strip():
result[lang_code] = str(lang_val)
has_language_data = True
if not has_language_data:
translation = item.get("translation")
if translation is not None:
result["translation"] = str(translation)
else:
continue
translations[row_id] = result
return translations
# #endregion parse_llm_response
# #region extract_data_rows [C:1] [TYPE Function] [SEMANTICS superset, data, extraction]
# @BRIEF Extract data rows from Superset chart data API response.
def extract_data_rows(response: dict[str, Any]) -> list[dict[str, Any]]:
"""Extract data rows from Superset chart data API response."""
result = response.get("result")
if isinstance(result, list):
for item in result:
if isinstance(item, dict):
data = item.get("data")
if isinstance(data, list) and data:
return data
if isinstance(result, dict):
data = result.get("data")
if isinstance(data, list) and data:
return data
data = response.get("data")
if isinstance(data, list) and data:
return data
if isinstance(result, list):
return result
return []
# #endregion extract_data_rows
# #region compute_config_hash [C:1] [TYPE Function] [SEMANTICS config, hash, deterministic]
# @BRIEF Compute a deterministic hash of job configuration for snapshot comparison.
def compute_config_hash(job: TranslationJob) -> str:
"""Compute a deterministic hash of job configuration for snapshot comparison."""
config_str = json.dumps({
"source_dialect": job.source_dialect,
"target_dialect": job.target_dialect,
"source_datasource_id": job.source_datasource_id,
"translation_column": job.translation_column,
"context_columns": job.context_columns,
"provider_id": job.provider_id,
"batch_size": job.batch_size,
"upsert_strategy": job.upsert_strategy,
}, sort_keys=True)
return hashlib.sha256(config_str.encode()).hexdigest()[:16]
# #endregion compute_config_hash
# #region compute_dict_snapshot_hash [C:2] [TYPE Function] [SEMANTICS dictionary, hash, snapshot]
# @BRIEF Compute a hash of dictionary state for snapshot comparison.
def compute_dict_snapshot_hash(db: Session, job_id: str) -> str:
"""Compute a hash of dictionary state for snapshot comparison."""
dict_links = (
db.query(TranslationJobDictionary)
.filter(TranslationJobDictionary.job_id == job_id)
.all()
)
dict_ids = sorted([dl.dictionary_id for dl in dict_links])
hash_input = ",".join(dict_ids)
return hashlib.sha256(hash_input.encode()).hexdigest()[:16]
# #endregion compute_dict_snapshot_hash
# #endregion preview_response_parser