feat(translate,scheduler,trace): new-key-only mode, stale PENDING protection, trace_id propagation
Translations: - FR-045: new-key-only execution mode — translate only rows with unseen keys - Compare source row keys against TranslationRecord.source_data from last succeeded run - Baseline expired (>90 days) fallback to full mode with baseline_expired event - run_noop early return when zero new keys (skip LLM + SQL) Scheduler reliability: - Stale PENDING protection: runs older than 1h auto-marked FAILED, no longer block schedule - load_schedules() reloads active translation schedules from DB on restart - add_translation_job/remove_translation_job register/unregister with APScheduler - execution_mode column on translation_schedules with additive DB migration Automation view: - GET /settings/automation/translation-schedules endpoint - Translation schedules displayed on Automation page below validation policies Trace propagation: - seed_trace_id() in all background entry points: TaskManager._run_task/_flusher_loop, SchedulerService._trigger_backup, websocket_endpoint, IdMappingService, all standalone scripts Migrations: - dictionary_entries: origin_run_id, origin_row_key, origin_user_id - translation_schedules: execution_mode Tests: - 3 new test modules (22 tests): core_scheduler, executor_filter, scheduler_execution+guard - Fix pre-existing translate test isolation (conftest.py) - Fix test_list_runs_filter_status parameter
This commit is contained in:
@@ -44,6 +44,7 @@ async def get_schedule(
|
||||
"cron_expression": schedule.cron_expression,
|
||||
"timezone": schedule.timezone,
|
||||
"is_active": schedule.is_active,
|
||||
"execution_mode": schedule.execution_mode,
|
||||
"last_run_at": schedule.last_run_at.isoformat() if schedule.last_run_at else None,
|
||||
"next_run_at": schedule.next_run_at.isoformat() if schedule.next_run_at else None,
|
||||
"created_by": schedule.created_by,
|
||||
@@ -77,12 +78,14 @@ async def set_schedule(
|
||||
existing = db.query(TranslationSchedule).filter(
|
||||
TranslationSchedule.job_id == job_id
|
||||
).first()
|
||||
execution_mode = getattr(payload, 'execution_mode', 'full')
|
||||
if existing:
|
||||
schedule = scheduler.update_schedule(
|
||||
job_id,
|
||||
cron_expression=payload.cron_expression,
|
||||
timezone_str=payload.timezone,
|
||||
is_active=payload.is_active,
|
||||
execution_mode=execution_mode,
|
||||
)
|
||||
else:
|
||||
schedule = scheduler.create_schedule(
|
||||
@@ -90,6 +93,7 @@ async def set_schedule(
|
||||
cron_expression=payload.cron_expression,
|
||||
timezone=payload.timezone,
|
||||
is_active=payload.is_active,
|
||||
execution_mode=execution_mode,
|
||||
)
|
||||
# Register with APScheduler via SchedulerService
|
||||
from ....dependencies import get_scheduler_service
|
||||
@@ -99,6 +103,7 @@ async def set_schedule(
|
||||
job_id=job_id,
|
||||
cron_expression=schedule.cron_expression,
|
||||
timezone=schedule.timezone,
|
||||
execution_mode=schedule.execution_mode,
|
||||
)
|
||||
return {
|
||||
"id": schedule.id,
|
||||
@@ -106,6 +111,7 @@ async def set_schedule(
|
||||
"cron_expression": schedule.cron_expression,
|
||||
"timezone": schedule.timezone,
|
||||
"is_active": schedule.is_active,
|
||||
"execution_mode": schedule.execution_mode,
|
||||
"last_run_at": schedule.last_run_at.isoformat() if schedule.last_run_at else None,
|
||||
"created_by": schedule.created_by,
|
||||
"created_at": schedule.created_at.isoformat() if schedule.created_at else None,
|
||||
@@ -140,6 +146,7 @@ async def enable_schedule(
|
||||
job_id=job_id,
|
||||
cron_expression=schedule.cron_expression,
|
||||
timezone=schedule.timezone,
|
||||
execution_mode=schedule.execution_mode,
|
||||
)
|
||||
return {"status": "enabled", "job_id": job_id}
|
||||
except ValueError as e:
|
||||
|
||||
Reference in New Issue
Block a user