Chapter 1.6 AI Automation Projects

Learning Objectives

After completing this chapter, you will be able to:

  • Translate a client brief into an architectural specification by applying the progressive problem expansion discipline to identify failure modes, functional requirements, and constraints.
  • Design a complete multi-channel intake workflow that handles free-text classification, structured field validation, and routing across at least three distinct processing paths.
  • Apply the full Part I architecture advisory pattern, prompt engineering, production API service layer, workflow composition, reliability controls to a previously unseen client domain.
  • Build and document the AI reliability model for a new project, specifying confidence thresholds, fallback behavior, and audit trail fields appropriate to the client’s operational context.
  • Evaluate whether a proposed AI integration is justified for a given client problem using the four AI capability categories and the suitability routing decision framework.
  • Troubleshoot a cross-domain AI automation project by systematically isolating failures to the evaluation layer, the AI service layer, or the action layer.

Introduction

Across Chapter 1.0 — Introduction: From APIs to AI Systems through Chapter 1.5, you built a complete AI advisory workflow for a recruiting agency: a governed, reliable, auditable system that evaluates job applications by combining deterministic rules with an AI assessment, routes on confidence quality, escalates uncertain cases to human review, and logs every decision. You understand every architectural element: the pre-flight suitability filter, the structured prompt, the AI service layer with retry and error handling, the IF/Merge composition pattern, the confidence-band multiplier, the requires_manual_review flag, and the audit log.

That architecture is not a recruiting-specific pattern. It is a general-purpose framework for combining AI judgment with deterministic rules in any domain where structured assessments are needed. An automation engineer who has completed Chapters 1.0–1.5 possesses a framework not just a workflow.

The framework is deployable across any domain where these conditions hold: the input is text-based or structured data that contains signal, the business decision downstream is costly to make manually at volume, the AI assessment can be bounded by deterministic rules, and the decision needs to be auditable. These conditions describe a very large fraction of professional automation use cases lead qualification in sales, email triage in customer service, support ticket routing in IT, document data extraction in finance and legal, intent scoring in CRM.

In every one of these domains, automation engineers are asked to add AI to workflows that previously operated on rules alone. The engineers who arrive with a tested, governed, auditable architecture and adapt it to the domain deliver faster, more reliably, and at lower operational risk than those who build each workflow from first principles.

That is the professional leverage the advisory architecture provides. A system built from scratch for each new client takes days. A domain adaptation new role statement, new evaluation dimensions, new scale anchors, identical infrastructure takes hours. The difference is not speed alone: it is the difference between delivering a workflow and delivering a system with documented design decisions, calibrated thresholds, and a governance layer a client’s compliance team can actually review.

NoteEngineering Rationale

A reusable architecture is not a convenience it is a professional differentiator. Engineers who deploy a tested, auditable advisory workflow in 3–6 hours instead of three days win client engagements at lower risk and with more credible delivery estimates than those building from first principles each time.

This chapter demonstrates the framework’s generality by applying it to five distinct business problems. Section 1.6.1 names the invariant architectural core the elements that never change between deployments and the domain adaptation layer the configuration decisions that make the architecture specific to a context. The five portfolio projects that follow each implement the same invariant core with a different adaptation layer: lead qualification, lead intent scoring, email triage and response drafting, support ticket classification, and document data extraction. By the end of this chapter, the pattern of identifying what changes and what stays constant should feel automatic because that skill is what enables a new AI-powered workflow to be deployed in hours rather than days.

Build the project that is most relevant to your professional context, or build more than one. Each project teaches the same adaptation skill through a different domain. If you are proceeding directly to Part II, Portfolio Project B the Hybrid AI Lead Intent Scoring Engine is the closest architectural match: it uses the same prompt structure, the same confidence-band model, the same scoring formula, and the same audit log fields as the Part II production implementation. Building Project B before Part II means you will recognize every component when you encounter them again in Chapter 2.5, adapted to the HubSpot CRM context.

Project AI Pipeline Type Input Key AI Output Advisory Output
A Lead Qualification Classification + Enrichment Lead form + use_case_text need_quality, icp_fit, urgency_signal hot / warm / cold composite score
B Lead Intent Scoring Classification (Part II recommended) Contact record + intent_text + engagement metrics lead_intent, intent_strength, buying_stage hot_lead / warm_lead / nurture / cold_lead composite score
C Email Assistant Classification + Generation Inbound email body text email_category, sentiment, urgency [+ draft_body] Routed to team queue + draft reply for human review
D Support Ticket Classifier Classification + Routing Support ticket description priority_tier, team_queue, issue_category P1/P2 → on-call; P3 → team queue; P4 → low-priority queue
E Document Analyzer Extraction Document text (from PDF parser) document_type, vendor_name, amount, document_date, per_field_conf Routed to downstream system + human verification for low-confidence fields

All projects include: Rule-First, AI-Second; Five-Element Reliability; Audit Log; Confidence Bands; requires_manual_review; IF/Merge.

TipDesign Practice

Before starting any portfolio project, estimate the per-call token cost and multiply by expected daily volume. Five projects at 200 calls per day each at approximately 600 tokens per call costs roughly $0.13/day total on gpt-4o-mini well within any professional budget. The cost ceiling rises significantly if gpt-4o is substituted (15× higher per call) or if max_tokens is set well above the response size. Establish the cost ceiling in writing before the first test execution.


1.6.1 The Reusable Advisory Architecture

Invariant Architectural Core vs. Domain Adaptation Layer

The advisory architecture built in Chapters 1.0–1.5 has two layers: an invariant architectural core and a domain adaptation layer. The architectural core never changes between deployments. The domain adaptation layer is the set of decisions that make the architecture specific to a business context.

The invariant core consists of six elements: the three-segment modular structure (Evaluation Layer, AI Processing Layer, Advisory Output Layer); the five-element reliability model (pre-flight validation, structural output validation, fallback path, confidence-band gating, audit log); the AI/Rule composition formula (advisory_score = rule_score + Math.min(cap, ai_score × multiplier)); the confidence bands (high ≥ 0.80 with multiplier 1.0; medium 0.55–0.79 with multiplier 0.5; low < 0.55 with multiplier 0.0 plus escalation); the normalized output contract (fixed schema with evaluation_source, requires_manual_review, and audit_log); and the IF/Merge composition pattern (AI path and rule fallback path merge into a single normalized output).

These elements are copied verbatim from the Chapter 1.5 workflow and changed only if the domain requires a structural extension not because the engineer prefers a different implementation.

TipDesign Practice

Copy the invariant core verbatim from the Chapter 1.5 workflow do not rebuild it from memory or adapt it incrementally. The five-element reliability model, the IF/Merge pattern, the confidence-band formula, and the normalized output contract are correct as built. Apply changes only in the domain adaptation layer.

The domain adaptation layer is the set of decisions that varies per project: the input fields and their suitability thresholds; the output schema field names and their enum values; the system prompt role assignment and evaluation dimensions; the intent-to-score mapping and contribution cap value; the rule scoring logic and rule score range; the advisory thresholds (what score level triggers which routing decision); the audit log destination; and the human review notification format and channel. This is typically three to five configuration decisions.

A new domain deployment starts with the complete Chapter 1.5 architecture and modifies only these elements. The core nodes HTTP Request with retry, Parse Response with four-category validation, IF/Merge pattern, confidence-band multiplier, audit log structure are copied and their internals updated for the new domain. Estimated time: 3–6 hours depending on domain complexity.

The invariant core is what makes the architecture valuable as a professional tool rather than a one-time implementation. An engineer who has built the recruiting agency workflow can offer a client a new AI workflow with a credible delivery estimate precisely because the core architecture is solved. The domain adaptation takes hours, not days, because the hard problems retry logic, confidence gating, normalized output contracts, audit logging do not need to be re-engineered.

The seven adaptation questions answer before any node is configured.

The seven adaptation questions that define a complete domain specification are: what fields arrive in the webhook payload, and which are required? What AI pipeline type is this enrichment, classification, extraction, routing, generation and what are the output fields and permitted values? What deterministic rules produce a rule_score, and what is the rule score range? What is the maximum AI contribution (the cap in Math.min(cap, ...))? What score levels map to which recommended actions? What happens to low-confidence items, and who is notified through what channel? Where should the structured audit record be written? These seven questions, answered before any node is configured, produce a complete domain specification from which the implementation follows directly.

NoteEngineering Rationale

Answering the seven adaptation questions in writing before opening n8n is the single habit that separates a 3-hour domain adaptation from a 3-day rework. Each question corresponds directly to a workflow component. Answer a question incorrectly and that component must be rebuilt. Answer it correctly first and the component is configured once and verified against the spec.


Portfolio Project A AI Lead Qualification Workflow

Portfolio Project A Lead Qualification

Business Scenario

A sales development team at a B2B software company receives 150–200 inbound leads per week from a web form. Each lead provides company name, job title, company size, primary use case, and a free-text field describing their needs. The team manually qualifies each lead determining whether it fits the ideal customer profile (ICP) and whether the stated need is genuine before assigning it to an account executive. This qualification takes 4–6 minutes per lead and accounts for roughly 15 hours of SDR time per week.

The Problem

The team cannot reduce qualification time without sacrificing quality. Structured ICP fields (company size, job title tier, industry code) can be evaluated by rules, but the use_case_text field requires reading and interpretation: determining whether a stated need is genuine, specific, and aligned with core product use cases is a classification task that rule-based keyword matching performs poorly. A use_case_text of “better data management” matches no keyword list entry but may represent a genuine, high-urgency need or a vague exploratory inquiry indistinguishable without semantic analysis.

The Architectural Solution

A domain adaptation of the Chapter 1.5 advisory architecture with three-tier output: hot (immediate AE assignment), warm (nurture sequence), cold (disqualify). The rule layer scores structural ICP fit from fields that do not require AI company size, title tier, industry match. The AI layer assesses need_quality from use_case_text. The bounded combination produces a composite score that reflects both signals, with the requires_manual_review path handling low-confidence assessments. The AI task fits all four suitability criteria: semantic understanding is required; the output is a finite classification; consistency across hundreds of leads matters more than perfect accuracy on each one; and the ICP structural score provides a valid deterministic fallback when AI is unavailable.

Architecture and Design Rationale

Business objective: reduce manual SDR qualification time from 4–6 minutes per lead to under 30 seconds while maintaining accuracy for clear-cut cases and routing borderline cases to human review.

Engineering objective: implement a domain adaptation of the Chapter 1.5 advisory architecture with the smallest possible delta reusing all invariant core components verbatim and modifying only the input schema, output schema, system prompt, and rule scoring logic.

Expected outcome: hot leads routed to AE assignment within seconds of form submission; warm leads enrolled in nurture sequence automatically; cold leads disqualified without SDR time; borderline low-confidence assessments escalated to Slack #lead-review.

Why this architecture? Three alternatives exist: (1) rules-only ICP scoring fast and cheap but cannot assess need quality from free text; (2) AI-only scoring eliminates the deterministic fallback and loses the rule layer’s structural signal; (3) full redesign from scratch adds days of engineering for no benefit when the Chapter 1.5 architecture already solves every reliability requirement. The advisory architecture (rule score + bounded AI contribution) is the correct choice: the rule layer scores structural fit reliably, the AI layer scores need quality from free text, and the bounded combination prevents AI overconfidence from dominating the score.

Trade-offs accepted: the use_case_text minimum word count (20 words) will reject some leads with short but legitimate use cases. This is intentional the AI assessment of a five-word use case produces low-signal output that inflates review volume without improving qualification accuracy. Rejected short-text leads are routed to the review path for human classification.

Solution Architecture

Domain adaptation decisions:

Parameter Value
Input fields company_name, job_title, company_size, industry, use_case_text
Suitability threshold use_case_text ≥ 20 words; job_title present; company_size present
AI output schema need_quality (enum), icp_fit (enum), urgency_signal (enum), confidence, signals, reasoning
need_quality values strong \| moderate \| weak \| unclear
icp_fit values core_icp \| adjacent \| outside_icp \| unknown
urgency_signal values active_project \| evaluating \| exploring \| no_signal
Rule score basis ICP structural fields: company size band (0–3), title tier (0–2), industry match (0–1)
Rule score range 0–6
AI score mapping need_quality → (strong: 4, moderate: 3, weak: 1, unclear: 0)
Contribution cap Math.min(4, need_quality_score × multiplier)
Advisory thresholds ≥ 8: hot; 4–7: warm; < 4: cold
Escalation requires_manual_review = true → Slack #lead-review
Audit log destination HTTP POST to CRM webhook (or Google Sheets append)

Implementation Sequence:

Step 1 Webhook and Lead Suitability

Purpose

Accept the lead form POST and validate that sufficient data is present for a meaningful AI classification. This step determines whether the lead record contains enough signal to justify an AI assessment or should be routed to rule-only scoring or human review.


Operation Summary

Property Value
Node Type Webhook (input) + Code node (suitability evaluation)
Primary Function Receive lead form POST; compute processing_path
Input Fields company_name, job_title, company_size, industry, use_case_text
Output processing_path: "ai" | "rules" | "review"

Implementation Logic

const use_case_text  = $json.use_case_text  || "";
const job_title      = $json.job_title      || "";
const company_size   = $json.company_size   || "";

const word_count = use_case_text.trim().split(/\s+/).filter(w => w.length > 0).length;

let processing_path;
if (!job_title || !company_size) {
  processing_path = "review";           // required structural fields missing
} else if (word_count < 20) {
  processing_path = "review";           // insufficient text for AI assessment
} else {
  processing_path = "ai";
}

Count words by splitting on whitespace, not by character count. A 20-character use_case_text (“need better reports”) provides far less AI signal than a 20-word statement of need. The word-count threshold sets the minimum for a meaningful classification result.


Output Table

Output Description
processing_path Routing decision: ai, rules, or review
use_case_word_count Word count used for suitability decision
job_title Validated present; passed downstream
company_size Validated present; passed downstream

Production Considerations

NoteEngineering Rationale

The 20-word minimum rejects some leads with short but legitimate use cases. This is intentional: the AI classification of “need better data” produces a low-signal result that inflates the manual review queue without improving qualification accuracy. Short-text leads are routed to human review rather than degraded AI assessment.


Step 2 Build Prompt

Purpose

Assemble the lead qualification system prompt with the structured output schema and the injected lead fields. This step adapts the Chapter 1.5 Build Prompt node to the lead qualification domain.


Operation Summary

Property Value
Node Type Code node
Primary Function Construct structured prompt with output schema and lead data
Input Fields use_case_text, company_name, job_title, industry
Output Complete prompt object with PROMPT_VERSION constant

Implementation Logic

const PROMPT_VERSION = "lead-qual-v1.0.0";

const system_prompt = `You are an expert B2B sales qualification analyst. Evaluate the lead's stated use case and determine:
1. Need quality how specific, urgent, and genuine is the stated need?
2. ICP fit how well does this lead match a typical core customer profile for a B2B software product?
3. Urgency signal what timeline and urgency are implied by the stated need?

OUTPUT SCHEMA return exactly these fields as valid JSON:
{
  "need_quality":    string,  // strong | moderate | weak | unclear
  "icp_fit":         string,  // core_icp | adjacent | outside_icp | unknown
  "urgency_signal":  string,  // active_project | evaluating | exploring | no_signal
  "confidence":      number,  // 0.00–1.00
  "signals":         array,   // 1–3 specific phrases from the text
  "reasoning":       string   // max 20 words: primary reason for need_quality assessment
}`;

const user_message = `Company: ${company_name}
Job Title: ${job_title}
Industry: ${industry || "not specified"}
Use Case: ${use_case_text}`;

The evaluation dimensions need specificity, urgency, ICP alignment are the domain adaptation from Chapter 1.5’s candidate_intent dimensions. The output schema fields are updated for lead qualification; the schema structure is copied verbatim.


Output Table

Output Description
system_prompt Complete system prompt string with role and output schema
user_message Formatted lead data for the user turn
PROMPT_VERSION Version constant written to audit log

Production Considerations

TipDesign Practice

Copy the Build Prompt node from Chapter 1.5 verbatim; update only the role assignment string, the three evaluation dimensions, and the output schema field names. The JSON schema format, injection defenses, and PROMPT_VERSION constant are invariant.


Step 3 HTTP Request: OpenAI

Purpose

Call the OpenAI Chat Completions API with the assembled prompt and receive the raw JSON response. This node is invariant across all domain adaptations no changes are required for Project A.


Operation Summary

Property Value
Method POST
Endpoint https://api.openai.com/v1/chat/completions
Primary Function AI classification of lead need quality
Auth Bearer token from n8n Credential Store
Output Raw API response with choices[0].message.content

Request Payload

{
  "model": "gpt-4o-mini",
  "messages": [
    { "role": "system", "content": "{{system_prompt}}" },
    { "role": "user",   "content": "{{user_message}}" }
  ],
  "response_format": { "type": "json_object" },
  "temperature": 0.1,
  "max_tokens": 300
}

response_format: json_object enforces structured JSON output. temperature: 0.1 minimizes classification variance lead qualification requires consistent scoring across comparable leads, not creative variation.


Response Processing

// Copied unchanged from Chapter 1.5 HTTP Request node
// On Error: continue (outputs to error path for fallback handling)
// Retry: 2 attempts, 1000ms delay
// Timeout: 30000ms

This node is copied without modification from Chapter 1.5. The endpoint, model parameter, retry configuration, timeout, and On Error behavior are invariant.


Production Considerations

CautionProduction Risk

Do not change the response_format, retry count, or On Error behavior for domain adaptations. These settings are the invariant reliability controls established in Chapter 1.3. Modifying them for a specific domain produces inconsistent error handling behavior across the portfolio projects.


Step 4 Parse Response

Purpose

Validate the API response across four categories and extract the structured lead qualification assessment. The four-category validation structure is copied verbatim from Chapter 1.5; only the permitted enum values are updated for the lead qualification domain.


Operation Summary

Property Value
Node Type Code node
Primary Function Validate response; extract need_quality, icp_fit, urgency_signal, confidence
Input Raw API response from Step 3
Output ai_result_valid flag; parsed classification fields

Implementation Logic

const VALID_NEED_QUALITY   = ["strong", "moderate", "weak", "unclear"];
const VALID_ICP_FIT        = ["core_icp", "adjacent", "outside_icp", "unknown"];
const VALID_URGENCY_SIGNAL = ["active_project", "evaluating", "exploring", "no_signal"];

let parse_error = null;
let ai_result_valid = false;
let parsed = {};

try {
  const raw_content = items[0].json.choices[0].message.content;
  parsed = JSON.parse(raw_content);

  // Category 1: required fields present
  if (!parsed.need_quality || !parsed.icp_fit || !parsed.urgency_signal || parsed.confidence == null) {
    throw new Error("Missing required fields in AI response");
  }
  // Category 2: enum values valid
  if (!VALID_NEED_QUALITY.includes(parsed.need_quality)) {
    throw new Error(`Invalid need_quality: ${parsed.need_quality}`);
  }
  if (!VALID_ICP_FIT.includes(parsed.icp_fit)) {
    throw new Error(`Invalid icp_fit: ${parsed.icp_fit}`);
  }
  if (!VALID_URGENCY_SIGNAL.includes(parsed.urgency_signal)) {
    throw new Error(`Invalid urgency_signal: ${parsed.urgency_signal}`);
  }
  // Category 3: confidence range valid
  if (parsed.confidence < 0 || parsed.confidence > 1) {
    throw new Error(`confidence out of range: ${parsed.confidence}`);
  }
  // Category 4: structural types correct
  if (typeof parsed.need_quality !== "string") {
    throw new Error("need_quality must be a string");
  }

  ai_result_valid = true;
} catch (e) {
  parse_error = e.message;
  ai_result_valid = false;
}

The four validation categories required fields, enum validity, numeric range, structural types are copied verbatim from Chapter 1.5. The only adaptation is the enum lists: VALID_NEED_QUALITY, VALID_ICP_FIT, and VALID_URGENCY_SIGNAL replace the Chapter 1.5 candidate intent enums.


Output Table

Output Description
ai_result_valid Boolean true if all four validation categories passed
need_quality Enum: strong \| moderate \| weak \| unclear
icp_fit Enum: core_icp \| adjacent \| outside_icp \| unknown
urgency_signal Enum: active_project \| evaluating \| exploring \| no_signal
confidence Float 0.00–1.00; AI’s stated certainty in the assessment
parse_error Error message if validation failed; null on success

Production Considerations

ImportantCritical Requirement

If ai_result_valid is consistently false, log parse_error to the audit record to identify the failure category. Common causes: the model returned a need_quality value outside the permitted enum (e.g., "high" instead of "strong"); confidence arrived as a string instead of a number; the response contained explanatory text outside the JSON object. All three are fixed by adjusting the system prompt’s output schema instruction.


Step 5 AI Qualification Score

Purpose

Apply the confidence-band multiplier to the AI need quality score and compute the composite score from the deterministic rule score and bounded AI contribution. This step produces the routing signal that drives the advisory output.


Operation Summary

Property Value
Node Type Code node
Primary Function Confidence-band scoring; composite score computation
Input need_quality, confidence (from Parse Response); rule_score (from ICP scoring)
Output composite_score, confidence_band, requires_manual_review

Implementation Logic

const CONFIDENCE_THRESHOLDS = { HIGH: 0.80, MEDIUM: 0.55 };

const NEED_QUALITY_SCORE_MAP = {
  "strong":   4,
  "moderate": 3,
  "weak":     1,
  "unclear":  0
};

// Confidence band → multiplier
let confidence_band, confidence_multiplier, requires_manual_review;
if (confidence >= CONFIDENCE_THRESHOLDS.HIGH) {
  confidence_band = "high";   confidence_multiplier = 1.0; requires_manual_review = false;
} else if (confidence >= CONFIDENCE_THRESHOLDS.MEDIUM) {
  confidence_band = "medium"; confidence_multiplier = 0.5; requires_manual_review = false;
} else {
  confidence_band = "low";    confidence_multiplier = 0.0; requires_manual_review = true;
}

const need_quality_score = NEED_QUALITY_SCORE_MAP[need_quality] || 0;
const ai_contribution    = Math.min(4, need_quality_score * confidence_multiplier);
const composite_score    = rule_score + ai_contribution;

The scoring formula is identical to Chapter 1.5: rule_score + Math.min(4, ai_score × multiplier). The NEED_QUALITY_SCORE_MAP replaces the Chapter 1.5 INTENT_SCORE_MAP same structure, domain-adapted field names. The confidence thresholds (0.80 / 0.55) and multiplier values (1.0 / 0.5 / 0.0) are invariant.


Output Table

Output Description
composite_score rule_score + bounded AI contribution; drives routing
confidence_band high \| medium \| low; determines multiplier applied
confidence_multiplier 1.0, 0.5, or 0.0; weight applied to AI need quality score
requires_manual_review True when confidence is below medium threshold
ai_contribution Bounded AI score component (0–4)

Production Considerations

NoteEngineering Rationale

The contribution cap of 4 prevents the AI assessment from dominating the composite score when the rule layer produces a strong structural signal. A lead with perfect ICP structural fit (rule_score = 6) and a strong need quality (ai_contribution = 4) produces composite_score = 10 well above hot threshold. A lead with zero structural fit (rule_score = 0) cannot reach hot threshold on AI assessment alone.


Step 6 Advisory Output and Routing

Purpose

