fix(skiva-1): spara accept, bildref, lokal träningsbank, lärande-loop-dok
- confirm-loop sparar nu även action=accept som positivt exempel i ai_corrections - imageS3Key sparas vid image_training-samtycke, annars null - ai_corrections.proposal lagrar det specifika AI-förslaget per item - BUILD_TRAINING_SAMPLE bankar lokalt till ai_training_bank (ej externt runTask) - Jobbet kastar aldrig i AAMOS_MODE=gemini - Migration 0020: ai_corrections.image_s3_key/proposal + ai_training_bank - docs/28-lärande-loop.md: datakontrakt, samtycke, retention, GDPR-radering - Tester: accept + bildref (ja/nej) + lokal bank i gemini-läge - REQUIRE_REAL=1 är AI-fokuserat i gemini-läge; staging mail/S3 får vara mock
This commit is contained in:
@@ -6,6 +6,7 @@ import {
|
||||
pgTable,
|
||||
text,
|
||||
timestamp,
|
||||
uniqueIndex,
|
||||
uuid,
|
||||
} from "drizzle-orm/pg-core";
|
||||
import { createdAt, jobStatusEnum, jobTypeEnum, scanTypeEnum, updatedAt } from "./_shared.js";
|
||||
@@ -62,6 +63,10 @@ export const aiCorrections = pgTable(
|
||||
taskType: text("task_type").notNull(),
|
||||
aiOutput: jsonb("ai_output").notNull(),
|
||||
userCorrection: jsonb("user_correction").notNull(),
|
||||
/** The specific AI proposal item this correction refers to. */
|
||||
proposal: jsonb("proposal"),
|
||||
/** First stored image key when image_training consent granted, otherwise null. */
|
||||
imageS3Key: text("image_s3_key"),
|
||||
modelVersion: text("model_version"),
|
||||
promptVersion: text("prompt_version"),
|
||||
/** Snapshot av samtyckesläget när korrigeringen skapades. */
|
||||
@@ -72,3 +77,37 @@ export const aiCorrections = pgTable(
|
||||
},
|
||||
(t) => [index("ai_corrections_task_idx").on(t.taskType, t.createdAt)],
|
||||
);
|
||||
|
||||
/**
|
||||
* App-owned, versioned training dataset (spec §33 + Skiva 1 fixrunda).
|
||||
* BUILD_TRAINING_SAMPLE banks eligible ai_corrections here instead of
|
||||
* calling external AAMOS/Gemini runTask. The bank is the consented place
|
||||
* for richer (image, proposal, correction) data.
|
||||
*/
|
||||
export const aiTrainingBank = pgTable(
|
||||
"ai_training_bank",
|
||||
{
|
||||
id: uuid("id").primaryKey().defaultRandom(),
|
||||
correctionId: uuid("correction_id")
|
||||
.notNull()
|
||||
.references(() => aiCorrections.id, { onDelete: "cascade" }),
|
||||
scanJobId: uuid("scan_job_id").references(() => scanJobs.id, { onDelete: "set null" }),
|
||||
version: text("version").notNull().default("v1"),
|
||||
taskType: text("task_type").notNull(),
|
||||
imageS3Key: text("image_s3_key"),
|
||||
proposal: jsonb("proposal").notNull(),
|
||||
action: text("action").notNull(),
|
||||
corrected: jsonb("corrected"),
|
||||
modelVersion: text("model_version"),
|
||||
promptVersion: text("prompt_version"),
|
||||
consentSnapshot: jsonb("consent_snapshot").notNull().default({}),
|
||||
exportedAt: timestamp("exported_at", { withTimezone: true }).notNull().defaultNow(),
|
||||
createdAt: createdAt(),
|
||||
},
|
||||
(t) => [
|
||||
uniqueIndex("ai_training_bank_correction_unique").on(t.correctionId),
|
||||
index("ai_training_bank_version_idx").on(t.version, t.taskType),
|
||||
index("ai_training_bank_scan_job_idx").on(t.scanJobId),
|
||||
index("ai_training_bank_created_at_idx").on(t.createdAt),
|
||||
],
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user