This document describes the JSON message formats generated by the processor and sent to SQS.
Generated once per client, aggregating taxes paid across all their portfolios/accounts.
{
"type": "client_message",
"client_reference": "9e40659b-8b9f-4fc4-814b-5a7b5a23b64",
"tax_free_allowance": 801,
"taxes_paid": 0.00
}| Field | Type | Description |
|---|---|---|
type |
string | Always "client_message" |
client_reference |
string | Unique client identifier (UUID) |
tax_free_allowance |
number | Client's tax-free allowance amount |
taxes_paid |
number | Total taxes paid across all client's accounts |
taxes_paidis the sum oftaxes_paidfrom all accounts belonging to the client's portfolios- One message generated per unique client in the
clientsCSV - If a client has no portfolios,
taxes_paidwill be 0
Generated once per portfolio with transaction metrics and account balance.
{
"type": "portfolio_message",
"portfolio_reference": "90755e32-7438-4354-ad37-ad900e297844",
"cash_balance": 15000.00,
"number_of_transactions": 15,
"sum_of_deposits": 5000.00
}| Field | Type | Description |
|---|---|---|
type |
string | Always "portfolio_message" |
portfolio_reference |
string | Unique portfolio identifier (UUID) |
cash_balance |
number | Current cash balance from linked account |
number_of_transactions |
integer | Total count of all transactions |
sum_of_deposits |
number | Sum of all transactions with keyword "DEPOSIT" |
cash_balancecomes from the account linked to the portfolionumber_of_transactionscounts all transaction records for the accountsum_of_depositssums only transactions wherekeyword = "DEPOSIT"- One message per portfolio in the
portfoliosCSV
Generated when data validation fails or expected relationships are missing.
{
"type": "error_message",
"client_reference": "f4a0cc2c-d0b4-4f14-b202-c8a5e45e90e7",
"portfolio_reference": "439695b4-508d-4562-8576-670e70024627",
"message": "Account 123456789 not found for portfolio"
}| Field | Type | Description |
|---|---|---|
type |
string | Always "error_message" |
client_reference |
string or null | Related client reference if known |
portfolio_reference |
string or null | Related portfolio reference if known |
message |
string | Human-readable error description |
Error messages are generated for:
-
Missing Client Reference
- Portfolio references a client that doesn't exist in clients CSV
{ "type": "error_message", "client_reference": "unknown-client-id", "portfolio_reference": "port-123", "message": "Client reference unknown-client-id not found for portfolio" } -
Missing Account
- Portfolio references an account that doesn't exist in accounts CSV
{ "type": "error_message", "client_reference": "client-123", "portfolio_reference": "port-456", "message": "Account 987654321 not found for portfolio" } -
Invalid Data Format
- CSV record has invalid or missing required fields
{ "type": "error_message", "client_reference": "client-123", "portfolio_reference": null, "message": "Error processing client record: could not convert string to float: 'invalid'" } -
Processing Errors
- Unexpected errors during data processing
{ "type": "error_message", "client_reference": null, "portfolio_reference": null, "message": "Error processing transaction record: 'account_number'" }
- Messages are sent to SQS in batches (up to 10 per batch)
- Order is not guaranteed (Standard SQS queue)
- All client messages typically sent first, then portfolio messages, then errors
No custom message attributes are currently set. Each message only contains the JSON body.
The processor sends messages in batches using send_message_batch:
- Up to 10 messages per API call
- Reduces SQS API costs
- Failed messages in a batch are retried
Messages that fail processing after 3 attempts are moved to the DLQ:
- Queue:
{environment}-portfolio-messages-dlq - Retention: 14 days
- Requires manual investigation and replay
For the example data provided, the complete output would be:
// Client message for Frida Muller
{
"type": "client_message",
"client_reference": "9e40659b-8b9f-4fc4-814b-5a7b5a23b64",
"tax_free_allowance": 801,
"taxes_paid": 0.00
}
// Client message for Fritz Maier
{
"type": "client_message",
"client_reference": "f4a0cc2c-d0b4-4f14-b202-c8a5e45e90e7",
"tax_free_allowance": 0,
"taxes_paid": 789.56
}
// Portfolio message for first portfolio
{
"type": "portfolio_message",
"portfolio_reference": "90755e32-7438-4354-ad37-ad900e297844",
"cash_balance": 15000.00,
"number_of_transactions": 1,
"sum_of_deposits": 5000.00
}
// Portfolio message for second portfolio
{
"type": "portfolio_message",
"portfolio_reference": "439695b4-508d-4562-8576-670e70024627",
"cash_balance": -56.00,
"number_of_transactions": 1,
"sum_of_deposits": 0.00
}aws sqs receive-message \
--queue-url https://sqs.us-east-1.amazonaws.com/123456789/staging-portfolio-messages \
--max-number-of-messages 10 \
--wait-time-seconds 20import boto3
import json
sqs = boto3.client('sqs')
queue_url = 'your-queue-url'
while True:
response = sqs.receive_message(
QueueUrl=queue_url,
MaxNumberOfMessages=10,
WaitTimeSeconds=20
)
if 'Messages' in response:
for message in response['Messages']:
body = json.loads(message['Body'])
if body['type'] == 'client_message':
process_client_message(body)
elif body['type'] == 'portfolio_message':
process_portfolio_message(body)
elif body['type'] == 'error_message':
handle_error_message(body)
# Delete message after processing
sqs.delete_message(
QueueUrl=queue_url,
ReceiptHandle=message['ReceiptHandle']
)For consumers that want to validate messages:
from typing import Literal, Optional
from pydantic import BaseModel
class ClientMessage(BaseModel):
type: Literal["client_message"]
client_reference: str
tax_free_allowance: float
taxes_paid: float
class PortfolioMessage(BaseModel):
type: Literal["portfolio_message"]
portfolio_reference: str
cash_balance: float
number_of_transactions: int
sum_of_deposits: float
class ErrorMessage(BaseModel):
type: Literal["error_message"]
client_reference: Optional[str]
portfolio_reference: Optional[str]
message: strUse the provided test data:
# Upload test files
make upload-test-data
# Or manually
aws s3 cp example_data/clients_20200130.csv s3://your-bucket/
aws s3 cp example_data/portfolios_20200130.csv s3://your-bucket/
aws s3 cp example_data/accounts_20200130.csv s3://your-bucket/
aws s3 cp example_data/transactions_20200130.csv s3://your-bucket/
# Check CloudWatch Logs
aws logs tail /aws/lambda/staging-csv-processor --follow
# Receive messages
aws sqs receive-message --queue-url your-queue-url --max-number-of-messages 10