feat(ops): /ops/v1/summary med ai/aktivering/engagemang/betalning/jobb/sakerhet + canary
- Nya toppnivåblock i /ops/v1/summary: ai_scan, aktivering, engagemang, betalning, jobb, sakerhet. Alla värden läses från DB/cache; null där data saknas, inga påhittade värden. - AI-kostnad i USD mikrocent (native); intäkter fortsatt SEK-öre. - product_analytics_events är källa för volym, latens, lyckandegrad, felfrekvens, aktivering, retention och engagemang. - Nya händelser: scan_started (API) och scan_failed/scan_completed med latencyMs + felkod (worker). latencyMs flödar nu in i scan_completed. - Safety canary-jobb varje timme: re-härleder allergener för alla recept, räknar överifierade publika recept och food-safety-lint; skriver EN rad till ops_safety_canary. Endpointen läser endast sista raden. - Cache-refresh-jobb var 60 s skriver hela summariet till Redis; endpointen serverar cachen med 503 vid cache-miss. - Bearer-token-skydd med OPS_TOKEN; HTTPS-tvång i produktion; ingen PII. - Tester för endpoint, auth, cache-miss och safety canary.
This commit is contained in:
@@ -20,6 +20,8 @@ import {
|
||||
processTrainingExport,
|
||||
processTrustDecay,
|
||||
} from "./processors/maintenance.js";
|
||||
import { processSafetyCanary } from "./processors/safety-canary.js";
|
||||
import { refreshOpsSummaryCache } from "./processors/ops-summary.js";
|
||||
|
||||
import {
|
||||
DEAD_LETTER_QUEUE_NAME,
|
||||
@@ -131,6 +133,18 @@ const worker = new Worker(
|
||||
return;
|
||||
}
|
||||
|
||||
case "RUN_SAFETY_CANARY": {
|
||||
const result = await processSafetyCanary(ctx);
|
||||
log(`Safety canary: ${JSON.stringify(result)}`);
|
||||
return;
|
||||
}
|
||||
|
||||
case "REFRESH_OPS_SUMMARY_CACHE": {
|
||||
await refreshOpsSummaryCache(ctx);
|
||||
log("Ops summary cache uppdaterad.");
|
||||
return;
|
||||
}
|
||||
|
||||
// Deterministiska/planerade jobb som inte kräver egen processor ännu
|
||||
case "NORMALIZE_PRODUCTS":
|
||||
case "DEDUPLICATE_INVENTORY":
|
||||
@@ -230,12 +244,20 @@ async function registerRepeatableJobs() {
|
||||
await queue.upsertJobScheduler(
|
||||
"scheduler-cooking-timeout",
|
||||
{ every: 60 * 60 * 1000 }, // varje timme räcker för 24 h-timeout
|
||||
{ name: "COOKING_SESSION_TIMEOUT", data: { jobType: "COOKING_SESSION_TIMEOUT" }, opts: baseOpts },
|
||||
{
|
||||
name: "COOKING_SESSION_TIMEOUT",
|
||||
data: { jobType: "COOKING_SESSION_TIMEOUT" },
|
||||
opts: baseOpts,
|
||||
},
|
||||
);
|
||||
await queue.upsertJobScheduler(
|
||||
"scheduler-expiry",
|
||||
{ pattern: "0 7 * * *", tz: "Europe/Stockholm" },
|
||||
{ name: "SEND_EXPIRY_NOTIFICATION", data: { jobType: "SEND_EXPIRY_NOTIFICATION" }, opts: baseOpts },
|
||||
{
|
||||
name: "SEND_EXPIRY_NOTIFICATION",
|
||||
data: { jobType: "SEND_EXPIRY_NOTIFICATION" },
|
||||
opts: baseOpts,
|
||||
},
|
||||
);
|
||||
await queue.upsertJobScheduler(
|
||||
"scheduler-memory",
|
||||
@@ -257,6 +279,20 @@ async function registerRepeatableJobs() {
|
||||
{ pattern: "45 2 * * 0", tz: "Europe/Stockholm" },
|
||||
{ name: "BUILD_TRAINING_SAMPLE", data: { jobType: "BUILD_TRAINING_SAMPLE" }, opts: baseOpts },
|
||||
);
|
||||
await queue.upsertJobScheduler(
|
||||
"scheduler-safety-canary",
|
||||
{ every: 60 * 60 * 1000 }, // varje timme
|
||||
{ name: "RUN_SAFETY_CANARY", data: { jobType: "RUN_SAFETY_CANARY" }, opts: baseOpts },
|
||||
);
|
||||
await queue.upsertJobScheduler(
|
||||
"scheduler-ops-summary",
|
||||
{ every: 60 * 1000 }, // var 60:e sekund
|
||||
{
|
||||
name: "REFRESH_OPS_SUMMARY_CACHE",
|
||||
data: { jobType: "REFRESH_OPS_SUMMARY_CACHE" },
|
||||
opts: baseOpts,
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
// --- Minimal healthcheck-server så att flera instanser kan övervakas ---
|
||||
|
||||
Reference in New Issue
Block a user