Skip to main content
PreviewAgent Builder is currently in preview and may change before general availability.

When to use

Use this pattern when your workflow requires both AI capabilities (extracting data from unstructured documents, classifying items with fuzzy matching, generating natural language reports) and deterministic business logic (financial formulas, eligibility checks, compliance scoring). The key requirement is auditability — you need to trace exactly how a decision was reached, which means the AI handles what humans cannot codify as rules, and business rules handle everything else. Common scenarios:
  • Financial product eligibility where income data comes from scanned documents but calculations must follow exact regulatory formulas
  • Insurance underwriting that extracts risk factors from free-text medical reports, then applies deterministic scoring tables
  • Procurement workflows that classify vendor proposals with AI, then rank them using weighted scoring matrices
Without this pattern, you face two bad options: pure AI (fast but opaque, hard to audit) or pure rules (auditable but unable to handle unstructured input).

Architecture

The pattern alternates between AI nodes and Script/Business Rule nodes in a pipeline:
Documents / unstructured input
    |
    v
AI EXTRACTION
(extract structured data from documents)
    |
    v
BUSINESS RULES
(deterministic calculations, formulas)
    |
    v
AI UNDERSTANDING
(fuzzy matching, qualitative filtering)
    |
    v
BUSINESS RULES
(scoring, ranking, normalization)
    |
    v
AI GENERATION
(produce human-readable report)
    |
    v
Final output (structured data + report)
Each AI node produces structured JSON output that feeds directly into the next Script node. Each Script node produces deterministic, reproducible results that feed into the next AI node. This alternating structure means every decision point is either fully deterministic (and logged) or clearly marked as AI-assisted (with the prompt and response recorded).
The alternating AI-then-rules structure creates a natural audit trail. For any final decision, you can trace backwards: which AI extraction produced the input values, which formula computed the result, and which AI classification influenced the filtering. This is critical for regulated industries where decisions must be explainable.

Implementation

AI vs. business rule responsibilities

The core principle is a clean separation: AI handles what cannot be expressed as deterministic rules, and business rules handle everything that can.
ResponsibilityHandled byWhy
Extract income, debts, employment data from bank statementsAI (TEXT_EXTRACTION)Unstructured documents, varying formats
Compute monthly payment (PMT), debt-to-income ratioBusiness rules (Script node)Exact financial formulas, must be auditable
Filter products by qualitative criteria (loan type, sustainability features)AI (TEXT_UNDERSTANDING)Fuzzy matching, natural language descriptions
Normalize scores, rank products, apply weight matricesBusiness rules (Script node)Deterministic scoring, reproducible results
Generate consultant report with recommendationsAI (TEXT_GENERATION)Natural language output, professional formatting

Step 1: AI extraction

Configure a TEXT_EXTRACTION node to pull structured data from unstructured documents. The node receives bank-specific rule documents and extracts the parameters your business rules need.

Prompt

Extract the following financial parameters from the provided bank rule document:

- income_weights: object mapping income source types to their weight factors
- debt_factors: object mapping debt types to their monthly burden factors
- max_ltv: maximum loan-to-value ratio (as decimal)
- min_income_threshold: minimum gross monthly income required
- supported_currencies: array of accepted currency codes
- special_conditions: any additional eligibility conditions described in the document

Return a JSON object with these fields. Use null for any parameter not found in the document.

Response Schema

{
  "type": "object",
  "properties": {
    "income_weights": {
      "type": "object",
      "description": "Income source type to weight factor mapping"
    },
    "debt_factors": {
      "type": "object",
      "description": "Debt type to monthly burden factor mapping"
    },
    "max_ltv": {
      "type": "number"
    },
    "min_income_threshold": {
      "type": "number"
    },
    "supported_currencies": {
      "type": "array",
      "items": { "type": "string" }
    },
    "special_conditions": {
      "type": "array",
      "items": { "type": "string" }
    }
  },
  "required": ["income_weights", "debt_factors", "max_ltv", "min_income_threshold"]
}
SettingValue
Node typeTEXT_EXTRACTION
InputBank rule document (PDF or text)
Output keyextractedParams
Temperature0 — extraction should be deterministic

Step 2: Business rules (calculations)

Add a Script node immediately after the extraction. This node takes the AI-extracted parameters and applies deterministic financial formulas. Because this is a Script node, every calculation is reproducible and auditable.

Example script

// Inputs from AI extraction
const params = input.extractedParams.output;
const client = input.clientData;

