Files
ewoooc/database/external_market_models.py
OoO 9260cc1740
All checks were successful
CD Pipeline / deploy (push) Successful in 1m4s
V10.607 建立外部市場來源正規化層
2026-06-15 16:19:03 +08:00

108 lines
4.0 KiB
Python

#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""外部市場來源與報價的正規化 ORM models。"""
from datetime import datetime, timedelta, timezone
from sqlalchemy import (
Boolean,
Column,
DateTime,
Float,
ForeignKey,
Index,
Integer,
String,
Text,
UniqueConstraint,
)
from sqlalchemy.orm import relationship
from database.models import Base
TAIPEI_TZ = timezone(timedelta(hours=8))
def taipei_now():
return datetime.now(TAIPEI_TZ).replace(tzinfo=None)
class ExternalMarketSource(Base):
"""外部市場資料來源,例如 MOMO 參考價、蝦皮 API、酷澎 CSV。"""
__tablename__ = "external_market_sources"
id = Column(Integer, primary_key=True, autoincrement=True)
code = Column(String(80), unique=True, nullable=False, index=True)
display_name = Column(String(160), nullable=False)
platform_code = Column(String(80), nullable=False, index=True)
source_kind = Column(String(60), nullable=False, index=True)
status = Column(String(40), default="paused", nullable=False, index=True)
enabled = Column(Boolean, default=False, nullable=False)
write_enabled = Column(Boolean, default=False, nullable=False)
allowed_input_methods_json = Column(Text)
quality_policy_json = Column(Text)
plain_note = Column(Text)
created_at = Column(DateTime, default=taipei_now, nullable=False)
updated_at = Column(DateTime, default=taipei_now, onupdate=taipei_now, nullable=False)
offers = relationship("ExternalOffer", back_populates="source")
__table_args__ = (
Index("idx_external_market_sources_status", "status", "enabled"),
)
class ExternalOffer(Base):
"""正規化後的外部商品報價。"""
__tablename__ = "external_offers"
id = Column(Integer, primary_key=True, autoincrement=True)
source_code = Column(String(80), ForeignKey("external_market_sources.code"), nullable=False, index=True)
platform_code = Column(String(80), nullable=False, index=True)
source_product_id = Column(String(220), nullable=False, index=True)
source_offer_key = Column(String(260), nullable=False)
title = Column(Text, nullable=False)
brand = Column(String(180), index=True)
category_text = Column(String(320), index=True)
product_url = Column(Text)
image_url = Column(Text)
price = Column(Float)
original_price = Column(Float)
currency = Column(String(12), default="TWD", nullable=False)
stock_status = Column(String(80), index=True)
sold_count = Column(Integer)
rating = Column(Float)
review_count = Column(Integer)
observed_at = Column(DateTime, default=taipei_now, nullable=False, index=True)
expires_at = Column(DateTime, index=True)
ingestion_method = Column(String(60), nullable=False, index=True)
connector_key = Column(String(120), index=True)
pchome_product_id = Column(String(120), index=True)
momo_sku = Column(String(80), index=True)
match_status = Column(String(40), default="unmatched", nullable=False, index=True)
quality_score = Column(Float, default=0.0, nullable=False)
data_quality_status = Column(String(40), default="needs_review", nullable=False, index=True)
quality_notes_json = Column(Text)
raw_payload_json = Column(Text)
created_at = Column(DateTime, default=taipei_now, nullable=False)
updated_at = Column(DateTime, default=taipei_now, onupdate=taipei_now, nullable=False)
source = relationship("ExternalMarketSource", back_populates="offers")
__table_args__ = (
UniqueConstraint(
"source_code",
"source_product_id",
"observed_at",
"ingestion_method",
name="uq_external_offer_source_product_observed",
),
Index("idx_external_offers_source_seen", "source_code", "observed_at"),
Index("idx_external_offers_platform_product", "platform_code", "source_product_id"),
Index("idx_external_offers_pchome_product", "pchome_product_id", "source_code"),
Index("idx_external_offers_match_quality", "match_status", "data_quality_status", "quality_score"),
)