Skip to content

Commit bf6f15e

Browse files
Merge branch 'main' into rob/413-ttc-not-using-nonstandardcodeinstance
2 parents 14f464a + 9d7a9ff commit bf6f15e

File tree

6 files changed

+124
-9
lines changed

6 files changed

+124
-9
lines changed

.github/workflows/deploy.yaml

Lines changed: 18 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,7 @@ jobs:
6060
- name: Create ECR repositories
6161
if: ${{ inputs.apply && !inputs.destroy }}
6262
working-directory: terraform
63-
run: terraform apply -auto-approve -target=aws_ecr_repository.index_lambda -target=aws_ecr_repository.ttc_lambda
63+
run: terraform apply -auto-approve -target=aws_ecr_repository.index_lambda -target=aws_ecr_repository.ttc_lambda -target=aws_ecr_repository.augmentation_lambda
6464

6565
- name: Login to Amazon ECR
6666
if: ${{ inputs.apply && !inputs.destroy }}
@@ -76,22 +76,36 @@ jobs:
7676
echo "index_ecr_url=$INDEX_ECR_URL" >> "$GITHUB_OUTPUT"
7777
ECR_URL=$(terraform output -raw ecr_repository_url)
7878
echo "ecr_url=$ECR_URL" >> "$GITHUB_OUTPUT"
79+
AUG_ECR_URL=$(terraform output -raw augmentation_ecr_repository_url)
80+
echo "aug_ecr_url=$AUG_ECR_URL" >> "$GITHUB_OUTPUT"
7981
8082
- name: Build and push Index Docker image
8183
if: ${{ inputs.apply && !inputs.destroy }}
8284
run: |
8385
INDEX_ECR_URL="${{ steps.ecr-url.outputs.index_ecr_url }}"
84-
docker build -f Dockerfile.index -t "$INDEX_ECR_URL:${{ github.sha }}" -t "$INDEX_ECR_URL:latest" .
86+
docker build -f Dockerfile.index -t "$INDEX_ECR_URL:${{ github.sha }}" -t "$INDEX_ECR_URL:latest" --secret id=huggingface_token,env=HF_TOKEN .
8587
docker push "$INDEX_ECR_URL:${{ github.sha }}"
8688
docker push "$INDEX_ECR_URL:latest"
89+
env:
90+
HF_TOKEN: ${{ secrets.HF_TOKEN }}
8791

8892
- name: Build and push TTC Docker image
8993
if: ${{ inputs.apply && !inputs.destroy }}
9094
run: |
9195
ECR_URL="${{ steps.ecr-url.outputs.ecr_url }}"
92-
docker build -f Dockerfile.ttc -t "$ECR_URL:${{ github.sha }}" -t "$ECR_URL:latest" .
96+
docker build -f Dockerfile.ttc -t "$ECR_URL:${{ github.sha }}" -t "$ECR_URL:latest" --secret id=huggingface_token,env=HF_TOKEN .
9397
docker push "$ECR_URL:${{ github.sha }}"
9498
docker push "$ECR_URL:latest"
99+
env:
100+
HF_TOKEN: ${{ secrets.HF_TOKEN }}
101+
102+
- name: Build and push Augmentation Docker image
103+
if: ${{ inputs.apply && !inputs.destroy }}
104+
run: |
105+
AUG_ECR_URL="${{ steps.ecr-url.outputs.aug_ecr_url }}"
106+
docker build -f Dockerfile.augmentation -t "$AUG_ECR_URL:${{ github.sha }}" -t "$AUG_ECR_URL:latest" .
107+
docker push "$AUG_ECR_URL:${{ github.sha }}"
108+
docker push "$AUG_ECR_URL:latest"
95109
96110
- name: Terraform Plan
97111
if: ${{ !inputs.apply && !inputs.destroy }}
@@ -101,7 +115,7 @@ jobs:
101115
- name: Terraform Apply
102116
if: ${{ inputs.apply && !inputs.destroy }}
103117
working-directory: terraform
104-
run: terraform apply -auto-approve -var="index_lambda_image_tag=${{ github.sha }}" -var="ttc_lambda_image_tag=${{ github.sha }}"
118+
run: terraform apply -auto-approve -var="index_lambda_image_tag=${{ github.sha }}" -var="ttc_lambda_image_tag=${{ github.sha }}" -var="augmentation_lambda_image_tag=${{ github.sha }}"
105119

