新增发布校准领域类型
This commit is contained in:
@@ -0,0 +1,157 @@
|
||||
import { z } from "zod";
|
||||
|
||||
import { publishPlatformSchema } from "../domain/validation";
|
||||
import type {
|
||||
CalibrationDirection,
|
||||
CalibrationEvent,
|
||||
PerformanceMetrics,
|
||||
PerformanceSnapshot,
|
||||
PerformanceSource,
|
||||
PublicationRecord,
|
||||
RubricVersion,
|
||||
ScoringRun,
|
||||
} from "./types";
|
||||
|
||||
const optionalTextSchema = z
|
||||
.preprocess((value) => (value == null ? "" : value), z.string())
|
||||
.transform((value) => value.trim());
|
||||
|
||||
function optionalMetric(value: unknown) {
|
||||
if (value == null || value === "") return undefined;
|
||||
const numberValue = typeof value === "number" ? value : Number(value);
|
||||
return Number.isFinite(numberValue) && numberValue >= 0 ? numberValue : value;
|
||||
}
|
||||
|
||||
const metricsShape = {
|
||||
views: z.preprocess(optionalMetric, z.number().nonnegative().optional()),
|
||||
impressions: z.preprocess(optionalMetric, z.number().nonnegative().optional()),
|
||||
clicks: z.preprocess(optionalMetric, z.number().nonnegative().optional()),
|
||||
inquiries: z.preprocess(optionalMetric, z.number().nonnegative().optional()),
|
||||
likes: z.preprocess(optionalMetric, z.number().nonnegative().optional()),
|
||||
comments: z.preprocess(optionalMetric, z.number().nonnegative().optional()),
|
||||
shares: z.preprocess(optionalMetric, z.number().nonnegative().optional()),
|
||||
saves: z.preprocess(optionalMetric, z.number().nonnegative().optional()),
|
||||
average_position: z.preprocess(optionalMetric, z.number().nonnegative().optional()),
|
||||
};
|
||||
|
||||
export const performanceMetricsSchema = z
|
||||
.object(metricsShape)
|
||||
.transform(
|
||||
(metrics) =>
|
||||
Object.fromEntries(
|
||||
Object.entries(metrics).filter(([, value]) => value !== undefined),
|
||||
) as PerformanceMetrics,
|
||||
);
|
||||
|
||||
export const publicationInputSchema = z.object({
|
||||
platform: publishPlatformSchema,
|
||||
url: z.string().trim().url(),
|
||||
published_at: z.string().datetime(),
|
||||
notes: optionalTextSchema.default(""),
|
||||
});
|
||||
|
||||
export const manualPerformanceInputSchema = z
|
||||
.object({
|
||||
window_label: z.string().trim().min(1),
|
||||
feedback_summary: optionalTextSchema.default(""),
|
||||
raw_reference: optionalTextSchema.optional(),
|
||||
...metricsShape,
|
||||
})
|
||||
.transform(({ window_label, feedback_summary, raw_reference, ...metrics }) => ({
|
||||
window_label,
|
||||
feedback_summary,
|
||||
raw_reference: raw_reference || undefined,
|
||||
metrics: performanceMetricsSchema.parse(metrics),
|
||||
}));
|
||||
|
||||
function isPerformanceSource(value: unknown): value is PerformanceSource {
|
||||
return (
|
||||
value === "manual" ||
|
||||
(typeof value === "string" &&
|
||||
value.startsWith("adapter:") &&
|
||||
value.length > "adapter:".length)
|
||||
);
|
||||
}
|
||||
|
||||
function rejectUnsafeRawReference(value: string | undefined) {
|
||||
if (!value) return value;
|
||||
if (/cookie|sessionid|token|secret|api[_-]?key|authorization/i.test(value)) {
|
||||
throw new Error("raw_reference must not contain credentials");
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
export const rubricVersionSchema = z.object({
|
||||
id: z.string().trim().min(1),
|
||||
version: z.string().trim().min(1),
|
||||
name: z.string().trim().min(1),
|
||||
dimensions: z.array(
|
||||
z.object({
|
||||
id: z.string().trim().min(1),
|
||||
label: z.string().trim().min(1),
|
||||
weight: z.number().positive(),
|
||||
description: z.string().trim().min(1),
|
||||
}),
|
||||
),
|
||||
formula: z.literal("weighted_average_0_to_10"),
|
||||
is_active: z.boolean(),
|
||||
created_at: z.string().datetime(),
|
||||
}) satisfies z.ZodType<RubricVersion>;
|
||||
|
||||
export const scoringRunSchema = z.object({
|
||||
id: z.string().trim().min(1),
|
||||
job_id: z.string().trim().min(1),
|
||||
revision: z.number().int().positive(),
|
||||
rubric_version_id: z.string().trim().min(1),
|
||||
dimension_scores: z.record(z.string(), z.number().min(0).max(5)),
|
||||
composite_score: z.number().min(0).max(10),
|
||||
rationale: z.string().trim(),
|
||||
created_at: z.string().datetime(),
|
||||
}) satisfies z.ZodType<ScoringRun>;
|
||||
|
||||
export const publicationRecordSchema = z.object({
|
||||
id: z.string().trim().min(1),
|
||||
job_id: z.string().trim().min(1),
|
||||
revision: z.number().int().positive(),
|
||||
platform: publishPlatformSchema,
|
||||
url: z.string().trim().url(),
|
||||
published_at: z.string().datetime(),
|
||||
status: z.enum(["draft", "published", "archived"]),
|
||||
notes: z.string().trim(),
|
||||
created_at: z.string().datetime(),
|
||||
updated_at: z.string().datetime(),
|
||||
}) satisfies z.ZodType<PublicationRecord>;
|
||||
|
||||
export const performanceSnapshotSchema = z
|
||||
.object({
|
||||
id: z.string().trim().min(1),
|
||||
publication_id: z.string().trim().min(1),
|
||||
source: z.custom<PerformanceSource>(isPerformanceSource),
|
||||
window_label: z.string().trim().min(1),
|
||||
metrics: performanceMetricsSchema,
|
||||
feedback_summary: z.string().trim(),
|
||||
raw_reference: z.string().trim().optional(),
|
||||
snapshot_at: z.string().datetime(),
|
||||
})
|
||||
.transform((snapshot) => ({
|
||||
...snapshot,
|
||||
raw_reference: rejectUnsafeRawReference(snapshot.raw_reference),
|
||||
})) satisfies z.ZodType<PerformanceSnapshot>;
|
||||
|
||||
export const calibrationDirectionSchema = z.enum([
|
||||
"better_than_expected",
|
||||
"as_expected",
|
||||
"worse_than_expected",
|
||||
"needs_more_data",
|
||||
]) satisfies z.ZodType<CalibrationDirection>;
|
||||
|
||||
export const calibrationEventSchema = z.object({
|
||||
id: z.string().trim().min(1),
|
||||
publication_id: z.string().trim().min(1),
|
||||
scoring_run_id: z.string().trim().min(1),
|
||||
performance_snapshot_id: z.string().trim().min(1),
|
||||
direction: calibrationDirectionSchema,
|
||||
observations: z.array(z.string().trim().min(1)),
|
||||
recommended_action: z.string().trim().min(1),
|
||||
created_at: z.string().datetime(),
|
||||
}) satisfies z.ZodType<CalibrationEvent>;
|
||||
Reference in New Issue
Block a user