Apply advisory thresholds to the composite score and produce the normalized output contract. This step generates the recommended_action string that drives downstream routing to AE assignment, nurture enrollment, or disqualification.


Operation Summary

Property Value
Node Type Code node
Primary Function Threshold evaluation; normalized output contract construction
Input composite_score, confidence_band, requires_manual_review
Output recommended_action, evaluation_source, requires_manual_review, full normalized contract

Implementation Logic

let recommended_action;
if (composite_score >= 8) {
  recommended_action = "hot";
} else if (composite_score >= 4) {
  recommended_action = "warm";
} else {
  recommended_action = "cold";
}

let evaluation_source;
if (ai_result_valid) {
  evaluation_source = "ai_enhanced";
} else {
  evaluation_source = "rule_only";
}

const output = {
  recommended_action:    recommended_action,
  evaluation_source:     evaluation_source,
  requires_manual_review: requires_manual_review,
  composite_score:       composite_score,
  confidence_band:       confidence_band,
  need_quality:          need_quality,
  icp_fit:               icp_fit,
  urgency_signal:        urgency_signal,
  confidence:            confidence,
  output_timestamp:      new Date().toISOString()
};

The evaluation_source field distinguishes AI-enhanced decisions from rule-only fallback decisions a required field for audit trail integrity. The threshold values (≥ 8 for hot; 4–7 for warm; < 4 for cold) are the Project A domain adaptation of the Chapter 1.5 advisory thresholds.


Output Table

Output Description
recommended_action hot \| warm \| cold; primary routing signal
evaluation_source ai_enhanced \| rule_only; how the score was produced
requires_manual_review True when confidence is low; routes to Slack #lead-review
composite_score Final combined score (rule + bounded AI)
output_timestamp ISO 8601 timestamp of the advisory decision

Production Considerations

CautionProduction Risk

Using icp_fit as the primary AI scoring dimension instead of need_quality means the AI is re-classifying what the rule layer already measures from structured fields. The AI’s distinctive contribution is assessing the quality of the stated need, which requires semantic text analysis. Orient the AI score mapping toward need_quality, not icp_fit.


Step 7 Audit Log

Purpose

Write a complete structured decision record covering input data, AI assessment, scoring components, and the final routing decision. This record is the compliance trail and the data source for future scoring calibration.


Operation Summary

Property Value
Method POST (to CRM webhook) or Append (to Google Sheets)
Endpoint CRM contact webhook or Google Sheets API
Primary Function Persist complete decision record for audit and reporting
Output Confirmed write to audit destination

Request Payload

const log_record = {
  log_id:               "audit_" + Date.now() + "_" + Math.random().toString(36).substring(2, 7),
  timestamp:            new Date().toISOString(),
  processing_path:      processing_path,

  // Input summary
  company_name:         company_name,
  job_title:            job_title,
  company_size:         company_size,
  industry:             industry,
  use_case_word_count:  use_case_word_count,

  // AI assessment
  ai_result_valid:      ai_result_valid,
  need_quality:         need_quality,
  icp_fit:              icp_fit,
  urgency_signal:       urgency_signal,
  confidence:           confidence,
  confidence_band:      confidence_band,
  confidence_multiplier: confidence_multiplier,

  // Scoring
  rule_score:           rule_score,
  ai_contribution:      ai_contribution,
  composite_score:      composite_score,
  evaluation_source:    evaluation_source,

  // Decision
  recommended_action:      recommended_action,
  requires_manual_review:  requires_manual_review,

  // API metadata
  model_used:           api_metadata?.model_used   || null,
  prompt_version:       prompt_version,
  total_tokens:         api_metadata?.total_tokens  || 0,
  latency_ms:           api_metadata?.latency_ms    || 0,
  parse_error:          parse_error || null
};

The log_id, timestamp, and evaluation_source fields are structural fields copied verbatim from Chapter 1.5. All other field names are updated for the lead qualification domain. This record is ideally written to the CRM contact as structured properties rather than to a Slack channel leads are CRM records, and the audit trail belongs alongside the lead record.


Output Table

Output Description
log_id Unique identifier for this decision record
timestamp ISO 8601 decision timestamp
evaluation_source ai_enhanced \| rule_only; supports reporting split
composite_score Final score; enables calibration analysis
parse_error Null on success; populated for failed AI calls

Production Considerations

NoteEngineering Rationale

Writing the audit log to a CRM webhook (rather than Google Sheets) attaches the qualification decision directly to the contact record. This enables downstream workflows AE outreach, nurture enrollment, deal stage progression to read the qualification score, confidence band, and evaluation source from the contact without querying a separate log store. Chapter 2.2 implements this CRM property write using the HubSpot Contacts API.

Data transformation table domain field mapping from Chapter 1.5:

Chapter 1.5 Field Project A Field Notes
cover_letter use_case_text Free-text field for AI assessment
candidate_intent need_quality AI classification target
candidate_name company_name Input identity field
advisory_score composite_score Scoring formula output
advance_to_interview hot Top routing tier
manual_review requires_manual_review = true Low-confidence escalation

Three-segment modular structure:

Segment 1 Evaluation Layer: Webhook → Code: Lead Suitability → Switch on processing_path

Segment 2 AI Processing Layer: Build Prompt (lead qualification schema) → HTTP Request: OpenAI → Parse Response

Segment 3 Advisory Output Layer: IF on ai_result_valid → AI Qualification Score / Rule Fallback → IF on requires_manual_review → Merge → Advisory Output → Audit Log → Slack / CRM webhook

System prompt role assignment:

You are an expert B2B sales qualification analyst. Evaluate the lead's stated use case and determine:
1. Need quality how specific, urgent, and genuine is the stated need?
2. ICP fit how well does this lead match a typical core customer profile for a B2B software product?
3. Urgency signal what timeline and urgency are implied by the stated need?

Project A is a direct domain transplant of the recruiting advisory workflow. The use_case_text field maps to cover_letter. The need_quality classification maps to candidate_intent. The ICP structural field score maps to rule_score. The scoring formula is identical: rule_score + Math.min(4, need_quality_score × multiplier). The only substantive architectural difference is the audit log destination: leads are CRM records, so the audit log is ideally written to the CRM contact as structured properties rather than to a Slack channel. The workflow mechanics are unchanged.

A complete Project A implementation requires: Webhook for the lead form POST endpoint; Lead Suitability Code node with use_case_word_count ≥ 20 threshold; Build Prompt node with the lead qualification system prompt; HTTP Request node copied from Chapter 1.5 (endpoint, auth, retry, On Error unchanged); Parse Response node with updated enum validation for need_quality, icp_fit, urgency_signal; AI Qualification Score node with updated INTENT_SCORE_MAP; Rule Fallback Score node with ICP structural scoring; Advisory Output node with hot | warm | cold routing thresholds; Audit Log node. Estimated adaptation time from Chapter 1.5: 3–4 hours.

Project A’s ICP structural scoring and need quality AI assessment are the lead evaluation components that Chapter 2.2 (Contact Scoring) builds on. The composite score structure deterministic ICP fields plus AI need quality assessment is the same architecture, extended with engagement history, recency signals, and a multi-dimensional AI assessment.

CautionProduction Risk

Using icp_fit as the primary AI scoring dimension instead of need_quality means the AI is re-classifying what the rule layer already measures from structured fields. The AI’s distinctive contribution is assessing the quality of the stated need, which requires semantic text analysis. Orient the AI score mapping toward need_quality, not icp_fit.

CautionProduction Risk

A use_case_text minimum of five words (“need better data tracking”) provides almost no signal for need quality assessment. Set the minimum at 20 words to ensure the AI has sufficient context for a useful classification.

Production Consideration

Project A routes hot and warm leads to downstream team queues via Slack notification, but produces no CRM record of the qualification decision. Without a persistent record, the AE who receives the assignment cannot see what score, what need_quality classification, or what confidence level produced the routing. Chapter 2.2 introduces the HubSpot Contact Scoring architecture that writes qualification scores to contact properties, making this data available across all downstream workflows and team members.


Portfolio Project B Hybrid AI Lead Intent Scoring Engine

Portfolio Project B Lead Intent Scoring (Part II Recommended)

If you are proceeding to Part II, build this project. The prompt structure, confidence-band model, scoring formula, and audit log structure are preserved identically in Chapter 2.5. When you encounter Chapter 2.5, you will recognize every component.

Business Scenario

A revenue operations team manages a contact database of 50,000 leads across multiple acquisition channels. Leads enter the database from web forms, event registrations, content downloads, and email campaigns. Each contact record contains structured engagement data (email open rate, website page views, form submission history, lead source) and a free-text field capturing the contact’s stated interest or intent from their most recent interaction.

The Problem

Engagement data (opens, clicks, page views) provides a strong behavioral signal, but it is indirect. A contact who downloads a pricing page and opens three nurture emails is behaviorally engaged but whether they are actively evaluating the product, casually browsing, or researching for a competitor analysis is not captured by engagement metrics alone. The free-text intent field captures direct intent signals (“evaluating for Q3 deployment,” “comparing with Competitor X”) but processing 50,000 contacts manually for these signals is not feasible.

The Architectural Solution

A scoring engine combining a deterministic rule score (engagement rate, page views, form submissions) with an AI intent score (from the free-text intent_text field) to produce a composite lead score that drives CRM lifecycle stage assignment. The engine must be reliable under high volume, auditable for the revenue operations team, and architected so it can be embedded within a HubSpot-connected CRM workflow in Part II. The current scope is a standalone n8n workflow that accepts a contact record via webhook, computes the hybrid score, produces a normalized output object, and posts the result to a Slack notification and an audit log. HubSpot integration, CRM property writes, and lifecycle management are Part II responsibilities.

Architecture and Design Rationale

Business objective: automate lead tier assignment for a 50,000-contact database, replacing manual RevOps review with a scored, auditable, CRM-ready output that assigns hot_lead | warm_lead | nurture | cold_lead tiers based on combined behavioral and intent signals.

Engineering objective: produce a standalone scoring engine with an architecture identical to Chapter 2.5 so that the Part II HubSpot integration requires only four integration-point changes, not a workflow redesign.

Expected outcome: composite scores written to audit log and Slack for every contact processed; requires_manual_review escalation for low-confidence assessments; Part II CRM property mapping pre-defined in the output contract.

Why this architecture? The direct alternative is engagement-only scoring (rule layer only), which scores behavioral signals but cannot distinguish between an engaged-but-browsing contact and an engaged-and-evaluating contact. The AI intent classification fills this gap. The hybrid architecture (engagement rule score + bounded AI intent contribution) produces a more predictive composite than either signal alone, while the bounded cap (Math.min(4, ...)) prevents the AI from overriding a strong engagement score with an uncertain intent classification.

Why not redesign for Part II? Project B is the exact scoring engine Chapter 2.5 embeds in the HubSpot CRM. Redesigning it now would mean redesigning it again in Part II. The Part I standalone version and the Part II CRM-integrated version share 100% of the scoring logic and 100% of the audit log structure. Part II adds only the HubSpot API input and property write output.

Solution Architecture

Domain adaptation decisions:

Parameter Value
Input fields contact_id, contact_name, company_name, lead_source, intent_text, email_open_rate, page_views_last_30d, form_submissions, days_since_last_activity
Suitability threshold intent_text ≥ 10 words; contact_id present; days_since_last_activity ≤ 90
AI output schema lead_intent (enum), intent_strength (enum), buying_stage (enum), confidence, signals, reasoning
lead_intent values highly_interested \| interested \| exploratory \| generic
intent_strength values explicit \| implied \| weak \| absent
buying_stage values decision \| evaluation \| awareness \| no_signal
Rule score basis Engagement: open_rate band (0–2) + page_views band (0–2) + form_submissions band (0–2)
Rule score range 0–6
AI score mapping lead_intent → (highly_interested: 4, interested: 3, exploratory: 2, generic: 1)
Contribution cap Math.min(4, lead_intent_score × multiplier)
Confidence bands High ≥ 0.80 → 1.0; Medium 0.55–0.79 → 0.5; Low < 0.55 → 0.0 + review
Advisory thresholds ≥ 8: hot_lead; 5–7: warm_lead; 3–4: nurture; < 3: cold_lead
Escalation requires_manual_review = true → Slack #revenue-ops-review
Audit log destination HTTP POST to logging endpoint (Part II will write to HubSpot contact properties)

