refactor: Add JobSummary and Transaction models, client to get currency exchange rate

This commit is contained in:
2026-06-29 20:57:06 +05:30
parent 2e556173d4
commit f3727af697
14 changed files with 518 additions and 150 deletions

View File

@@ -1,4 +1,6 @@
from app.db.base import Base
from app.db.models.job import Job
from app.db.models.transaction import Transaction
from app.db.models.job_summary import JobSummary
__all__ = ["Base", "Job"]
__all__ = ["Base", "Job", "Transaction", "JobSummary"]

View File

@@ -1,10 +1,15 @@
from __future__ import annotations
import uuid
from datetime import datetime
from typing import Any, Dict, Optional
from sqlalchemy import String, Integer, DateTime, JSON, func
from sqlalchemy.orm import Mapped, mapped_column
from typing import List, Optional, TYPE_CHECKING
from sqlalchemy import DateTime, Integer, String, func
from sqlalchemy.orm import Mapped, mapped_column, relationship
from app.db.base import Base
if TYPE_CHECKING:
from app.db.models.transaction import Transaction
from app.db.models.job_summary import JobSummary
class Job(Base):
__tablename__ = "jobs"
@@ -21,24 +26,31 @@ class Job(Base):
default="pending",
index=True,
)
row_count: Mapped[Optional[int]] = mapped_column(Integer, nullable=True)
row_count_raw: Mapped[Optional[int]] = mapped_column(Integer, nullable=True)
row_count_clean: Mapped[Optional[int]] = mapped_column(Integer, nullable=True)
created_at: Mapped[datetime] = mapped_column(
DateTime(timezone=True),
server_default=func.now(),
nullable=False,
)
updated_at: Mapped[datetime] = mapped_column(
completed_at: Mapped[Optional[datetime]] = mapped_column(
DateTime(timezone=True),
server_default=func.now(),
onupdate=func.now(),
nullable=False,
)
# JSON summaries and results
summary: Mapped[Optional[Dict[str, Any]]] = mapped_column(
JSON,
nullable=True,
)
results: Mapped[Optional[Dict[str, Any]]] = mapped_column(
JSON,
error_message: Mapped[Optional[str]] = mapped_column(
String(500),
nullable=True,
)
# Relationships
transactions: Mapped[List["Transaction"]] = relationship(
"Transaction",
back_populates="job",
cascade="all, delete-orphan",
)
summary: Mapped[Optional["JobSummary"]] = relationship(
"JobSummary",
back_populates="job",
cascade="all, delete-orphan",
uselist=False,
)

View File

@@ -0,0 +1,38 @@
from __future__ import annotations
import uuid
from typing import Any, Dict, Optional, TYPE_CHECKING
from sqlalchemy import Float, ForeignKey, Integer, JSON, String
from sqlalchemy.orm import Mapped, mapped_column, relationship
from app.db.base import Base
if TYPE_CHECKING:
from app.db.models.job import Job
class JobSummary(Base):
__tablename__ = "job_summaries"
id: Mapped[str] = mapped_column(
String(36),
primary_key=True,
default=lambda: str(uuid.uuid4()),
index=True,
)
job_id: Mapped[str] = mapped_column(
String(36),
ForeignKey("jobs.id", ondelete="CASCADE"),
nullable=False,
unique=True,
index=True,
)
total_spend_inr: Mapped[float] = mapped_column(Float, nullable=False, default=0.0)
total_spend_usd: Mapped[float] = mapped_column(Float, nullable=False, default=0.0)
top_merchants: Mapped[Dict[str, Any]] = mapped_column(
JSON, nullable=False, default=dict
)
anomaly_count: Mapped[int] = mapped_column(Integer, nullable=False, default=0)
narrative: Mapped[Optional[str]] = mapped_column(String(2000), nullable=True)
risk_level: Mapped[Optional[str]] = mapped_column(String(50), nullable=True)
# Relationships
job: Mapped["Job"] = relationship("Job", back_populates="summary")

View File

@@ -0,0 +1,43 @@
from __future__ import annotations
import uuid
from datetime import date
from typing import Optional, TYPE_CHECKING
from sqlalchemy import Boolean, Date, Float, ForeignKey, String
from sqlalchemy.orm import Mapped, mapped_column, relationship
from app.db.base import Base
if TYPE_CHECKING:
from app.db.models.job import Job
class Transaction(Base):
__tablename__ = "transactions"
id: Mapped[str] = mapped_column(
String(36),
primary_key=True,
default=lambda: str(uuid.uuid4()),
index=True,
)
job_id: Mapped[str] = mapped_column(
String(36),
ForeignKey("jobs.id", ondelete="CASCADE"),
nullable=False,
index=True,
)
txn_id: Mapped[str] = mapped_column(String(100), nullable=False)
date: Mapped[date] = mapped_column(Date, nullable=False)
merchant: Mapped[str] = mapped_column(String(255), nullable=False)
amount: Mapped[float] = mapped_column(Float, nullable=False)
currency: Mapped[str] = mapped_column(String(10), nullable=False, default="INR")
status: Mapped[Optional[str]] = mapped_column(String(50), nullable=True)
category: Mapped[Optional[str]] = mapped_column(String(100), nullable=True)
account_id: Mapped[Optional[str]] = mapped_column(String(100), nullable=True)
is_anomaly: Mapped[bool] = mapped_column(Boolean, default=False)
anomaly_reason: Mapped[Optional[str]] = mapped_column(String(255), nullable=True)
llm_category: Mapped[Optional[str]] = mapped_column(String(100), nullable=True)
llm_raw_response: Mapped[Optional[str]] = mapped_column(String(1000), nullable=True)
llm_failed: Mapped[Optional[bool]] = mapped_column(Boolean, default=False)
# Relationships
job: Mapped["Job"] = relationship("Job", back_populates="transactions")