Scoring engine
Once a meeting transcript lands on HELIX_SQS_QUEUE_URL (see Architecture), api/modules/helixProcessing/processHelixData.js — the heart of Helix in Hub — turns it into per-user, per-skill scores. This page covers that pipeline, plus the weekly roll-up and manual reprocessing.
From transcript to stored scores
- Speaker matching (
getHubMemberDetails.js,addEmailsToTranscript.js): transcript speaker labels are matched to Hubusers/user_emails, fuzzy-matching unresolved speakers against the calendar invitee/participant lists forwarded by thehelixrepo. - Sentence grouping: consecutive sentences from the same speaker are grouped for better LLM performance.
helix_meetingrow created/updated: transcript, model, temperature,reasoning_effort, and the raw LLM response (raw_analysis) are stored, withhelix_meeting_skillrows linking which skills were assessed.- Per-user analysis via
analyseMeetingTranscript()(api/modules/helixProcessing/prompt.js), which calls OpenAI (gptMessage, default model configurable, e.g.gpt-5) once per user, using one of two response formats/engines (see below). - Results stored:
helix_user_analysis(a weekly bucket per user/domain),helix_user_meeting,helix_user_meeting_skill. Reprocessing an existing meeting purges and recreates these dependent rows rather than appending. - Email sent: a "meeting analysis" templated email per participant, showing top/bottom skill, respecting the domain's
helixUIModeandhelixSendAnalysisEmailssettings (see Admin config), with an optional link out to Mindset Coach — a separate AI-coaching feature that Helix links to (getMindsetCoachUrl,api/modules/helixProcessing/utils/index.js).
Which skills get analysed for a given user?
getSkillPrompts(userIds) (prompt.js) resolves, per user, which helix_skills apply to them: a user's applicable skills come from helix_set rows scoped to their helix_domain_id, either targeting audience: 'all' or audience: 'teams' (joined through helix_set_team against the user's team memberships). Each skill carries its own helix_prompt (the skill-specific prompt text + 3Q weights) and its helix_agent (which in turn has its own prompt — the overall system/developer prompt for the analysis call). See Data model for the full table relationships.
Two scoring engines
The LLM call's response schema is picked by responseFormat ('3Q' vs. anything else, treated as 'open'), driven by helix_agent.name:
3Q engine
The LLM (ZodUserAnalysis3Q schema) returns, per skill, a list of positive/negative "skill usage" phrases, each with a contextScore and sentimentScore. get3QSkillsWithScores() then computes a weighted score in code — this is the actual "3Q Engine" the product page refers to (quantitative key-term count, qualitative context, sentiment):
// api/modules/helixProcessing/processHelixData.js
const raw_key_count = Math.max(pos_count - neg_count, 0);
const max_term_count_reference = pos_count + neg_count;
const context_indicator = average(contextScore across all usages);
const sentiment_indicator = average(sentimentScore across all usages);
const keyword_indicator = raw_key_count / max_term_count_reference;
const raw_score = keyword_indicator * w1 + context_indicator * w2 + sentiment_indicator * w3;
const score = 100 * raw_score / (w1 + w2 + w3);
w1/w2/w3 (key-term / context / sentiment weights) come from helix_prompt.w1_keyterms / w2_context / w3_sentiment, set per skill-prompt. A code comment (processHelixData.js) flags a known quirk in this formula worth being aware of before changing it: raw_key_count clamps negative differences to zero, so e.g. 1 positive + 1 negative and 1 positive + 2 negative both currently produce the same raw_key_count of 0.
Open engine
The LLM (ZodUserAnalysisSkillOpen schema) is simply asked to return a score (0–100) directly per skill, alongside the same positive/negative usage phrases and a summary/suggestion, and that score is used as-is. This is a simpler, more directly LLM-judged alternative to 3Q, selected purely by which helix_agent a skill is wired to.
Both engines write to the same helix_user_meeting_skill columns (pos_count, neg_count, context_indicator, sentiment_indicator, raw_key_count, max_term_count_reference, keyword_indicator, raw_score, score) — for the Open engine, the indicator columns are simply left at their defaults since only score is meaningful.
Weekly roll-up
Independent of the per-meeting flow, weeklyAnalysis.js + sendWeeklyAnalysis.js average each user's scores per skill across the week's meetings, produce a GPT-generated summary (summarise()), and populate helix_user_analysis/helix_user_analysis_skill. It emails a "weekly analysis" template to every Helix-enabled domain — the query filters on domains.config->'$.helixIntegrationMode' IN ('both', 'helix') and helixSendAnalysisEmails != 'false'.
This can be triggered manually (outside of any schedule) via POST /api/sendWeeklyAnalysis (api/routes/sendWeeklyAnalysis/post.js), exposed in the Admin Settings page as "Run Helix weekly email for all users on all hubs" (frontend/src/admin/pages/settings/.../RunHelixWeeklyEmail/).
Manual reprocessing
If a meeting needs to be re-analysed (new prompt, different model, fixed transcript), an admin can re-run this whole pipeline directly, without going back through a bot: POST/PUT /meeting/{id} (api/routes/meeting/{id}/) re-runs processHelixData against an existing or duplicated meeting, optionally overriding model, temperature, reasoning_effort, service_tier, or the prompt/skills/transcript entirely. The Admin app's Meeting Reprocessing page (frontend/src/admin/pages/meetingReprocessing/) is the UI for this, gated to SUPER_USER.