Confidence Status Lambda

This function is responsible for checking data integrity (Schema completeness), evaluating the extraction confidence (Confidence Score), and determining the final status of the document (EXTRACTED or REVIEW_REQUIRED).


Step-by-Step Console Configuration

  1. Create the Lambda function:

    • Go to Lambda ➔ Click Create function.

    image1.png

    • Choose Author from scratch (default). image2.png
    • Function name: Enter docuflow-dev-ai-confidence-status-lambda.

    image3.png

    • Runtime: Select Node.js 24.x. image4.png
    • Expand Additional settings and under Architecture select arm64. image30.png
    • Under PermissionsChange default execution role, choose Use an existing role and select docuflow-dev-ai-confidence-status-lambda-role. image31.png
    • Required - Add Tags: Scroll down to the Tags tab and add the following standard tags for cost allocation and governance:
      • Project: DocuFlowAI
      • Environment: dev
      • ManagedBy: SAM
      • Module: ai
      • CostCenter: Workshop
      • Owner: Team image32.png image33.png
    • Click Save to save the tags. image34.png
    • Click Create function. image11.png
  2. Configure General settings:

    • Go to the Configuration tab ➔ General configuration ➔ Click Edit. image35.png
    • Change Timeout to 30 seconds and click Save. image36.png
    • Under Environment variables, add CONFIDENCE_THRESHOLD = 0.9.
  3. Deploy Code:

    • In the Code tab, paste the following Node.js code over the boilerplate code in the index.mjs editor.
    • Click Deploy.
const CONFIDENCE_THRESHOLD = normalizeThreshold(
  process.env.CONFIDENCE_THRESHOLD,
  0.9
);

export const handler = async (event) => {
  const rootFields = isRecord(event) ? { ...event } : {};
  delete rootFields.normalizedResult;

  const normalizedResult = unwrapNormalizedResult(event?.normalizedResult);
  const source = { ...rootFields, ...normalizedResult };
  const invoice = isRecord(source.invoice) ? source.invoice : {};
  const confidence = isRecord(source.confidence) ? source.confidence : {};
  const reviewReasonCodes = [];

  if (!['INVOICE', 'RECEIPT'].includes(source.documentType)) {
    reviewReasonCodes.push("UNKNOWN_DOCUMENT_TYPE");
  }

  if (!hasText(invoice.vendorName)) {
    reviewReasonCodes.push("MISSING_VENDOR_NAME");
  }
  if (!hasText(invoice.invoiceNumber)) {
    reviewReasonCodes.push("MISSING_INVOICE_NUMBER");
  }
  if (!hasText(invoice.invoiceDate)) {
    reviewReasonCodes.push("MISSING_INVOICE_DATE");
  }

  if (invoice.totalAmount === undefined || invoice.totalAmount === null) {
    reviewReasonCodes.push("MISSING_TOTAL_AMOUNT");
  } else if (
    typeof invoice.totalAmount !== "number" ||
    !Number.isFinite(invoice.totalAmount)
  ) {
    reviewReasonCodes.push("INVALID_AMOUNT_FORMAT");
  }

  if (!hasText(invoice.currency)) {
    reviewReasonCodes.push("MISSING_CURRENCY");
  }

  const finalConfidenceScore = resolveConfidenceScore(confidence);
  if (
    finalConfidenceScore < CONFIDENCE_THRESHOLD ||
    confidence.hasLowConfidence === true
  ) {
    reviewReasonCodes.push("LOW_CONFIDENCE");
  }

  const uniqueReasonCodes = [...new Set(reviewReasonCodes)];
  const isReviewRequired = uniqueReasonCodes.length > 0;
  const status = isReviewRequired ? "REVIEW_REQUIRED" : "EXTRACTED";

  log("INFO", {
    documentId: source.documentId,
    documentType: source.documentType || null,
    status,
    confidenceScore: finalConfidenceScore,
    confidenceThreshold: CONFIDENCE_THRESHOLD,
    reviewReasonCodes: uniqueReasonCodes,
    message: "Confidence status evaluated",
  });

  return {
    ...source,
    status,
    invoice,
    confidence: {
      confidenceScore: finalConfidenceScore,
      hasLowConfidence: uniqueReasonCodes.includes("LOW_CONFIDENCE"),
      fieldConfidence: isRecord(confidence.fieldConfidence)
        ? confidence.fieldConfidence
        : {},
    },
    review: {
      reviewStatus: isReviewRequired ? "PENDING" : "NOT_REQUIRED",
      reviewReasonCodes: uniqueReasonCodes,
      reviewedBy: null,
      reviewedAt: null,
      corrections: [],
    },
  };
};

