Skip to content

Commit b8aade6

Browse files
first review pass
1 parent bfea977 commit b8aade6

21 files changed

Lines changed: 643 additions & 88 deletions

File tree

CONTRIBUTING.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,3 +45,9 @@ GitHooks **must** be configured and run on commits before pushing to remote. Ref
4545
## Testing Your Branch
4646

4747
You can test your branch in a dynamic environment prior to merging to `main`. These are created as part of the `cicd-1-pull-request.yaml` workflow, triggered when a PR is created or updated.
48+
49+
## Function Documentation
50+
51+
Each Lambda and internal package has a `README.md` alongside the source describing its purpose, flow, integration points, and peculiarities. These are bundled into the docs site via `docs/generate-includes.sh`.
52+
53+
When making changes to a Lambda or internal package, check whether the corresponding README needs updating. Function documentation is not auto-generated and can become stale if not maintained alongside code changes.

README.md

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,6 @@ This repository documents the Supplier API specification and provides an SDK wit
2424
- [Packages](#packages)
2525
- [Documentation](#documentation)
2626
- [SDK Assets](#sdk-assets)
27-
- [Examples](#examples)
2827
- [API Developers](#api-developers)
2928
- [Setup](#setup)
3029
- [Prerequisites and Configuration](#prerequisites-and-configuration)
@@ -64,10 +63,6 @@ If packages are unavailable the latest SDKs can be downloaded directly from:
6463
- TypeScript `sdk-ts-[Version].zip`
6564
- CSharp `sdk-csharp-[Version].zip`
6665

67-
### Examples
68-
69-
TODO:CCM-11209 Links to example clients.
70-
7166
## API Developers
7267

7368
New developers of the NHS Notify Supplier API should understand the below.
@@ -153,6 +148,14 @@ by default they will be available at [http://localhost:3050](http://localhost:30
153148

154149
These are generated using [https://hub.docker.com/r/openapitools/openapi-generator-cli](https://hub.docker.com/r/openapitools/openapi-generator-cli)
155150

151+
### Unit Testing
152+
153+
Run unit tests from the repository root:
154+
155+
```bash
156+
npm run test:unit
157+
```
158+
156159
### Documentation
157160

158161
- You can preview the OAS locally by running `make serve-oas`

config/suppliers/README.md

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
# Supplier Configuration
2+
3+
## Purpose
4+
5+
Static JSON configuration files that define the supplier allocation rules for the Supplier API. These are loaded into DynamoDB by infrastructure tooling and are queried at runtime by the `supplier-allocator` Lambda. Check relevant repositories (nhs-notify-internal, nhs-notify-supplier-config) as they orchestrate supplier config data ingress depending on target account, environment, etc.
6+
7+
## Configuration Entities
8+
9+
| Entity | Directory | Description |
10+
| --- | --- | --- |
11+
| **Supplier** | `supplier/` | Print supplier definitions with ID, name, channel type, daily capacity, and status (PROD/DRAFT) |
12+
| **Letter Variant** | `letter-variant/` | Letter type definitions with physical constraints (sheets, sides, ink coverage, delivery days), associated pack specification IDs, and volume group assignment |
13+
| **Volume Group** | `volume-group/` | Groupings of letter variants for allocation purposes, with status and date range validity |
14+
| **Supplier Allocation** | `supplier-allocation/` | Maps a supplier to a volume group with a target `allocationPercentage` and status |
15+
| **Pack Specification** | `pack-specification/` | Detailed print assembly specs (paper, envelope, print colour, duplex) with constraints and billing ID |
16+
| **Supplier Pack** | `supplier-pack/` | Links a supplier to a pack specification with approval status |
17+
18+
## Allocation Lookup Chain
19+
20+
When the `supplier-allocator` Lambda processes a `LetterRequestPreparedEvent`:
21+
22+
1. The event's `letterVariantId` identifies the **Letter Variant**.
23+
2. The variant's `volumeGroupId` identifies the **Volume Group** (must be `PROD` status and within date range).
24+
3. **Supplier Allocations** for that volume group determine which suppliers are eligible and their target allocation percentages (must sum to 100).
25+
4. The variant's `packSpecificationIds` are filtered by the letter's physical constraints.
26+
5. **Supplier Packs** confirm which eligible suppliers support the selected pack specification.
27+
6. The supplier with the lowest weighted allocation factor (furthest below their target share) is selected.
28+
29+
## Nuances and Peculiarities
30+
31+
- These files are the source of truth for the supplier config DynamoDB table (`SUPPLIER_CONFIG_TABLE_NAME`). Changes here flow into DynamoDB via infrastructure deployment.
32+
- Runtime persistence is event-driven: supplier-config events are routed through SQS to the `supplier-config-ingress` Lambda, which upserts records into the config table.
33+
- `status: "PROD"` is required at multiple levels (supplier, volume group, allocation) for an allocation to be active.
34+
- Volume groups have `startDate` (and optional `endDate`) fields. Allocations are only valid when the current date falls within this range (evaluated in London timezone).
35+
- Supplier `dailyCapacity` is tracked separately in `SUPPLIER_QUOTAS_TABLE` and resets at midnight London time. It is not stored in these config files.

docs/collections/_consumers/acceptance.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -616,7 +616,7 @@ Once you pass this stage, we'll confirm your readiness for going live.
616616
### Letter Status Definitions
617617

618618
| Status | Description | Initiated By | Mandatory/Optional
619-
|---|---|---|---|
619+
| --- | --- | --- | --- |
620620
| PENDING | Initial state for all new letters. Indicates that the letter has been allocated to a supplier but not yet retrieved or accepted | NHS Notify | Mandatory Starting Status |
621621
| REJECTED | Used when a supplier determines a letter cannot be processed Occurs immediately after PENDING , before any production begins | Supplier | Conditional |
622622
| ACCEPTED | Letter has passed validation checks and is ready for production | Supplier | Mandatory (core workflow transition) |
@@ -632,7 +632,7 @@ Once you pass this stage, we'll confirm your readiness for going live.
632632
### Possible Scenarios and Applicable Letter Statuses
633633

634634
| Scenario | Description | Typical Status flow |
635-
|---|---|---|
635+
| --- | --- | --- |
636636
| Standard print and delivery | Letter specification successfully provided, letter printed, dispatch and delivered to patient. | a) PENDING → ACCEPTED → PRINTED → ENCLOSED → DISPATCHED → DELIVERED<br>b) PENDING → ACCEPTED → PRINTED → DISPATCHED → DELIVERED<br>c) PENDING → ACCEPTED → ENCLOSED → DISPATCHED → DELIVERED<br>d) PENDING → ACCEPTED → DISPATCHED |
637637
| Letter rejected by letter Supplier | Letter fails validation before acceptance by the letter supplier. | PENDING → REJECTED |
638638
| Cancelled by client | Client cancels production before dispatched. | a) PENDING → ACCEPTED → CANCELLED<br>b) PENDING → ACCEPTED → PRINTED → CANCELLED<br>c) PENDING → ACCEPTED → PRINTED → ENCLOSED → CANCELLED |
Lines changed: 124 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,124 @@
1+
---
2+
title: Function Documentation
3+
nav_order: 6
4+
parent: Developer Guides
5+
has_children: false
6+
has_toc: true
7+
---
8+
9+
This page bundles function-level README files from the Supplier API codebase.
10+
11+
## Data Flow Overview
12+
13+
Primary data flows passing through the system:
14+
15+
### Inbound (letter allocation and creation)
16+
17+
```
18+
LetterRequestPreparedEvent (SNS)
19+
→ supplier-allocator (SQS consumer)
20+
→ resolves supplier via weighted fair-share algorithm
21+
→ upsert-letter (SQS consumer)
22+
→ inserts letter record into DynamoDB (letters table)
23+
→ DynamoDB Stream → Kinesis
24+
→ update-letter-queue: adds PENDING letters to queue table
25+
→ letter-updates-transformer: publishes LetterStatusChangeEvent to SNS
26+
```
27+
28+
### Supplier-facing (status updates)
29+
30+
```
31+
Supplier calls GET /letters (api-handler)
32+
→ reads from pending queue table with visibility timeout
33+
Supplier calls PATCH /letters/{id} or POST /letters (api-handler)
34+
→ enqueues UpdateLetterCommand to SQS
35+
→ transformAmendmentEvent (SQS consumer, in api-handler package)
36+
→ fetches current letter, publishes LetterStatusChangeEvent to SNS
37+
→ upsert-letter (SQS consumer)
38+
→ updates letter status in DynamoDB (letters table)
39+
→ DynamoDB Stream → Kinesis
40+
→ update-letter-queue: removes letter from pending queue
41+
→ letter-updates-transformer: publishes LetterStatusChangeEvent to SNS
42+
```
43+
44+
### MI submission
45+
46+
```
47+
Supplier calls POST /mi (api-handler)
48+
→ persists MI record to DynamoDB (mi table)
49+
→ DynamoDB Stream → Kinesis
50+
→ mi-updates-transformer: publishes MISubmittedEvent to SNS
51+
```
52+
53+
### Supplier config ingestion
54+
55+
```
56+
Supplier config event (SNS, type prefix uk.nhs.notify.supplier-config)
57+
→ supplier-config SQS queue
58+
→ supplier-config-ingress (SQS consumer)
59+
→ upserts entity into supplier-config DynamoDB table
60+
```
61+
62+
## Lambda Packages
63+
64+
### API Handler
65+
66+
{% include components/generated/readmes/lambda-api-handler.md %}
67+
68+
### Authorizer
69+
70+
{% include components/generated/readmes/lambda-authorizer.md %}
71+
72+
### Supplier Allocator
73+
74+
{% include components/generated/readmes/lambda-supplier-allocator.md %}
75+
76+
### Upsert Letter
77+
78+
{% include components/generated/readmes/lambda-upsert-letter.md %}
79+
80+
### Update Letter Queue
81+
82+
{% include components/generated/readmes/lambda-update-letter-queue.md %}
83+
84+
### Letter Updates Transformer
85+
86+
{% include components/generated/readmes/lambda-letter-updates-transformer.md %}
87+
88+
### MI Updates Transformer
89+
90+
{% include components/generated/readmes/lambda-mi-updates-transformer.md %}
91+
92+
### Supplier Config Ingress
93+
94+
{% include components/generated/readmes/lambda-supplier-config-ingress.md %}
95+
96+
## Internal Packages
97+
98+
### Datastore
99+
100+
{% include components/generated/readmes/internal-datastore.md %}
101+
102+
### Events
103+
104+
{% include components/generated/readmes/internal-events.md %}
105+
106+
### Event Builders
107+
108+
{% include components/generated/readmes/internal-event-builders.md %}
109+
110+
### Helpers
111+
112+
{% include components/generated/readmes/internal-helpers.md %}
113+
114+
## Tests
115+
116+
{% include components/generated/readmes/tests-overview.md %}
117+
118+
## Sandbox
119+
120+
{% include components/generated/readmes/sandbox.md %}
121+
122+
## Supplier Configuration
123+
124+
{% include components/generated/readmes/config-suppliers.md %}

docs/generate-includes.sh

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,32 @@ cd "$(git rev-parse --show-toplevel)"
77
mkdir -p ./docs/_includes/components/generated
88

99
# Database mermaid diagrams
10+
# NOTE: This also regenerates internal/datastore/src/types.md as a side effect.
11+
# Review any changes to that file before committing.
1012
npm run -w internal/datastore diagrams
1113
cp ./internal/datastore/src/types.md ./docs/_includes/components/generated/types.md
1214

1315
#Contributing file
1416
cp ./CONTRIBUTING.md ./docs/_includes/components/generated/contributing.md
17+
18+
# Function documentation (lambdas)
19+
mkdir -p ./docs/_includes/components/generated/readmes
20+
cp ./lambdas/api-handler/README.md ./docs/_includes/components/generated/readmes/lambda-api-handler.md
21+
cp ./lambdas/authorizer/README.md ./docs/_includes/components/generated/readmes/lambda-authorizer.md
22+
cp ./lambdas/supplier-allocator/README.md ./docs/_includes/components/generated/readmes/lambda-supplier-allocator.md
23+
cp ./lambdas/upsert-letter/README.md ./docs/_includes/components/generated/readmes/lambda-upsert-letter.md
24+
cp ./lambdas/update-letter-queue/README.md ./docs/_includes/components/generated/readmes/lambda-update-letter-queue.md
25+
cp ./lambdas/letter-updates-transformer/README.md ./docs/_includes/components/generated/readmes/lambda-letter-updates-transformer.md
26+
cp ./lambdas/mi-updates-transformer/README.md ./docs/_includes/components/generated/readmes/lambda-mi-updates-transformer.md
27+
cp ./lambdas/supplier-config-ingress/README.md ./docs/_includes/components/generated/readmes/lambda-supplier-config-ingress.md
28+
29+
# Function documentation (internal packages)
30+
cp ./internal/datastore/README.md ./docs/_includes/components/generated/readmes/internal-datastore.md
31+
cp ./internal/events/README.md ./docs/_includes/components/generated/readmes/internal-events.md
32+
cp ./internal/event-builders/README.md ./docs/_includes/components/generated/readmes/internal-event-builders.md
33+
cp ./internal/helpers/README.md ./docs/_includes/components/generated/readmes/internal-helpers.md
34+
35+
# Function documentation (other)
36+
cp ./tests/README.md ./docs/_includes/components/generated/readmes/tests-overview.md
37+
cp ./sandbox/README.md ./docs/_includes/components/generated/readmes/sandbox.md
38+
cp ./config/suppliers/README.md ./docs/_includes/components/generated/readmes/config-suppliers.md

internal/README.md

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
# Internal Packages
2+
3+
## Purpose
4+
5+
This directory contains shared workspace packages that export code used by other packages in the repository (especially Lambda workspaces).
6+
7+
These packages provide common schemas, datastore repositories, event builders, and helper utilities so implementation code can reuse a single source of truth.
8+
9+
## What is here
10+
11+
- `datastore/`: shared DynamoDB repositories, domain types, and related errors.
12+
- `events/`: shared event schemas and TypeScript types.
13+
- `event-builders/`: shared mappers/builders for CloudEvent payloads.
14+
- `helpers/`: shared logging, metrics, environment, and utility helpers.
15+
16+
## Usage
17+
18+
Import from these packages in other workspaces rather than duplicating logic locally.

internal/datastore/README.md

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
# @internal/datastore
2+
3+
## Purpose
4+
5+
Shared data-access layer providing DynamoDB repository implementations, domain types, and error classes for the supplier API.
6+
7+
## General Structure
8+
9+
- **`src/types.ts`**: Zod schemas and TypeScript types for `Letter`, `InsertLetter`, `UpdateLetter`, `PendingLetter`, `MI`, `Supplier`, and related entities.
10+
- **`src/letter-repository.ts`**: `LetterRepository` — CRUD for the main letters DynamoDB table
11+
- **`src/letter-queue-repository.ts`**: `LetterQueueRepository` — manages the pending letter queue projection table:
12+
- `getLetters`: paginated query with visibility-timeout update to prevent duplicate dispatch.
13+
- **`src/mi-repository.ts`**: `MIRepository` — writes MI records.
14+
- **`src/supplier-repository.ts`**: `SupplierRepository` — looks up suppliers by APIM application ID / supplierId.
15+
- **`src/supplier-config-repository.ts`**: `SupplierConfigRepository` — reads letter variants, volume groups, supplier allocations, pack specifications, and supplier packs from the config table.
16+
- **`src/supplier-quotas-repository.ts`**: `SupplierQuotasRepository` — reads and writes daily and overall allocation counts used by the supplier-allocator.
17+
- **`src/healthcheck.ts`**: `DBHealthcheck` — verifies DynamoDB table and S3 bucket connectivity.
18+
- **`src/errors/`**: Common application errors, which determine some of the API's error responses.
19+
20+
## Key Integration Points
21+
22+
- **`@nhsdigital/nhs-notify-event-schemas-supplier-config`**: Zod schemas for supplier configuration entities.
23+
- **Consumers**: every Lambda in this repository depends on this package.
24+
25+
## Nuances and Peculiarities
26+
27+
- The **letter queue table** is a separate DynamoDB table from the main letters table. It acts as a priority queue projection, maintained by the `update-letter-queue` Lambda from Kinesis stream events.
28+
- `LetterQueueRepository` implements a **visibility timeout** pattern: each returned letter's `visibilityTimestamp` is updated so subsequent queries within the timeout window skip it, preventing duplicate dispatch.

internal/event-builders/README.md

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,19 @@
11
# @internal/event-builders
22

3-
Helper utilities to produce supplier api event types.
3+
## Purpose
44

5-
This package contains functions for constructing CloudEvent-compliant event payloads and related helpers.
5+
Provides functions to construct CloudEvent-compliant payloads from domain entities. Centralises event-building logic so that multiple lambdas produce structurally identical events.
66

7-
Independent package to allow for type imports across the project without circular dependencies.
7+
## General Structure
8+
9+
- **`src/letter-mapper.ts`**: exports `mapLetterToCloudEvent(letter, source)` which converts a `Letter` domain object into a full `LetterStatusChangeEvent` CloudEvent.
10+
11+
## Key Integration Points
12+
13+
- **Used by**: `letter-updates-transformer` (publish letter updates to core) and `amendment-event-transformer` (process incoming letter updates).
14+
15+
## Nuances and Peculiarities
16+
17+
- Each call to `mapLetterToCloudEvent` generates a fresh `randomUUID` for the event `id` and random bytes for `traceparent`, so the same letter domain object will produce a unique event every time it is called.
18+
- The `data.origin.event` field is set to the **new event ID** (not the original event that created the letter), establishing a fresh trace link per emission.
19+
- Independent package to allow for type imports across the project without circular dependencies.

internal/events/README.md

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
# @nhsdigital/nhs-notify-event-schemas-supplier-api
2+
3+
## Purpose
4+
5+
Defines the Zod schemas and TypeScript types for all CloudEvents produced and consumed within the supplier API domain. Published externally as `@nhsdigital/nhs-notify-event-schemas-supplier-api` and used by both internal lambdas and external consumers.
6+
7+
## General Structure
8+
9+
- **`src/domain/letter.ts`**: defines the `$Letter` schema (extends `DomainBase`) and `$LetterStatus`.
10+
- **`src/domain/mi.ts`**: defines the `$MI` schema for management information records.
11+
- **`src/events/event-envelope.ts`**: the `EventEnvelope` factory that creates the main schema for any domain entity. Generates `type` as `uk.nhs.notify.supplier-api.<resource>.<status>.v1`, representing letter status change events and MI submissions.
12+
- **`src/events/letter-events.ts`**: exports the `$LetterStatusChangeEvent` schema for letter lifecycle events.
13+
- **`src/events/mi-events.ts`**: exports `$MISubmittedEvent` for MI submission CloudEvents.
14+
15+
## Key Integration Points
16+
17+
- `upsert-letter` uses `$LetterStatusChangeEvent` on its update path. The same handler uses letter-rendering schemas (`LetterRequestPreparedEvent` v1/v2) for inserts, so `LetterStatusChangeEvent` is the key discriminator for *update* processing.
18+
- `api-handler` (`transformAmendmentEvent`) emits `LetterStatusChangeEvent` after supplier update commands are accepted and enriched.
19+
- `letter-updates-transformer` emits `LetterStatusChangeEvent` for downstream consumers when a letter is inserted or when `status` or `reasonCode` changes.
20+
- Those published events are consumed through EventPub by Core (and any other downstream consumers).
21+
- Published to npm for external consumers who need to parse or validate supplier API events.
22+
23+
## Nuances and Peculiarities
24+
25+
- **`LetterStatusChangeEvent` is a central domain event, not just a transport type.** It links supplier-facing updates, async persistence, and outbound publication to Core.
26+
- **Supplier status updates become domain events.** When suppliers call `PATCH /letters/{id}` or `POST /letters`, the API handler enqueues `UpdateLetterCommand` messages. These are transformed into `LetterStatusChangeEvent` payloads and ultimately persisted by `upsert-letter`.
27+
- **Outbound lifecycle publication uses the same event shape.** `letter-updates-transformer` emits `LetterStatusChangeEvent` on letter INSERT and on `status`/`reasonCode` updates, which are then routed to downstream subscribers.
28+
- **Must remain free of internal dependencies.** Since this package is published externally, it cannot import from `@internal/datastore`, `@internal/helpers`, or any other internal workspace package. All types are self-contained.
29+
- **Schema version alignment**: the `dataschemaversion` in CloudEvents is derived from this package's `package.json` version, so bumping the package version directly affects the schema version in all emitted events. There's a GH workflow related to this.

0 commit comments

Comments
 (0)