first commit

This commit is contained in:
2026-03-11 16:28:04 +08:00
commit c0f9ddabbf
101 changed files with 11601 additions and 0 deletions

View File

@@ -0,0 +1,19 @@
from app.models.case import Case
from app.models.evidence_image import EvidenceImage
from app.models.ocr_block import OcrBlock
from app.models.transaction import TransactionRecord
from app.models.transaction_cluster import TransactionCluster
from app.models.fund_flow import FundFlowEdge
from app.models.assessment import FraudAssessment
from app.models.report import ExportReport
__all__ = [
"Case",
"EvidenceImage",
"OcrBlock",
"TransactionRecord",
"TransactionCluster",
"FundFlowEdge",
"FraudAssessment",
"ExportReport",
]

View File

@@ -0,0 +1,42 @@
import uuid
from datetime import datetime
import enum
from sqlalchemy import String, Numeric, Text, DateTime, ForeignKey, Enum as SAEnum, func
from sqlalchemy.dialects.postgresql import UUID
from sqlalchemy.orm import Mapped, mapped_column, relationship
from app.core.database import Base
class ConfidenceLevel(str, enum.Enum):
high = "high"
medium = "medium"
low = "low"
class ReviewStatus(str, enum.Enum):
pending = "pending"
confirmed = "confirmed"
rejected = "rejected"
needs_info = "needs_info"
class FraudAssessment(Base):
__tablename__ = "fraud_assessments"
id: Mapped[uuid.UUID] = mapped_column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4)
case_id: Mapped[uuid.UUID] = mapped_column(UUID(as_uuid=True), ForeignKey("cases.id"), index=True)
transaction_id: Mapped[uuid.UUID] = mapped_column(UUID(as_uuid=True), ForeignKey("transaction_records.id"))
confidence_level: Mapped[ConfidenceLevel] = mapped_column(SAEnum(ConfidenceLevel))
assessed_amount: Mapped[float] = mapped_column(Numeric(14, 2), default=0)
reason: Mapped[str] = mapped_column(Text, default="")
exclude_reason: Mapped[str] = mapped_column(Text, default="")
review_status: Mapped[ReviewStatus] = mapped_column(SAEnum(ReviewStatus), default=ReviewStatus.pending)
review_note: Mapped[str] = mapped_column(Text, default="")
reviewed_by: Mapped[str] = mapped_column(String(128), default="")
reviewed_at: Mapped[datetime | None] = mapped_column(DateTime(timezone=True), nullable=True)
created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), server_default=func.now())
case = relationship("Case", back_populates="assessments")
transaction = relationship("TransactionRecord")

View File

@@ -0,0 +1,40 @@
import uuid
from datetime import datetime
from sqlalchemy import String, Numeric, Integer, DateTime, Enum as SAEnum, func
from sqlalchemy.dialects.postgresql import UUID
from sqlalchemy.orm import Mapped, mapped_column, relationship
from app.core.database import Base
import enum
class CaseStatus(str, enum.Enum):
pending = "pending"
uploading = "uploading"
analyzing = "analyzing"
reviewing = "reviewing"
completed = "completed"
class Case(Base):
__tablename__ = "cases"
id: Mapped[uuid.UUID] = mapped_column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4)
case_no: Mapped[str] = mapped_column(String(64), unique=True, index=True)
title: Mapped[str] = mapped_column(String(256))
victim_name: Mapped[str] = mapped_column(String(128))
handler: Mapped[str] = mapped_column(String(128), default="")
status: Mapped[CaseStatus] = mapped_column(SAEnum(CaseStatus), default=CaseStatus.pending)
image_count: Mapped[int] = mapped_column(Integer, default=0)
total_amount: Mapped[float] = mapped_column(Numeric(14, 2), default=0)
created_by: Mapped[str] = mapped_column(String(128), default="")
created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), server_default=func.now())
updated_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), server_default=func.now(), onupdate=func.now())
deleted_at: Mapped[datetime | None] = mapped_column(DateTime(timezone=True), nullable=True)
images = relationship("EvidenceImage", back_populates="case", lazy="selectin")
transactions = relationship("TransactionRecord", back_populates="case", lazy="selectin")
assessments = relationship("FraudAssessment", back_populates="case", lazy="selectin")
reports = relationship("ExportReport", back_populates="case", lazy="selectin")

