Apply deterministic QX values to streak tasks

This commit is contained in:
Sai-karthik
2026-07-07 20:06:48 +00:00
parent 82859d8d08
commit 6d996882bf
2 changed files with 179 additions and 8 deletions
+3 -1
View File
@@ -786,7 +786,9 @@ function buildTask(
actorName: serviceName(seedTask.serviceId).replace(" service", ""),
toolName: serviceToolName(seedTask.serviceId),
status: "ready",
rewardCoins: seedTask.taskType === "measurement" ? 12 : seedTask.taskType === "proof" ? 15 : seedTask.taskType === "recovery" ? 8 : 18,
rewardCoins:
seedTask.rewardCoins ??
(seedTask.taskType === "measurement" ? 12 : seedTask.taskType === "proof" ? 15 : seedTask.taskType === "recovery" ? 8 : 18),
qxImpact: seedTask.qxImpact,
effort: seedTask.effort,
route: "",
+176 -7
View File
@@ -8,6 +8,7 @@ export type TaskSeed = {
subtitle: string;
effort: string;
qxImpact: string;
rewardCoins?: number;
cta: string;
signals: string[];
};
@@ -122,8 +123,9 @@ export function seed(
qxImpact: string,
cta: string,
signals: string[],
rewardCoins?: number,
): TaskSeed {
return { taskType, serviceId, title, subtitle, effort, qxImpact, cta: unavailableCta(serviceId, cta), signals };
return { taskType, serviceId, title, subtitle, effort, qxImpact, rewardCoins, cta: unavailableCta(serviceId, cta), signals };
}
@@ -178,11 +180,174 @@ function day(
};
}
function staticWeekTemplate(days: StaticSprintDay[]): WeekTemplate {
type QxDimension = "IQ" | "EQ" | "SQ";
type QxKpiWeight = {
weight: number;
dims: QxDimension[];
};
const QX_PROFILE_BY_ICP: Record<CuratorIcpId, keyof typeof QX_PROFILE_KPI_WEIGHTS> = {
student_recent_grad: "Student",
intern: "Student",
fresher_early_professional: "Student",
experienced_professional: "Prof_Leadership",
freelancer: "Prof_Creative",
founder: "Entrepreneur",
enterprise: "Prof_Leadership",
};
const QX_PROFILE_KPI_WEIGHTS = {
Student: {
1: { weight: 2.84, dims: ["SQ"] },
4: { weight: 1.41, dims: [] },
10: { weight: 3.78, dims: [] },
12: { weight: 1.89, dims: [] },
14: { weight: 4.72, dims: ["IQ"] },
18: { weight: 5.66, dims: ["IQ"] },
22: { weight: 6.62, dims: ["IQ", "EQ", "SQ"] },
23: { weight: 3.78, dims: ["EQ", "SQ"] },
25: { weight: 4.25, dims: ["SQ"] },
29: { weight: 1.41, dims: ["SQ"] },
37: { weight: 2.8, dims: [] },
39: { weight: 1.89, dims: ["IQ", "EQ", "SQ"] },
41: { weight: 1.89, dims: ["EQ", "SQ"] },
},
Prof_Leadership: {
1: { weight: 3.34, dims: ["SQ"] },
5: { weight: 2.5, dims: ["IQ"] },
6: { weight: 3.34, dims: ["SQ"] },
10: { weight: 2.92, dims: [] },
12: { weight: 1.67, dims: [] },
14: { weight: 2.5, dims: ["IQ"] },
18: { weight: 3.72, dims: ["IQ"] },
22: { weight: 4.98, dims: ["IQ", "EQ", "SQ"] },
23: { weight: 3.75, dims: ["EQ", "SQ"] },
25: { weight: 4.14, dims: ["SQ"] },
29: { weight: 1.67, dims: ["SQ"] },
37: { weight: 3.4, dims: [] },
39: { weight: 1.67, dims: ["IQ", "EQ", "SQ"] },
41: { weight: 2.08, dims: ["EQ", "SQ"] },
},
Prof_Creative: {
1: { weight: 2.58, dims: ["SQ"] },
4: { weight: 4.27, dims: [] },
5: { weight: 3.44, dims: ["IQ"] },
6: { weight: 2.58, dims: ["SQ"] },
10: { weight: 3.01, dims: [] },
12: { weight: 1.72, dims: [] },
14: { weight: 3.44, dims: ["IQ"] },
18: { weight: 4.27, dims: ["IQ"] },
22: { weight: 5.1, dims: ["IQ", "EQ", "SQ"] },
23: { weight: 3.44, dims: ["EQ", "SQ"] },
25: { weight: 4.27, dims: ["SQ"] },
29: { weight: 1.72, dims: ["SQ"] },
37: { weight: 2.8, dims: [] },
39: { weight: 1.72, dims: ["IQ", "EQ", "SQ"] },
41: { weight: 1.72, dims: ["EQ", "SQ"] },
},
Entrepreneur: {
1: { weight: 2.05, dims: ["SQ"] },
4: { weight: 2.05, dims: [] },
5: { weight: 2.05, dims: ["IQ"] },
6: { weight: 2.05, dims: ["SQ"] },
10: { weight: 2.05, dims: [] },
12: { weight: 1.03, dims: [] },
14: { weight: 2.05, dims: ["IQ"] },
18: { weight: 3.05, dims: ["IQ"] },
22: { weight: 3.87, dims: ["IQ", "EQ", "SQ"] },
23: { weight: 2.71, dims: ["EQ", "SQ"] },
25: { weight: 3.05, dims: ["SQ"] },
29: { weight: 1.71, dims: ["SQ"] },
37: { weight: 6, dims: [] },
39: { weight: 1.37, dims: ["IQ", "EQ", "SQ"] },
41: { weight: 1.37, dims: ["EQ", "SQ"] },
},
} satisfies Record<string, Record<number, QxKpiWeight>>;
const FALLBACK_DIMS_BY_SERVICE: Partial<Record<CuratorServiceId, QxDimension[]>> = {
"social-branding-service": ["SQ"],
"qscore-service": ["IQ"],
"assessment-service": ["IQ"],
"resume-service": ["IQ"],
"interview-service": ["EQ"],
"roleplay-service": ["EQ", "SQ"],
"courses-service": ["IQ"],
"matchmaking-service": ["SQ"],
"expert-service": ["EQ", "SQ"],
"events-service": ["SQ"],
};
type StaticTaskRef = {
title: string;
subtitle: string;
serviceId: CuratorServiceId;
};
function qxKpiIdForTask(task: StaticTaskRef): number {
const text = `${task.title} ${task.subtitle}`.toLowerCase();
if (task.serviceId === "social-branding-service") {
if (/(publish|share|post|content|insight|article|story|build in public|update)/.test(text)) return 5;
if (/(speaking|introduction|pitch|video)/.test(text)) return 6;
return 1;
}
if (task.serviceId === "qscore-service") {
if (/(assessment|baseline|know your number|skill gap|capability)/.test(text)) return 39;
return 18;
}
if (task.serviceId === "assessment-service") return 18;
if (task.serviceId === "resume-service") {
if (text.includes("cover letter")) return 12;
if (/(portfolio|project|case study|pitch deck|mvp|proof|showcase|blueprint|framework|playbook|roadmap)/.test(text)) return 4;
return 10;
}
if (task.serviceId === "interview-service") {
if (/(introduction|pitch|presentation|video|record|deliver)/.test(text)) return 25;
return 22;
}
if (task.serviceId === "roleplay-service") return 23;
if (task.serviceId === "courses-service") return 14;
if (task.serviceId === "matchmaking-service") return 37;
if (task.serviceId === "expert-service") return 41;
if (task.serviceId === "events-service") return 29;
return 39;
}
function qxRawWeightForTask(icpId: CuratorIcpId, task: StaticTaskRef): number {
const profile = QX_PROFILE_BY_ICP[icpId];
const kpiWeights: Record<number, QxKpiWeight> = QX_PROFILE_KPI_WEIGHTS[profile];
const kpi = kpiWeights[qxKpiIdForTask(task)];
const dims = kpi?.dims.length ? kpi.dims : FALLBACK_DIMS_BY_SERVICE[task.serviceId] ?? ["IQ"];
return kpi?.weight ?? dims.length;
}
function staticTaskRefs(dayItem: StaticSprintDay): StaticTaskRef[] {
return [
{ title: dayItem.socialTitle, subtitle: dayItem.socialTask, serviceId: dayItem.socialServiceId },
{ title: dayItem.measurementTitle, subtitle: dayItem.measurementTask, serviceId: dayItem.measurementServiceId },
{ title: dayItem.proofTitle, subtitle: dayItem.proofTask, serviceId: dayItem.proofServiceId },
{ title: dayItem.practiceTitle, subtitle: dayItem.practiceTask, serviceId: dayItem.practiceServiceId },
];
}
function qxImpactForTask(icpId: CuratorIcpId, days: StaticSprintDay[], task: StaticTaskRef) {
const totalRaw = days
.flatMap(staticTaskRefs)
.reduce((sum, item) => sum + qxRawWeightForTask(icpId, item), 0);
const qxDelta = totalRaw > 0 ? (qxRawWeightForTask(icpId, task) / totalRaw) * 100 : 0;
const rounded = Math.max(1, Math.round(qxDelta));
return { qxImpact: `+${rounded} QX`, rewardCoins: rounded };
}
function staticWeekTemplate(icpId: CuratorIcpId, days: StaticSprintDay[]): WeekTemplate {
return {
theme: "Static 7-Day CareerSprint",
summary: "Static day-wise sprint tasks sourced from the GrowQR task library and 7-day sprint document.",
days: days.map((item) => {
const socialImpact = qxImpactForTask(icpId, days, { title: item.socialTitle, subtitle: item.socialTask, serviceId: item.socialServiceId });
const measurementImpact = qxImpactForTask(icpId, days, { title: item.measurementTitle, subtitle: item.measurementTask, serviceId: item.measurementServiceId });
const proofImpact = qxImpactForTask(icpId, days, { title: item.proofTitle, subtitle: item.proofTask, serviceId: item.proofServiceId });
const practiceImpact = qxImpactForTask(icpId, days, { title: item.practiceTitle, subtitle: item.practiceTask, serviceId: item.practiceServiceId });
return {
focus: item.focus,
outcome: item.outcome,
@@ -192,9 +357,10 @@ function staticWeekTemplate(days: StaticSprintDay[]): WeekTemplate {
item.socialTitle,
item.socialTask,
"10 min",
"+6 projected",
socialImpact.qxImpact,
"Open social proof",
["static social proof", item.socialTitle.toLowerCase()],
socialImpact.rewardCoins,
),
proof: seed(
"measurement",
@@ -202,9 +368,10 @@ function staticWeekTemplate(days: StaticSprintDay[]): WeekTemplate {
item.measurementTitle,
item.measurementTask,
"10 min",
"+6 projected",
measurementImpact.qxImpact,
"Open measurement",
["static measurement", item.measurementTitle.toLowerCase()],
measurementImpact.rewardCoins,
),
practice: seed(
"proof",
@@ -212,9 +379,10 @@ function staticWeekTemplate(days: StaticSprintDay[]): WeekTemplate {
item.proofTitle,
item.proofTask,
"10 min",
"+7 projected",
proofImpact.qxImpact,
"Open proof task",
["static proof", item.proofTitle.toLowerCase()],
proofImpact.rewardCoins,
),
roleplay: seed(
"practice",
@@ -222,9 +390,10 @@ function staticWeekTemplate(days: StaticSprintDay[]): WeekTemplate {
item.practiceTitle,
item.practiceTask,
"10 min",
"+10 projected",
practiceImpact.qxImpact,
"Open practice task",
["static practice", item.practiceTitle.toLowerCase()],
practiceImpact.rewardCoins,
),
};
}),
@@ -243,7 +412,7 @@ function staticIcpTemplate(
label,
sprintTheme,
goal,
weeks: [staticWeekTemplate(days)],
weeks: [staticWeekTemplate(id, days)],
};
}