Use nightly analytics signals in curator day generation

This commit is contained in:
Sai-karthik
2026-06-20 10:08:21 +00:00
parent 9a6518a5d8
commit 292e375a37
+107
View File
@@ -7,6 +7,7 @@ import { emitCuratorEvent } from "./curator-events.js";
import type {
CuratorPlan,
CuratorPlanDay,
CuratorImprovementSignal,
CuratorServiceId,
CuratorSprintResponse,
CuratorStreak,
@@ -868,6 +869,99 @@ function hasDirectServiceCompletion(serviceId: CuratorServiceId) {
return !(events.length === 1 && events[0] === "curator.task.completed");
}
function practiceServiceLabel(serviceId: CuratorServiceId) {
if (serviceId === "roleplay-service") return "guided roleplay";
if (serviceId === "interview-service") return "interview rep";
if (serviceId === "matchmaking-service") return "pathways review";
return "practice rep";
}
function overnightSignalTaskSeed(signal: CuratorImprovementSignal, weekTheme: string): TaskSeed {
const serviceId = signal.recommendedServiceId ?? "qscore-service";
const nudge = signal.nudgeText?.trim() || signal.reason;
if (serviceId === "resume-service") {
return seed(
"proof",
"resume-service",
"Turn the overnight curator signal into stronger proof",
`${nudge} Use the resume workspace to convert that signal into a clearer proof asset for ${weekTheme.toLowerCase()}.`,
"10 min",
"+7 projected",
"Open resume workspace",
["overnight signal", "resume proof"],
);
}
if (serviceId === "social-branding-service") {
return seed(
"proof",
"social-branding-service",
"Turn the overnight curator signal into stronger positioning",
`${nudge} Use your public profile or headline to make that signal visible.`,
"10 min",
"+6 projected",
"Open social profile flow",
["overnight signal", "positioning"],
);
}
if (serviceId === "interview-service" || serviceId === "roleplay-service" || serviceId === "matchmaking-service") {
return seed(
"practice",
serviceId,
`Practice the overnight curator signal in a ${practiceServiceLabel(serviceId)}`,
`${nudge} Use a live ${practiceServiceLabel(serviceId)} so the next sprint move is backed by a real event.`,
"10 min",
serviceId === "matchmaking-service" ? "+5 projected" : "+9 projected",
serviceId === "interview-service"
? "Open interview preview"
: serviceId === "roleplay-service"
? "Open roleplay preview"
: "Open pathways",
["overnight signal", "practice loop"],
);
}
return seed(
"measurement",
"qscore-service",
"Review the overnight curator signal before continuing",
`${nudge} Use analytics first so today starts from a measured signal instead of guesswork.`,
"5 min",
"+5 projected",
"Review Q Score",
["overnight signal", "analytics"],
);
}
function latestImprovementSignal(
rows: Awaited<ReturnType<typeof loadRecentContextRows>>,
focusDate: string,
): CuratorImprovementSignal | null {
for (const row of rows) {
if (row.type !== "curator.improvement_signal.applied") continue;
const candidate = row.payload?.signal;
if (!candidate || typeof candidate !== "object") continue;
const signal = candidate as Partial<CuratorImprovementSignal>;
if (typeof signal.id !== "string" || typeof signal.userId !== "string" || typeof signal.date !== "string") continue;
if (signal.date > focusDate) continue;
return {
id: signal.id,
userId: signal.userId,
date: signal.date,
priority: typeof signal.priority === "number" ? signal.priority : 50,
reason: typeof signal.reason === "string" ? signal.reason : "Follow the overnight curator signal.",
recommendedServiceId: signal.recommendedServiceId,
recommendedMissionId: signal.recommendedMissionId,
memoryPatch: typeof signal.memoryPatch === "string" ? signal.memoryPatch : undefined,
nudgeText: typeof signal.nudgeText === "string" ? signal.nudgeText : undefined,
status: signal.status === "applied" || signal.status === "skipped" ? signal.status : "created",
};
}
return null;
}
function normalizeEventBackedTask(task: PlannedTask, planDay: PlanDaySeed, recentRows: Awaited<ReturnType<typeof loadRecentContextRows>>): PlannedTask {
if (hasDirectServiceCompletion(task.serviceId)) return task;
@@ -1166,6 +1260,7 @@ function adaptCurrentDayPlan(
const plan = planDays.map(clonePlanDay);
const current = plan[focusDayIndex - 1];
if (!current) return plan;
const focusDate = addDaysIso(sprintStartDate, focusDayIndex - 1);
const hasResumeSignal = hasRecentServiceSignal(recentRows, /\bresume(\.|-|\s)|cv\b|linkedin\b/);
const hasInterviewSignal = hasRecentServiceSignal(recentRows, /\binterview(\.|-|\s)|mock\b/);
@@ -1252,6 +1347,18 @@ function adaptCurrentDayPlan(
reasons.push(`qscore ${currentQScore}`);
}
const overnightSignal = latestImprovementSignal(recentRows, focusDate);
if (overnightSignal) {
const signalTask = overnightSignalTaskSeed(overnightSignal, current.weekTheme);
const slot = signalTask.taskType === "measurement" ? 0 : signalTask.taskType === "proof" ? 1 : 2;
current.plannedTasks[slot] = makePlannedTask(signalTask, current.weekTheme, current.weekSummary);
current.focus = overnightSignal.nudgeText?.trim()
? `${current.focus} Overnight curator signal: ${overnightSignal.nudgeText.trim()}`
: `${current.focus} Overnight curator signal: ${overnightSignal.reason}`;
adapted = true;
reasons.push(`analytics signal ${overnightSignal.id}`);
}
if (adapted) {
current.generationStatus = "adapted";
current.adaptationReason = reasons.join(" · ");