perf: fix translate deadlock, speed, trace_id, UI bugs — fullstack patch

## Backend (7 production files + 6 test files)

### P0-2: LLM output truncation cascade fix
- _token_budget.py: OUTPUT_PER_ROW_PER_LANG 120→200, OUTPUT_SAFETY_FACTOR 0.70→0.55
- Prevents finish_reason=length → split → retry cascade (3 calls → 1 call per batch)
- P2-8: added qwen-flash/qwen-plus/qwen-max/qwen-coder to PROVIDER_DEFAULTS

### P1-4/P1-5: EncryptionManager singleton
- encryption.py: get_encryption_manager() process-wide singleton
- llm_provider.py: use singleton instead of new EncryptionManager() per batch
- Eliminates ~90 redundant Fernet key validations per translation run

### P1-6: Cache-hit log aggregation
- _batch_proc.py: one log per batch (batch_rows + cache_hits) instead of per-row
- 1076 log lines → ~30 per run

### P1-7: Timezone-aware datetime fix
- scheduler.py: _ensure_aware() helper for naive DB datetime → UTC-aware
- Fixes TypeError in scheduled translation concurrency check

### P2-9: Connection test timeout
- connection_service.py: asyncio.wait_for(15s) on all dialect tests
- Prevents 2-minute UI hangs from DNS/TCP stalls

### Trace ID propagation
- middleware/trace.py: inject x-trace-id response header via ASGI send wrapper

### Test fixes & integration tests
- test_scheduler.py: AsyncMock for execute_run, mock get_async_job_runner
- test_sql_insert_service.py: AsyncMock for execute_sql
- test_token_budget.py: batch_size 50→45 for new OUTPUT_PER_ROW_PER_LANG=200
- test_encryption.py: +2 singleton tests
- test_scheduler_ensure_aware.py: +4 (naive→aware, passthrough, None, subtraction)
- test_batch_classify_persist.py: +2 cache-hit aggregation tests
- test_connection_service_edge.py: +2 timeout tests
- test_trace_middleware.py: +4 x-trace-id header tests
- test_token_budget.py: +4 qwen-flash/O200 tests

## Frontend (7 production files + 5 test files)

### Trace ID propagation
- api.ts: _captureTraceId() reads x-trace-id → setTraceId() in fetchApi/requestApi/postApi/deleteApi

### Duplicate datasource columns fetch
- ConfigTabForm.svelte: guard availableColumns.length === 0 before fetch

### Admin pages Svelte 5 runes fix
- admin/users/+page.svelte: plain let → () for all template-bound vars
- admin/roles/+page.svelte: same fix
- Both pages were stuck on «Загрузка...» due to mixed reactivity models

### Validation popover positioning
- +page.svelte: pass trigger HTMLElement instead of event
- DashboardHubModel.svelte.ts: toggleValidationPopover(HTMLElement), closeValidationPopover()
- Added X close button + click-outside overlay + i18n

### Test fixes & integration tests
- api.test.ts: mock setTraceId/getTraceId, +3 _captureTraceId tests
- provider_config.integration.test.ts: handleDelete→promptDeleteProvider
- DatasetPreview.test.ts: dashboards/ → ROUTES.dashboards
- test_config_tab_form.svelte.js: +2 columns fetch guard tests (NEW)
- admin-users.test.ts: +3 loading→table tests (NEW)
- admin-roles.test.ts: +2 loading→table tests (NEW)

## Semantic curation
- Removed @COMPLEXITY N from 6 route files + metrics.py (duplicate of [C:N])
- Added [C:N] to 2 orphan child contracts in metrics.py
- Added [C:N] + @BRIEF to 4 frontend anchors
- Fixed #region → # #region consistency in validation_tasks.py

## Verification
- Backend: 608 pytest passed (0 failures)
- Frontend: 2472 vitest passed (128 files, 0 failures)
- Frontend build: ✓ built in 18s
- Browser: dashboards, admin/users, admin/roles, validation popover — all green
This commit is contained in:
2026-06-18 23:54:57 +03:00
parent 4a6fe8db58
commit 3133e50645
59 changed files with 1774 additions and 13928 deletions

View File

@@ -0,0 +1,80 @@
# #region Test.Scheduler.EnsureAware [C:2] [TYPE Module] [SEMANTICS test,scheduler,datetime,utc]
# @BRIEF Verify _ensure_aware contract — naive→UTC conversion, aware passthrough, None passthrough.
# @RELATION BINDS_TO -> [TranslationScheduler._ensure_aware]
# @TEST_EDGE naive_to_aware -> Naive datetime gets UTC tzinfo
# @TEST_EDGE already_aware -> Aware datetime returned unchanged
# @TEST_EDGE none_passthrough -> None returns None
# @TEST_EDGE subtraction_works -> Conversion enables subtraction with datetime.now(UTC)
#
# @RATIONALE SQLAlchemy DateTime (without timezone=True) returns naive datetimes
# even when stored as UTC. This module verifies the normalization
# helper used throughout execute_scheduled_translation for safe
# datetime arithmetic.
from pathlib import Path
import sys
sys.path.insert(0, str(Path(__file__).parent.parent / "src"))
from datetime import UTC, datetime, timedelta
# #region TestEnsureAware [C:2] [TYPE Class] [SEMANTICS test,scheduler,datetime]
# @BRIEF Tests for _ensure_aware() — naive-to-UTC conversion and edge cases.
class TestEnsureAware:
"""_ensure_aware — convert naive DB datetime to UTC-aware for safe arithmetic."""
# #region test_naive_to_aware [C:2] [TYPE Function] [SEMANTICS test,datetime,naive]
# @BRIEF Naive datetime gets UTC tzinfo attached.
def test_naive_to_aware(self):
from src.plugins.translate.scheduler import _ensure_aware
naive = datetime(2024, 6, 15, 10, 30, 0)
result = _ensure_aware(naive)
assert result.tzinfo is not None
assert result.tzinfo == UTC
assert result == naive.replace(tzinfo=UTC)
assert result.hour == 10 # no offset shift
# #endregion test_naive_to_aware
# #region test_already_aware_passthrough [C:2] [TYPE Function] [SEMANTICS test,datetime,aware]
# @BRIEF Already UTC-aware datetime returned unchanged (same object).
def test_already_aware_passthrough(self):
from src.plugins.translate.scheduler import _ensure_aware
aware = datetime(2024, 6, 15, 10, 30, 0, tzinfo=UTC)
result = _ensure_aware(aware)
assert result is aware # same object identity
assert result.tzinfo == UTC
# #endregion test_already_aware_passthrough
# #region test_none_returns_none [C:2] [TYPE Function] [SEMANTICS test,datetime,none]
# @BRIEF None input returns None.
def test_none_returns_none(self):
from src.plugins.translate.scheduler import _ensure_aware
result = _ensure_aware(None)
assert result is None
# #endregion test_none_returns_none
# #region test_subtraction_works [C:2] [TYPE Function] [SEMANTICS test,datetime,arithmetic]
# @BRIEF Naive→aware conversion enables subtraction with datetime.now(UTC)
# without TypeError (the original problem this function solves).
def test_subtraction_works(self):
from src.plugins.translate.scheduler import _ensure_aware
naive = datetime(2024, 6, 15, 10, 0, 0)
aware = _ensure_aware(naive)
now = datetime.now(UTC)
# This would raise TypeError without _ensure_aware:
# TypeError: can't subtract offset-naive and offset-aware datetimes
elapsed = now - aware
assert isinstance(elapsed, timedelta)
assert elapsed.total_seconds() > 0 # always positive vs past date
# #endregion test_subtraction_works
# #endregion TestEnsureAware
# #endregion Test.Scheduler.EnsureAware