148 lines
6.4 KiB
Python
148 lines
6.4 KiB
Python
|
|
"""不可变 PlanData 快照、字段证据和人工覆盖模型。"""
|
||
|
|
import json
|
||
|
|
|
||
|
|
from sqlalchemy import BigInteger, Boolean, Column, Float, ForeignKey, Index, Integer, String, Text, TIMESTAMP, UniqueConstraint, func
|
||
|
|
|
||
|
|
from insurance.db.compat import db
|
||
|
|
|
||
|
|
|
||
|
|
SNAPSHOT_ID_TYPE = BigInteger().with_variant(Integer, "sqlite")
|
||
|
|
|
||
|
|
|
||
|
|
def _json(value, fallback):
|
||
|
|
if not value:
|
||
|
|
return fallback
|
||
|
|
try:
|
||
|
|
return json.loads(value)
|
||
|
|
except (TypeError, ValueError):
|
||
|
|
return fallback
|
||
|
|
|
||
|
|
|
||
|
|
class InsurancePlanSnapshot(db.Model):
|
||
|
|
__tablename__ = "insurance_plan_snapshots"
|
||
|
|
__table_args__ = (
|
||
|
|
UniqueConstraint("document_id", "snapshot_version", name="uq_plan_snapshots_document_version"),
|
||
|
|
Index("idx_plan_snapshots_user_status", "user_id", "status"),
|
||
|
|
Index("idx_plan_snapshots_hash", "snapshot_hash"),
|
||
|
|
Index("idx_plan_snapshots_expires", "expires_at"),
|
||
|
|
)
|
||
|
|
|
||
|
|
id = Column(SNAPSHOT_ID_TYPE, primary_key=True, autoincrement=True)
|
||
|
|
user_id = Column(String(64), nullable=False)
|
||
|
|
document_id = Column(SNAPSHOT_ID_TYPE, ForeignKey("insurance_documents.id", ondelete="CASCADE"), nullable=False)
|
||
|
|
schema_version = Column(String(30), nullable=False, default="plan-data-v1")
|
||
|
|
snapshot_version = Column(Integer, nullable=False)
|
||
|
|
previous_snapshot_id = Column(SNAPSHOT_ID_TYPE, ForeignKey("insurance_plan_snapshots.id"), nullable=True)
|
||
|
|
status = Column(String(30), nullable=False, default="draft")
|
||
|
|
plan_data_json = Column(Text, nullable=False)
|
||
|
|
validation_results_json = Column(Text, nullable=False)
|
||
|
|
snapshot_hash = Column(String(64), nullable=False)
|
||
|
|
document_sha256 = Column(String(64), nullable=False)
|
||
|
|
confirmed_by = Column(String(64), nullable=True)
|
||
|
|
confirmed_at = Column(TIMESTAMP, nullable=True)
|
||
|
|
is_golden = Column(Boolean, nullable=False, default=False)
|
||
|
|
golden_approved_by = Column(String(64), nullable=True)
|
||
|
|
golden_approved_at = Column(TIMESTAMP, nullable=True)
|
||
|
|
golden_review_note = Column(String(500), nullable=True)
|
||
|
|
expires_at = Column(TIMESTAMP, nullable=True)
|
||
|
|
created_at = Column(TIMESTAMP, server_default=func.now())
|
||
|
|
|
||
|
|
def plan_data(self) -> dict:
|
||
|
|
return _json(self.plan_data_json, {})
|
||
|
|
|
||
|
|
def validation(self) -> dict:
|
||
|
|
return _json(self.validation_results_json, {})
|
||
|
|
|
||
|
|
def to_public_dict(self) -> dict:
|
||
|
|
return {
|
||
|
|
"id": self.id,
|
||
|
|
"schemaVersion": self.schema_version,
|
||
|
|
"snapshotVersion": self.snapshot_version,
|
||
|
|
"previousSnapshotId": self.previous_snapshot_id,
|
||
|
|
"status": self.status,
|
||
|
|
"document": {"documentId": self.document_id, "sha256": self.document_sha256},
|
||
|
|
"planData": self.plan_data(),
|
||
|
|
"validation": self.validation(),
|
||
|
|
"snapshotHash": self.snapshot_hash,
|
||
|
|
"confirmedBy": self.confirmed_by,
|
||
|
|
"confirmedAt": self.confirmed_at.isoformat() if self.confirmed_at else None,
|
||
|
|
"isGolden": bool(self.is_golden),
|
||
|
|
"goldenApprovedBy": self.golden_approved_by,
|
||
|
|
"goldenApprovedAt": self.golden_approved_at.isoformat() if self.golden_approved_at else None,
|
||
|
|
"goldenReviewNote": self.golden_review_note or "",
|
||
|
|
"expiresAt": self.expires_at.isoformat() if self.expires_at else None,
|
||
|
|
"createdAt": self.created_at.isoformat() if self.created_at else None,
|
||
|
|
}
|
||
|
|
|
||
|
|
|
||
|
|
class InsuranceFieldEvidence(db.Model):
|
||
|
|
__tablename__ = "insurance_field_evidence"
|
||
|
|
__table_args__ = (
|
||
|
|
Index("idx_field_evidence_snapshot_path", "snapshot_id", "field_path"),
|
||
|
|
Index("idx_field_evidence_document_page", "document_id", "page_number"),
|
||
|
|
)
|
||
|
|
|
||
|
|
id = Column(SNAPSHOT_ID_TYPE, primary_key=True, autoincrement=True)
|
||
|
|
snapshot_id = Column(SNAPSHOT_ID_TYPE, ForeignKey("insurance_plan_snapshots.id", ondelete="CASCADE"), nullable=False)
|
||
|
|
field_path = Column(String(500), nullable=False)
|
||
|
|
document_id = Column(SNAPSHOT_ID_TYPE, ForeignKey("insurance_documents.id", ondelete="CASCADE"), nullable=False)
|
||
|
|
page_number = Column(Integer, nullable=False)
|
||
|
|
bbox_json = Column(Text, nullable=True)
|
||
|
|
table_id = Column(String(100), nullable=True)
|
||
|
|
row_id = Column(String(100), nullable=True)
|
||
|
|
column_id = Column(String(100), nullable=True)
|
||
|
|
raw_text = Column(String(200), nullable=True)
|
||
|
|
text_hash = Column(String(64), nullable=True)
|
||
|
|
extractor = Column(String(50), nullable=True)
|
||
|
|
extractor_version = Column(String(100), nullable=True)
|
||
|
|
confidence = Column(Float, nullable=True)
|
||
|
|
evidence_status = Column(String(20), nullable=False, default="auto")
|
||
|
|
created_at = Column(TIMESTAMP, server_default=func.now())
|
||
|
|
|
||
|
|
def to_public_dict(self) -> dict:
|
||
|
|
return {
|
||
|
|
"id": self.id,
|
||
|
|
"snapshotId": self.snapshot_id,
|
||
|
|
"fieldPath": self.field_path,
|
||
|
|
"evidence": {
|
||
|
|
"documentId": self.document_id,
|
||
|
|
"pageNumber": self.page_number,
|
||
|
|
"bbox": _json(self.bbox_json, None),
|
||
|
|
"tableId": self.table_id,
|
||
|
|
"rowId": self.row_id,
|
||
|
|
"columnId": self.column_id,
|
||
|
|
"textHash": self.text_hash,
|
||
|
|
},
|
||
|
|
"snippet": self.raw_text or "",
|
||
|
|
"extractor": self.extractor,
|
||
|
|
"extractorVersion": self.extractor_version,
|
||
|
|
"confidence": self.confidence,
|
||
|
|
"status": self.evidence_status,
|
||
|
|
}
|
||
|
|
|
||
|
|
|
||
|
|
class InsurancePlanOverride(db.Model):
|
||
|
|
__tablename__ = "insurance_plan_overrides"
|
||
|
|
__table_args__ = (Index("idx_plan_overrides_snapshot", "snapshot_id"),)
|
||
|
|
|
||
|
|
id = Column(SNAPSHOT_ID_TYPE, primary_key=True, autoincrement=True)
|
||
|
|
snapshot_id = Column(SNAPSHOT_ID_TYPE, ForeignKey("insurance_plan_snapshots.id", ondelete="CASCADE"), nullable=False)
|
||
|
|
field_path = Column(String(500), nullable=False)
|
||
|
|
old_value_json = Column(Text, nullable=True)
|
||
|
|
new_value_json = Column(Text, nullable=True)
|
||
|
|
reason = Column(String(500), nullable=False)
|
||
|
|
operator_id = Column(String(64), nullable=False)
|
||
|
|
created_at = Column(TIMESTAMP, server_default=func.now())
|
||
|
|
|
||
|
|
def to_public_dict(self) -> dict:
|
||
|
|
return {
|
||
|
|
"id": self.id,
|
||
|
|
"snapshotId": self.snapshot_id,
|
||
|
|
"fieldPath": self.field_path,
|
||
|
|
"oldValue": _json(self.old_value_json, None),
|
||
|
|
"newValue": _json(self.new_value_json, None),
|
||
|
|
"reason": self.reason,
|
||
|
|
"operatorId": self.operator_id,
|
||
|
|
"createdAt": self.created_at.isoformat() if self.created_at else None,
|
||
|
|
}
|