Implementation Sequence:

Step 1 Engagement Rule Score

Purpose

Compute the deterministic behavioral rule score from structured engagement metrics before the AI branch executes. This node establishes the rule-first foundation of the composite score: behavioral signals (email opens, page views, form submissions) are quantified into a 0–6 score that is available on both the AI path and the rule fallback path regardless of whether the AI call succeeds.


Operation Summary

Property Value
Node Type Code (JavaScript)
Node Name [S1] Code: Engagement Rule Score
Primary Function Band-based behavioral scoring from engagement metrics
Input email_open_rate, page_views_last_30d, form_submissions
Output rule_score (0–6)

Implementation Logic

// Engagement Rule Score banded behavioral scoring
const email_open_rate      = $json.email_open_rate      || 0;
const page_views_last_30d  = $json.page_views_last_30d  || 0;
const form_submissions     = $json.form_submissions     || 0;

const open_rate_score = email_open_rate >= 0.40 ? 2 :
                        email_open_rate >= 0.20 ? 1 : 0;

const page_view_score = page_views_last_30d >= 10 ? 2 :
                        page_views_last_30d >= 4  ? 1 : 0;

const form_score      = form_submissions >= 2 ? 2 :
                        form_submissions >= 1 ? 1 : 0;

const rule_score = open_rate_score + page_view_score + form_score;

return { ...($json), rule_score, open_rate_score, page_view_score, form_score };

Output Table

Output Range Description
rule_score 0–6 Sum of three behavioral band scores; primary rule signal
open_rate_score 0–2 Email engagement band component
page_view_score 0–2 Website engagement band component
form_score 0–2 Form submission engagement band component

Engineering Rationale

NoteEngineering Rationale

Rule score must be computed unconditionally before the Switch node so it is available on both the AI path and the rule fallback path. If rule_score is computed inside the AI score node, it is not available when the AI call fails and the fallback path executes.


Step 2 Contact Suitability

Purpose

Determine the processing_path based on input data completeness and contact recency. A contact with no intent_text cannot be meaningfully classified. A contact whose last activity was 120 days ago has stale behavioral data that no longer reflects current intent scoring them wastes an API call and produces a misleading score.


Operation Summary

Property Value
Node Type Code (JavaScript)
Node Name [S1] Code: Contact Suitability
Primary Function Validate input completeness and contact recency; set processing_path
Input contact_id, intent_text, days_since_last_activity
Output processing_path: "ai" | "rules" | "review"

Implementation Logic

const contact_id               = $json.contact_id               || null;
const intent_text              = $json.intent_text              || "";
const days_since_last_activity = $json.days_since_last_activity || 999;

const intent_word_count = intent_text.trim().split(/\s+/).filter(w => w.length > 0).length;

let processing_path;
if (!contact_id) {
  processing_path = "review";           // no ID cannot track or deduplicate
} else if (days_since_last_activity > 90) {
  processing_path = "rules";            // stale contact score on behavior only
} else if (intent_word_count < 10) {
  processing_path = "rules";            // insufficient intent text for AI classification
} else {
  processing_path = "ai";
}

return { ...($json), processing_path, intent_word_count };

Output Table

Output Description
processing_path Routing decision: "ai", "rules", or "review"
intent_word_count Word count used for the suitability threshold check

Engineering Rationale

NoteEngineering Rationale

The 90-day recency gate prevents scoring stale contacts whose engagement data no longer reflects current intent. A contact who was active 120 days ago may have changed role, company, or interest their behavioral score is no longer meaningful. The rules path for stale contacts produces a conservative score without wasting an AI API call.


Step 3 Build Prompt

Purpose

Construct the lead intent system prompt with the four evaluation dimensions that Chapter 2.5 preserves verbatim. This prompt is adapted to assess buying intent from free-text fields replacing the recruiting advisory’s candidate_intent dimensions with the revenue operations dimensions of specificity, urgency, competitive awareness, and decision authority.


Operation Summary

Property Value
Node Type Code (JavaScript)
Node Name [S2] Code: Build Prompt
Primary Function Assemble structured prompt with lead intent output schema
Input intent_text, contact_name, company_name, lead_source
Output system_prompt, user_message, PROMPT_VERSION

Implementation Logic

const PROMPT_VERSION = "lead-intent-v1.0.0";

const system_prompt = `You are an expert revenue operations analyst. Evaluate the lead's stated intent and return a structured JSON assessment.

OUTPUT SCHEMA return exactly these fields:
{
  "lead_intent":     string,  // highly_interested | interested | exploratory | generic
  "intent_strength": string,  // explicit | implied | weak | absent
  "buying_stage":    string,  // decision | evaluation | awareness | no_signal
  "confidence":      number,  // 0.00–1.00
  "signals":         array,   // 1–3 specific phrases from the text
  "reasoning":       string   // max 20 words: primary reason for lead_intent assessment
}

EVALUATION DIMENSIONS:
1. Specificity does the contact reference specific products, use cases, timelines, or pricing?
2. Urgency is there a stated timeline or active project?
3. Competitive awareness is the contact comparing vendors or evaluating alternatives?
4. Decision authority does the language suggest the contact can influence or make a purchase decision?`;

const user_message = `Contact: ${$json.contact_name || "Unknown"}
Company: ${$json.company_name || "Unknown"}
Lead Source: ${$json.lead_source || "not specified"}
Intent Statement: ${$json.intent_text}`;

return {
  ...($json),
  system_prompt,
  user_message,
  PROMPT_VERSION
};

Engineering Rationale

NoteEngineering Rationale

This system prompt is preserved identically in Chapter 2.5. Do not alter the four evaluation dimensions they are the Part II production dimensions. The PROMPT_VERSION constant is written to the audit log so that any future prompt change is traceable to its effect on scoring outcomes.


Step 4 HTTP Request: OpenAI

Purpose

Call the OpenAI Chat Completions API with the assembled lead intent prompt. This node is invariant across all domain adaptations the endpoint, model, retry configuration, timeout, and On Error behavior are copied unchanged from Chapter 1.5.


Operation Summary

Property Value
Node Type HTTP Request
Method POST
Endpoint https://api.openai.com/v1/chat/completions
Auth Bearer token from n8n Credential Store
Primary Function AI lead intent classification
Output Raw API response with choices[0].message.content

Request Payload

{
  "model": "gpt-4o-mini",
  "messages": [
    { "role": "system", "content": "{{system_prompt}}" },
    { "role": "user",   "content": "{{user_message}}" }
  ],
  "response_format": { "type": "json_object" },
  "temperature": 0.1,
  "max_tokens": 300
}

Response Processing

// Node configuration Options panel:
// Retry on Fail: On
// Max Tries: 3
// Wait Between Tries: 2000ms
// On Error: Continue (connect to Parse Response)

Engineering Rationale

CautionProduction Risk

Do not change the response_format, retry count, or On Error behavior for domain adaptations. These settings are the invariant reliability controls established in Chapter 1.3. Modifying them for a specific domain produces inconsistent error handling behavior across portfolio projects.


Step 5 Parse Response

Purpose

Validate the API response across four categories and extract the lead intent fields. The four-category validation structure is copied verbatim from Chapter 1.5; only the permitted enum values are updated for the lead intent domain.


Operation Summary

Property Value
Node Type Code (JavaScript)
Node Name [S2] Code: Parse Response
Primary Function Four-category validation; extract lead_intent, intent_strength, buying_stage, confidence
Input Raw API response from HTTP Request node
Output ai_result_valid flag; parsed lead intent fields

Implementation Logic

const VALID_LEAD_INTENT    = ["highly_interested", "interested", "exploratory", "generic"];
const VALID_INTENT_STRENGTH = ["explicit", "implied", "weak", "absent"];
const VALID_BUYING_STAGE   = ["decision", "evaluation", "awareness", "no_signal"];

let parse_error = null;
let ai_result_valid = false;
let parsed = {};

try {
  const raw_content = items[0].json.choices[0].message.content;
  parsed = JSON.parse(raw_content);

  // Category 1: required fields present
  if (!parsed.lead_intent || !parsed.intent_strength || !parsed.buying_stage
      || parsed.confidence == null) {
    throw new Error("Missing required fields in AI response");
  }
  // Category 2: enum values valid
  if (!VALID_LEAD_INTENT.includes(parsed.lead_intent)) {
    throw new Error(`Invalid lead_intent: ${parsed.lead_intent}`);
  }
  if (!VALID_INTENT_STRENGTH.includes(parsed.intent_strength)) {
    throw new Error(`Invalid intent_strength: ${parsed.intent_strength}`);
  }
  if (!VALID_BUYING_STAGE.includes(parsed.buying_stage)) {
    throw new Error(`Invalid buying_stage: ${parsed.buying_stage}`);
  }
  // Category 3: confidence range valid
  if (parsed.confidence < 0 || parsed.confidence > 1) {
    throw new Error(`confidence out of range: ${parsed.confidence}`);
  }
  // Category 4: structural types correct
  if (typeof parsed.lead_intent !== "string") {
    throw new Error("lead_intent must be a string");
  }

  ai_result_valid = true;
} catch (e) {
  parse_error = e.message;
  ai_result_valid = false;
}

Output Table

Output Description
ai_result_valid Boolean true if all four validation categories passed
lead_intent Enum: highly_interested \| interested \| exploratory \| generic
intent_strength Enum: explicit \| implied \| weak \| absent
buying_stage Enum: decision \| evaluation \| awareness \| no_signal
confidence Float 0.00–1.00; AI’s stated certainty in the assessment
parse_error Error message if validation failed; null on success

Engineering Rationale

NoteEngineering Rationale

Update only the field names and permitted enum values in the validation block. The four-category validation structure, the fallbackOutput pattern, and the ai_result_valid flag logic are copied verbatim from Chapter 1.5. The invariant structure is what makes this node predictable and testable across domains.


Step 6 AI Lead Intent Score

Purpose

Apply the confidence-band multiplier to the AI lead intent score and compute the composite score from the behavioral rule score and bounded AI contribution. This is the composite advisory formula applied to the lead scoring domain identical in structure to Chapter 1.5, with the LEAD_INTENT_SCORE_MAP replacing the INTENT_SCORE_MAP.


Operation Summary

Property Value
Node Type Code (JavaScript)
Node Name [S3] Code: AI Lead Intent Score
Primary Function Confidence-band multiplier; composite score rule_score + Math.min(4, ai_intent_score × multiplier)
Input lead_intent, confidence, rule_score
Output composite_score, confidence_band, confidence_multiplier, requires_manual_review

Implementation Logic

const CONFIDENCE_THRESHOLDS = { HIGH: 0.80, MEDIUM: 0.55 };
const LEAD_INTENT_SCORE_MAP = {
  "highly_interested": 4,
  "interested":        3,
  "exploratory":       2,
  "generic":           1
};
const AI_CONTRIBUTION_CAP = 4;

const confidence   = typeof $json.confidence === "number" ? $json.confidence : 0;
const lead_intent  = $json.lead_intent || "generic";
const rule_score   = $json.rule_score  || 0;

// Assign a confidence band based on how certain the AI was
let confidence_band;
if (confidence >= CONFIDENCE_THRESHOLDS.HIGH) {
  confidence_band = "high";
} else if (confidence >= CONFIDENCE_THRESHOLDS.MEDIUM) {
  confidence_band = "medium";
} else {
  confidence_band = "low";
}

