Apply deterministic QX values to streak tasks
This commit is contained in:
@@ -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: "",
|
||||
|
||||
@@ -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)],
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user