Module 8: Capstone The Andes Cargo Genai Extractor
2. Architecture review: the full system, two paths
Description
Each of the previous seven modules drew, in its own architecture lesson, a fragment of the complete system: M1.3 drew the parser's "before/after"; M4.4 drew the exact gap between the managed guardrail and the custom checks; M8.1 named the seven inherited pieces without drawing them together. This lesson does what no previous lesson has done yet: it draws the complete system, end to end, with the two routes — deterministic and escalation — overlaid on the infrastructure this ecosystem's seven sibling guides already left built. No component in this diagram is new; what's new is seeing them all together, in one place, for the first time.
Connection to the module
This lesson is the map lessons 3 and 4 walk through, each following one of the two routes end to end. Without this map, "the deterministic path" and "the escalation path" would be loose phrases; with it, they're two concrete trajectories on a diagram anyone can point to.
Analogy: the ATM's complete map, not just the counter
A bank that wants to explain how its customer service works doesn't start by showing an isolated ATM — it shows the complete map: the branch, the row of ATMs at the entrance, the service counter in the back, and the rule connecting both — the customer first tries the ATM, which resolves more than 90% of transactions on its own, and only if the ATM can't resolve it (a case outside its scope, an account problem) does the line move toward a human at the counter, slower, more expensive to run, but able to handle what the ATM can't. This lesson is exactly that complete map, applied to Andes Cargo's system: process-shipment-manifest is the ATM, resolving the vast majority of manifests on its own, free, in milliseconds; extract-shipment-manifest-fields is the human counter, expensive and slow by comparison, that enters the scene only when the ATM — the deterministic parser — already tried and couldn't.
Step 1 — The ASCII diagram: two paths, one destination
manifest (S3: andes-cargo-shipment-docs)
│
▼
┌────────────────────────────────────┐
│ process-shipment-manifest │
│ (aws-core-services-guide, M6-M7) │
│ parse_manifest() -- key=value │
└────────────────────────────────────┘
│
┌─────────────────┴─────────────────┐
│ │
all 5 fields DID AT LEAST 1 of the 5
show up fields is missing
│ │
▼ ▼
╔═══════════════════════╗ ╔══════════════════════════╗
║ DETERMINISTIC PATH ║ ║ ESCALATION PATH ║
║ (M8.3 -- EXECUTED) ║ ║ (M8.4 -- MIXED) ║
╚═══════════════════════╝ ╚══════════════════════════╝
│ │
│ publishes ManifestParseFailed
│ (andes-cargo-events bus)
│ │
│ ▼
│ ┌────────────────────────────────┐
│ │ extract-shipment-manifest- │
│ │ fields (M3, M5) │
│ │ 1. pre_invoke_checks.py (M4) │
│ │ scrub PII -- REAL │
│ │ 2. bedrock:InvokeModel │
│ │ + guardrail (M3, M4) │
│ │ -- REPRESENTATIVE from here │
│ │ 3. post_invoke_checks.py (M4) │
│ │ validates ShipmentFields -- REAL│
│ └────────────────────────────────┘
│ │
│ ┌────────────┴────────────┐
│ schema OK schema FAIL
│ │ │
▼ ▼ ▼
write_shipment_record write_shipment_record record is NOT
│ │ written -- stays
└───────────┬────────────┘ pending manual
▼ review
Shipments (DynamoDB)
Notice the exact point where the diagram forks: it isn't an extra step before process-shipment-manifest — it's an outcome of that same Lambda, the same decision M1.3 already traced in prose. No manifest passes through extract-shipment-manifest-fields "just in case"; only a manifest the cheap path already tried and couldn't resolve ever gets there.
Step 2 — The complete sequence diagram, in mermaid
The ASCII diagram above shows the decisions; this sequence diagram shows the exact temporal order in which every inherited component and every new component in this guide talk to each other, including the representative boundary marked unambiguously:
sequenceDiagram
participant S3 as S3 (andes-cargo-shipment-docs)
participant PSM as process-shipment-manifest
participant EB as EventBridge (andes-cargo-events)
participant ESM as extract-shipment-manifest-fields
participant PRE as pre_invoke_checks.py
participant BR as bedrock-runtime + Guardrail
participant POST as post_invoke_checks.py
participant DDB as Shipments (DynamoDB)
S3->>PSM: manifest uploaded (S3 event)
PSM->>PSM: parse_manifest(text)
alt all 5 fields present
PSM->>DDB: write_shipment_record(fields)
Note over PSM,DDB: Deterministic path -- M8.3, EXECUTED
else at least 1 field missing
PSM->>EB: put-events ManifestParseFailed
Note over PSM,EB: Real -- M8.4, EXECUTED
EB->>ESM: invokes (EventBridge rule, M3)
ESM->>PRE: scrub_pii(rawText)
PRE-->>ESM: redacted_text, found_pii
Note over ESM,PRE: Real -- M4, M8.4
ESM->>BR: InvokeModel + guardrailIdentifier
Note over ESM,BR: REPRESENTATIVE -- never executed (M3.6, M4.7)
BR-->>ESM: representativeModelResponse (fixed, labeled dict)
ESM->>POST: validate_shipment_fields(candidate)
POST-->>ESM: is_valid, errors
Note over ESM,POST: Real -- M4, M8.4
alt is_valid
ESM->>DDB: write_shipment_record(candidate)
else not is_valid
ESM->>ESM: DO NOT WRITE -- stays for manual review
end
end
Two annotations (Note over) mark, inside the diagram itself, exactly where the real ends and the representative begins — the same labeling discipline every lesson in this guide has already applied to its own code block, now applied to the complete-system view.
Step 3 — What this diagram inherits, component by component
| Diagram component | Originating guide | State in this guide |
|---|---|---|
S3: andes-cargo-shipment-docs | aws-core-services-guide, Module 5 | Inherited, with no change |
process-shipment-manifest / parse_manifest() | aws-core-services-guide, Module 6-7 | Inherited, with no change (M1.3) |
EventBridge / andes-cargo-events bus | aws-serverless-and-containers-guide, Module 4 | Inherited; ManifestParseFailed is the new event this guide adds, with the same shape as ShipmentProcessed |
extract-shipment-manifest-fields (function) | New in this guide | Declared in M3 (bedrock.tf), secured in M5, signed in M5.6 |
pre_invoke_checks.py / post_invoke_checks.py | New in this guide | M4, built and tested with pytest |
bedrock-runtime + Guardrail | New in this guide | Declared as code (M3-M4), never invoked (M1.2, M3.6, M4.7) |
Shipments (DynamoDB) | aws-core-services-guide, Module 7 | Inherited, with no change to its schema — ShipmentFields (M4.6) is, exactly, the shape it already expected |
No row in this table introduces infrastructure this guide hasn't already built, in a previous module — this lesson's exact point is showing the seven pieces talk to each other, not presenting an eighth.
Common mistakes
Mentally drawing Bedrock's guardrail as a separate step, before bedrock-runtime, instead of part of the same call (fragmenting a mechanism the API treats as one). What happens: someone, seeing "Guardrails" mentioned so many times in M4, imagines a separate intermediate service the request passes through before reaching the model. How to spot it: if your version of this lesson's diagram has a separate arrow from ESM toward "Guardrails" and another, distinct one, toward "bedrock-runtime." How to fix it: M4.2 already established this — a guardrail gets referenced with the guardrailIdentifier parameter inside the same InvokeModel/Converse call, not as a separate call. This lesson's diagram correctly draws it as a single participant (bedrock-runtime + Guardrail), precisely for that reason.
Assuming the escalation path replaces, at some point, the deterministic path for the same manifest (not understanding the fork as final). What happens: someone interprets the diagram as if a manifest could "retry" first with parse_manifest(), fail, and then that same manifest went back to trying the cheap path after the model responded. How to spot it: if your reading of the diagram includes an arrow going back from extract-shipment-manifest-fields to process-shipment-manifest. How to fix it: Step 1's fork is final for each specific manifest — once parse_manifest() fails to produce the five fields, that exact manifest follows only the escalation path; there's no loop back to the cheap path for the same text, exactly as M1.3 already established with its "TODAY / AFTER" diagram.
Reading the sequence diagram's "REPRESENTATIVE" note as if it applied to the entire alt/else block, including pre_invoke_checks/post_invoke_checks (over-generalizing the label). What happens: someone, seeing a single "REPRESENTATIVE" annotation near the diagram's middle, concludes the entire escalation path is representative. How to spot it: if your summary of the sequence diagram is "the bottom half is all representative." How to fix it: the label sits, deliberately, on the specific ESM->>BR/BR-->>ESM interaction — the interactions with PRE and POST, before and after that one, carry their own "Real" note, precisely because they're executable with no dependency on Bedrock. This module's lesson 4 makes this exact same distinction with really-run code, not just with a diagram.
Exercises
Exercise 1 — Without looking at this lesson's ASCII diagram, draw, yourself, in text, the exact fork that happens inside process-shipment-manifest. What exact condition decides between the two paths?
See solution
The exact condition is whether parse_manifest(text) produced SHIPMENT_FIELDS_SCHEMA's five fields (shipmentId, originCountry, destinationCountry, carrier, weightKg) — the same condition would_escalate() (M7.4) encodes as a single line: bool(set(SHIPMENT_FIELDS_SCHEMA) - set(parsed.keys())). If that set difference is empty, the manifest follows the deterministic path; if any of the five is missing, it publishes ManifestParseFailed and follows the escalation path. The condition lives entirely inside process-shipment-manifest, never in a separate component "deciding" from outside.
Exercise 2 — Explain why this lesson's sequence diagram includes NO interaction between Shipments (DynamoDB) and extract-shipment-manifest-fields in the not is_valid case. What exactly happens to a manifest that reaches that point?
See solution
Because post_invoke_checks.py (M4.6) exists, precisely, to prevent that write — a candidate that fails schema validation never reaches write_shipment_record(), with no exception, the same rule defense_in_depth_flow.py (M4.8) already demonstrated with Scenario B. A manifest that reaches that point stays, per the diagram's note, "for manual review" — there's no arrow toward DDB on that branch because, quite deliberately, no write happens. It's the same rule M4.4 already named with its missing-weightKg example: a response that would pass Bedrock Guardrails' six policies with no problem, but that this custom check does reject.
Exercise 3 — Predict what would change in this lesson's diagram if Andes Cargo, someday, added a second consumer of the ManifestParseFailed event (a manual-review queue, for example, mentioned as a possibility in M1.3, Exercise 2). Would extract-shipment-manifest-fields need to change?
See solution
No — not a single line of extract-shipment-manifest-fields would change. The diagram would simply add a second arrow from EB (EventBridge) toward the new consumer, in parallel with the one that already exists toward ESM — exactly the design advantage M1.3, Exercise 2 already explained in prose: ManifestParseFailed announces a fact ("deterministic parsing failed"), not an instruction ("use AI to solve it"), so any number of new consumers can subscribe to the same event without the producer (process-shipment-manifest) or any existing consumer having to be modified. It's the same event-driven principle that has sustained the entire andes-cargo-events bus since aws-serverless-and-containers-guide.
Summary and next step
This lesson drew, for the first time in this guide, the complete end-to-end system: an ASCII diagram of the two routes and their exact fork, and a mermaid sequence diagram with every inherited component and every new component talking in real temporal order, including the representative boundary marked unambiguously inside the diagram itself. You confirmed, with Step 3's table, that no component in this map is new — each has its own originating lesson, in this module or an earlier one.
Before moving on you should be able to: draw from memory the exact fork inside process-shipment-manifest; explain why Bedrock's guardrail appears as a single participant, not two; and point out, on the sequence diagram, exactly where the entire flow's one representative interaction begins and ends.
Lesson 3 walks through this diagram's deterministic path, end to end, with code executed in its entirety — the first of this capstone's two "hands-on" lessons.
Resources
- This same course, Module 1, lesson 3 (
03-andes-cargos-ai-workload-when-the-deterministic-parser-is-not-enough.md) — the origin of the "before/after" diagram this lesson's Step 1 extends with the rest of the system. - This same course, Module 4, lesson 8 (
08-project-andes-cargos-guardrails-layer.md) —defense_in_depth_flow.py, the exact source for the three-step order this lesson's sequence diagram represents. - Mermaid — Sequence diagrams — official reference for the syntax used in this lesson's Step 2.
- AWS Docs — Amazon Bedrock guardrails, using with InvokeModel — confirms a guardrail gets referenced inside the same inference call, the basis for this lesson's Common Mistake 1.