first commit

This commit is contained in:
2026-03-09 14:46:56 +08:00
commit 62236eb80e
63 changed files with 6143 additions and 0 deletions

View File

@@ -0,0 +1,8 @@
"""SQLAlchemy models - export Base and all models for create_all."""
from app.models.database import Base, get_db, init_db, engine, async_session_maker
from app.models.case import Case
from app.models.screenshot import Screenshot
from app.models.transaction import Transaction
__all__ = ["Base", "Case", "Screenshot", "Transaction", "get_db", "init_db", "engine", "async_session_maker"]

View File

@@ -0,0 +1,28 @@
"""Case model - 案件."""
from __future__ import annotations
from datetime import datetime
from decimal import Decimal
from sqlalchemy import String, Text, DateTime, Numeric
from sqlalchemy.orm import Mapped, mapped_column, relationship
from app.models.database import Base
class Case(Base):
__tablename__ = "cases"
id: Mapped[int] = mapped_column(primary_key=True, autoincrement=True)
case_number: Mapped[str] = mapped_column(String(64), unique=True, index=True)
victim_name: Mapped[str] = mapped_column(String(128))
description: Mapped[str] = mapped_column(Text, default="")
total_loss: Mapped[Decimal] = mapped_column(Numeric(18, 2), default=0)
status: Mapped[str] = mapped_column(String(32), default="in_progress") # in_progress | completed
created_at: Mapped[datetime] = mapped_column(DateTime, default=datetime.utcnow)
updated_at: Mapped[datetime] = mapped_column(DateTime, default=datetime.utcnow, onupdate=datetime.utcnow)
screenshots: Mapped[list["Screenshot"]] = relationship("Screenshot", back_populates="case", cascade="all, delete-orphan")
transactions: Mapped[list["Transaction"]] = relationship("Transaction", back_populates="case", cascade="all, delete-orphan")
def __repr__(self) -> str:
return f"<Case(id={self.id}, case_number={self.case_number})>"

View File

@@ -0,0 +1,33 @@
"""Database session and initialization."""
from sqlalchemy.ext.asyncio import create_async_engine, AsyncSession, async_sessionmaker
from sqlalchemy.orm import DeclarativeBase
from app.config import get_settings
class Base(DeclarativeBase):
pass
engine = None
async_session_maker = None
async def init_db():
global engine, async_session_maker
settings = get_settings()
engine = create_async_engine(
settings.database_url,
echo=settings.debug,
)
async_session_maker = async_sessionmaker(
engine, class_=AsyncSession, expire_on_commit=False
)
from app.models import Case, Screenshot, Transaction # noqa: F401
async with engine.begin() as conn:
await conn.run_sync(Base.metadata.create_all)
async def get_db():
async with async_session_maker() as session:
yield session

View File

@@ -0,0 +1,27 @@
"""Screenshot model - 截图记录."""
from __future__ import annotations
from datetime import datetime
from sqlalchemy import String, DateTime, ForeignKey
from sqlalchemy.orm import Mapped, mapped_column, relationship
from app.models.database import Base
class Screenshot(Base):
__tablename__ = "screenshots"
id: Mapped[int] = mapped_column(primary_key=True, autoincrement=True)
case_id: Mapped[int] = mapped_column(ForeignKey("cases.id", ondelete="CASCADE"), index=True)
filename: Mapped[str] = mapped_column(String(255))
file_path: Mapped[str] = mapped_column(String(512))
status: Mapped[str] = mapped_column(String(32), default="pending") # pending | extracted | failed
created_at: Mapped[datetime] = mapped_column(DateTime, default=datetime.utcnow)
case: Mapped["Case"] = relationship("Case", back_populates="screenshots")
transactions: Mapped[list["Transaction"]] = relationship(
"Transaction", back_populates="screenshot", cascade="all, delete-orphan"
)
def __repr__(self) -> str:
return f"<Screenshot(id={self.id}, filename={self.filename})>"

View File

@@ -0,0 +1,35 @@
"""Transaction model - 交易记录."""
from __future__ import annotations
from datetime import datetime
from decimal import Decimal
from sqlalchemy import String, Text, DateTime, Numeric, ForeignKey
from sqlalchemy.orm import Mapped, mapped_column, relationship
from app.models.database import Base
class Transaction(Base):
__tablename__ = "transactions"
id: Mapped[int] = mapped_column(primary_key=True, autoincrement=True)
case_id: Mapped[int] = mapped_column(ForeignKey("cases.id", ondelete="CASCADE"), index=True)
screenshot_id: Mapped[int] = mapped_column(ForeignKey("screenshots.id", ondelete="CASCADE"), index=True)
app_source: Mapped[str] = mapped_column(String(128))
transaction_type: Mapped[str] = mapped_column(String(32)) # 转出/转入/消费/收款/提现/充值
amount: Mapped[Decimal] = mapped_column(Numeric(18, 2))
currency: Mapped[str] = mapped_column(String(16), default="CNY")
counterparty_name: Mapped[str | None] = mapped_column(String(256), nullable=True)
counterparty_account: Mapped[str | None] = mapped_column(String(512), nullable=True)
order_number: Mapped[str | None] = mapped_column(String(128), nullable=True)
transaction_time: Mapped[datetime | None] = mapped_column(DateTime, nullable=True)
remark: Mapped[str | None] = mapped_column(Text, nullable=True)
raw_text: Mapped[str | None] = mapped_column(Text, nullable=True)
confidence: Mapped[str] = mapped_column(String(16), default="medium") # high | medium | low
created_at: Mapped[datetime] = mapped_column(DateTime, default=datetime.utcnow)
case: Mapped["Case"] = relationship("Case", back_populates="transactions")
screenshot: Mapped["Screenshot"] = relationship("Screenshot", back_populates="transactions")
def __repr__(self) -> str:
return f"<Transaction(id={self.id}, amount={self.amount}, app={self.app_source})>"