106120
- name: Terraform Destroy
107121
if: ${{ inputs.destroy }}

Dockerfile.ttc

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,17 +14,26 @@ COPY ./packages/lambda-handler ${LAMBDA_TASK_ROOT}/lambda-handler
1414
RUN pip install --no-cache-dir "${LAMBDA_TASK_ROOT}/lambda-handler"
1515

1616
COPY ./packages/text-to-code ${LAMBDA_TASK_ROOT}/text-to-code
17+
# Install CPU-only PyTorch to reduce image size (Lambda doesn't have GPUs)
18+
RUN pip install --no-cache-dir torch==2.9.1 --index-url https://download.pytorch.org/whl/cpu
1719
RUN pip install --no-cache-dir "${LAMBDA_TASK_ROOT}/text-to-code"
1820

1921
COPY ./packages/text-to-code-lambda ${LAMBDA_TASK_ROOT}/text-to-code-lambda
2022
RUN pip install --no-cache-dir "${LAMBDA_TASK_ROOT}/text-to-code-lambda"
2123

24+
# Remove build tools no longer needed at runtime
25+
RUN rpm -e --nodeps gcc-c++ gcc cpp make && microdnf clean all && rm -rf /var/cache/*
26+
2227
# Download retriever at build time (private repo, needs token)
2328
RUN --mount=type=secret,id=huggingface_token,env=HF_TOKEN \
24-
python -c "from huggingface_hub import snapshot_download; snapshot_download(repo_id='NCHS/ttc-retriever-mvp', local_dir='/opt/retriever_model', ignore_patterns=['*.git*', '*.md', 'onnx/*', 'openvino/*', 'pytorch_model.bin'])"
29+
python -c "from huggingface_hub import snapshot_download; snapshot_download(repo_id='NCHS/ttc-retriever-mvp', local_dir='/opt/retriever_model', ignore_patterns=['*.git*', '*.md', 'onnx/*', 'openvino/*', 'pytorch_model.bin', 'tf_model.h5', 'flax_model.msgpack', 'model.onnx'])"
2530

2631
# Download reranker at build time (public repo)
27-
RUN python -c "from huggingface_hub import snapshot_download; snapshot_download(repo_id='cross-encoder/stsb-roberta-large', local_dir='/opt/reranker_model', ignore_patterns=['*.git*', '*.md', 'onnx/*', 'openvino/*', 'pytorch_model.bin'])"
32+
RUN python -c "from huggingface_hub import snapshot_download; snapshot_download(repo_id='cross-encoder/stsb-roberta-large', local_dir='/opt/reranker_model', ignore_patterns=['*.git*', '*.md', 'onnx/*', 'openvino/*', 'pytorch_model.bin', 'tf_model.h5', 'flax_model.msgpack', 'model.onnx'])" \
33+
&& rm -rf /root/.cache/huggingface
34+
35+
# Clean up package source copies
36+
RUN rm -rf ${LAMBDA_TASK_ROOT}/shared-models ${LAMBDA_TASK_ROOT}/lambda-handler ${LAMBDA_TASK_ROOT}/text-to-code ${LAMBDA_TASK_ROOT}/text-to-code-lambda
2837

2938
ENV RETRIEVER_MODEL_PATH="/opt/retriever_model"
3039
ENV RERANKER_MODEL_PATH="/opt/reranker_model"

terraform/README.md

Lines changed: 20 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -42,10 +42,11 @@ All components live inside a private VPC (no NAT gateway, no internet gateway).
4242

4343
- **ECR Repository** (`aws_ecr_repository.ttc_lambda`): Stores the Docker container image for the main TTC Lambda. The image installs all workspace Python packages (`shared-models`, `lambda-handler`, `text-to-code`, `text-to-code-lambda`) and bakes in the SentenceTransformer model (`intfloat/e5-large-v2`) at build time. Images are built and pushed by CI/CD during `terraform apply`.
4444
- **ECR Repository** (`aws_ecr_repository.index_lambda`): Stores the Docker container image for the index bootstrap Lambda, built from `Dockerfile.index` at repo root.
45+
- **ECR Repository** (`aws_ecr_repository.augmentation_lambda`): Stores the Docker container image for the augmentation Lambda, built from `Dockerfile.augmentation` at repo root.
4546

4647
### IAM (`main.tf`)
4748

48-
- **Lambda IAM Role** (`aws_iam_role.lambda_role`): Shared by both Lambda functions. Attached policies:
49+
- **Lambda IAM Role** (`aws_iam_role.lambda_role`): Shared by all Lambda functions (TTC, index, and augmentation). Attached policies:
4950
- `AWSLambdaVPCAccessExecutionRole` — allows ENI creation for VPC placement
5051
- `AWSLambdaBasicExecutionRole` — allows CloudWatch Logs writes
5152
- `AmazonS3FullAccess` — S3 read/write (TODO: scope down to specific bucket/prefix)
@@ -74,6 +75,21 @@ At runtime, the Lambda runs the real `text_to_code_lambda.lambda_function.handle
7475

7576
Environment variables injected at deploy time: `OPENSEARCH_ENDPOINT_URL`, `OPENSEARCH_INDEX`, `REGION`, `S3_BUCKET`, `RETRIEVER_MODEL_PATH`, `RERANKER_MODEL_PATH`, `EICR_INPUT_PREFIX`, `SCHEMATRON_ERROR_PREFIX`, `TTC_INPUT_PREFIX`, `TTC_OUTPUT_PREFIX`, `TTC_METADATA_PREFIX`.
7677

78+
#### Augmentation Lambda (`ttc-augmentation-lambda`, `Dockerfile.augmentation`)
79+
80+
Deployed as a **container image** from ECR (`package_type = "Image"`). The Docker image (`Dockerfile.augmentation` at repo root) installs the `augmentation-lambda` package along with its workspace dependencies (`shared-models`, `lambda-handler`, `augmentation`).
81+
82+
At runtime, the Lambda processes augmentation requests containing eICR XML and nonstandard code mappings from the TTC Lambda. It:
83+
84+
1. Parses incoming eICR XML and nonstandard code instances
85+
2. Inserts standardized LOINC/SNOMED `<translation>` elements into the eICR
86+
3. Updates document headers (ID, effectiveTime, setId, versionNumber) and adds author/provenance metadata
87+
4. Writes the augmented eICR XML and metadata JSON to S3
88+
89+
The augmentation Lambda uses only the Lambda security group (not the OpenSearch security group) since it does not require OpenSearch access. It is configured with lower memory (512 MB) and timeout (300s) defaults compared to the TTC Lambda, as it does not load ML models.
90+
91+
Environment variables injected at deploy time: `S3_BUCKET`, `AUGMENTED_EICR_PREFIX`, `AUGMENTATION_METADATA_PREFIX`, `REGION`.
92+
7793
### OpenSearch Ingestion Pipeline (`main.tf`)
7894

7995
An **AWS OpenSearch Ingestion Service (OSIS)** pipeline (`aws_osis_pipeline.ttc_ingestion_pipeline`) that:
@@ -91,13 +107,14 @@ The pipeline **depends on** the index bootstrap invocation completing first, ens
91107
Terraform manages dependency ordering automatically, but conceptually the sequence is:
92108

93109
1. VPC, subnets, security groups, S3 endpoint created
94-
2. ECR repositories created (TTC lambda + index lambda)
110+
2. ECR repositories created (TTC lambda, index lambda, augmentation lambda)
95111
3. Docker images built and pushed to ECR (in CI/CD, before full `terraform apply`)
96112
4. OpenSearch domain and VPC endpoint created
97113
5. Lambda IAM role created
98114
6. Index bootstrap Lambda deployed and **immediately invoked** — creates the KNN index in OpenSearch
99115
7. Ingestion pipeline deployed — begins polling S3 for NDJSON embeddings to load
100116
8. Main TTC Lambda deployed with container image from ECR — loads model at cold start, ready to serve KNN queries
117+
9. Augmentation Lambda deployed with container image from ECR — ready to process augmentation requests
101118

102119
## State Backend
103120

@@ -134,7 +151,7 @@ Before running `terraform apply`:
134151

135152
1. **Bootstrap**: Run `terraform apply` in `bootstrap/` first to create the S3 state bucket and DynamoDB lock table.
136153
2. **Embedding files**: Upload NDJSON embedding files to `s3://dibbs-text-to-code/ingestion/`. The OSIS pipeline will ingest these into OpenSearch.
137-
3. **Docker**: CI/CD builds both container images (`Dockerfile.ttc` for TTC lambda, `Dockerfile.index` for index lambda) automatically. For local development, Docker must be available to build the images.
154+
3. **Docker**: CI/CD builds all container images (`Dockerfile.ttc` for TTC lambda, `Dockerfile.index` for index lambda, `Dockerfile.augmentation` for augmentation lambda) automatically. For local development, Docker must be available to build the images.
138155

139156
> **Note:** The SentenceTransformer model and heavy Python dependencies (sentence-transformers, torch) are baked into the Lambda container image at build time via the Dockerfile. The Dockerfile installs the real `text-to-code-lambda` package and all its workspace dependencies.
140157

terraform/_outputs.tf

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,3 +33,13 @@ output "index_ecr_repository_url" {
3333
value = aws_ecr_repository.index_lambda.repository_url
3434
description = "The URL of the ECR repository for the index Lambda container image"
3535
}
36+
37+
output "augmentation_ecr_repository_url" {
38+
value = aws_ecr_repository.augmentation_lambda.repository_url
39+
description = "The URL of the ECR repository for the augmentation Lambda container image"
40+
}
41+
42+
output "augmentation_lambda_function_name" {
43+
value = aws_lambda_function.augmentation_lambda.function_name
44+
description = "The name of the augmentation lambda function"
45+
}

terraform/_variables.tf

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -147,6 +147,25 @@ variable "augmentation_metadata_prefix" {
147147
description = "S3 prefix for augmentation metadata files"
148148
}
149149

150+
### Augmentation Lambda Variables
151+
variable "augmentation_lambda_function_name" {
152+
type = string
153+
default = "ttc-augmentation-lambda"
154+
description = "The name of the augmentation lambda function"
155+
}
156+
157+
variable "augmentation_lambda_memory_size" {
158+
type = number
159+
default = 512
160+
description = "Memory allocation in MB for the augmentation lambda. Lower than the TTC lambda since no ML models are loaded."
161+
}
162+
163+
variable "augmentation_lambda_timeout" {
164+
type = number
165+
default = 300
166+
description = "Timeout in seconds for the augmentation lambda function"
167+
}
168+
150169
### Container Image Variables
151170
variable "ttc_lambda_image_tag" {
152171
type = string
@@ -160,3 +179,9 @@ variable "index_lambda_image_tag" {
160179
description = "The image tag for the index Lambda container image in ECR"
161180
}
162181

182+
variable "augmentation_lambda_image_tag" {
183+
type = string
184+
default = "latest"
185+
description = "The image tag for the augmentation Lambda container image in ECR"
186+
}
187+

terraform/main.tf

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,17 @@ resource "aws_ecr_repository" "index_lambda" {
3333
tags = local.tags
3434
}
3535

36+
resource "aws_ecr_repository" "augmentation_lambda" {
37+
name = "ttc-augmentation-lambda"
38+
force_delete = true
39+
40+
image_scanning_configuration {
41+
scan_on_push = true
42+
}
43+
44+
tags = local.tags
45+
}
46+
3647
#############
3748
# VPC
3849
# Note: If APHL wants to use their own VPC without this module, they will need to provide
@@ -480,3 +491,32 @@ resource "aws_lambda_function" "index_lambda" {
480491
tags = { Name = var.index_lambda_function_name }
481492
}
482493

494+
#############
495+
# Augmentation Lambda
496+
#############
497+
498+
resource "aws_lambda_function" "augmentation_lambda" {
499+
function_name = var.augmentation_lambda_function_name
500+
role = aws_iam_role.lambda_role.arn
501+
package_type = "Image"
502+
image_uri = "${aws_ecr_repository.augmentation_lambda.repository_url}:${var.augmentation_lambda_image_tag}"
503+
timeout = var.augmentation_lambda_timeout
504+
memory_size = var.augmentation_lambda_memory_size
505+
506+
vpc_config {
507+
subnet_ids = module.vpc.private_subnets
508+
security_group_ids = [aws_security_group.lambda_sg.id]
509+
}
510+
511+
environment {
512+
variables = {
513+
S3_BUCKET = var.s3_bucket
514+
AUGMENTED_EICR_PREFIX = var.augmented_eicr_prefix
515+
AUGMENTATION_METADATA_PREFIX = var.augmentation_metadata_prefix
516+
REGION = var.region
517+
}
518+
}
519+
520+
tags = { Name = var.augmentation_lambda_function_name }
521+
}
522+

0 commit comments

Comments
 (0)