The Claude extraction prompt now explicitly asks for site_code (the Amazon facility code) and structured ship_to address fields (street, city, state, zip). The site-extractor Lambda prefers these direct fields when available, falling back to regex for older PO records. |
||
|---|---|---|
| cdk | ||
| lambdas | ||
| scripts | ||
| .gitignore | ||
| README.md | ||
PO Ingest
Coupa purchase-order email ingestion pipeline. SES receives Amazon PO emails, Claude extracts structured data, and the result lands in the shared purchase-orders DynamoDB table.
Flow
- Coupa sends a PO email to
amazon_po@int.seahaven.com. - SES (using the shared
INBOUND_MAILrule set) drops the raw MIME intos3://po-ingest-emails-{AccountId}/inbound/. - S3
ObjectCreatedfires thepo-email-processorLambda. - The Lambda parses the email, sends it to Claude Haiku 4.5 for structured JSON extraction, and writes to DynamoDB.
email_type: new_po— conditionalPutItemonpurchase-orders(idempotent onpo_number).email_type: cancellation—UpdateItemmarking the existing rowCancelled.
- DynamoDB Streams (NEW_AND_OLD_IMAGES) on
purchase-ordersfeeds two downstream consumers:- LedgerFlow (
seahaven-slack-bot/po-sync) — daily KB sync. - Verified-sites pipeline (
po-ingest-site-extractor) — real-time site address extraction (see below).
- LedgerFlow (
A separate po-web-ui Lambda (Function URL, unauthenticated) renders a simple HTML dashboard scanning the table.
Verified-sites pipeline
The po-ingest-site-extractor Lambda is triggered by the DynamoDB Stream on every PO INSERT/MODIFY. It:
- Extracts an Amazon facility site code from
ship_to.nameusing a regex cascade (parentheses,LLC - CODE,Station CODE,DS - CODE) with a fallback to the firstline_itemsdescription. - Parses
ship_to.addressinto structured fields (street, city, state, zip). - Upserts to the
verified-sitesDynamoDB table — atomically incrementspoCountand appends the PO number tosourcePOs.
POs with no extractable site code are logged and skipped.
Backfill stats (initial run): 14,825 POs scanned → 9,900 with extractable site codes → 1,100 unique sites.
Architecture
- IaC: AWS CDK (Python), stack name
PoIngestStack, regionus-east-1. - Lambdas (all Python 3.12, arm64, 60-day log retention):
po-email-processor— S3-triggered, parses PO emails via Claude Haiku.po-web-ui— Function URL, HTML dashboard.po-ingest-site-extractor— DynamoDB Streams-triggered, extracts site addresses.
- Storage:
- S3
po-ingest-emails-{AccountId}— 90-day lifecycle expiry. - DynamoDB
purchase-orders— owned by this stack, Streams enabled (NEW_AND_OLD_IMAGES). - DynamoDB
verified-sites— PKsiteCode, GSIby-stateonstate.
- S3
- Secrets: Anthropic API key in Secrets Manager at
po-ingest/anthropic-api-key. - SES: adds the
PoEmailRuleto the existingINBOUND_MAILreceipt rule set (shared withworkorder-ingest).
Setup
- Bootstrap CDK in the account if you haven't already:
cdk bootstrap aws://{AccountId}/us-east-1. - Store the Anthropic API key:
aws secretsmanager create-secret \ --name po-ingest/anthropic-api-key \ --secret-string "sk-ant-..." - Install Lambda dependencies into the deployable package directory (gitignored):
pip install -r lambdas/email_processor/requirements.txt -t lambdas/email_processor/package/ - Deploy:
cd cdk pip install -r requirements.txt cdk deploy - The
WebUIUrlCloudFormation output is the dashboard URL.
Reprocessing
To re-run the processor against every email still sitting in inbound/ (useful after a parser change):
python scripts/reprocess.py # dry-run — lists keys
python scripts/reprocess.py --execute # invokes po-email-processor for each
Inserts are conditional on po_number, so re-processing existing POs is a no-op.
Backfilling verified sites
The stream Lambda handles all future POs automatically. To backfill from historical PO data (one-time):
python scripts/backfill_sites.py
Uses the same extraction logic as the Lambda. Idempotent — safe to re-run.