242 lines
8.7 KiB
TypeScript
242 lines
8.7 KiB
TypeScript
import { z } from "zod";
|
|
|
|
export const rectificationV4Protocol = "rectification-evidence-v4" as const;
|
|
export const rectificationV4AlgorithmVersion = "rectification-v4-range-scoring-1" as const;
|
|
|
|
export const rectificationV4CaseStatusSchema = z.enum([
|
|
"awaiting_answer",
|
|
"processing",
|
|
"range_ready",
|
|
"paused",
|
|
"abandoned",
|
|
]);
|
|
export type RectificationV4CaseStatus = z.infer<typeof rectificationV4CaseStatusSchema>;
|
|
|
|
export const rectificationV4PhaseSchema = z.enum([
|
|
"collecting_evidence",
|
|
"extracting_evidence",
|
|
"scoring_candidates",
|
|
"checking_robustness",
|
|
"planning_question",
|
|
"complete",
|
|
]);
|
|
export type RectificationV4Phase = z.infer<typeof rectificationV4PhaseSchema>;
|
|
|
|
export const evidenceDomainSchema = z.enum([
|
|
"education",
|
|
"relocation",
|
|
"relationship",
|
|
"career",
|
|
"finance",
|
|
"health_pressure",
|
|
"family",
|
|
"other",
|
|
]);
|
|
export type EvidenceDomain = z.infer<typeof evidenceDomainSchema>;
|
|
|
|
export const eventKindSchema = z.enum([
|
|
"education_milestone",
|
|
"relocation",
|
|
"relationship_start",
|
|
"relationship_end",
|
|
"career_change",
|
|
"finance_change",
|
|
"health_event",
|
|
"family_event",
|
|
"other",
|
|
]);
|
|
export type EventKind = z.infer<typeof eventKindSchema>;
|
|
|
|
export const datePrecisionSchema = z.enum(["day", "month", "quarter", "year", "range"]);
|
|
export type DatePrecision = z.infer<typeof datePrecisionSchema>;
|
|
|
|
export const calendarDateSchema = z.string().regex(/^\d{4}-\d{2}-\d{2}$/);
|
|
export const clockTimeSchema = z.string().regex(/^([01]\d|2[0-3]):[0-5]\d$/);
|
|
|
|
export const eventDateRangeSchema = z.object({
|
|
start: calendarDateSchema,
|
|
end: calendarDateSchema,
|
|
precision: datePrecisionSchema,
|
|
label: z.string().trim().min(1).max(80),
|
|
}).strict().superRefine((value, context) => {
|
|
if (value.start > value.end) {
|
|
context.addIssue({ code: "custom", message: "event date range start must not exceed end" });
|
|
}
|
|
});
|
|
export type EventDateRange = z.infer<typeof eventDateRangeSchema>;
|
|
|
|
export const scoreabilitySchema = z.enum(["scoreable", "context_only"]);
|
|
export type Scoreability = z.infer<typeof scoreabilitySchema>;
|
|
|
|
export const lifeEventRevisionSchema = z.object({
|
|
id: z.string().uuid(),
|
|
eventId: z.string().uuid(),
|
|
revision: z.number().int().positive(),
|
|
domain: evidenceDomainSchema,
|
|
eventKind: eventKindSchema,
|
|
summary: z.string().trim().min(1).max(1_000),
|
|
rawText: z.string().trim().min(1).max(4_000),
|
|
dateRange: eventDateRangeSchema,
|
|
scoreability: scoreabilitySchema,
|
|
supersedesRevisionId: z.string().uuid().nullable(),
|
|
createdAt: z.string().datetime({ offset: true }),
|
|
}).strict();
|
|
export type LifeEventRevision = z.infer<typeof lifeEventRevisionSchema>;
|
|
|
|
export const calculationSpecSchema = z.object({
|
|
version: z.literal("rectification-calculation-spec-v4"),
|
|
birthDate: calendarDateSchema,
|
|
candidateRange: z.object({ start: clockTimeSchema, end: clockTimeSchema }).strict(),
|
|
latitude: z.number().finite().min(-90).max(90),
|
|
longitude: z.number().finite().min(-180).max(180),
|
|
timezoneOffsetHours: z.number().finite().min(-14).max(14),
|
|
ayanamsa: z.literal("lahiri"),
|
|
nodeMode: z.literal("mean"),
|
|
minuteStep: z.literal(1),
|
|
}).strict();
|
|
export type CalculationSpec = z.infer<typeof calculationSpecSchema>;
|
|
|
|
export const candidateMinuteSchema = z.object({
|
|
time: clockTimeSchema,
|
|
score: z.number().finite(),
|
|
supportingEventIds: z.array(z.string().uuid()),
|
|
conflictingEventIds: z.array(z.string().uuid()),
|
|
}).strict();
|
|
export type CandidateMinute = z.infer<typeof candidateMinuteSchema>;
|
|
|
|
export const candidateClusterSchema = z.object({
|
|
rank: z.number().int().positive(),
|
|
startTime: clockTimeSchema,
|
|
endTime: clockTimeSchema,
|
|
representativeTime: clockTimeSchema,
|
|
widthMinutes: z.number().int().positive(),
|
|
peakScore: z.number().finite(),
|
|
scoreMass: z.number().finite().nonnegative(),
|
|
}).strict();
|
|
export type CandidateCluster = z.infer<typeof candidateClusterSchema>;
|
|
|
|
export const robustnessSchema = z.object({
|
|
neighborSupportMinutes: z.number().int().nonnegative(),
|
|
leaveOneOutRetentionRate: z.number().finite().min(0).max(1),
|
|
dateSensitivityRetentionRate: z.number().finite().min(0).max(1),
|
|
calculationSpecHashMatched: z.boolean(),
|
|
}).strict();
|
|
export type Robustness = z.infer<typeof robustnessSchema>;
|
|
|
|
export const candidateSnapshotSchema = z.object({
|
|
id: z.string().uuid(),
|
|
caseId: z.string().uuid(),
|
|
caseVersion: z.number().int().nonnegative(),
|
|
evidenceSetHash: z.string().regex(/^[a-f0-9]{64}$/),
|
|
calculationSpecHash: z.string().regex(/^[a-f0-9]{64}$/),
|
|
algorithmVersion: z.literal(rectificationV4AlgorithmVersion),
|
|
candidates: z.array(candidateMinuteSchema).min(1).max(1_440),
|
|
clusters: z.array(candidateClusterSchema).max(20),
|
|
robustness: robustnessSchema,
|
|
canConfirmExactMinute: z.literal(false),
|
|
canAcceptRange: z.boolean(),
|
|
gateReasons: z.array(z.string().trim().min(1).max(120)).max(20),
|
|
createdAt: z.string().datetime({ offset: true }),
|
|
}).strict();
|
|
export type CandidateSnapshot = z.infer<typeof candidateSnapshotSchema>;
|
|
|
|
export const rectificationV4QuestionSchema = z.object({
|
|
id: z.string().uuid(),
|
|
domain: evidenceDomainSchema,
|
|
targetEventId: z.string().uuid().nullable(),
|
|
prompt: z.string().trim().min(1).max(1_000),
|
|
recallCost: z.enum(["low", "medium", "high"]),
|
|
reason: z.string().trim().min(1).max(240),
|
|
}).strict();
|
|
export type RectificationV4Question = z.infer<typeof rectificationV4QuestionSchema>;
|
|
|
|
export const rectificationV4CaseSchema = z.object({
|
|
id: z.string().uuid(),
|
|
userId: z.string().uuid(),
|
|
protocol: z.literal(rectificationV4Protocol),
|
|
version: z.number().int().nonnegative(),
|
|
status: rectificationV4CaseStatusSchema,
|
|
phase: rectificationV4PhaseSchema,
|
|
calculationSpec: calculationSpecSchema,
|
|
calculationSpecHash: z.string().regex(/^[a-f0-9]{64}$/),
|
|
evidenceSetHash: z.string().regex(/^[a-f0-9]{64}$/),
|
|
currentQuestion: rectificationV4QuestionSchema.nullable(),
|
|
latestSnapshot: candidateSnapshotSchema.nullable(),
|
|
acceptedRange: z.object({ start: clockTimeSchema, end: clockTimeSchema }).strict().nullable(),
|
|
createdAt: z.string().datetime({ offset: true }),
|
|
updatedAt: z.string().datetime({ offset: true }),
|
|
}).strict();
|
|
export type RectificationV4Case = z.infer<typeof rectificationV4CaseSchema>;
|
|
|
|
export const createCaseRequestSchema = z.object({
|
|
actionId: z.string().uuid(),
|
|
uncertaintyMinutes: z.number().int().min(5).max(720).optional(),
|
|
}).strict();
|
|
|
|
export const answerRequestSchema = z.object({
|
|
actionId: z.string().uuid(),
|
|
expectedCaseVersion: z.number().int().nonnegative(),
|
|
answer: z.string().trim().min(1).max(4_000),
|
|
}).strict();
|
|
|
|
export const reviseEventRequestSchema = z.object({
|
|
actionId: z.string().uuid(),
|
|
expectedCaseVersion: z.number().int().nonnegative(),
|
|
domain: evidenceDomainSchema,
|
|
eventKind: eventKindSchema,
|
|
summary: z.string().trim().min(1).max(1_000),
|
|
rawText: z.string().trim().min(1).max(4_000),
|
|
dateRange: eventDateRangeSchema,
|
|
scoreability: scoreabilitySchema.optional(),
|
|
}).strict();
|
|
|
|
export const caseActionRequestSchema = z.object({
|
|
actionId: z.string().uuid(),
|
|
expectedCaseVersion: z.number().int().nonnegative(),
|
|
}).strict();
|
|
|
|
export const acceptRangeRequestSchema = caseActionRequestSchema.extend({
|
|
startTime: clockTimeSchema,
|
|
endTime: clockTimeSchema,
|
|
}).strict();
|
|
|
|
export const rectificationV4JobSchema = z.object({
|
|
id: z.string().uuid(),
|
|
caseId: z.string().uuid(),
|
|
status: z.enum(["pending", "processing", "completed", "failed", "stale"]),
|
|
phase: rectificationV4PhaseSchema,
|
|
expectedCaseVersion: z.number().int().nonnegative(),
|
|
evidenceSetHash: z.string().regex(/^[a-f0-9]{64}$/),
|
|
calculationSpecHash: z.string().regex(/^[a-f0-9]{64}$/),
|
|
errorCode: z.string().trim().min(1).max(120).nullable(),
|
|
createdAt: z.string().datetime({ offset: true }),
|
|
updatedAt: z.string().datetime({ offset: true }),
|
|
}).strict();
|
|
export type RectificationV4Job = z.infer<typeof rectificationV4JobSchema>;
|
|
|
|
export const rectificationV4ApiResponseSchema = z.object({
|
|
case: rectificationV4CaseSchema,
|
|
job: rectificationV4JobSchema.nullable(),
|
|
events: z.array(lifeEventRevisionSchema),
|
|
}).strict();
|
|
export type RectificationV4ApiResponse = z.infer<typeof rectificationV4ApiResponseSchema>;
|
|
|
|
export const rectificationV4HandoffStatusSchema = z.enum([
|
|
"pending",
|
|
"claimed",
|
|
"in_progress",
|
|
"consumed",
|
|
]);
|
|
|
|
export const rectificationV4HandoffSchema = z.object({
|
|
protocol: z.literal(rectificationV4Protocol),
|
|
caseId: z.string().uuid(),
|
|
caseVersion: z.number().int().nonnegative(),
|
|
question: z.string().trim().min(1).max(500),
|
|
questionFingerprint: z.string().regex(/^[0-9a-f]{64}$/),
|
|
requestId: z.string().uuid(),
|
|
status: rectificationV4HandoffStatusSchema,
|
|
acceptedRange: z.object({ start: clockTimeSchema, end: clockTimeSchema }).strict().nullable(),
|
|
}).strict();
|
|
export type RectificationV4Handoff = z.infer<typeof rectificationV4HandoffSchema>;
|