diff --git a/src/events/projectors/qscore-projector.ts b/src/events/projectors/qscore-projector.ts index 84c4e42..a5636bc 100644 --- a/src/events/projectors/qscore-projector.ts +++ b/src/events/projectors/qscore-projector.ts @@ -113,11 +113,57 @@ function extractRoleplaySignals(event: GrowEventRow): QscoreSignal[] { return signals; } +function sourceSignalPrefix(source: string) { + return source + .toLowerCase() + .replace(/-service$/, "") + .replace(/[^a-z0-9]+/g, "_") + .replace(/^_+|_+$/g, "") || "service"; +} + +function extractScoredServiceSignals(event: GrowEventRow): QscoreSignal[] { + const payload = event.payload ?? {}; + const review = asRecord(payload.review ?? payload.result ?? payload); + const status = String(review.status ?? payload.status ?? ""); + const isCompletion = + event.type.includes("completed") || + event.type.includes("updated") || + event.type.includes("signal_projected") || + status === "completed"; + if (!isCompletion) return []; + + const score = getNumber( + payload.score ?? + payload.qscore ?? + payload.q_score ?? + payload.readiness_score ?? + payload.overall_score ?? + review.score ?? + review.qscore ?? + review.q_score ?? + review.readiness_score ?? + review.overall_score, + ); + if (score === undefined) return []; + + const prefix = sourceSignalPrefix(event.source); + return [ + signal(`${prefix}.service_completion_score`, score, { + eventId: event.id, + source: event.source, + type: event.type, + }), + ]; +} + export function extractQscoreSignals(event: GrowEventRow): QscoreSignal[] { const source = event.source.toLowerCase(); if (source.includes("resume") || event.type.startsWith("resume.")) return extractResumeSignals(event); if (source.includes("interview") || event.type.startsWith("interview.")) return extractInterviewSignals(event); if (source.includes("roleplay") || event.type.startsWith("roleplay.")) return extractRoleplaySignals(event); + if (source.includes("qscore") || event.type.startsWith("qscore.")) return extractScoredServiceSignals(event); + const scoredServiceSignals = extractScoredServiceSignals(event); + if (scoredServiceSignals.length) return scoredServiceSignals; if (event.type === "mission.interview_to_offer.started") { return [signal("goals.goals_set", 100, { eventId: event.id })]; }