mirror of
https://github.com/Sea-Haven-Industries/workorder-ingest.git
synced 2026-05-18 20:20:12 +00:00
Add CI workflow and apply ruff formatting (#10)
This commit is contained in:
parent
885379f85d
commit
853fb3253f
7 changed files with 50 additions and 20 deletions
12
.github/workflows/ci.yaml
vendored
Normal file
12
.github/workflows/ci.yaml
vendored
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
name: CI
|
||||
on:
|
||||
pull_request:
|
||||
branches: [main]
|
||||
|
||||
jobs:
|
||||
ci:
|
||||
uses: Sea-Haven-Industries/.github/.github/workflows/ci-python-sam.yaml@main
|
||||
with:
|
||||
source-dirs: "lambdas cdk shared"
|
||||
run-cdk-synth: true
|
||||
run-sam-validate: false
|
||||
|
|
@ -3,7 +3,9 @@ import aws_cdk as cdk
|
|||
from stack import WorkorderIngestStack
|
||||
|
||||
app = cdk.App()
|
||||
WorkorderIngestStack(app, "workorder-ingest",
|
||||
WorkorderIngestStack(
|
||||
app,
|
||||
"workorder-ingest",
|
||||
stack_name="WorkorderIngestStack",
|
||||
env=cdk.Environment(region="us-east-1"),
|
||||
)
|
||||
|
|
|
|||
32
cdk/stack.py
32
cdk/stack.py
|
|
@ -12,7 +12,6 @@ from aws_cdk import (
|
|||
aws_ses as ses,
|
||||
aws_ses_actions as ses_actions,
|
||||
aws_secretsmanager as secretsmanager,
|
||||
aws_iam as iam,
|
||||
)
|
||||
from constructs import Construct
|
||||
|
||||
|
|
@ -23,7 +22,8 @@ class WorkorderIngestStack(Stack):
|
|||
|
||||
# --- S3 bucket for raw emails ---
|
||||
email_bucket = s3.Bucket(
|
||||
self, "EmailBucket",
|
||||
self,
|
||||
"EmailBucket",
|
||||
bucket_name=f"workorder-ingest-emails-{self.account}",
|
||||
removal_policy=RemovalPolicy.RETAIN,
|
||||
lifecycle_rules=[
|
||||
|
|
@ -33,7 +33,8 @@ class WorkorderIngestStack(Stack):
|
|||
|
||||
# --- DynamoDB tables ---
|
||||
work_orders_table = dynamodb.Table(
|
||||
self, "WorkOrdersTable",
|
||||
self,
|
||||
"WorkOrdersTable",
|
||||
table_name="WorkOrders",
|
||||
partition_key=dynamodb.Attribute(
|
||||
name="work_order_id",
|
||||
|
|
@ -68,7 +69,8 @@ class WorkorderIngestStack(Stack):
|
|||
)
|
||||
|
||||
comments_table = dynamodb.Table(
|
||||
self, "CommentsTable",
|
||||
self,
|
||||
"CommentsTable",
|
||||
table_name="WorkOrderComments",
|
||||
partition_key=dynamodb.Attribute(
|
||||
name="work_order_id",
|
||||
|
|
@ -84,14 +86,16 @@ class WorkorderIngestStack(Stack):
|
|||
|
||||
# --- Secrets Manager for Anthropic API key ---
|
||||
anthropic_secret = secretsmanager.Secret(
|
||||
self, "AnthropicApiKey",
|
||||
self,
|
||||
"AnthropicApiKey",
|
||||
secret_name="workorder-ingest/anthropic-api-key",
|
||||
description="Anthropic API key for work order email parsing",
|
||||
)
|
||||
|
||||
# --- Lambda function ---
|
||||
email_processor = lambda_.Function(
|
||||
self, "EmailProcessor",
|
||||
self,
|
||||
"EmailProcessor",
|
||||
function_name="workorder-email-processor",
|
||||
runtime=lambda_.Runtime.PYTHON_3_12,
|
||||
handler="handler.handler",
|
||||
|
|
@ -101,10 +105,11 @@ class WorkorderIngestStack(Stack):
|
|||
image=lambda_.Runtime.PYTHON_3_12.bundling_image,
|
||||
platform="linux/amd64",
|
||||
command=[
|
||||
"bash", "-c",
|
||||
"bash",
|
||||
"-c",
|
||||
"pip install --platform manylinux2014_x86_64 --only-binary=:all: "
|
||||
"-r requirements.txt -t /asset-output && "
|
||||
"cp -r . /asset-output/"
|
||||
"cp -r . /asset-output/",
|
||||
],
|
||||
),
|
||||
),
|
||||
|
|
@ -132,7 +137,9 @@ class WorkorderIngestStack(Stack):
|
|||
|
||||
# --- SES Receipt Rule ---
|
||||
rule_set = ses.ReceiptRuleSet.from_receipt_rule_set_name(
|
||||
self, "ExistingRuleSet", "INBOUND_MAIL",
|
||||
self,
|
||||
"ExistingRuleSet",
|
||||
"INBOUND_MAIL",
|
||||
)
|
||||
|
||||
rule_set.add_rule(
|
||||
|
|
@ -148,7 +155,8 @@ class WorkorderIngestStack(Stack):
|
|||
|
||||
# --- Web UI Lambda ---
|
||||
web_ui = lambda_.Function(
|
||||
self, "WebUI",
|
||||
self,
|
||||
"WebUI",
|
||||
function_name="workorder-web-ui",
|
||||
runtime=lambda_.Runtime.PYTHON_3_12,
|
||||
handler="handler.handler",
|
||||
|
|
@ -169,4 +177,6 @@ class WorkorderIngestStack(Stack):
|
|||
auth_type=lambda_.FunctionUrlAuthType.NONE,
|
||||
)
|
||||
|
||||
cdk.CfnOutput(self, "WebUIUrl", value=web_url.url, description="Work Order Dashboard URL")
|
||||
cdk.CfnOutput(
|
||||
self, "WebUIUrl", value=web_url.url, description="Work Order Dashboard URL"
|
||||
)
|
||||
|
|
|
|||
|
|
@ -13,7 +13,6 @@ import os
|
|||
import re
|
||||
from datetime import datetime
|
||||
from email import policy
|
||||
from typing import Optional
|
||||
|
||||
import anthropic
|
||||
import boto3
|
||||
|
|
@ -253,7 +252,9 @@ def handler(event, context):
|
|||
|
||||
# Extract structured data with Claude
|
||||
parsed = extract_with_claude(email_data)
|
||||
logger.info(f"Parsed: type={parsed.get('email_type')}, wo={parsed.get('work_order_id')}")
|
||||
logger.info(
|
||||
f"Parsed: type={parsed.get('email_type')}, wo={parsed.get('work_order_id')}"
|
||||
)
|
||||
|
||||
if not parsed.get("work_order_id"):
|
||||
logger.warning(f"No work order ID found in email, skipping: {key}")
|
||||
|
|
|
|||
|
|
@ -1,5 +1,4 @@
|
|||
import email
|
||||
import json
|
||||
import logging
|
||||
import os
|
||||
import re
|
||||
|
|
@ -120,6 +119,8 @@ def handler(event, context):
|
|||
}
|
||||
)
|
||||
|
||||
logger.info(f"Saved vendor reply: WO={wo_number} DSP={dispatch_number} from={sender_email}")
|
||||
logger.info(
|
||||
f"Saved vendor reply: WO={wo_number} DSP={dispatch_number} from={sender_email}"
|
||||
)
|
||||
|
||||
return {"statusCode": 200, "body": "OK"}
|
||||
|
|
|
|||
|
|
@ -5,9 +5,7 @@ Serves a simple HTML dashboard for viewing work orders and comments.
|
|||
Accessed via Lambda Function URL.
|
||||
"""
|
||||
|
||||
import json
|
||||
import os
|
||||
import urllib.parse
|
||||
|
||||
import boto3
|
||||
|
||||
|
|
@ -71,7 +69,9 @@ def render_work_order_detail(wo, events):
|
|||
created = e.get("created_at", "")
|
||||
text = e.get("text", "")
|
||||
badge = render_badge(record_type, RECORD_TYPE_COLORS)
|
||||
commenter_str = f"<strong>{commenter}</strong> — " if commenter else ""
|
||||
commenter_str = (
|
||||
f"<strong>{commenter}</strong> — " if commenter else ""
|
||||
)
|
||||
|
||||
border_colors = {
|
||||
"new_work_order": "#3b82f6",
|
||||
|
|
@ -95,7 +95,10 @@ def render_work_order_detail(wo, events):
|
|||
fields = [
|
||||
("Description", wo.get("description")),
|
||||
("Status", render_badge(wo.get("wo_status", "unknown"), STATUS_COLORS)),
|
||||
("Record Type", render_badge(wo.get("record_type", "unknown"), RECORD_TYPE_COLORS)),
|
||||
(
|
||||
"Record Type",
|
||||
render_badge(wo.get("record_type", "unknown"), RECORD_TYPE_COLORS),
|
||||
),
|
||||
("Site Code", wo.get("site_code")),
|
||||
("Building", wo.get("building")),
|
||||
("Address", wo.get("address")),
|
||||
|
|
|
|||
|
|
@ -63,6 +63,7 @@ class WorkOrder:
|
|||
@dataclass
|
||||
class ParsedEmail:
|
||||
"""Result of AI parsing an inbound email."""
|
||||
|
||||
email_type: str # EmailType value
|
||||
work_order_id: Optional[str] = None
|
||||
description: Optional[str] = None
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue