Model/deployment/terraform/lambda/modelling_e2e/main.tf
Jun-te Kim f0f9c8d8d7 feat(terraform): point modelling_e2e at the historic-EPC backup (ADR-0054)
Sets HISTORIC_EPC_S3_ROOT, the env var the handler reads to build the historic-EPC
reader. With it set, an EPC-less Property whose expired pre-2012 certificate is in
the backup has its prediction conditioned on that certificate and persisted as
source="expired" rather than "predicted" — which is what lets reporting tell "no EPC
at all" apart from "only an expired one".

The bucket comes from the shared remote state rather than a rebuilt string:
`retrofit_sap_data_bucket_name` is — despite the name — the retrofit-data-<stage>
bucket (shared/main.tf:167), and engine and fast-api already read it from that output.
modelling_e2e was the outlier hardcoding "retrofit-data-${var.stage}".

No IAM change: modelling_e2e_s3_read already grants GetObject + ListBucket across the
whole retrofit-data-<stage> bucket, which covers historical_epc/.

Note this makes 'expired' rows start appearing once deployed. assessment-model's
epcSources.ts joins only 'lodged' and 'predicted', so until it addresses the predicted
slot — source IN ('predicted','expired') — an 'expired' row matches neither and the
home drops out of both the "Homes Without an EPC" and "Expired EPCs" cards.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-14 12:08:29 +00:00

82 lines
3.4 KiB
HCL
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

data "terraform_remote_state" "shared" {
backend = "s3"
config = {
bucket = "assessment-model-terraform-state"
key = "env:/${var.stage}/terraform.tfstate"
region = "eu-west-2"
}
}
data "aws_secretsmanager_secret_version" "db_credentials" {
secret_id = "${var.stage}/assessment_model/db_credentials"
}
locals {
db_credentials = jsondecode(data.aws_secretsmanager_secret_version.db_credentials.secret_string)
# Per-container minimum gap (seconds) between Google Solar API calls. The Solar
# API caps the whole GCP project at a hard 600 QPM shared across Building
# Insights + Data Layers. Each of the up-to-`maximum_concurrency` containers
# loops its ~50-property batch sequentially, so pacing each one to
# 0.8 (safety headroom) × 600 QPM ÷ 60 ÷ N keeps the fleet sum under the
# ceiling — eliminating the sustained 429 storm that per-process backoff alone
# can't ride out. Derived from `maximum_concurrency` so N has one source of
# truth. At N=32 → 4.0s. Consumed by the handler's throttle (see
# infrastructure/solar/google_solar_api_client.py).
solar_min_request_interval_seconds = var.maximum_concurrency / (0.8 * 600 / 60)
# Root of the expired-EPC backup — the old register's final dump, sharded one
# `{POSTCODE}/data.csv.gz` per postcode under `historical_epc/` in the data bucket.
# Turns on Expired-Enhanced Prediction (ADR-0054): a prediction conditioned by the
# dwelling's expired pre-2012 certificate is persisted as source="expired" rather
# than "predicted", so reporting can tell "no EPC at all" apart from "only an
# expired one".
#
# `retrofit_sap_data_bucket_name` is — despite the name — the `retrofit-data-<stage>`
# bucket (shared/main.tf); engine and fast-api already take it from this output rather
# than rebuilding the string, so the bucket name has one source of truth. Reads need no
# IAM change: modelling_e2e_s3_read already grants GetObject + ListBucket across that
# whole bucket, which covers historical_epc/.
historic_epc_s3_root = "s3://${data.terraform_remote_state.shared.outputs.retrofit_sap_data_bucket_name}/historical_epc"
}
module "lambda" {
source = "../../modules/lambda_with_sqs"
name = var.lambda_name
stage = var.stage
image_uri = local.image_uri
reserved_concurrent_executions = var.reserved_concurrent_executions
batch_size = var.batch_size
maximum_concurrency = var.maximum_concurrency
timeout = 900
memory_size = 3008
environment = {
STAGE = var.stage
LOG_LEVEL = "info"
POSTGRES_USERNAME = local.db_credentials.db_assessment_model_username
POSTGRES_PASSWORD = local.db_credentials.db_assessment_model_password
POSTGRES_HOST = var.db_host
POSTGRES_DATABASE = var.db_name
POSTGRES_PORT = var.db_port
OPEN_EPC_API_TOKEN = var.open_epc_api_token
GOOGLE_SOLAR_API_KEY = var.google_solar_api_key
DATA_BUCKET = "retrofit-data-${var.stage}"
SOLAR_MIN_REQUEST_INTERVAL_SECONDS = tostring(local.solar_min_request_interval_seconds)
# Expired-Enhanced Prediction (ADR-0054). Empty ⇒ the handler never builds the
# reader and every prediction stays plain "predicted".
HISTORIC_EPC_S3_ROOT = local.historic_epc_s3_root
}
}
resource "aws_iam_role_policy_attachment" "modelling_e2e_s3_read" {
role = module.lambda.role_name
policy_arn = data.terraform_remote_state.shared.outputs.modelling_e2e_s3_read_arn
}