// High confidence = full AI contribution; low confidence = none applied
let confidence_multiplier;
if (confidence_band === "high") {
  confidence_multiplier = 1.0;
} else if (confidence_band === "medium") {
  confidence_multiplier = 0.5;
} else {
  confidence_multiplier = 0.0;
}

const ai_intent_score = LEAD_INTENT_SCORE_MAP[lead_intent] || 0;

// Cap AI contribution so it cannot overwhelm the rule score
let ai_contribution = ai_intent_score * confidence_multiplier;
if (ai_contribution > AI_CONTRIBUTION_CAP) {
  ai_contribution = AI_CONTRIBUTION_CAP;
}

const composite_score        = rule_score + ai_contribution;
const requires_manual_review = (confidence_band === "low");

Output Table

Output Description
composite_score rule_score + bounded AI contribution; drives tier assignment
confidence_band high \| medium \| low; determines multiplier applied
confidence_multiplier 1.0, 0.5, or 0.0
ai_contribution Bounded AI component (0–4)
requires_manual_review True when confidence is below medium threshold (< 0.55)

Engineering Rationale

NoteEngineering Rationale

The formula is identical to Chapter 1.5: rule_score + Math.min(cap, ai_score × multiplier). The LEAD_INTENT_SCORE_MAP replaces the INTENT_SCORE_MAP same structure, domain-adapted field names. The confidence thresholds (0.80 / 0.55) and multiplier values (1.0 / 0.5 / 0.0) are invariant across all portfolio projects and Part II.


Step 7 Advisory Output

Purpose

Apply tier thresholds to the composite score and produce the normalized output contract with the Part II CRM property mapping stub. This node is where the composite score becomes a business action: hot_lead, warm_lead, nurture, or cold_lead. The crm_properties stub defines the exact HubSpot property schema that Part II will write, validating the field mapping now.


Operation Summary

Property Value
Node Type Code (JavaScript)
Node Name [S3] Code: Advisory Output
Primary Function Threshold evaluation; normalized output contract; crm_properties stub
Input composite_score, confidence_band, requires_manual_review
Output recommended_action, evaluation_source, requires_manual_review, crm_properties

Implementation Logic

const composite_score        = $json.composite_score        || 0;
const requires_manual_review = $json.requires_manual_review || false;

let evaluation_source;
if ($json.ai_result_valid) {
  evaluation_source = "ai_enhanced";
} else {
  evaluation_source = "rule_only";
}

let recommended_action;
if (requires_manual_review) {
  recommended_action = "manual_review";
} else if (composite_score >= 8) {
  recommended_action = "hot_lead";
} else if (composite_score >= 5) {
  recommended_action = "warm_lead";
} else if (composite_score >= 3) {
  recommended_action = "nurture";
} else {
  recommended_action = "cold_lead";
}

// Part II CRM property mapping stub
// CRM expects "true"/"false" strings rather than boolean values
let manual_review_string;
if (requires_manual_review) {
  manual_review_string = "true";
} else {
  manual_review_string = "false";
}

const crm_properties = {
  hs_lead_score:          composite_score,
  ai_confidence:          $json.confidence      || null,
  ai_confidence_band:     $json.confidence_band || null,
  ai_evaluation_source:   evaluation_source,
  ai_recommended_action:  recommended_action,
  last_scored_at:         new Date().toISOString(),
  ai_prompt_version:      $json.PROMPT_VERSION  || "unknown",
  requires_manual_review: manual_review_string
};

return {
  ...($json),
  recommended_action,
  evaluation_source,
  composite_score,
  crm_properties,
  crm_write_deferred: true,
  output_timestamp: new Date().toISOString()
};

Output Table

Output Description
recommended_action hot_lead \| warm_lead \| nurture \| cold_lead; primary routing
evaluation_source ai_enhanced \| rule_only; audit trail field
requires_manual_review True when confidence is low; routes to Slack #revenue-ops-review
crm_properties Part II HubSpot write payload; fields match property names
crm_write_deferred true in Part I; removed when Part II write is active

Engineering Rationale

NoteEngineering Rationale

Including the crm_properties stub validates the field mapping between the advisory output contract and the Part II CRM schema before Part II implementation begins. When Chapter 2.5 implements the HubSpot property write, the field names are already correct the implementation work is a single PATCH call to the HubSpot Contacts API using this pre-defined payload.


Step 8 Audit Log

Purpose

Write the complete decision record to the logging endpoint, preserving the Chapter 1.5 field structure so that Part II can redirect this write to HubSpot contact properties without schema changes.


Operation Summary

Property Value
Node Type HTTP Request (POST) or Code node (inline)
Node Name [S3] Code: Audit Log
Method POST (to logging endpoint or Google Sheets)
Primary Function Persist complete decision record for audit, reporting, and Part II CRM write
Input Full output contract
Output Confirmed write to audit destination

Request Payload

const log_record = {
  log_id:              "audit_" + Date.now() + "_" + Math.random().toString(36).substring(2, 7),
  timestamp:           new Date().toISOString(),
  processing_path:     $json.processing_path,

  // Input summary
  contact_id:          $json.contact_id,
  contact_name:        $json.contact_name,
  company_name:        $json.company_name,
  intent_word_count:   $json.intent_word_count,

  // AI assessment
  ai_result_valid:     $json.ai_result_valid,
  lead_intent:         $json.lead_intent,
  intent_strength:     $json.intent_strength,
  buying_stage:        $json.buying_stage,
  confidence:          $json.confidence,
  confidence_band:     $json.confidence_band,
  confidence_multiplier: $json.confidence_multiplier,

  // Scoring
  rule_score:          $json.rule_score,
  ai_intent_score:     $json.ai_intent_score,
  ai_contribution:     $json.ai_contribution,
  composite_score:     $json.composite_score,
  evaluation_source:   $json.evaluation_source,

  // Decision
  recommended_action:      $json.recommended_action,
  requires_manual_review:  $json.requires_manual_review,

  // API metadata
  request_id:          $json.api_metadata?.request_id  || null,
  model_used:          $json.api_metadata?.model_used  || null,
  prompt_version:      $json.PROMPT_VERSION,
  total_tokens:        $json.api_metadata?.total_tokens || 0,
  latency_ms:          $json.api_metadata?.latency_ms   || 0,
  parse_error:         $json.parse_error || null
};

Engineering Rationale

NoteEngineering Rationale

Part II redirects this write to HubSpot contact properties. The field set must be preserved identically so the Part II mapping requires no schema changes. The log_id and timestamp structural fields are invariant across all portfolio projects and Part II. Only the domain-specific field names (lead_intent, composite_score, etc.) differ from the Chapter 1.5 baseline.

Scoring formula (identical to Chapter 1.5):

const CONFIDENCE_THRESHOLDS = { HIGH: 0.80, MEDIUM: 0.55 };

const LEAD_INTENT_SCORE_MAP = {
  "highly_interested": 4,
  "interested":        3,
  "exploratory":       2,
  "generic":           1
};

// Confidence band → multiplier (identical to Chapter 1.5)
let confidence_band, confidence_multiplier, requires_manual_review;
if (confidence >= CONFIDENCE_THRESHOLDS.HIGH) {
  confidence_band = "high"; confidence_multiplier = 1.0; requires_manual_review = false;
} else if (confidence >= CONFIDENCE_THRESHOLDS.MEDIUM) {
  confidence_band = "medium"; confidence_multiplier = 0.5; requires_manual_review = false;
} else {
  confidence_band = "low"; confidence_multiplier = 0.0; requires_manual_review = true;
}

const ai_intent_score = LEAD_INTENT_SCORE_MAP[lead_intent] || 0;
const ai_contribution = Math.min(4, ai_intent_score * confidence_multiplier);
const composite_score = rule_score + ai_contribution;

Rule score computation:

// Engagement Rule Score (0–6)
const open_rate_score = email_open_rate >= 0.40 ? 2 :
                        email_open_rate >= 0.20 ? 1 : 0;

const page_view_score = page_views_last_30d >= 10 ? 2 :
                        page_views_last_30d >= 4  ? 1 : 0;

const form_score      = form_submissions >= 2 ? 2 :
                        form_submissions >= 1 ? 1 : 0;

const rule_score = open_rate_score + page_view_score + form_score;

System prompt (adapted for lead intent):

You are an expert revenue operations analyst. Your task is to evaluate a lead's stated intent
from their most recent interaction and return a structured JSON assessment.

OUTPUT SCHEMA return exactly these fields:
{
  "lead_intent":     string,  // highly_interested | interested | exploratory | generic
  "intent_strength": string,  // explicit | implied | weak | absent
  "buying_stage":    string,  // decision | evaluation | awareness | no_signal
  "confidence":      number,  // 0.00–1.00
  "signals":         array,   // 1–3 specific phrases from the text
  "reasoning":       string   // max 20 words: primary reason for lead_intent assessment
}

EVALUATION DIMENSIONS:
1. Specificity does the contact reference specific products, use cases, timelines, or pricing?
2. Urgency is there a stated timeline or active project?
3. Competitive awareness is the contact comparing vendors or evaluating alternatives?
4. Decision authority signals does the language suggest the contact can influence or make a purchase decision?

Audit log record (identical structure to Chapter 1.5):

const log_record = {
  log_id:              "audit_" + Date.now() + "_" + Math.random().toString(36).substring(2, 7),
  timestamp:           new Date().toISOString(),
  processing_path:     processing_path,

  // Input summary (domain-adapted field names)
  contact_id:          contact_id,
  contact_name:        contact_name,
  company_name:        company_name,
  input_char_count:    input_char_count,

  // AI assessment
  ai_result_valid:     ai_result_valid,
  lead_intent:         lead_intent,
  intent_strength:     intent_strength,
  buying_stage:        buying_stage,
  confidence:          confidence,
  confidence_band:     confidence_band,
  confidence_multiplier: confidence_multiplier,

  // Scoring
  rule_score:          rule_score,
  ai_intent_score:     ai_intent_score,
  ai_contribution:     ai_contribution,
  composite_score:     composite_score,    // renamed from advisory_score
  evaluation_source:   evaluation_source,

  // Decision
  recommended_action:      recommended_action,
  requires_manual_review:  requires_manual_review,

  // API metadata (identical to Chapter 1.5)
  request_id:          api_metadata?.request_id  || null,
  model_used:          api_metadata?.model_used  || null,
  prompt_version:      prompt_version,
  total_tokens:        api_metadata?.total_tokens || 0,
  latency_ms:          api_metadata?.latency_ms   || 0,

  parse_error:         parse_error || null
};

Relationship to the Advisory Workflow and Part II

Project B is the direct continuation of the advisory architecture, not an adaptation of it. lead_intent is the same enum as candidate_intent identical values: highly_interested | interested | exploratory | generic. The confidence band thresholds are identical (0.80 / 0.55). The multiplier values are identical (1.0 / 0.5 / 0.0). The scoring formula is identical (rule_score + Math.min(4, ai_score × multiplier)). The five-element reliability model is present in full. The audit log structure is identical field names updated for domain, values and structure preserved.

What changes: field names in the input schema (intent_text vs. cover_letter), the system prompt evaluation dimensions, the rule score computation (engagement metrics vs. cover letter heuristics), and the advisory threshold labels (hot_lead vs. advance_to_interview). The workflow’s node topology is byte-for-byte equivalent.

Chapter 2.5 extends Project B by embedding it in the HubSpot-connected CRM architecture:

Component Project B (Part I) Chapter 2.5
Scoring formula rule_score + Math.min(4, ai_score × multiplier) Identical
Confidence bands 0.80 / 0.55 thresholds Identical
Multipliers 1.0 / 0.5 / 0.0 Identical
lead_intent values highly_interested \| interested \| exploratory \| generic Identical
Audit log fields Identical structure Written to HubSpot contact properties
Input source Webhook payload HubSpot contact via API
Output destination Slack + logging endpoint HubSpot contact score property + lifecycle stage
CRM integration Not present HubSpot read/write
Multi-workflow orchestration Not present Part of CRM pipeline

A complete Project B implementation requires: Webhook for contact record ingestion; Engagement Rule Score Code node (open rate, page views, form submissions); Contact Suitability Code node; Build Prompt node with lead intent system prompt; HTTP Request node from Chapter 1.5 (unchanged); Parse Response node with updated enum validation; AI Lead Intent Score node with confidence-band multiplier; Rule Fallback Score node; Advisory Output node with hot_lead | warm_lead | nurture | cold_lead thresholds; Audit Log node; IF: requires_manual_review → Slack escalation. Estimated adaptation time from Chapter 1.5: 4–5 hours.

CautionProduction Risk

Do not replace lead_intent with a proprietary intent taxonomy. Using custom intent labels that differ from the Part II vocabulary breaks the direct continuity with Chapter 2.5. Use the same four values: highly_interested | interested | exploratory | generic. The Part II integration is built around this vocabulary.

CautionProduction Risk

Do not compute the rule score inside the AI Advisory Score node. The rule score must be computed before the AI branch decision in the Suitability Evaluation or a dedicated Rule Score node so it is available on both the AI path and the rule fallback path. See Section 1.5.3 (Rule-First, AI-Second Architecture).

Production Consideration

Project B produces its audit log as a JSON object posted to a logging endpoint. The field names in the audit log are designed to match Part II’s HubSpot contact property schema but the write itself is deferred. Before deploying Project B in a production context, verify that the logging endpoint is durable (not a temporary test URL) and that the audit log’s retention policy covers the compliance window applicable to your domain. Chapter 2.5 replaces the logging endpoint write with a HubSpot contact property write that persists in the CRM alongside the contact record.


Portfolio Project C AI Email Assistant

Portfolio Project C Two-Stage AI Pipeline (Classification + Generation)

Business Scenario

A customer success team at a SaaS company receives 80–120 inbound emails per day from customers, prospects, and partners spanning a wide range of categories: billing questions, technical support requests, feature requests, renewal inquiries, onboarding assistance, and general feedback. The team manually reads each email, determines the correct handling team, and drafts an initial reply before routing it.

The Problem

Email triage combines two tasks that rule-based systems handle poorly: classification of unstructured text into a category that may not be determinable from keywords alone, and generation of a contextually appropriate draft reply. A customer asking “when does my subscription renew?” is a billing question even if the word “billing” does not appear semantic understanding is required for correct classification. Draft generation requires composing coherent, professional language that references the specific content of the incoming email. At 80–120 emails per day, manual triage and drafting consumes significant team time that does not scale.

The Architectural Solution

A two-stage AI pipeline: a classification call that assigns the email to a defined category, and a generation call that produces a contextually appropriate draft reply. Each stage has its own validity gate, confidence score, and fallback path. The generation call is gated on Stage 1 validity an incorrectly classified email will not have a draft generated and sent to the wrong team. Human review is required before any reply is sent; the AI output is a starting point, not a final reply.

Architecture and Design Rationale

Business objective: reduce email triage and draft time from 3–5 minutes per email to under 30 seconds, routing to the correct team queue with a review-ready draft reply.

Engineering objective: extend the single-stage advisory architecture to a two-stage AI pipeline (classification then generation), with each stage having its own validity gate, confidence score, and fallback path.

Expected outcome: each inbound email classified into a category, routed to the correct team queue, and accompanied by a draft reply ready for human review; high urgency + frustrated sentiment emails escalated immediately to Slack.

Why a two-stage pipeline? A single AI call combining classification and generation in one prompt produces lower-quality output on both tasks the model divides attention between classification accuracy and draft quality. Separating the calls allows the classification call to be optimized for precision (short output, constrained schema, low temperature) and the generation call to be optimized for quality (longer output, domain context injected, moderate temperature). The separation also allows the generation call to be gated on classification validity an incorrectly classified email will not have a generated draft reply sent to the wrong team.

Key trade-off: two AI calls doubles per-email API cost compared to a single-call architecture. At 80–120 emails per day on gpt-4o-mini, the additional cost is negligible. If volume were 10,000 emails per day, a single-call architecture would be more appropriate. At the volumes stated in the business problem, quality separation outweighs cost.

Solution Architecture

Domain adaptation decisions:

Parameter Value
Input fields from_email, subject, body_text, received_at
Suitability threshold body_text ≥ 15 words; from_email present
Classification AI output email_category (enum), sentiment (enum), urgency (enum), confidence, signals, routing_note
email_category values billing \| technical_support \| feature_request \| renewal \| onboarding \| general
sentiment values positive \| neutral \| frustrated \| urgent
urgency values high \| medium \| low
Generation AI output draft_subject (string), draft_body (string), draft_tone (enum), generation_confidence (number)
draft_tone values professional \| empathetic \| escalation
Rule routing email_category → team assignment (rule-based mapping, no AI required)
Escalation urgency = "high" + sentiment = "frustrated" → immediate Slack alert
Audit log destination CRM ticket system or Google Sheets

Decision logic table Stage 2 gate condition:

Stage 1 Result confidence_band generate_reply Gate Action
Valid high or medium true Proceed to generation
Valid low false Route category; use template draft; flag for review
Invalid any false Route via keyword fallback; use template draft; flag for review

Two-stage AI pipeline structure:

Stage 1 (Classification):

[Webhook] → [Email Suitability] → [Build Classification Prompt]
→ [HTTP: OpenAI Classification] → [Parse Classification Response]
→ [IF: classification_valid] → [AI Routing / Rule Fallback Routing]
→ [Merge] → [Classification Output]

Stage 2 (Generation conditional on successful classification):

[Classification Output]
→ [IF: generate_reply] (true when classification is valid + category ≠ "general")
→ [Build Generation Prompt] → [HTTP: OpenAI Generation] → [Parse Generation Response]
→ [IF: generation_valid] → [Draft Output / Default Template]
→ [Merge] → [Final Output] → [Audit Log] → [Slack: Team Notification]

The key architectural decision is that the generation call is conditional on Stage 1. If Stage 1 fails or produces a low-confidence classification, Stage 2 does not execute the workflow routes to a rule-based category assignment using keywords, and the generation stage uses a template reply rather than an AI draft. This preserves the graceful degradation property across both AI calls.

Each AI call has its own ai_result_valid flag and confidence score. The advisory output uses the lower of the two confidence values for the confidence-band evaluation. If either stage falls to the low-confidence band, requires_manual_review = true applies the draft is flagged for full human review rather than used as a starting point.

const effective_confidence = Math.min(
  classification_confidence ?? 0,
  generation_confidence     ?? 0
);
// confidence_band evaluated on effective_confidence

Project C extends the advisory architecture with a second AI service layer operating in sequence. Stage 1 (classification) maps to the main advisory workflow’s AI service layer with a classification output schema. Stage 2 (generation) is an additional AI service layer whose input is the Stage 1 output. The IF/Merge pattern applies independently to each stage. The audit log covers both stages in a single record.

Project C’s email classification component maps to Part II’s inbound lead processing workflows, where emails and form submissions are classified by intent and routed to the appropriate CRM stage.

CautionProduction Risk

If Stage 1 fails or produces a low-confidence classification and Stage 2 proceeds anyway, the generation call will produce a draft reply based on an incorrect category. Gate Stage 2 on Stage 1 validity: the IF: generate_reply node should check classification_valid = true AND confidence_band ≠ "low" before proceeding.

CautionProduction Risk

Evaluate generation_confidence in the confidence-band calculation, not just classification_confidence. A correctly classified email whose draft was generated with low confidence still requires full human review. Use Math.min(classification_confidence, generation_confidence) as the effective confidence.

Production Consideration

Project C generates draft replies and routes them to team queues via Slack notification. The draft reply is not tracked against the original email in a CRM or ticketing system there is no persistent record of which draft was sent, whether it was used, or how it was modified before the human sent the final reply. In production customer success environments, this traceability is required for quality assurance and training. Chapter 5.7 integrates Project C’s classification output with a CRM-connected email workflow that creates a ticket record, attaches the draft, and records the reviewer’s modification history.


Portfolio Project D AI Support Ticket Classifier

Portfolio Project D Critical-Bypass Pattern

Business Scenario

An IT support team manages incoming support tickets from internal employees across 12 departments. Tickets arrive via an API-connected ticketing system and range from P1 incidents (production-down events requiring immediate response) to P4 requests (low-priority cosmetic improvements). The team manually triages each ticket, assigns a priority, and routes it to the correct team queue.

The Problem

Priority classification from ticket text requires semantic understanding of technical content. A rule-based system can match keywords (down, outage, breach) but misses contextual escalation signals: “database query times have increased to 45 seconds” is a P2 performance incident that may not contain any keyword triggers. The AI classification adds the semantic layer the rule layer cannot provide. However, one critical constraint overrides everything: P1 incident escalation must not depend on AI availability. An AI API call that takes 800ms or fails is unacceptable when a production system is down.

The Architectural Solution

A four-path advisory architecture extending the three-path model from Chapter 1.1 with a critical_escalation path that runs before any AI call. P1 keyword detection in the Suitability Evaluation node immediately escalates down | outage | breach | data loss tickets to the on-call channel with no AI dependency. The AI classification applies only to P2–P4 tickets where latency is acceptable and semantic nuance adds value.

NoteEngineering Rationale

An AI API call that takes 800ms is 800ms a production system is down without the on-call engineer being paged. The critical-bypass pattern is not an optimization it is the reliability boundary that makes an AI-assisted triage workflow safe to deploy for P1 incidents. Without it, AI availability becomes a dependency on incident response.

Architecture and Design Rationale

Business objective: reduce ticket triage time and eliminate delayed P1 escalations by routing critical incidents pre-AI and applying AI classification only to the P2–P4 triage decision.

Engineering objective: extend the three-path advisory architecture (ai | rules | review) to four paths (ai | rules | review | critical_escalation), with the fourth path executing before any AI call and producing an immediate Slack page.

Expected outcome: P1 keyword-matched tickets escalated to #on-call in under 200ms; P2–P4 tickets classified by AI advisory score; borderline P2/P3 assessments with low confidence escalated to #ticket-review; all tickets logged to audit record.

Why the critical-bypass pattern? The alternative running AI classification on all tickets including P1s and checking the result before escalating introduces API latency (500ms–2,000ms) and API availability as dependencies on incident response time. If the OpenAI API is experiencing degraded performance at the same time as a production outage (a plausible correlated failure), the escalation would be delayed. The pre-AI keyword check eliminates this dependency. The AI classification is retained for P2–P4 nuance precisely because those tickets can tolerate the latency.

Trade-off: the keyword list for P1 detection must be maintained. Keywords that are too broad (e.g., “error”) will route too many P3 tickets to the #on-call channel, creating alert fatigue. Keywords that are too narrow will miss critical incidents. The recommended default list (down | outage | breach | data loss | unreachable | critical failure) is conservative. Expand it only after reviewing false-negative incidents in the audit log.

Solution Architecture

Domain adaptation decisions:

Parameter Value
Input fields ticket_id, submitter_name, department, summary, description, created_at
Pre-AI escalation Keywords in summary matching ["down", "outage", "breach", "data loss", "unreachable"] → immediate P1 Slack alert and route to on_call queue (bypasses AI)
Suitability threshold description ≥ 10 words; summary present
AI output schema priority_tier (enum), team_queue (enum), issue_category (enum), confidence, signals, reasoning
priority_tier values P1 \| P2 \| P3 \| P4
team_queue values infrastructure \| application \| security \| end_user
issue_category values incident \| performance \| access \| request \| question
Rule score Keyword-based urgency: critical keywords (3), performance keywords (2), access keywords (1), else (0)
AI score mapping priority_tier → (P1: 4, P2: 3, P3: 2, P4: 1)
Contribution cap Math.min(3, ai_priority_score × multiplier)
Advisory thresholds ≥ 6: route P1/P2; 3–5: route P3; < 3: route P4
Escalation requires_manual_review = true → Slack #ticket-review; P1/P2 result → Slack #on-call