function unwrapNormalizedResult(value) {
  let result = value?.Payload ?? value?.payload ?? value ?? {};

  if (typeof result === "string") {
    try {
      result = JSON.parse(result);
    } catch {
      return {};
    }
  }

  return isRecord(result) ? result : {};
}

function resolveConfidenceScore(confidence) {
  let rawScore = finiteNumber(confidence.confidenceScore);

  if (rawScore === null) {
    const fieldScores = Object.values(confidence.fieldConfidence || {})
      .map(finiteNumber)
      .filter((value) => value !== null)
      .map((value) => (value > 1 ? value / 100 : value));
    rawScore = fieldScores.length
      ? fieldScores.reduce((sum, value) => sum + value, 0) /
        fieldScores.length
      : 0;
  }

  const normalized = rawScore > 1 ? rawScore / 100 : rawScore;
  return Number(Math.max(0, Math.min(1, normalized)).toFixed(4));
}

function normalizeThreshold(value, fallback) {
  const numeric = finiteNumber(value);
  if (numeric === null) return fallback;
  const normalized = numeric > 1 ? numeric / 100 : numeric;
  return Math.max(0, Math.min(1, normalized));
}

function finiteNumber(value) {
  if (
    value === null ||
    value === undefined ||
    value === "" ||
    typeof value === "boolean"
  ) {
    return null;
  }
  const numeric = Number(value);
  return Number.isFinite(numeric) ? numeric : null;
}

function hasText(value) {
  return typeof value === "string" && Boolean(value.trim());
}

function isRecord(value) {
  return Boolean(value) && typeof value === "object" && !Array.isArray(value);
}

function log(level, data) {
  console.log(
    JSON.stringify({
      level,
      service: "docuflow-dev-ai-confidence-status-lambda",
      ...data,
    })
  );
}
  1. Test the Lambda function:
    • Switch to the Test tab. image37.png
    • Select Create new event. image38.png
    • Select Synchronous. image39.png
    • Enter Test in the Event name field. image40.png
    • In the Event JSON block, paste the test output result from the previous docuflow-dev-ai-proxy-lambda execution.
    • Click Save and then click Test. If the execution succeeds and returns output similar to the following JSON, the Lambda is configured correctly: image41.png
      {
        "status": "EXTRACTED",
        "invoice": {
          "vendorName": "East Repair Inc.",
          "invoiceNumber": "US-001",
          "invoiceDate": "2019-02-11",
          "dueDate": "2019-02-26",
          "currency": "USD",
          "subtotalAmount": 145,
          "taxAmount": 9.06,
          "totalAmount": 154.06
        },
        "lineItems": [
          {
            "lineItemId": "item-001",
            "description": "Front and rear brake cables",
            "quantity": 1,
            "unitPriceAmount": 100,
            "taxAmount": 0,
            "totalAmount": 100,
            "confidenceScore": 0.9996
          },
          {
            "lineItemId": "item-002",
            "description": "New set of pedal arms",
            "quantity": 2,
            "unitPriceAmount": 15,
            "taxAmount": 0,
            "totalAmount": 30,
            "confidenceScore": 0.9998
          },
          {
            "lineItemId": "item-003",
            "description": "Labor 3hrs",
            "quantity": 3,
            "unitPriceAmount": 5,
            "taxAmount": 0,
            "totalAmount": 15,
            "confidenceScore": 0.9999
          }
        ],
        "confidence": {
          "confidenceScore": 0.9976,
          "hasLowConfidence": false,
          "fieldConfidence": {
            "dueDate": 0.9999,
            "invoiceDate": 0.9997,
            "invoiceNumber": 0.9965,
            "subtotalAmount": 0.9999,
            "taxAmount": 0.9991,
            "totalAmount": 1,
            "vendorName": 0.988
          }
        },
        "review": {
          "reviewStatus": "NOT_REQUIRED",
          "reviewReasonCodes": [],
          "reviewedBy": null,
          "reviewedAt": null,
          "corrections": []
        }
      }