LeadRefinery — How the Platform Works

D4 — qualification

Traced at commit e53d0dc · libs/contracts/src/lib/{vector-resolution,extractor,canonicalization,contracts,outbox}.ts · libs/temporal/src/{workflows,activities}/call-attempt.ts · apps/worker/src/main.ts · live state read 10 September 2026

Summary

  1. D4 records whether the person meets the criteria of the offer. It holds one value or nothing.
  2. Three values exist. The model can also answer UNKNOWN, which the platform discards.
  3. QUALIFIED gives qualified_no_action. DISQUALIFIED gives not_qualified.
  4. D4 is step 5 of the headline waterfall. It outranks D3.
  5. The voice provider sets D4 on every call that it marks successful.
  6. It sets QUALIFIED only when the custom field qualified is the boolean true. Otherwise it sets DISQUALIFIED.
  7. No live agent declares a field named qualified.
  8. In a test, a successful call from the developer test agent became not_qualified, with no review.
  9. Two of the three agents do not declare call_successful either. No real call yet shows what the provider then sends.
  10. Where step 1 leaves D4 empty, steps 2 and 3 decide it.
  11. The phrase list and the model prompt describe a home loan. The live agents offer merchant working capital.

Workflow 1 — where the value comes from

D4 has three sources, and they run in a fixed order. Step 1, the voice provider’s analysis, sets D4 on every call it marks successful. The custom field named qualified set to the boolean true gives QUALIFIED. Anything else gives DISQUALIFIED, including false, an absent field or a string. A value from step 1 ends the flow for D4. If D4 is still empty, step 2, a phrase list of 29 phrases, joins the agent’s and the caller’s turns into one string and checks the three values in a fixed order. The first value with a match wins and discards the model answer. Only when no phrase matched does step 3, the language model, fill D4, and an UNKNOWN answer never fills it. The model request goes out before the phrase list runs, so a phrase match discards a paid answer. Steps 2 and 3 both record the source as llm. No live agent declares the field named qualified, so a successful real call gets DISQUALIFIED.
Workflow 1 of 3 — the sources. Step 1 sets D4 on every call that the provider marks successful.

Step 1 — the voice provider

  1. The platform reads call_successful from the analysis.
  2. It reads qualified from custom_analysis_data. It accepts only a boolean.
  3. When call_successful is not the boolean true, step 1 leaves D4 empty.
  4. When call_successful is true and qualified is true, step 1 sets QUALIFIED. It also sets D3 INTERESTED.
  5. When call_successful is true and qualified is anything else, step 1 sets DISQUALIFIED.
  6. Anything else includes false, an absent field and the string "yes".
  7. A code comment gives the reason: parity with an older reconciler that returned not_qualified.
  8. Step 1 cannot set PENDING_INFO.
  9. If two analysis messages arrive, one true for qualified wins.

Steps 2 and 3

  1. The phrase list and the model work as they do for D2. See D2 — who answered.
  2. Steps 2 and 3 fill D4 only when step 1 left it empty.
  3. The D4 list holds 29 phrases. Their confidence runs from 0.80 to 0.90.
  4. Step 2 checks QUALIFIED first, then DISQUALIFIED, then PENDING_INFO.
  5. A qualifying phrase anywhere in the call therefore beats a disqualifying phrase anywhere in the call.
  6. In a test, I rent the unit, but yes I have steady income. gave QUALIFIED from yes i have.

Workflow 2 — what each value does

A table of the D4 values in the order the phrase list checks them. Row 1, QUALIFIED, meets the criteria: 10 phrases at 0.85, set by step 1 when the qualified field is the boolean true, headline qualified no action. Row 2, DISQUALIFIED, does not meet the criteria: 11 phrases at 0.90, set by step 1 whenever the qualified field is anything else, headline not qualified. Row 3, PENDING_INFO, needs more information first: 8 phrases at 0.80, never set by step 1, and no headline, so the waterfall moves on to D3. UNKNOWN comes only from the model and is never stored. D4 is step 5 of the waterfall, after D5, D6, D1 and D2 and before D3. The phrases and the model prompt describe a home loan, while the live agents offer working capital.
Workflow 2 of 3 — the values. Three values. Only QUALIFIED and DISQUALIFIED reach the headline.
  1. D4 is step 5 of the waterfall. D5, D6, D1 and D2 come first.
  2. QUALIFIED gives qualified_no_action.
  3. DISQUALIFIED gives not_qualified.
  4. PENDING_INFO gives no headline. The waterfall moves on to D3.
  5. Any D4 value except PENDING_INFO outranks every D3 value.
  6. A caller who asked for a callback but did not qualify gets not_qualified.
  7. Outside the headline and the evaluation harness, no code reads D4 today. The outbox and the gauges ignore it.