Decision logic table processing path routing:

Condition processing_path AI Called? Routing
P1 keywords matched in summary critical_escalation No Immediate Slack #on-call
description < 10 words review No Slack #ticket-review
All checks pass ai Yes AI advisory scoring
AI unavailable rules No Keyword rule score only

Implementation Sequence:

Step 1 Suitability Evaluation (extended with critical-bypass)

Purpose: determine processing path, with pre-AI P1 keyword detection as the first check.

Field Detail
Input ticket_id, summary, description
Configuration P1_KEYWORDS list; word count gate; processing_path assignment
Output processing_path: "critical_escalation" | "ai" | "rules" | "review"
Engineering Notes The P1 keyword check must be the first condition evaluated before word count and before any other suitability logic. Order matters.

Step 2 Switch (four outputs)

Purpose: route to one of four processing paths.

Field Detail
Input processing_path
Configuration Four outputs: critical_escalation, ai, rules, review
Output Routed execution to the corresponding branch
Engineering Notes The critical_escalation output connects directly to the Slack P1 alert node no AI call, no scoring, no wait.

Step 3 AI Classification (P2–P4 path only)

Purpose: classify ticket by priority tier, team queue, and issue category.

Field Detail
Input summary, description, department
Configuration System prompt: IT support triage analyst. Output schema: priority_tier, team_queue, issue_category, confidence, signals, reasoning
Output Validated AI assessment
Engineering Notes The AI classification is meaningful for P2–P4 nuance. P1 escalation is handled by Step 1 and never reaches this node.

Steps 4–8: Parse Response, AI Priority Score, Rule Fallback Score, Advisory Output, Audit Log copied from Chapter 1.5 with field name updates. Identical logic.

Critical-path bypass new pattern in Project D:

The pre-AI P1 escalation is implemented in the Suitability Evaluation node:

// Pre-AI critical escalation (Element 1 of reliability model extended)
const P1_KEYWORDS = ["down", "outage", "breach", "data loss", "unreachable", "critical failure"];
const is_critical = P1_KEYWORDS.some(kw =>
  (summary + " " + description).toLowerCase().includes(kw)
);

if (is_critical) {
  processing_path = "critical_escalation";  // bypasses AI, routes to immediate alert
} else if (description_word_count < 10) {
  processing_path = "review";
} else {
  processing_path = "ai";
}

The Switch node adds a fourth output: "critical_escalation" → immediate Slack P1 alert (no AI call, no rule scoring). This is not a degradation path it is a designed fast path for the highest-priority events where latency matters more than scoring accuracy.

Project D extends the advisory architecture with this fourth processing path. The three paths from Chapter 1.1 (ai | rules | review) become four (ai | rules | review | critical_escalation). Adding a fourth path is a domain adaptation of Segment 1, not an architectural change. The Suitability Evaluation node’s job is to determine the appropriate processing path; adding a condition for a new business requirement is always valid.

The critical-path bypass pattern appears in Part II’s CRM workflows as a direct escalation for hot leads leads with explicit buying intent signals that should reach an account executive immediately, before full scoring.

CautionProduction Risk

The AI call adds 500ms–2,000ms of latency. For P1 incidents, this latency is unacceptable: a production-down event should not wait for an API round trip before the on-call engineer is paged. Pre-AI keyword detection for P1 is the correct architectural choice; the AI adds value for P2–P4 nuance, not for P1 detection.

CautionProduction Risk

Do not use the AI’s priority_tier classification as the direct routing signal. A ticket classified as P1 by AI with confidence 0.52 should be treated as a borderline P2 requiring human review, not automatically escalated. The advisory score formula rule score plus bounded AI contribution provides the more appropriate routing signal.

Production Consideration

Project D routes P1 escalations to the #on-call Slack channel. The on-call engineer receives the escalation but has no mechanism in the workflow to acknowledge receipt, claim ownership, or record resolution. In production incident management, this open loop creates accountability gaps: multiple engineers may respond simultaneously, or an escalation may be seen and not acted upon. Chapter 2.8 integrates the escalation path with a ticketing system (PagerDuty or Jira) that creates an incident record, assigns ownership, and tracks resolution status closing the loop the Slack-only notification leaves open.


Portfolio Project E AI Document Analyzer

Portfolio Project E Per-Field Confidence Scoring

Business Scenario

A financial services operations team processes 60–80 incoming documents per week: vendor invoices, client contracts, expense reports, and compliance disclosures. Each document arrives as a text extraction (from a PDF parser upstream of the workflow). A team member reads each document, extracts the key data fields, classifies the document type, and routes it to the appropriate downstream system accounts payable, contracts management, or compliance review.

The Problem

Document field extraction from unstructured text is the AI task most commercially valuable in business automation and most sensitive to quality failures. A rule-based extractor can find dates, amounts, and company names using regex patterns but fails on formatting variations: “Invoice No: INV-2024-0042” and “Reference: 2024/0042/INV” contain the same invoice number in different formats that regex does not generalize across. The quality risk is material: an extracted amount of $12,500 that should be $125,000 is a financial error. A single document-level confidence score masks field-level quality differences a document may have four high-confidence extractions and one critically low-confidence amount field.

The Architectural Solution

An extraction pipeline with two additions beyond the standard advisory architecture: per-field confidence scoring that supplements the overall confidence score, and a domain validation layer that applies semantic sanity checks after structural validation. Any field whose per_field_confidence falls below 0.75 triggers requires_human_verification = true, flagging the specific field for review rather than requiring re-review of the entire document.

NoteEngineering Rationale

A single incorrect amount extraction may not surface until an invoice is paid, a contract is executed, or a compliance audit runs. Per-field confidence scoring is the control that makes the extraction pipeline auditable before data reaches a downstream financial system every low-confidence field is flagged for human verification before the record is written.

Architecture and Design Rationale

Business objective: automate document data extraction and routing for 60–80 documents per week, reducing per-document processing time from 5–10 minutes to under 1 minute, while maintaining a human verification gate for any field where extraction confidence is below threshold.

Engineering objective: extend the advisory architecture with two new capabilities: (1) per-field confidence scoring that supplements the overall confidence score, and (2) a domain validation layer that applies semantic sanity checks after structural validation.

Expected outcome: each document classified, key fields extracted, routed to the correct downstream system, and flagged for human verification on any field where per_field_confidence < 0.75; all extractions written to the audit log with per-field confidence values.

Why per-field confidence? The overall confidence score masks field-level quality differences. A document where vendor_name is clearly printed but amount appears in an ambiguous format may have overall confidence: 0.82 while per_field_confidence.amount = 0.44. Routing this document to accounts payable without human review would pass a potentially incorrect amount to a financial system. Per-field confidence provides the granularity required for financial data quality control.

Why domain validation? The four-category structural validation from Chapter 1.3 catches format errors (missing required fields, wrong field types, invalid enum values). Domain validation catches semantic errors that structural validation passes: a structurally valid amount of $1,200,000 for a coffee expense report is almost certainly an extraction error. Both layers are required for financial extraction pipelines.

Solution Architecture

Domain adaptation decisions:

Parameter Value
Input fields document_id, document_text, filename, received_from, received_at
Suitability threshold document_text ≥ 50 words; document_id present
AI output schema document_type (enum), vendor_name (str), document_date (str), amount (number), currency (str), reference_number (str), confidence, per_field_confidence (object), signals, extraction_notes
document_type values invoice \| contract \| expense_report \| compliance_disclosure \| unknown
per_field_confidence {vendor_name: 0.xx, document_date: 0.xx, amount: 0.xx, reference_number: 0.xx}
Rule routing document_type → downstream system (rule-based: invoice → AP, contract → contracts mgmt)
Review trigger Any per_field_confidence value < 0.75 → requires_human_verification = true
Review trigger (2) document_type = "unknown"requires_human_verification = true
Audit log destination Document management system or Google Sheets

Data transformation table extraction output routing:

document_type Downstream System Human Verification Gate
invoice Accounts Payable webhook Any field < 0.75 OR amount < 0.80
contract Contracts Management system Any field < 0.75
expense_report Expense system Any field < 0.75
compliance_disclosure Compliance Review queue Always: requires_human_verification = true
unknown Manual Review queue Always: requires_human_verification = true

Implementation Sequence:

Step 1 Document Suitability

Purpose: validate that the document text is sufficient for meaningful extraction.

Field Detail
Input document_id, document_text
Configuration Word count ≥ 50; document_id present
Output processing_path: "ai" | "review"
Engineering Notes The 50-word minimum ensures the AI has sufficient text context for field extraction. Documents shorter than 50 words are almost certainly incomplete extractions from the PDF parser and should be routed to human review.

Step 2 Build Extraction Prompt

Purpose: construct the document extraction system prompt with per-field confidence instruction.

Field Detail
Input document_text, filename
Configuration System prompt: financial document analyst. Output schema includes per_field_confidence object with a confidence score for each extracted field.
Output Complete prompt object with PROMPT_VERSION
Engineering Notes The prompt must explicitly instruct the model to return null for fields it cannot extract and 0.10 for the corresponding per_field_confidence value. Without this instruction, the model may hallucinate field values rather than reporting low confidence.

Step 3 HTTP Request: OpenAI

Purpose: call OpenAI with the extraction prompt.

Field Detail
Input Prompt object
Configuration Copied unchanged from Chapter 1.5
Output Raw API response
Engineering Notes Invariant node.

Step 4 Parse Response (extended with per-field validation)

Purpose: validate the structural response and extract the per_field_confidence object; check for low-confidence fields.

Field Detail
Input Raw API response
Configuration Four-category structural validation + per-field confidence check: any field < 0.75 → requires_human_verification = true
Output ai_result_valid, extracted fields, per_field_confidence, low_confidence_fields array, requires_human_verification
Engineering Notes Do not rely on overall confidence to pass extraction to downstream systems. Always check per_field_confidence independently.

Step 5 Domain Validation

Purpose: apply semantic sanity checks to structurally valid extracted values.

Field Detail
Input Extracted fields from Parse Response
Configuration Amount range check (0 < amount < 10,000,000); date sanity check (not in future); reference number length check (≥ 4 characters)
Output Updated validation_errors array; updated requires_human_verification if domain errors found
Engineering Notes Domain validation runs after structural validation. Both layers are required they catch different error classes.

Step 6 Advisory Output and Document Routing

Purpose: apply document_type → downstream system mapping and produce normalized output with routing target.

Field Detail
Input document_type, requires_human_verification, extracted fields
Configuration Rule-based routing table: invoice → AP, contract → contracts mgmt, expense_report → expense system, unknown → manual review
Output routing_target, requires_human_verification, normalized output contract
Engineering Notes Routing is rule-based the AI document_type enum drives the routing table, not the raw AI text. Always validate the enum value before using it as a routing key.

Step 7 Audit Log

Purpose: write a complete extraction record with per-field confidence values for every document.

Field Detail
Input Full output contract including per_field_confidence and low_confidence_fields
Configuration Destination: document management system or Google Sheets
Output Audit record
Engineering Notes Include per_field_confidence as a nested object in the audit record this is the traceability record for any future dispute about extracted values.

Per-field confidence validation in Parse Response:

// Extended validation for extraction pipelines
const per_field_confidence = parsed.per_field_confidence || {};

