Use nightly analytics signals in curator day generation
This commit is contained in:
@@ -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(" · ");
|
||||
|
||||
Reference in New Issue
Block a user