View File

@@ -0,0 +1,51 @@
import uuid
from datetime import datetime
import enum
from sqlalchemy import String, Integer, DateTime, ForeignKey, Enum as SAEnum, func
from sqlalchemy.dialects.postgresql import UUID
from sqlalchemy.orm import Mapped, mapped_column, relationship
from app.core.database import Base
class SourceApp(str, enum.Enum):
wechat = "wechat"
alipay = "alipay"
bank = "bank"
digital_wallet = "digital_wallet"
other = "other"
class PageType(str, enum.Enum):
bill_list = "bill_list"
bill_detail = "bill_detail"
transfer_receipt = "transfer_receipt"
sms_notice = "sms_notice"
balance = "balance"
unknown = "unknown"
class OcrStatus(str, enum.Enum):
pending = "pending"
processing = "processing"
done = "done"
failed = "failed"
class EvidenceImage(Base):
__tablename__ = "evidence_images"
id: Mapped[uuid.UUID] = mapped_column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4)
case_id: Mapped[uuid.UUID] = mapped_column(UUID(as_uuid=True), ForeignKey("cases.id"), index=True)
file_path: Mapped[str] = mapped_column(String(512))
thumb_path: Mapped[str] = mapped_column(String(512), default="")
source_app: Mapped[SourceApp] = mapped_column(SAEnum(SourceApp), default=SourceApp.other)
page_type: Mapped[PageType] = mapped_column(SAEnum(PageType), default=PageType.unknown)
ocr_status: Mapped[OcrStatus] = mapped_column(SAEnum(OcrStatus), default=OcrStatus.pending)
file_hash: Mapped[str] = mapped_column(String(128), unique=True, index=True)
file_size: Mapped[int] = mapped_column(Integer, default=0)
uploaded_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), server_default=func.now())
case = relationship("Case", back_populates="images")
ocr_blocks = relationship("OcrBlock", back_populates="image", lazy="selectin")

View File

@@ -0,0 +1,23 @@
import uuid
from datetime import datetime
from sqlalchemy import String, Numeric, Integer, DateTime, ForeignKey, func
from sqlalchemy.dialects.postgresql import UUID
from sqlalchemy.orm import Mapped, mapped_column
from app.core.database import Base
class FundFlowEdge(Base):
__tablename__ = "fund_flow_edges"
id: Mapped[uuid.UUID] = mapped_column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4)
case_id: Mapped[uuid.UUID] = mapped_column(UUID(as_uuid=True), ForeignKey("cases.id"), index=True)
source_node: Mapped[str] = mapped_column(String(256))
target_node: Mapped[str] = mapped_column(String(256))
source_type: Mapped[str] = mapped_column(String(32), default="unknown")
target_type: Mapped[str] = mapped_column(String(32), default="unknown")
amount: Mapped[float] = mapped_column(Numeric(14, 2), default=0)
tx_count: Mapped[int] = mapped_column(Integer, default=1)
earliest_time: Mapped[datetime | None] = mapped_column(DateTime(timezone=True), nullable=True)
created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), server_default=func.now())

View File

@@ -0,0 +1,20 @@
import uuid
from sqlalchemy import String, Integer, Float, Text, ForeignKey
from sqlalchemy.dialects.postgresql import UUID, JSONB
from sqlalchemy.orm import Mapped, mapped_column, relationship
from app.core.database import Base
class OcrBlock(Base):
__tablename__ = "ocr_blocks"
id: Mapped[uuid.UUID] = mapped_column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4)
image_id: Mapped[uuid.UUID] = mapped_column(UUID(as_uuid=True), ForeignKey("evidence_images.id"), index=True)
content: Mapped[str] = mapped_column(Text, default="")
bbox: Mapped[dict] = mapped_column(JSONB, default=dict)
seq_order: Mapped[int] = mapped_column(Integer, default=0)
confidence: Mapped[float] = mapped_column(Float, default=0.0)
image = relationship("EvidenceImage", back_populates="ocr_blocks")

View File