// Find all fields whose confidence is below the threshold
const LOW_FIELD_CONFIDENCE_THRESHOLD = 0.75;
const low_confidence_fields = [];
const all_fields = Object.keys(per_field_confidence);
for (let i = 0; i < all_fields.length; i++) {
  const field = all_fields[i];
  const conf  = per_field_confidence[field];
  if (conf < LOW_FIELD_CONFIDENCE_THRESHOLD) {
    low_confidence_fields.push(field);
  }
}

// Flag for human review if any field is low-confidence or type is unknown
const requires_human_verification = (
  low_confidence_fields.length > 0 ||
  parsed.document_type === "unknown"
);

// Describe why human review is needed
let field_review_reason = null;
if (low_confidence_fields.length > 0) {
  field_review_reason = "Low confidence on: " + low_confidence_fields.join(", ");
} else if (parsed.document_type === "unknown") {
  field_review_reason = "Document type could not be determined";
}

Domain validation rules (semantic checks beyond structural validation):

// After AI field validation, apply domain sanity checks
const today = new Date();
const extracted_date = new Date(parsed.document_date);

if (parsed.amount <= 0 || parsed.amount > 10000000) {
  validation_errors.push("amount out of expected range: " + parsed.amount);
}
if (isNaN(extracted_date) || extracted_date > today) {
  validation_errors.push("document_date invalid or in future: " + parsed.document_date);
}
if (parsed.reference_number && parsed.reference_number.length < 4) {
  validation_errors.push("reference_number suspiciously short: " + parsed.reference_number);
}

System prompt for document extraction:

You are an expert financial document analyst. Extract structured data from the provided
document text and report your confidence in each extracted field.

OUTPUT SCHEMA:
{
  "document_type":       string,   // invoice | contract | expense_report | compliance_disclosure | unknown
  "vendor_name":         string,   // as it appears in the document
  "document_date":       string,   // ISO 8601 format: YYYY-MM-DD
  "amount":              number,   // primary financial amount as a decimal number
  "currency":            string,   // 3-letter ISO currency code (USD, EUR, GBP)
  "reference_number":    string,   // invoice number, contract ID, or reference code
  "confidence":          number,   // 0.00–1.00 overall extraction confidence
  "per_field_confidence": {        // per-field confidence scores
    "vendor_name":       number,
    "document_date":     number,
    "amount":            number,
    "reference_number":  number
  },
  "signals":             array,    // 1–3 phrases that informed key extractions
  "extraction_notes":    string    // max 20 words: notable challenges or ambiguities
}

IMPORTANT:
- Return amounts as numbers, not strings. Remove currency symbols.
- Return dates in YYYY-MM-DD format.
- If a field cannot be extracted with reasonable certainty, set its value to null and
  its per_field_confidence to 0.10.

Project E extends the advisory architecture in two ways specific to extraction pipelines. First, per-field confidence: the standard confidence field is supplemented by a per_field_confidence object that provides a confidence score for each extracted field. The requires_manual_review flag (renamed requires_human_verification in this domain) is triggered by any field’s confidence falling below the threshold, not just the overall confidence.

Second, domain validation: after the four-category structural validation from Chapter 1.3, an additional semantic validation layer applies domain rules (amount range check, date sanity check, reference number format check). This is the semantic validation layer described in Chapter 1.5 Section 1.5.4 applicable in extraction pipelines where structurally valid values can be semantically incorrect.

The IF/Merge pattern, confidence-band model, audit log, and five-element reliability model are all preserved. Project E’s per-field confidence and domain validation patterns appear in Part II’s inbound data processing workflows, where extracted fields from document attachments must pass per-field confidence checks before being written to CRM contact properties.

Estimated adaptation time from Chapter 1.5: 5–6 hours (the per-field confidence extension and domain validation layer add complexity beyond a standard domain transplant).

CautionProduction Risk

A document extraction with overall confidence: 0.82 may have per_field_confidence.amount: 0.44. The high overall confidence masks a critical low-confidence field. Always check per-field confidence for extraction pipelines do not rely on the overall confidence score alone.

CautionProduction Risk

LLMs sometimes return amounts as strings with currency symbols: "$12,500.00". Add a post-parse normalization step const amount = parseFloat(String(parsed.amount).replace(/[^0-9.]/g, "")) then re-validate that the result is a finite positive number before using it in downstream computations.

CautionProduction Risk

The four-category structural validation catches format errors. Domain validation catches semantic errors that format validation passes a structurally valid amount of $1,200,000 for a coffee expense is almost certainly an extraction error. Both validation layers are required for financial document extraction.

Production Consideration

Project E routes extracted documents to downstream systems and flags low-confidence fields for human verification via Slack notification. The human verifier reviews the flagged field, corrects it if necessary, and manually enters the corrected value into the downstream system. This correction is not captured in the workflow audit log there is no record that the extracted value was reviewed, what the original extraction was, or what the corrected value is. In financial audit contexts, this traceability is required. Chapter 2.6 introduces the data enrichment architecture that attaches extraction records to the originating document in a document management system, recording both the AI extraction and any human corrections.


Technologies Used

System Projects Purpose
OpenAI Chat Completions API A, B, C, D, E Classification, extraction, and generation
Slack Incoming Webhooks A, B, C, D, E Advisory notifications and escalation alerts
Google Sheets (optional) A, B, D, E Lightweight audit log destination
CRM webhook A, B Advisory output and audit log destination
Document management system E Extraction output destination

All five projects use the same node set established in Chapters 1.0–1.5:

Node Chapter Origin Role
Webhook 1.0 Input
Code: Suitability Evaluation 1.1 Pre-flight validation, processing_path
Switch 1.1 Path routing (3 or 4 paths)
Code: Build Prompt 1.2 Prompt assembly (domain-adapted)
HTTP Request: OpenAI 1.3 AI API call (unchanged)
Code: Parse Response 1.3 Output validation (domain-adapted)
IF: ai_result_valid 1.4 Routes on validity flag
Code: AI Advisory Score 1.4 (1.5) Confidence-band scoring (domain-adapted)
Code: Rule Fallback Score 1.4 Deterministic fallback (domain-adapted)
Merge 1.4 Branch reunification (unchanged)
Code: Advisory Output 1.4 (1.5) Normalized output contract (domain-adapted)
IF: requires_manual_review 1.5 Routes low-confidence items
HTTP: Slack - Escalation 1.5 Human review alert (domain-adapted message)
Code: Audit Log 1.5 Structured decision record (domain-adapted fields)
HTTP: Slack (Advisory) 1.0 (updated) Final notification

Project C adds a second three-node AI service segment (Build Generation Prompt → HTTP Request: OpenAI Generation → Parse Generation Response) operating in sequence after the classification segment.


Key Principle: The advisory architecture’s value is that the invariant core five-element reliability model, IF/Merge pattern, confidence bands, audit log never needs to be rebuilt. Only the adaptation layer changes per domain. An engineer who has built the Chapter 1.5 workflow can deploy a new domain in 3–6 hours precisely because the hard engineering problems are already solved.


Chapter Summary

This chapter demonstrated that the AI advisory architecture built in Chapters 1.0–1.5 is a general-purpose framework, not a recruiting-specific workflow. Five portfolio projects across sales, marketing, customer service, IT operations, and document processing all implemented the same invariant core: pre-flight validation, structured prompt construction, AI service layer with retry and error handling, confidence-band gating, IF/Merge composition pattern, requires_manual_review escalation, and structured audit logging.

The domain adaptation layer the relatively small set of configuration decisions that makes the architecture specific to a business context was different in each project. The input fields changed. The output schema changed. The system prompt role assignment and evaluation dimensions changed. The rule scoring logic changed. The advisory thresholds changed. The architectural core did not.

Project B is the chapter’s Part II bridge: it preserves the scoring formula, confidence-band thresholds, multiplier values, and audit log structure from Chapter 1.5 without modification, and adapts only the domain layer to lead qualification. When Chapter 2.5 arrives, its scoring engine will be familiar the same formula, the same model, the same output contract, now embedded in a HubSpot-connected CRM architecture.

The other three projects introduced architectural extensions beyond the Chapter 1.5 baseline: a two-stage AI pipeline in Project C, a fourth critical-bypass processing path in Project D, and per-field confidence scoring with domain validation in Project E. Each extension is a legitimate domain adaptation of the core architecture, not a redesign of it.

PHASE 4.5 PORTFOLIO                    PHASE 5 ARCHITECTURE

Project A                     ──────▶  Chapter 2.2
AI Lead Qualification                  Contact Scoring
(need_quality + ICP fit)               (multi-dimensional rule + AI score)

Project B                     ══════▶  Chapter 2.5  ← recommended Part II bridge
Hybrid AI Lead Intent Scoring          Lead Scoring System: Hybrid Rules + AI
(same formula, same model,             (same engine + HubSpot CRM integration)
 same audit log)

Project C                     ──────▶  Part II Sections 5.3 / 5.7
AI Email Assistant                     Inbound Lead Processing
(classification + generation)          (email triage + CRM enrichment)

Project D                     ──────▶  Chapter 2.8
AI Support Ticket Classifier           Workflow Automation Health Monitoring
(multi-tier routing +                  (escalation patterns + governance gates)
 critical bypass)

Project E                     ──────▶  Chapter 2.6
AI Document Analyzer                   Data Enrichment & Contact Intelligence
(extraction + per-field conf           (structured extraction from
 + domain validation)                   inbound data sources)

COMMON FOUNDATION (all projects → all Part II sections):
  Five-element reliability model ─────────────────────────────▶ Part II governance
  Confidence-band scoring ─────────────────────────────────────▶ Part II AI weighting
  Audit log structure ─────────────────────────────────────────▶ Part II CRM properties
  IF/Merge composition pattern ────────────────────────────────▶ Part II workflow topology

Transition to Chapter 1.7

You have now built the advisory architecture from first principles (Chapter 1.0) to a complete five-element reliability model (Chapter 1.5) and demonstrated its application across five business domains (this chapter). The architecture is mature, tested, and documented.

What has not yet been explicitly addressed is the connection between what you have built here and what Part II builds on top of it. Chapter 1.7 Transition to AI-Powered CRM Systems makes that connection explicit. It maps the Part I workflow patterns to the Part II CRM architecture, explains how the governed advisory workflow becomes the scoring and routing engine embedded within a HubSpot-integrated revenue operations system, and prepares you for the scale, governance requirements, and system integration complexity of Part II.

If you built Project B from this chapter, you have already built the Part II scoring engine. Chapter 1.7 will show you where it fits.


Key Takeaways

  1. The advisory architecture has an invariant core and a domain adaptation layer. The core is unchanged across all five projects; only the adaptation layer changes.
  2. Seven adaptation questions input fields, AI task, rule layer, contribution cap, advisory thresholds, escalation policy, audit log destination define the complete domain specification before any node is configured.
  3. Project B is the recommended implementation for readers proceeding to Part II. The scoring formula, confidence bands, and audit log structure are identical to Chapter 2.5. HubSpot integration and CRM lifecycle management are the only Part II additions.
  4. Project C introduces two-stage AI pipelines (classify then generate). Gate the second AI call on the first stage’s validity and confidence band, and use Math.min(classification_confidence, generation_confidence) as the effective confidence.
  5. Project D introduces a fourth processing path (critical bypass) for events where AI latency is unacceptable. Critical path routing uses deterministic keyword detection, not AI classification.
  6. Project E introduces per-field confidence scoring and domain validation for extraction tasks. Check per-field confidence independently of overall confidence, and apply domain sanity checks after structural validation.
  7. The most common design mistake across all domain adaptations: modifying the architectural core instead of the adaptation layer. Preserve the invariant core exactly as built in Chapter 1.5.
  8. Adaptation time from Chapter 1.5: 3–6 hours per project depending on complexity. The architecture is already designed.

End of Chapter 1.6 AI Automation Projects