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
138 lines
5.4 KiB
Python
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
|