Files
ss-tools/backend/src/plugins/translate/orchestrator_query.py
busya 15d47450a3 feat: add deduplicate + metrics to Run tab
Backend:
- deduplicate param in GET /runs/{id}/records — NOT EXISTS subquery
  with (created_at, id) tiebreaker for one row per source_hash
- source_data and source_hash added to records JSON response
- fix missing status import in _run_history_routes.py (NameError)
- fix tab/space mixup in orchestrator_aggregator.py
- remove duplicated @RELATION edges in metrics.py
- fix #region/#endregion style and @PURPOSE→@BRIEF in metrics.py

Frontend:
- RunTabContent: summary metrics bar (fetchJobMetrics) with HelpTooltips
- RunTabContent: trigger_type badge, duration, cache rate in run rows
- TranslationRunResult: deduplicate=true by default, paginated Load more
- TranslationRunResult: per-language token_count and estimated_cost
- TranslationRunResult: collapsible batch breakdown with timing
- TranslationRunResult: Bulk Replace button in header and records table
- TranslationRunResult: source_data key values shown under source text

i18n: all new keys in EN + RU (load_more_records, showing_records,
sum_*, help_sum_*, trigger_*, batch_*, duration_label, cost_label,
cache_rate, load_more_records)
2026-06-04 13:23:10 +03:00

149 lines
5.4 KiB
Python

# #region orchestrator_query [C:3] [TYPE Module] [SEMANTICS translate, query, records, history]
# @BRIEF Query translation run records and history with pagination.
# @RELATION DEPENDS_ON -> [TranslationRecord]
# @RELATION DEPENDS_ON -> [TranslationRun]
from typing import Any
from sqlalchemy.orm import Session, selectinload
from ...models.translate import TranslationRecord, TranslationRun
# #region get_run_records [C:2] [TYPE Function] [SEMANTICS records, query, paginated]
# @BRIEF Get paginated records for a run. Supports deduplicate=true to return unique source_hash rows.
def get_run_records(
db: Session,
run_id: str,
page: int = 1,
page_size: int = 50,
status_filter: str | None = None,
deduplicate: bool = False,
) -> dict[str, Any]:
"""Get paginated records for a run."""
import sqlalchemy as sa
query = (
db.query(TranslationRecord)
.options(selectinload(TranslationRecord.languages))
.filter(TranslationRecord.run_id == run_id)
)
if status_filter:
query = query.filter(TranslationRecord.status == status_filter)
if deduplicate:
# One row per unique source_hash using NOT EXISTS on (source_hash, created_at, id).
# When created_at ties, the larger id wins. No relationship joins inside the
# correlated subquery — avoids row multiplication from languages.
RecAlias = sa.orm.aliased(TranslationRecord)
newer_exists = (
db.query(RecAlias.id)
.filter(
RecAlias.run_id == run_id,
RecAlias.source_hash == TranslationRecord.source_hash,
RecAlias.source_hash.isnot(None),
RecAlias.source_hash != "",
sa.or_(
RecAlias.created_at > TranslationRecord.created_at,
sa.and_(
RecAlias.created_at == TranslationRecord.created_at,
RecAlias.id > TranslationRecord.id,
),
),
)
).exists()
query = query.filter(
TranslationRecord.source_hash.isnot(None),
TranslationRecord.source_hash != "",
~newer_exists,
)
total = query.count()
offset_val = (page - 1) * page_size
records = (
query.order_by(TranslationRecord.created_at.desc())
.offset(offset_val)
.limit(page_size)
.all()
)
return {
"items": [
{
"id": r.id,
"batch_id": r.batch_id,
"source_sql": r.source_sql,
"target_sql": r.target_sql,
"source_object_type": r.source_object_type,
"source_object_id": r.source_object_id,
"source_object_name": r.source_object_name,
"source_data": r.source_data,
"source_hash": r.source_hash,
"status": r.status,
"error_message": r.error_message,
"created_at": r.created_at.isoformat() if r.created_at else None,
"languages": [
{
"language_code": tl.language_code,
"translated_value": tl.translated_value,
"final_value": tl.final_value or tl.translated_value,
"source_language_detected": tl.source_language_detected,
"status": tl.status,
"needs_review": tl.needs_review or False,
}
for tl in (r.languages or [])
],
}
for r in records
],
"total": total,
"page": page,
"page_size": page_size,
"status_filter": status_filter,
"deduplicate": deduplicate,
}
# #endregion get_run_records
# #region get_run_history [C:2] [TYPE Function] [SEMANTICS history, query, paginated]
# @BRIEF Get run history for a job with pagination. Returns trigger_type, cache_hits for enhanced run list display.
def get_run_history(
db: Session,
job_id: str,
page: int = 1,
page_size: int = 20,
) -> tuple[int, list[dict[str, Any]]]:
"""Get run history for a job with pagination."""
query = db.query(TranslationRun).filter(TranslationRun.job_id == job_id)
total = query.count()
runs = (
query.order_by(TranslationRun.created_at.desc())
.offset((page - 1) * page_size)
.limit(page_size)
.all()
)
return total, [
{
"id": r.id,
"job_id": r.job_id,
"status": r.status,
"trigger_type": r.trigger_type,
"started_at": r.started_at.isoformat() if r.started_at else None,
"completed_at": r.completed_at.isoformat() if r.completed_at else None,
"error_message": r.error_message,
"total_records": r.total_records or 0,
"successful_records": r.successful_records or 0,
"failed_records": r.failed_records or 0,
"skipped_records": r.skipped_records or 0,
"cache_hits": r.cache_hits or 0,
"insert_status": r.insert_status,
"superset_execution_id": r.superset_execution_id,
"created_by": r.created_by,
"created_at": r.created_at.isoformat() if r.created_at else None,
}
for r in runs
]
# #endregion get_run_history
# #endregion orchestrator_query