Workflow 3 — a successful call

How step 1 sets D4, next to what the three live agents declare. The provider sends its call analysis. If call successful is not the boolean true, step 1 leaves D4 empty. If it is true, step 1 checks whether the custom field named qualified is the boolean true. If so, D4 becomes QUALIFIED, D3 becomes INTERESTED and the headline is qualified no action. Anything else, including false, an absent field or the string yes, gives DISQUALIFIED and the headline not qualified, at confidence 0.8 with no review, and steps 2 and 3 cannot change it. The developer test agent declares call successful and a field named qualified lead holding the strings yes, no or unknown: the wrong name and the wrong type, so it gets DISQUALIFIED. Agent B, the Screener, and Agent C, the Router, declare neither call successful nor qualified. No real call on record shows whether the provider still sends call successful for them. If it does, a successful call gets DISQUALIFIED. If not, step 1 leaves D2, D3 and D4 empty, and steps 2 and 3 decide them. Measured on 10 September 2026 with the real resolver.
Workflow 3 of 3 — a successful call. Step 1 needs a field that no live agent declares.
  1. The test ran the real resolver on a call analysis shaped like one from the developer test agent.
  2. The analysis held call_successful: true and qualified_lead: "yes".
  3. The resolver set D4 DISQUALIFIED and the headline not_qualified.
  4. It gave confidence 0.8 and no review. Steps 2 and 3 cannot change that headline.
  5. A second test sent qualified: "yes" as a string. The result did not change.
  6. A third test sent qualified: true. The resolver set QUALIFIED, INTERESTED and qualified_no_action.
  7. Agents B and C declare neither call_successful nor qualified.
  8. A fourth test left out call_successful. Step 1 left D2, D3 and D4 empty, and the headline at needs_review.
  9. Steps 2 and 3 then fill all three and derive the headline again.
  10. For agents B and C, that headline would rest on the home-loan phrase list and prompt.
  11. The call goes to review if extraction leaves D2, D3 or D4 empty.
  12. It also goes to review for an UNKNOWN in D2 or D3, or a score below 0.7.
  13. The rehearsal simulator sends qualified: true as a boolean. A rehearsal therefore takes a path that no live agent can take.

The phrase list

Step 2 checks the rows from top to bottom. The first row with a match wins. Dictionary version 1.0.

OrderValueConfidencePhrases
1QUALIFIED0.85yes i own · i own the property · homeowner · i have equity · my credit is good · i qualify · i meet the requirements · yes i have · currently employed · have income
2DISQUALIFIED0.90i rent · i'm a renter · don't own · no equity · bad credit · bankruptcy · in foreclosure · unemployed · no income · don't qualify · not eligible
3PENDING_INFO0.80not sure · i don't know · let me check · have to look · need to verify · i'll have to find out · maybe · possibly

The phrases describe another offer. homeowner, i have equity and my credit is good describe a property loan. The model prompt names homeowners too. The live agents ask about monthly revenue, time in business and existing advances.

Known defects

  1. The field name. Step 1 reads qualified. The developer test agent sends qualified_lead. Agents B and C send neither.
  2. The field type. Step 1 accepts only a boolean. The provider sends enum fields as strings such as "yes". Other readers in the same file use asYesNo, which accepts "yes".
  3. Silence gives a definite answer. An absent field produces DISQUALIFIED at confidence 0.8. An absent field should leave D4 empty.
  4. Step 1 ties D3 to D4. Only a QUALIFIED call gets a D3 value from step 1.
  5. Value order beats word order. A qualifying phrase beats a disqualifying phrase. The model stub in the renter test answered DISQUALIFIED.
  6. The wrong offer. The phrase list and the model prompt describe a home loan. The live agents offer merchant working capital. Those decide real D4 values whenever step 1 leaves D4 empty.
  7. Short phrases match ordinary speech. maybe, possibly, not sure and i don't know set PENDING_INFO.
  8. Rehearsals hide the first defect. The simulator sends the field that no live agent sends.

What rehearsal data can show

  1. A sample of about 1% of stage attempts held 338 D4 values.
  2. Step 1 set 138 of them: 107 DISQUALIFIED and 31 QUALIFIED.
  3. The impersonated model set the other 200, all DISQUALIFIED. The phrase list set none.
  4. Every QUALIFIED value in the sample came from the simulator’s boolean qualified field.
  5. No real call can produce that value from step 1 today.

D2 — who answered — the gate, and the phrase list reading the agent.
D3 — intent — the value that D4 outranks.
Outcome resolution — how D4 fits among the seven dimensions.