* Add CI/CD pipeline and fix stack name to kebab-case CodePipeline V2 (po-ingest-pipeline) triggers CodeBuild on push to main, running cdk deploy via buildspec.yml. Stack name changed from PoIngestStack to po-ingest to match naming conventions. * Add RETAIN policy to Secrets Manager secret Prevents the Anthropic API key from being deleted if the stack is ever removed. Matches the RETAIN policy on all other stateful resources (DynamoDB tables, S3 bucket). |
||
|---|---|---|
| cdk | ||
| lambdas | ||
| scripts | ||
| .gitignore | ||
| buildspec.yml | ||
| 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 fall through to an address reverse-lookup against the verified-sites cache (normalized street + zip). If still unresolved, the PO is written to the pending-site-review table for manual verification against Payee Central.
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
po-ingest, 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. - DynamoDB
pending-site-review— PKpo_number. POs with no extractable site code and no address match, awaiting manual Payee Central verification.
- 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). - CI/CD: CodePipeline V2 (
po-ingest-pipeline) → CodeBuild (po-ingest-build). Pushes tomainauto-deploy viabuildspec.yml.
CI/CD
Merges to main trigger the po-ingest-pipeline (CodePipeline V2) which runs CodeBuild to cdk deploy. The pipeline uses the existing CodeStar connection to the Sea-Haven-Industries GitHub org.
Branch protection: main requires a PR (no direct push), no deletion, no force push.
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.