@@ -0,0 +1,29 @@
import uuid
from datetime import datetime
import enum
from sqlalchemy import String, Integer, DateTime, ForeignKey, Enum as SAEnum, func
from sqlalchemy.dialects.postgresql import UUID, JSONB
from sqlalchemy.orm import Mapped, mapped_column, relationship
from app.core.database import Base
class ReportType(str, enum.Enum):
pdf = "pdf"
excel = "excel"
word = "word"
class ExportReport(Base):
__tablename__ = "export_reports"
id: Mapped[uuid.UUID] = mapped_column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4)
case_id: Mapped[uuid.UUID] = mapped_column(UUID(as_uuid=True), ForeignKey("cases.id"), index=True)
report_type: Mapped[ReportType] = mapped_column(SAEnum(ReportType))
file_path: Mapped[str] = mapped_column(String(512), default="")
version: Mapped[int] = mapped_column(Integer, default=1)
content_snapshot: Mapped[dict] = mapped_column(JSONB, default=dict)
created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), server_default=func.now())
case = relationship("Case", back_populates="reports")

View File

@@ -0,0 +1,43 @@
import uuid
from datetime import datetime
import enum
from sqlalchemy import (
String, Numeric, Float, Boolean, DateTime, Text,
ForeignKey, Enum as SAEnum, func,
)
from sqlalchemy.dialects.postgresql import UUID
from sqlalchemy.orm import Mapped, mapped_column, relationship
from app.core.database import Base
from app.models.evidence_image import SourceApp
class Direction(str, enum.Enum):
in_ = "in"
out = "out"
class TransactionRecord(Base):
__tablename__ = "transaction_records"
id: Mapped[uuid.UUID] = mapped_column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4)
case_id: Mapped[uuid.UUID] = mapped_column(UUID(as_uuid=True), ForeignKey("cases.id"), index=True)
evidence_image_id: Mapped[uuid.UUID | None] = mapped_column(UUID(as_uuid=True), ForeignKey("evidence_images.id"), nullable=True)
cluster_id: Mapped[uuid.UUID | None] = mapped_column(UUID(as_uuid=True), ForeignKey("transaction_clusters.id"), nullable=True)
source_app: Mapped[SourceApp] = mapped_column(SAEnum(SourceApp), default=SourceApp.other)
trade_time: Mapped[datetime] = mapped_column(DateTime(timezone=True), index=True)
amount: Mapped[float] = mapped_column(Numeric(14, 2), default=0)
direction: Mapped[Direction] = mapped_column(SAEnum(Direction))
counterparty_name: Mapped[str] = mapped_column(String(256), default="")
counterparty_account: Mapped[str] = mapped_column(String(256), default="")
self_account_tail_no: Mapped[str] = mapped_column(String(32), default="")
order_no: Mapped[str] = mapped_column(String(128), default="", index=True)
remark: Mapped[str] = mapped_column(Text, default="")
confidence: Mapped[float] = mapped_column(Float, default=0.0)
is_duplicate: Mapped[bool] = mapped_column(Boolean, default=False)
is_transit: Mapped[bool] = mapped_column(Boolean, default=False)
created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), server_default=func.now())
case = relationship("Case", back_populates="transactions")
cluster = relationship("TransactionCluster", back_populates="transactions", foreign_keys=[cluster_id])

View File

@@ -0,0 +1,22 @@
import uuid
from sqlalchemy import String, ForeignKey
from sqlalchemy.dialects.postgresql import UUID
from sqlalchemy.orm import Mapped, mapped_column, relationship
from app.core.database import Base
class TransactionCluster(Base):
__tablename__ = "transaction_clusters"
id: Mapped[uuid.UUID] = mapped_column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4)
case_id: Mapped[uuid.UUID] = mapped_column(UUID(as_uuid=True), ForeignKey("cases.id"), index=True)
primary_tx_id: Mapped[uuid.UUID | None] = mapped_column(UUID(as_uuid=True), nullable=True)
match_reason: Mapped[str] = mapped_column(String(512), default="")
transactions = relationship(
"TransactionRecord",
back_populates="cluster",
foreign_keys="TransactionRecord.cluster_id",
)