first commit
This commit is contained in:
19
backend/app/models/__init__.py
Normal file
19
backend/app/models/__init__.py
Normal 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",
|
||||
]
|
||||
42
backend/app/models/assessment.py
Normal file
42
backend/app/models/assessment.py
Normal 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")
|
||||
40
backend/app/models/case.py
Normal file
40
backend/app/models/case.py
Normal 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")
|
||||
51
backend/app/models/evidence_image.py
Normal file
51
backend/app/models/evidence_image.py
Normal 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")
|
||||
23
backend/app/models/fund_flow.py
Normal file
23
backend/app/models/fund_flow.py
Normal 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())
|
||||
20
backend/app/models/ocr_block.py
Normal file
20
backend/app/models/ocr_block.py
Normal 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")
|
||||
29
backend/app/models/report.py
Normal file
29
backend/app/models/report.py
Normal 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")
|
||||
43
backend/app/models/transaction.py
Normal file
43
backend/app/models/transaction.py
Normal 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])
|
||||
22
backend/app/models/transaction_cluster.py
Normal file
22
backend/app/models/transaction_cluster.py
Normal 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",
|
||||
)
|
||||
Reference in New Issue
Block a user