Fas 1a punkt 3+4: worker-härdning (DLQ, larm, multi-instance), AAMOS training export

This commit is contained in:
Sven (AAMOS AI)
2026-08-05 23:32:18 +07:00
parent d1455fb581
commit b2b6601f54
15 changed files with 9355 additions and 32 deletions
+12
View File
@@ -237,6 +237,18 @@ export function mockOutputFor(taskType: AamosTaskType, input: unknown): unknown
case "MODERATE_RECIPE":
return { flags: [], recommendation: "approve", confidence: 0.9 };
case "EXPORT_TRAINING_SAMPLE": {
const samples =
typeof input === "object" && input !== null
? ((input as { samples?: unknown[] }).samples ?? [])
: [];
return {
batchId: `mock-batch-${Date.now()}`,
accepted: samples.length,
rejected: 0,
};
}
case "TRANSLATE_RECIPE": {
// Deterministisk pseudo-översättning: bevarar alla tal och stegstruktur,
// markerar texten med målspråket så flödet är testbart utan riktig AI.
+28
View File
@@ -24,6 +24,7 @@ export const AAMOS_TASK_TYPES = [
"GENERATE_WEEK_PLAN",
"MODERATE_RECIPE",
"TRANSLATE_RECIPE",
"EXPORT_TRAINING_SAMPLE",
] as const;
export type AamosTaskType = (typeof AAMOS_TASK_TYPES)[number];
@@ -412,6 +413,29 @@ export const translateRecipeOutput = z.object({
confidence,
});
// ---------------------------------------------------------------------------
// EXPORT_TRAINING_SAMPLE: anonymiserade korrigeringar för AAMOS training-pipeline
// Ingen PII, inga bild-URL:er, inga användar-ID:n.
// ---------------------------------------------------------------------------
export const exportTrainingSampleInput = z.object({
marketLocale: z.string().default("sv-SE"),
samples: z.array(
z.object({
taskType: z.string(),
aiOutput: z.record(z.string(), z.unknown()),
userCorrection: z.record(z.string(), z.unknown()),
modelVersion: z.string().nullable(),
promptVersion: z.string().nullable(),
}),
),
});
export const exportTrainingSampleOutput = z.object({
batchId: z.string(),
accepted: z.number().int().min(0),
rejected: z.number().int().min(0),
});
// ---------------------------------------------------------------------------
// Kontraktsregister
// ---------------------------------------------------------------------------
@@ -436,6 +460,10 @@ export const TASK_CONTRACTS = {
GENERATE_WEEK_PLAN: { input: generateWeekPlanInput, output: generateWeekPlanOutput },
MODERATE_RECIPE: { input: moderateRecipeInput, output: moderateRecipeOutput },
TRANSLATE_RECIPE: { input: translateRecipeInput, output: translateRecipeOutput },
EXPORT_TRAINING_SAMPLE: {
input: exportTrainingSampleInput,
output: exportTrainingSampleOutput,
},
} as const satisfies Record<AamosTaskType, { input: z.ZodType; output: z.ZodType }>;
export type TaskInput<T extends AamosTaskType> = z.infer<(typeof TASK_CONTRACTS)[T]["input"]>;
+1
View File
@@ -67,6 +67,7 @@ export const aiCorrections = pgTable(
/** Snapshot av samtyckesläget när korrigeringen skapades. */
consentSnapshot: jsonb("consent_snapshot").notNull(),
exportedToTraining: timestamp("exported_to_training", { withTimezone: true }),
trainingBatchId: text("training_batch_id"),
createdAt: createdAt(),
},
(t) => [index("ai_corrections_task_idx").on(t.taskType, t.createdAt)],
+2 -2
View File
@@ -1,5 +1,5 @@
import { describe, expect, it, beforeEach, afterAll } from "vitest";
import { eq } from "drizzle-orm";
import { eq, sql } from "drizzle-orm";
import { createDatabase, closeDatabase } from "../src/client.js";
import { schema, seedReleaseGates, evaluateReleaseGates, summarizeReleaseGates } from "../src/index.js";
import { BUILT_IN_RELEASE_GATES } from "@app/shared-types";
@@ -11,7 +11,7 @@ async function reset() {
await db.delete(schema.releaseGates);
await db
.delete(schema.users)
.where(eq(schema.users.email, "release-gates-test@example.invalid"));
.where(sql`${schema.users.email} LIKE 'release-gates-test-%@example.invalid'`);
}
async function createTestUser(id: string) {
+7
View File
@@ -20,3 +20,10 @@ export const BRAND: BrandConfig = brandConfig as BrandConfig;
/** Delat könamn för BullMQ (API producerar, workern konsumerar). */
export const JOB_QUEUE_NAME = `${BRAND.slug}-jobs`;
/** Kö för misslyckade jobb som ska granskas manuellt (dead letter). */
export const DEAD_LETTER_QUEUE_NAME = `${BRAND.slug}-dead-letter`;
/** Standardvärden för jobb kan överstyras per jobb. */
export const WORKER_DEFAULT_ATTEMPTS = 3;
export const WORKER_DEFAULT_BACKOFF_MS = 5000;