mirror of
https://github.com/Sea-Haven-Industries/procurement-ingest.git
synced 2026-09-30 11:53:13 +00:00
186 lines
6.2 KiB
Python
186 lines
6.2 KiB
Python
|
|
"""CDK stack for the work order email ingestion pipeline."""
|
||
|
|
|
||
|
|
import aws_cdk as cdk
|
||
|
|
from aws_cdk import (
|
||
|
|
Duration,
|
||
|
|
RemovalPolicy,
|
||
|
|
Stack,
|
||
|
|
aws_dynamodb as dynamodb,
|
||
|
|
aws_lambda as lambda_,
|
||
|
|
aws_logs as logs,
|
||
|
|
aws_s3 as s3,
|
||
|
|
aws_s3_notifications as s3n,
|
||
|
|
aws_ses as ses,
|
||
|
|
aws_ses_actions as ses_actions,
|
||
|
|
aws_secretsmanager as secretsmanager,
|
||
|
|
)
|
||
|
|
from constructs import Construct
|
||
|
|
|
||
|
|
|
||
|
|
class WorkorderIngestStack(Stack):
|
||
|
|
def __init__(self, scope: Construct, construct_id: str, **kwargs):
|
||
|
|
super().__init__(scope, construct_id, **kwargs)
|
||
|
|
|
||
|
|
# --- S3 bucket for raw emails ---
|
||
|
|
email_bucket = s3.Bucket(
|
||
|
|
self,
|
||
|
|
"EmailBucket",
|
||
|
|
bucket_name=f"workorder-ingest-emails-{self.account}",
|
||
|
|
removal_policy=RemovalPolicy.RETAIN,
|
||
|
|
lifecycle_rules=[
|
||
|
|
s3.LifecycleRule(expiration=Duration.days(90)),
|
||
|
|
],
|
||
|
|
)
|
||
|
|
|
||
|
|
# --- DynamoDB tables ---
|
||
|
|
work_orders_table = dynamodb.Table(
|
||
|
|
self,
|
||
|
|
"WorkOrdersTable",
|
||
|
|
table_name="WorkOrders",
|
||
|
|
partition_key=dynamodb.Attribute(
|
||
|
|
name="work_order_id",
|
||
|
|
type=dynamodb.AttributeType.STRING,
|
||
|
|
),
|
||
|
|
billing_mode=dynamodb.BillingMode.PAY_PER_REQUEST,
|
||
|
|
removal_policy=RemovalPolicy.RETAIN,
|
||
|
|
)
|
||
|
|
work_orders_table.add_global_secondary_index(
|
||
|
|
index_name="site-code-index",
|
||
|
|
partition_key=dynamodb.Attribute(
|
||
|
|
name="site_code",
|
||
|
|
type=dynamodb.AttributeType.STRING,
|
||
|
|
),
|
||
|
|
sort_key=dynamodb.Attribute(
|
||
|
|
name="updated_at",
|
||
|
|
type=dynamodb.AttributeType.STRING,
|
||
|
|
),
|
||
|
|
)
|
||
|
|
work_orders_table.add_global_secondary_index(
|
||
|
|
index_name="status-index",
|
||
|
|
partition_key=dynamodb.Attribute(
|
||
|
|
name="wo_status",
|
||
|
|
type=dynamodb.AttributeType.STRING,
|
||
|
|
),
|
||
|
|
sort_key=dynamodb.Attribute(
|
||
|
|
name="updated_at",
|
||
|
|
type=dynamodb.AttributeType.STRING,
|
||
|
|
),
|
||
|
|
)
|
||
|
|
|
||
|
|
comments_table = dynamodb.Table(
|
||
|
|
self,
|
||
|
|
"CommentsTable",
|
||
|
|
table_name="WorkOrderComments",
|
||
|
|
partition_key=dynamodb.Attribute(
|
||
|
|
name="work_order_id",
|
||
|
|
type=dynamodb.AttributeType.STRING,
|
||
|
|
),
|
||
|
|
sort_key=dynamodb.Attribute(
|
||
|
|
name="comment_id",
|
||
|
|
type=dynamodb.AttributeType.STRING,
|
||
|
|
),
|
||
|
|
billing_mode=dynamodb.BillingMode.PAY_PER_REQUEST,
|
||
|
|
removal_policy=RemovalPolicy.RETAIN,
|
||
|
|
)
|
||
|
|
|
||
|
|
# --- Secrets Manager for Anthropic API key ---
|
||
|
|
anthropic_secret = secretsmanager.Secret(
|
||
|
|
self,
|
||
|
|
"AnthropicApiKey",
|
||
|
|
secret_name="workorder-ingest/anthropic-api-key",
|
||
|
|
description="Anthropic API key for work order email parsing",
|
||
|
|
removal_policy=RemovalPolicy.RETAIN,
|
||
|
|
)
|
||
|
|
|
||
|
|
# --- Lambda function ---
|
||
|
|
email_processor = lambda_.Function(
|
||
|
|
self,
|
||
|
|
"EmailProcessor",
|
||
|
|
function_name="workorder-email-processor",
|
||
|
|
runtime=lambda_.Runtime.PYTHON_3_12,
|
||
|
|
architecture=lambda_.Architecture.ARM_64,
|
||
|
|
handler="handler.handler",
|
||
|
|
code=lambda_.Code.from_asset(
|
||
|
|
"../lambdas/wo/email_processor",
|
||
|
|
bundling=cdk.BundlingOptions(
|
||
|
|
image=lambda_.Runtime.PYTHON_3_12.bundling_image,
|
||
|
|
command=[
|
||
|
|
"bash",
|
||
|
|
"-c",
|
||
|
|
"pip install --platform manylinux2014_aarch64 --only-binary=:all: "
|
||
|
|
"-r requirements.txt -t /asset-output && "
|
||
|
|
"cp -r . /asset-output/",
|
||
|
|
],
|
||
|
|
),
|
||
|
|
),
|
||
|
|
timeout=Duration.seconds(60),
|
||
|
|
memory_size=256,
|
||
|
|
log_retention=logs.RetentionDays.TWO_MONTHS,
|
||
|
|
environment={
|
||
|
|
"WORK_ORDERS_TABLE": work_orders_table.table_name,
|
||
|
|
"COMMENTS_TABLE": comments_table.table_name,
|
||
|
|
"ANTHROPIC_API_KEY_SECRET_ARN": anthropic_secret.secret_arn,
|
||
|
|
},
|
||
|
|
)
|
||
|
|
|
||
|
|
# Grant permissions
|
||
|
|
email_bucket.grant_read(email_processor)
|
||
|
|
work_orders_table.grant_read_write_data(email_processor)
|
||
|
|
comments_table.grant_read_write_data(email_processor)
|
||
|
|
anthropic_secret.grant_read(email_processor)
|
||
|
|
|
||
|
|
# S3 event notification -> Lambda
|
||
|
|
email_bucket.add_event_notification(
|
||
|
|
s3.EventType.OBJECT_CREATED,
|
||
|
|
s3n.LambdaDestination(email_processor),
|
||
|
|
s3.NotificationKeyFilter(prefix="inbound/"),
|
||
|
|
)
|
||
|
|
|
||
|
|
# --- SES Receipt Rule ---
|
||
|
|
rule_set = ses.ReceiptRuleSet.from_receipt_rule_set_name(
|
||
|
|
self,
|
||
|
|
"ExistingRuleSet",
|
||
|
|
"INBOUND_MAIL",
|
||
|
|
)
|
||
|
|
|
||
|
|
rule_set.add_rule(
|
||
|
|
"WorkorderEmailRule",
|
||
|
|
recipients=["apm@int.seahaven.com"],
|
||
|
|
actions=[
|
||
|
|
ses_actions.S3(
|
||
|
|
bucket=email_bucket,
|
||
|
|
object_key_prefix="inbound/",
|
||
|
|
),
|
||
|
|
],
|
||
|
|
)
|
||
|
|
|
||
|
|
# --- Web UI Lambda ---
|
||
|
|
web_ui = lambda_.Function(
|
||
|
|
self,
|
||
|
|
"WebUI",
|
||
|
|
function_name="workorder-web-ui",
|
||
|
|
runtime=lambda_.Runtime.PYTHON_3_12,
|
||
|
|
architecture=lambda_.Architecture.ARM_64,
|
||
|
|
handler="handler.handler",
|
||
|
|
code=lambda_.Code.from_asset("../lambdas/wo/web_ui"),
|
||
|
|
timeout=Duration.seconds(15),
|
||
|
|
memory_size=128,
|
||
|
|
log_retention=logs.RetentionDays.TWO_MONTHS,
|
||
|
|
environment={
|
||
|
|
"WORK_ORDERS_TABLE": work_orders_table.table_name,
|
||
|
|
"COMMENTS_TABLE": comments_table.table_name,
|
||
|
|
},
|
||
|
|
)
|
||
|
|
|
||
|
|
work_orders_table.grant_read_data(web_ui)
|
||
|
|
comments_table.grant_read_data(web_ui)
|
||
|
|
|
||
|
|
# Function URL for direct access
|
||
|
|
web_url = web_ui.add_function_url(
|
||
|
|
auth_type=lambda_.FunctionUrlAuthType.NONE,
|
||
|
|
)
|
||
|
|
|
||
|
|
cdk.CfnOutput(
|
||
|
|
self, "WebUIUrl", value=web_url.url, description="Work Order Dashboard URL"
|
||
|
|
)
|