// Weighted income calculation
let weightedIncome = 0;
for (const [source, amount] of Object.entries(client.incomeSources)) {
  const weight = params.income_weights[source] || 0;
  weightedIncome += amount * weight;
}

// Monthly debt burden
let monthlyDebt = 0;
for (const [debtType, balance] of Object.entries(client.debts)) {
  const factor = params.debt_factors[debtType] || 0.05;
  monthlyDebt += balance * factor;
}

// PMT calculation (monthly payment for a given loan amount)
function computePMT(principal, annualRate, termMonths) {
  const r = annualRate / 12;
  if (r === 0) return principal / termMonths;
  return principal * (r * Math.pow(1 + r, termMonths)) /
         (Math.pow(1 + r, termMonths) - 1);
}

// Debt-to-income ratio
const dti = (monthlyDebt) / weightedIncome;

// Maximum loan amount (iterative solve for PMT <= allowed DTI share)
const maxDtiShare = 0.43; // Standard threshold
const allowablePayment = (maxDtiShare * weightedIncome) - monthlyDebt;
const maxLoan = allowablePayment > 0
  ? allowablePayment * ((Math.pow(1 + client.rate / 12, client.termMonths) - 1) /
    (client.rate / 12 * Math.pow(1 + client.rate / 12, client.termMonths)))
  : 0;

// Currency conversion (if needed)
const loanInClientCurrency = maxLoan * (client.exchangeRate || 1);

output.calculationResults = {
  weightedIncome,
  monthlyDebt,
  dti: Math.round(dti * 10000) / 10000,
  maxLoanAmount: Math.round(loanInClientCurrency),
  meetsIncomeThreshold: weightedIncome >= params.min_income_threshold,
  meetsLtvRequirement: (client.requestedLoan / client.propertyValue) <= params.max_ltv
};
Keep Script nodes focused on a single calculation domain (e.g., financial formulas in one node, scoring in another). This makes each node independently testable and easier to audit.

Step 3: AI understanding (qualitative filtering)

Add a TEXT_UNDERSTANDING node to filter or classify products based on criteria that do not reduce to simple boolean checks — loan type preferences, sustainability features, qualitative fit.

Prompt

You are a mortgage product filter. Given the client's preferences and a list of available
products, evaluate each product against the qualitative criteria.

Client preferences:
- Loan type preference: {{input.clientData.loanTypePreference}}
- Values sustainability features: {{input.clientData.sustainabilityPreference}}
- Risk tolerance: {{input.clientData.riskTolerance}}

For each product, return:
- product_id: the product identifier
- qualitative_match_score: a number between 0 and 1
- match_reasons: brief explanation of why it matches or does not match
- disqualified: boolean, true if the product is fundamentally incompatible

Only return products where disqualified is false.

Response Schema

{
  "type": "object",
  "properties": {
    "filtered_products": {
      "type": "array",
      "items": {
        "type": "object",
        "properties": {
          "product_id": { "type": "string" },
          "qualitative_match_score": { "type": "number" },
          "match_reasons": { "type": "string" },
          "disqualified": { "type": "boolean" }
        },
        "required": ["product_id", "qualitative_match_score", "disqualified"]
      }
    }
  },
  "required": ["filtered_products"]
}
SettingValue
Node typeTEXT_UNDERSTANDING
InputClient preferences + product catalog
Output keyfilteredProducts
Temperature0.1 — low but allows minor flexibility for fuzzy matching

Step 4: Business rules (scoring and ranking)

Add a second Script node to normalize scores and produce a final ranked list. This node combines the deterministic calculation results from Step 2 with the AI qualitative scores from Step 3 into a single weighted ranking.

Example script

const calcResults = input.calculationResults;
const aiFiltered = input.filteredProducts.output.filtered_products;

// Scoring weights
const WEIGHT_RATE = 0.35;
const WEIGHT_QUALITATIVE = 0.25;
const WEIGHT_LTV = 0.20;
const WEIGHT_FLEXIBILITY = 0.20;

