Files
temporal-ai-agent/tools/fin/move_money.py
Steve Androulakis eb06cf5c8d Enhance Dev Experience and Code Quality (#41)
* Format codebase to satisfy linters

* fixing pylance and ruff-checked files

* contributing md, and type and formatting fixes

* setup file capitalization

* test fix
2025-06-01 08:54:59 -07:00

152 lines
5.3 KiB
Python

import json
import os
from dataclasses import dataclass
from pathlib import Path
from temporalio.exceptions import WorkflowAlreadyStartedError
from shared.config import get_temporal_client
# enums for the java enum
# class ExecutionScenarios(Enum):
# HAPPY_PATH = 0
# ADVANCED_VISIBILITY = auto() # 1
# HUMAN_IN_LOOP = auto() # 2
# API_DOWNTIME = auto() # 3
# BUG_IN_WORKFLOW = auto() # 4
# INVALID_ACCOUNT = auto() # 5
# these dataclasses are for calling the Temporal Workflow
# Python equivalent of the workflow we're calling's Java WorkflowParameterObj
@dataclass
class MoneyMovementWorkflowParameterObj:
amount: int # Using snake_case as per Python conventions
scenario: str
# this is made to demonstrate functionality but it could just as durably be an API call
# this assumes it's a valid account - use check_account_valid() to verify that first
async def move_money(args: dict) -> dict:
account_key = args.get("email_address_or_account_ID")
account_type: str = args.get("accounttype")
amount = args.get("amount")
destinationaccount = args.get("destinationaccount")
file_path = (
Path(__file__).resolve().parent.parent / "data" / "customer_account_data.json"
)
if not file_path.exists():
return {"error": "Data file not found."}
with open(file_path, "r") as file:
data = json.load(file)
account_list = data["accounts"]
for account in account_list:
if account["email"] == account_key or account["account_id"] == account_key:
amount_str: str = str(amount)
from_account_combo = account_key + account_type
transfer_workflow_id = await start_workflow(
amount_cents=str_dollars_to_cents(amount_str),
from_account_name=from_account_combo,
to_account_name=destinationaccount,
)
if account_type.casefold() == "checking":
from_key = "checking_balance"
elif account_type.casefold() == "savings":
from_key = "savings_balance"
else:
return_msg = "Money order for account types other than checking or savings is not implemented."
return {"error": return_msg}
to_key = (
"savings_balance"
if destinationaccount.casefold() == "savings"
else "checking_balance"
)
# Update from-account balance
from_balance = float(str_dollars_to_cents(str(account[from_key])))
from_balance -= float(str_dollars_to_cents(amount_str))
account[from_key] = str(from_balance / 100)
# Update destination-account balance
to_balance = float(str_dollars_to_cents(str(account[to_key])))
to_balance += float(str_dollars_to_cents(amount_str))
account[to_key] = str(to_balance / 100)
with open(file_path, "w") as file:
json.dump(data, file, indent=4)
return {
"status": "money movement complete",
"confirmation id": transfer_workflow_id,
"new_balance": account[from_key],
"destination_balance": account[to_key],
}
return_msg = "Account not found with for " + account_key
return {"error": return_msg}
# Async function to start workflow
async def start_workflow(
amount_cents: int, from_account_name: str, to_account_name: str
) -> str:
start_real_workflow = os.getenv("FIN_START_REAL_WORKFLOW")
if start_real_workflow is not None and start_real_workflow.lower() == "false":
START_REAL_WORKFLOW = False
else:
START_REAL_WORKFLOW = True
if START_REAL_WORKFLOW:
# Connect to Temporal
client = await get_temporal_client()
# Create the parameter object
params = MoneyMovementWorkflowParameterObj(
amount=amount_cents, scenario="HAPPY_PATH"
)
workflow_id = (
"TRANSFER-ACCT-" + from_account_name + "-TO-" + to_account_name
) # business-relevant workflow ID
try:
handle = await client.start_workflow(
"moneyTransferWorkflow", # Workflow name
params, # Workflow parameters
id=workflow_id,
task_queue="MoneyTransferJava", # Task queue name
)
return handle.id
except WorkflowAlreadyStartedError:
existing_handle = client.get_workflow_handle(workflow_id=workflow_id)
return existing_handle.id
else:
return (
"TRANSFER-ACCT-" + from_account_name + "-TO-" + to_account_name + "not-real"
)
# cleans a string dollar amount description to cents value
def str_dollars_to_cents(dollar_str: str) -> int:
try:
# Remove '$' and any whitespace
cleaned_str = dollar_str.replace("$", "").strip()
# Handle empty string or invalid input
if not cleaned_str:
raise ValueError("Empty amount provided")
# Convert to float and then to cents
amount = float(cleaned_str)
if amount < 0:
raise ValueError("Negative amounts not allowed")
return int(amount * 100)
except ValueError as e:
raise ValueError(f"Invalid dollar amount format: {dollar_str}") from e