Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -45,3 +45,15 @@ OTEL_SERVICE_NAME=fintech-agentic-app
# Frontend (served by FastAPI in dev; CDN origin in prod)
FRONTEND_ORIGIN=http://localhost:5173
CORS_ALLOWED_ORIGINS=http://localhost:5173,http://localhost:3000

# ──────────────────────────────────────────────
# Frontend (Vite) variables
# ──────────────────────────────────────────────

# Where the frontend talks to the FastAPI backend.
# Default "/api" goes through the Vite dev proxy → VITE_API_PROXY_TARGET.
# In production, set this to your full backend URL, e.g. https://api.yourdomain.com
VITE_API_BASE_URL=/api

# Dev-only: where the Vite proxy forwards /api/* requests
VITE_API_PROXY_TARGET=http://localhost:8000
30 changes: 25 additions & 5 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ __pycache__/
.Python
build/
dist/
dist-ssr
*.egg-info/
.eggs/
*.egg
Expand All @@ -29,25 +30,40 @@ env/

# Node / Frontend
node_modules/
node_modules
frontend/dist/
frontend/build/
frontend/.next/
.output
.vinxi
.tanstack/**
.nitro
*.local

# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
.pnp/
.pnp.js
pnpm-debug.log*
lerna-debug.log*

# IDE
.vscode/
.vscode/*
!.vscode/extensions.json
.idea/
*.swp
*.swo
*.sublime-project
*.sublime-workspace
.DS_Store
Thumbs.db
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?

# Secrets / credentials
*.pem
Expand All @@ -56,9 +72,11 @@ Thumbs.db
secrets/
.secrets

# Wrangler / Cloudflare
.wrangler/
.dev.vars

# Test artifacts
.coverage
htmlcov/
junit*.xml
test-results/

Expand All @@ -68,3 +86,5 @@ test-results/
# Compiled / generated
*.pyc
*.js.map
.pnp/
.pnp.js
7 changes: 7 additions & 0 deletions .prettierignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
node_modules
dist
.output
.vinxi
pnpm-lock.yaml
package-lock.json
routeTree.gen.ts
6 changes: 6 additions & 0 deletions .prettierrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{
"printWidth": 100,
"semi": true,
"singleQuote": false,
"trailingComma": "all"
}
14 changes: 7 additions & 7 deletions backend/app/api/auth.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,12 @@
In production, tokens are validated against the configured OIDC IdP.
In development (no OIDC configured), a simple JWT with HS256 is used for testing.
"""

from __future__ import annotations

import logging
from datetime import datetime, timedelta
from enum import Enum
from typing import Optional
from enum import StrEnum

from fastapi import Depends, HTTPException, status
from fastapi.security import HTTPAuthorizationCredentials, HTTPBearer
Expand All @@ -23,7 +23,7 @@
bearer_scheme = HTTPBearer(auto_error=False)


class UserRole(str, Enum):
class UserRole(StrEnum):
FRAUD_ANALYST = "fraud_analyst"
UNDERWRITER = "underwriter"
BRANCH_MANAGER = "branch_manager"
Expand All @@ -37,8 +37,8 @@ class UserRole(str, Enum):
class CurrentUser(BaseModel):
user_id: str
roles: list[UserRole]
email: Optional[str] = None
name: Optional[str] = None
email: str | None = None
name: str | None = None


def create_dev_token(user_id: str, roles: list[str]) -> str:
Expand All @@ -60,7 +60,7 @@ def _decode_token(token: str) -> dict:


async def get_current_user(
credentials: Optional[HTTPAuthorizationCredentials] = Depends(bearer_scheme),
credentials: HTTPAuthorizationCredentials | None = Depends(bearer_scheme),
) -> CurrentUser:
if credentials is None:
raise HTTPException(
Expand All @@ -77,7 +77,7 @@ async def get_current_user(
raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail=f"Invalid token: {exc}",
)
) from exc


def require_roles(*required_roles: UserRole):
Expand Down
3 changes: 2 additions & 1 deletion backend/app/api/routers/advisory.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
"""Financial advisory API endpoints."""

from __future__ import annotations

from fastapi import APIRouter, Depends, HTTPException
from fastapi import APIRouter, Depends

from app.api.auth import UserRole, require_roles
from app.api.schemas import AdviceApproveRequest, AdviceDraftResponse
Expand Down
1 change: 1 addition & 0 deletions backend/app/api/routers/auth_router.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
"""Dev-only auth endpoints for obtaining test tokens."""

from __future__ import annotations

from fastapi import APIRouter, HTTPException
Expand Down
1 change: 1 addition & 0 deletions backend/app/api/routers/branches.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
"""Branch performance monitor API endpoints."""

from __future__ import annotations

from fastapi import APIRouter, Depends
Expand Down
1 change: 1 addition & 0 deletions backend/app/api/routers/cases.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
"""Case management and audit console API endpoints."""

from __future__ import annotations

from fastapi import APIRouter, Depends, HTTPException
Expand Down
4 changes: 2 additions & 2 deletions backend/app/api/routers/chat.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,16 +7,16 @@
- Fetches real data from repositories
- Returns a structured ChatQueryResponse with agent_type, content, and cards
"""

from __future__ import annotations

import json
import logging
import re
from typing import Optional

from fastapi import APIRouter, Depends

from app.api.auth import UserRole, get_current_user, require_roles
from app.api.auth import get_current_user
from app.api.schemas import (
ChatCard,
ChatQueryRequest,
Expand Down
20 changes: 13 additions & 7 deletions backend/app/api/routers/fraud.py
Original file line number Diff line number Diff line change
@@ -1,22 +1,23 @@
"""Fraud workbench API endpoints."""

from __future__ import annotations

from fastapi import APIRouter, Depends, HTTPException, status

from app.api.auth import UserRole, get_current_user, require_roles
from app.api.auth import UserRole, require_roles
from app.api.schemas import FraudAlertApproveRequest, FraudAlertResponse, TransactionIngestRequest
from app.application.orchestrator import Supervisor
from app.core.container import get_container
from app.core.ids import new_audit_id, new_id
from app.core.ids import new_audit_id
from app.domain.models import Transaction
from app.domain.models.audit import AuditAction, AuditActor, AuditEvent
from app.domain.models.transaction import TransactionChannel

router = APIRouter(prefix="/api/fraud", tags=["fraud"])


def _get_supervisor() -> Supervisor:
from app.application.orchestrator import build_supervisor

return build_supervisor(get_container())


Expand Down Expand Up @@ -141,7 +142,8 @@ async def approve_fraud_alert(
alert = await container.fraud.update_alert_status(
alert_id=alert_id,
status=(
"confirmed_fraud" if body.decision == "approved"
"confirmed_fraud"
if body.decision == "approved"
else ("escalated" if body.decision == "escalated" else "cleared")
),
analyst_id=body.analyst_id,
Expand All @@ -155,9 +157,13 @@ async def approve_fraud_alert(
event_id=new_audit_id(),
actor_type=AuditActor.HUMAN,
actor_id=body.analyst_id,
action=AuditAction.FRAUD_ALERT_APPROVED if body.decision == "approved"
else (AuditAction.FRAUD_ALERT_ESCALATED if body.decision == "escalated"
else AuditAction.FRAUD_ALERT_DECLINED),
action=AuditAction.FRAUD_ALERT_APPROVED
if body.decision == "approved"
else (
AuditAction.FRAUD_ALERT_ESCALATED
if body.decision == "escalated"
else AuditAction.FRAUD_ALERT_DECLINED
),
related_object_id=alert_id,
related_object_type="fraud_alert",
customer_id=alert.customer_id,
Expand Down
7 changes: 2 additions & 5 deletions backend/app/api/routers/interactions.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
"""Customer interactions and sentiment API endpoints."""

from __future__ import annotations

from fastapi import APIRouter, Depends, HTTPException, status
Expand All @@ -22,7 +23,6 @@ async def analyze_interaction(
_user=Depends(require_roles(UserRole.CX_LEAD, UserRole.SERVICE_ACCOUNT, UserRole.ADMIN)),
):
"""Ingest a customer interaction and run sentiment analysis."""
from datetime import datetime

container = get_container()
supervisor = build_supervisor(container)
Expand Down Expand Up @@ -58,14 +58,11 @@ async def get_customer_signals(
),
):
"""Return the aggregated customer sentiment signal."""
from datetime import datetime

container = get_container()
signal = await container.customers.get_customer_signal(customer_id)
if signal is None:
raise HTTPException(
status_code=404, detail=f"No signal found for customer {customer_id}"
)
raise HTTPException(status_code=404, detail=f"No signal found for customer {customer_id}")
return CustomerSignalResponse(
customer_id=signal.customer_id,
overall_sentiment=signal.overall_sentiment,
Expand Down
5 changes: 2 additions & 3 deletions backend/app/api/routers/loans.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
"""Loan review workbench API endpoints."""

from __future__ import annotations

from fastapi import APIRouter, Depends, HTTPException, status
Expand Down Expand Up @@ -98,9 +99,7 @@ async def record_loan_decision(
if review is None:
raise HTTPException(status_code=404, detail="Review not found")

valid_decisions = {
"approved", "conditionally_approved", "declined", "pending_documents"
}
valid_decisions = {"approved", "conditionally_approved", "declined", "pending_documents"}
if body.decision not in valid_decisions:
raise HTTPException(
status_code=400,
Expand Down
Loading
Loading