const scored = aiFiltered.map(product => {
  const details = input.productCatalog.find(p => p.id === product.product_id);
  if (!details) return null;

  // Normalize rate score (lower rate = higher score)
  const rateScore = 1 - (details.rate / 0.10); // Normalize against 10% max

  // LTV score (how close to max allowed)
  const ltvScore = details.maxLtv >= calcResults.dti ? 1.0 : details.maxLtv / calcResults.dti;

  // Flexibility score (prepayment, term options)
  const flexScore = (details.allowsPrepayment ? 0.5 : 0) +
                    (details.flexibleTerm ? 0.5 : 0);

  const totalScore = (rateScore * WEIGHT_RATE) +
                     (product.qualitative_match_score * WEIGHT_QUALITATIVE) +
                     (ltvScore * WEIGHT_LTV) +
                     (flexScore * WEIGHT_FLEXIBILITY);

  return {
    productId: product.product_id,
    bankName: details.bankName,
    totalScore: Math.round(totalScore * 1000) / 1000,
    breakdown: { rateScore, qualitativeScore: product.qualitative_match_score, ltvScore, flexScore },
    matchReasons: product.match_reasons
  };
}).filter(Boolean);

// Sort descending by total score
scored.sort((a, b) => b.totalScore - a.totalScore);

output.rankedProducts = scored.slice(0, 5); // Top 5
output.topRecommendation = scored[0] || null;

Step 5: AI generation (report)

Add a TEXT_GENERATION node at the end to produce a professional consultant report summarizing the analysis and recommendations.

Prompt

You are a professional mortgage consultant. Generate a concise recommendation report
for the client based on the following analysis results.

Client profile:
- Weighted monthly income: {{input.calculationResults.weightedIncome}}
- Debt-to-income ratio: {{input.calculationResults.dti}}
- Maximum eligible loan: {{input.calculationResults.maxLoanAmount}}

Top recommended products (ranked):
{{input.rankedProducts}}

Structure the report as:
1. Executive summary (2-3 sentences)
2. Eligibility assessment (income qualification, DTI status)
3. Top 3 product recommendations with key differentiators
4. Next steps for the client

Use professional financial language. Do not include disclaimers about being an AI.
SettingValue
Node typeTEXT_GENERATION
InputCalculation results + ranked products
Output keyconsultantReport
Temperature0.3 — allow natural variation in language

Pipeline summary

StepNode typePurposeOutput
1TEXT_EXTRACTIONExtract income weights, debt factors from bank rule documentsStructured financial parameters
2Script nodeCompute PMT, DTI, max loan amount, currency conversionDeterministic calculation results
3TEXT_UNDERSTANDINGFilter products by qualitative criteria with fuzzy matchingScored and filtered product list
4Script nodeNormalize scores, apply weight matrix, rank top productsFinal ranked recommendations
5TEXT_GENERATIONGenerate professional consultant reportHuman-readable recommendation report

Real-world example

The Mortgage advisor chatbot tutorial implements this pattern in its product recommendation handler. When a user asks for mortgage product recommendations, the workflow:
  1. Extracts financial parameters from each bank’s rule documents
  2. Computes eligibility using exact financial formulas (PMT, DTI)
  3. Filters the product catalog using AI-driven qualitative matching
  4. Ranks the remaining products with a deterministic scoring matrix
  5. Generates a personalized consultant report
The full pipeline processes 7 banks in parallel using a fan-out structure, then merges results for the final ranking.

Variations

Human-in-the-loop

Add approval gates after AI steps to let a human reviewer verify the AI output before it feeds into business rules. This is useful during initial deployment or in high-stakes decisions.
AI EXTRACTION --> [Human review gate] --> BUSINESS RULES --> AI UNDERSTANDING --> [Human review gate] --> BUSINESS RULES --> AI GENERATION
Implement the review gates using User Task nodes in the parent BPMN process. The reviewer sees the AI output and can correct it before the workflow continues.

Confidence-weighted rules

Instead of treating AI output as binary (pass/fail), feed the AI confidence score into the business rules as a weight. Low-confidence AI extractions trigger stricter thresholds or flag the result for manual review.
// Adjust DTI threshold based on extraction confidence
const extractionConfidence = input.extractedParams.confidence || 1.0;
const baseMaxDti = 0.43;

// Lower confidence = stricter threshold (more conservative)
const adjustedMaxDti = baseMaxDti * extractionConfidence;

output.meetsAdjustedDti = calcResults.dti <= adjustedMaxDti;
output.confidenceAdjustment = {
  original: baseMaxDti,
  adjusted: adjustedMaxDti,
  confidence: extractionConfidence
};

Multi-source comparison

Extend the pattern to process documents from multiple banks or sources in parallel (using the Fan-out extraction pattern), then merge the results into a single comparison matrix before the scoring step. This is the approach used in the mortgage advisor for comparing products across 7 banks.
Last modified on March 16, 2026