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:
Sven (AAMOS AI)
2026-08-10 05:47:43 +07:00
parent f4a603e977
commit 8f20b2ef1b
27 changed files with 1169 additions and 41 deletions
+1
View File
@@ -18,5 +18,6 @@ export * from "./memory.js";
export * from "./seasons.js";
export * from "./subscriptions.js";
export * from "./analytics.js";
export * from "./ops.js";
export * from "./platform.js";
export * from "./releaseGates.js";
+17
View File
@@ -0,0 +1,17 @@
import { integer, pgTable, timestamp, uuid } from "drizzle-orm/pg-core";
/**
* Safety canary: hourly deterministic checks written by a worker job and
* read by the ops endpoint. The endpoint never runs heavy computations.
*/
export const opsSafetyCanary = pgTable("ops_safety_canary", {
id: uuid("id").primaryKey().defaultRandom(),
occurredAt: timestamp("occurred_at", { withTimezone: true }).notNull().defaultNow(),
/** Recipes where stored allergens differ from freshly derived allergens. */
allergenInvariantBrott: integer("allergen_invariant_brott").notNull().default(0),
/** Publicly visible recipes that are not verificationStatus=verified. */
overifieradeVisade: integer("overifierade_visade").notNull().default(0),
/** Recipes rejected by the food-safety lint in the last 7 days. */
foodSafetyLintAvvisade7d: integer("food_safety_lint_avvisade_7d"),
computedAt: timestamp("computed_at", { withTimezone: true }).notNull().defaultNow(),
});