fix: keep onboarding qscore baseline at 35

This commit is contained in:
-Puter
2026-06-06 13:51:22 +05:30
parent f0ef57f054
commit 9fd478c095
2 changed files with 59 additions and 8 deletions

View File

@@ -1,4 +1,4 @@
import { and, eq, sql } from "drizzle-orm";
import { and, eq } from "drizzle-orm";
import { db } from "../db/client.js";
import { growQscoreLatest, growQscoreProjectionState, growQscoreSignals } from "../db/schema.js";
@@ -33,8 +33,8 @@ export async function ensureOnboardingBaselineQscore(
const completedAt = onboardingCompletedAt(preferences);
if (!completedAt) return false;
const [existingSignals] = await db
.select({ count: sql<number>`count(*)::int` })
const latestSignals = await db
.select({ signalId: growQscoreLatest.signalId, score: growQscoreLatest.score })
.from(growQscoreLatest)
.where(and(eq(growQscoreLatest.userId, userId), eq(growQscoreLatest.present, true)));
@@ -44,11 +44,57 @@ export async function ensureOnboardingBaselineQscore(
.where(eq(growQscoreProjectionState.userId, userId))
.limit(1);
if (Number(existingSignals?.count ?? 0) > 0 || (existingProjection?.score ?? 0) > 0) {
return false;
const now = new Date();
// Repair users affected by the old resume-upload projector, which treated a
// plain upload as a perfect 100 score. Uploading a resume during onboarding is
// only baseline evidence; parsed resume/interview/roleplay results should be
// what moves the score upward.
if (
latestSignals.length === 1 &&
latestSignals[0]?.signalId === "resume.uploaded" &&
latestSignals[0].score > ONBOARDING_BASELINE_QSCORE
) {
await db
.update(growQscoreLatest)
.set({
score: ONBOARDING_BASELINE_QSCORE,
raw: {
reason: "resume upload baseline correction",
correctedFrom: latestSignals[0].score,
correctedAt: now.toISOString(),
},
updatedAt: now,
})
.where(and(eq(growQscoreLatest.userId, userId), eq(growQscoreLatest.signalId, "resume.uploaded")));
await db
.insert(growQscoreProjectionState)
.values({
userId,
score: ONBOARDING_BASELINE_QSCORE,
signalCount: 1,
dimensions: { baseline: true, latestSignalIds: ["resume.uploaded"], corrected: true },
summary: "Baseline Q Score from onboarding resume upload.",
updatedAt: now,
})
.onConflictDoUpdate({
target: growQscoreProjectionState.userId,
set: {
score: ONBOARDING_BASELINE_QSCORE,
signalCount: 1,
dimensions: { baseline: true, latestSignalIds: ["resume.uploaded"], corrected: true },
summary: "Baseline Q Score from onboarding resume upload.",
updatedAt: now,
},
});
return true;
}
const now = new Date();
if (latestSignals.length > 0 || (existingProjection?.score ?? 0) > 0) {
return false;
}
const raw = {
reason: "completed onboarding baseline",
completedAt: completedAt.toISOString(),

View File

@@ -15,6 +15,8 @@ function nestedNumber(record: Record<string, unknown>, keys: string[]): number |
return undefined;
}
const RESUME_UPLOAD_BASELINE_SCORE = 35;
function extractResumeSignals(event: GrowEventRow): QscoreSignal[] {
const payload = event.payload ?? {};
const analysis = asRecord(payload.analysis ?? payload.result ?? payload);
@@ -38,8 +40,11 @@ function extractResumeSignals(event: GrowEventRow): QscoreSignal[] {
}
const signals: QscoreSignal[] = [];
if (event.type.includes("uploaded") || event.type.includes("created") || event.type.includes("analysis")) {
signals.push(signal("resume.uploaded", 100, { eventId: event.id }));
if (event.type.includes("uploaded") || event.type.includes("created")) {
// Uploading a resume is only a baseline readiness signal. The actual Q Score
// should rise from parsed resume/interview/roleplay evidence, not jump to 100
// immediately after onboarding.
signals.push(signal("resume.uploaded", RESUME_UPLOAD_BASELINE_SCORE, { eventId: event.id }));
}
const ats = byCategory.get("ATS Compatibility") ?? nestedNumber(analysis, ["ats_score", "ats_compatibility", "atsCompatibility"]);
if (ats !== undefined) signals.push(signal("resume.ats_compatibility", ats, { eventId: event.id }));