PR 4 du chantier admin. Board de sante temps reel des dependances.
- POST /internal/tech-steps/heartbeat (requireInternalWorker) ->
recordWorkerHeartbeat : upsert WorkerHeartbeat (cle fixe
"tech-step-llm-worker"), lastRunAt/lastResult pour un ping "job".
Schema workerHeartbeatSchema dans packages/shared.
- services/tech-step-llm-worker : api-client.postHeartbeat (best-effort,
ne throw jamais) appele au boot (index.ts), a chaque tick et apres
chaque job (scheduler.ts, avec job/ok/counts).
- admin-monitoring.service.ts + GET /admin/monitoring (requireAdmin) :
sonde active bornee (~2 s) de Postgres (SELECT 1), l'API (uptime/RSS),
tech-step-intent-service (/health), et le worker via son heartbeat.
Statut up/degraded/down/unknown ; une sonde down ne casse ni les autres
ni l'endpoint. Seuils worker : > 8 j degraded, > 21 j down.
- MonitoringView / ServiceHealthView dans packages/shared.
- Front : MonitoringPage (grille de cartes coloree par statut, re-poll
15 s), logique pure monitoring.ts, i18n admin.monitoring.*,
AdminApiClient.getMonitoring.
- Tests : Mocha admin-monitoring.test.ts (heartbeat 401/400/upsert
job+boot ; GET /admin/monitoring 401, board 4 cibles, worker unknown
sans heartbeat puis up apres) ; Cypress monitoring.cy.ts (2 verts).
Worker mocha : 6/6 toujours verts.
- specs/backend-architecture.md : section monitoring. .gitignore :
apps/admin-web/cypress/{screenshots,videos,downloads}.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
72 lines
2.8 KiB
TypeScript
72 lines
2.8 KiB
TypeScript
import cron from "node-cron";
|
|
import { postHeartbeat } from "./api-client.js";
|
|
import { env } from "./config.js";
|
|
import { runAuditLowConfidenceJob } from "./jobs/audit-low-confidence.js";
|
|
import { runTransformCorrectionsJob } from "./jobs/transform-corrections.js";
|
|
import { TechStepLlmService } from "./llm-verdict.js";
|
|
import { loadTechStepTaxonomy } from "./tech-step-taxonomy.js";
|
|
|
|
/**
|
|
* Runs one full cycle: fetch the taxonomy, load the model, run both jobs,
|
|
* dispose the model. The model is never kept loaded between scheduled
|
|
* runs (see `llm-verdict.ts`'s `dispose()` doc comment) — this function's
|
|
* own duration (model load/dispose easily adds several seconds) is an
|
|
* accepted cost of keeping this process's idle RAM footprint low between
|
|
* runs, not something to optimize away.
|
|
*/
|
|
export async function runOnce(): Promise<void> {
|
|
console.info("[tech-step-llm-worker] starting scheduled run...");
|
|
const taxonomy = await loadTechStepTaxonomy();
|
|
const techStepKeys = taxonomy.map((techStep) => techStep.key);
|
|
|
|
const llm = new TechStepLlmService();
|
|
try {
|
|
await llm.initialize(techStepKeys);
|
|
|
|
const auditCount = await runAuditLowConfidenceJob(llm, {
|
|
locale: env.TECH_STEP_WORKER_LOCALE,
|
|
limit: env.TECH_STEP_WORKER_BATCH_LIMIT,
|
|
});
|
|
console.info(`[tech-step-llm-worker] audit-low-confidence: ${auditCount} suggestion(s)`);
|
|
await postHeartbeat({
|
|
event: "job",
|
|
job: "audit-low-confidence",
|
|
ok: true,
|
|
counts: { suggestions: auditCount },
|
|
});
|
|
|
|
const correctionCount = await runTransformCorrectionsJob(llm, {
|
|
limit: env.TECH_STEP_WORKER_BATCH_LIMIT,
|
|
});
|
|
console.info(`[tech-step-llm-worker] transform-corrections: ${correctionCount} suggestion(s)`);
|
|
await postHeartbeat({
|
|
event: "job",
|
|
job: "transform-corrections",
|
|
ok: true,
|
|
counts: { suggestions: correctionCount },
|
|
});
|
|
} finally {
|
|
await llm.dispose();
|
|
}
|
|
console.info("[tech-step-llm-worker] scheduled run complete.");
|
|
}
|
|
|
|
/**
|
|
* Starts the long-lived cron loop — {@link runOnce} fires on
|
|
* `env.TECH_STEP_WORKER_CRON`'s schedule, indefinitely, until the process
|
|
* is stopped. A run that throws is logged, not left to crash the process —
|
|
* the next scheduled fire still happens; a transient API/model failure on
|
|
* one run shouldn't permanently kill the worker until someone notices and
|
|
* manually restarts its container.
|
|
*/
|
|
export function startScheduler(): void {
|
|
console.info(`[tech-step-llm-worker] scheduling runs on "${env.TECH_STEP_WORKER_CRON}"`);
|
|
cron.schedule(env.TECH_STEP_WORKER_CRON, () => {
|
|
// Prove liveness even for a fire that then fails inside `runOnce`.
|
|
void postHeartbeat({ event: "tick" });
|
|
runOnce().catch((err: unknown) => {
|
|
console.error("[tech-step-llm-worker] scheduled run failed:", err);
|
|
void postHeartbeat({ event: "job", ok: false });
|
|
});
|
|
});
|
|
}
|