Fas 1a punkt 3+4: worker-härdning (DLQ, larm, multi-instance), AAMOS training export
This commit is contained in:
@@ -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.
|
||||
|
||||
@@ -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"]>;
|
||||
|
||||
@@ -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)],
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user