176 lines
6.7 KiB
SQL
176 lines
6.7 KiB
SQL
-- =============================================================================
|
|
-- Migration 044: external market source / offer normalization
|
|
-- MOMO PRO / PChome revenue growth automation
|
|
-- 2026-06-15 Taipei
|
|
-- =============================================================================
|
|
-- Notes:
|
|
-- Additive only. This migration does not drop, truncate, or rewrite existing
|
|
-- competitor_prices / competitor_price_history / market_* tables.
|
|
-- =============================================================================
|
|
|
|
CREATE TABLE IF NOT EXISTS external_market_sources (
|
|
id BIGSERIAL PRIMARY KEY,
|
|
code VARCHAR(80) NOT NULL UNIQUE,
|
|
display_name VARCHAR(160) NOT NULL,
|
|
platform_code VARCHAR(80) NOT NULL,
|
|
source_kind VARCHAR(60) NOT NULL,
|
|
status VARCHAR(40) NOT NULL DEFAULT 'paused',
|
|
enabled BOOLEAN NOT NULL DEFAULT FALSE,
|
|
write_enabled BOOLEAN NOT NULL DEFAULT FALSE,
|
|
allowed_input_methods_json TEXT,
|
|
quality_policy_json TEXT,
|
|
plain_note TEXT,
|
|
created_at TIMESTAMP NOT NULL DEFAULT NOW(),
|
|
updated_at TIMESTAMP NOT NULL DEFAULT NOW()
|
|
);
|
|
|
|
CREATE INDEX IF NOT EXISTS idx_external_market_sources_code
|
|
ON external_market_sources (code);
|
|
CREATE INDEX IF NOT EXISTS idx_external_market_sources_platform_code
|
|
ON external_market_sources (platform_code);
|
|
CREATE INDEX IF NOT EXISTS idx_external_market_sources_source_kind
|
|
ON external_market_sources (source_kind);
|
|
CREATE INDEX IF NOT EXISTS idx_external_market_sources_status
|
|
ON external_market_sources (status, enabled);
|
|
|
|
CREATE TABLE IF NOT EXISTS external_offers (
|
|
id BIGSERIAL PRIMARY KEY,
|
|
source_code VARCHAR(80) NOT NULL REFERENCES external_market_sources(code),
|
|
platform_code VARCHAR(80) NOT NULL,
|
|
source_product_id VARCHAR(220) NOT NULL,
|
|
source_offer_key VARCHAR(260) NOT NULL,
|
|
title TEXT NOT NULL,
|
|
brand VARCHAR(180),
|
|
category_text VARCHAR(320),
|
|
product_url TEXT,
|
|
image_url TEXT,
|
|
price DOUBLE PRECISION,
|
|
original_price DOUBLE PRECISION,
|
|
currency VARCHAR(12) NOT NULL DEFAULT 'TWD',
|
|
stock_status VARCHAR(80),
|
|
sold_count INTEGER,
|
|
rating DOUBLE PRECISION,
|
|
review_count INTEGER,
|
|
observed_at TIMESTAMP NOT NULL DEFAULT NOW(),
|
|
expires_at TIMESTAMP,
|
|
ingestion_method VARCHAR(60) NOT NULL,
|
|
connector_key VARCHAR(120),
|
|
pchome_product_id VARCHAR(120),
|
|
momo_sku VARCHAR(80),
|
|
match_status VARCHAR(40) NOT NULL DEFAULT 'unmatched',
|
|
quality_score DOUBLE PRECISION NOT NULL DEFAULT 0,
|
|
data_quality_status VARCHAR(40) NOT NULL DEFAULT 'needs_review',
|
|
quality_notes_json TEXT,
|
|
raw_payload_json TEXT,
|
|
created_at TIMESTAMP NOT NULL DEFAULT NOW(),
|
|
updated_at TIMESTAMP NOT NULL DEFAULT NOW(),
|
|
CONSTRAINT uq_external_offer_source_product_observed
|
|
UNIQUE (source_code, source_product_id, observed_at, ingestion_method)
|
|
);
|
|
|
|
CREATE INDEX IF NOT EXISTS idx_external_offers_source_code
|
|
ON external_offers (source_code);
|
|
CREATE INDEX IF NOT EXISTS idx_external_offers_platform_code
|
|
ON external_offers (platform_code);
|
|
CREATE INDEX IF NOT EXISTS idx_external_offers_source_product_id
|
|
ON external_offers (source_product_id);
|
|
CREATE INDEX IF NOT EXISTS idx_external_offers_brand
|
|
ON external_offers (brand);
|
|
CREATE INDEX IF NOT EXISTS idx_external_offers_category_text
|
|
ON external_offers (category_text);
|
|
CREATE INDEX IF NOT EXISTS idx_external_offers_stock_status
|
|
ON external_offers (stock_status);
|
|
CREATE INDEX IF NOT EXISTS idx_external_offers_observed_at
|
|
ON external_offers (observed_at);
|
|
CREATE INDEX IF NOT EXISTS idx_external_offers_expires_at
|
|
ON external_offers (expires_at);
|
|
CREATE INDEX IF NOT EXISTS idx_external_offers_ingestion_method
|
|
ON external_offers (ingestion_method);
|
|
CREATE INDEX IF NOT EXISTS idx_external_offers_connector_key
|
|
ON external_offers (connector_key);
|
|
CREATE INDEX IF NOT EXISTS idx_external_offers_pchome_product_id
|
|
ON external_offers (pchome_product_id);
|
|
CREATE INDEX IF NOT EXISTS idx_external_offers_momo_sku
|
|
ON external_offers (momo_sku);
|
|
CREATE INDEX IF NOT EXISTS idx_external_offers_match_status
|
|
ON external_offers (match_status);
|
|
CREATE INDEX IF NOT EXISTS idx_external_offers_data_quality_status
|
|
ON external_offers (data_quality_status);
|
|
CREATE INDEX IF NOT EXISTS idx_external_offers_source_seen
|
|
ON external_offers (source_code, observed_at);
|
|
CREATE INDEX IF NOT EXISTS idx_external_offers_platform_product
|
|
ON external_offers (platform_code, source_product_id);
|
|
CREATE INDEX IF NOT EXISTS idx_external_offers_pchome_product
|
|
ON external_offers (pchome_product_id, source_code);
|
|
CREATE INDEX IF NOT EXISTS idx_external_offers_match_quality
|
|
ON external_offers (match_status, data_quality_status, quality_score);
|
|
|
|
INSERT INTO external_market_sources (
|
|
code,
|
|
display_name,
|
|
platform_code,
|
|
source_kind,
|
|
status,
|
|
enabled,
|
|
write_enabled,
|
|
allowed_input_methods_json,
|
|
quality_policy_json,
|
|
plain_note
|
|
) VALUES
|
|
(
|
|
'momo_reference',
|
|
'MOMO 外部價格參考',
|
|
'momo',
|
|
'legacy_bridge',
|
|
'active',
|
|
TRUE,
|
|
FALSE,
|
|
'["legacy_competitor_cache","manual_csv","provider_api"]',
|
|
'{"minimum_match_status":"verified","minimum_quality_score":76}',
|
|
'目前只採用已確認同款的 MOMO 參考價。'
|
|
),
|
|
(
|
|
'shopee',
|
|
'蝦皮',
|
|
'shopee',
|
|
'connector_contract',
|
|
'paused',
|
|
FALSE,
|
|
FALSE,
|
|
'["official_api","provider_api","manual_csv"]',
|
|
'{"minimum_match_status":"verified","manual_review_required":true}',
|
|
'先暫停,不進告警;未來可接官方 API、供應商資料或手動 CSV。'
|
|
),
|
|
(
|
|
'coupang',
|
|
'酷澎',
|
|
'coupang',
|
|
'connector_contract',
|
|
'paused',
|
|
FALSE,
|
|
FALSE,
|
|
'["official_api","provider_api","manual_csv"]',
|
|
'{"minimum_match_status":"verified","manual_review_required":true}',
|
|
'先暫停,不進告警;未來可接官方 API、供應商資料或手動 CSV。'
|
|
)
|
|
ON CONFLICT (code) DO UPDATE SET
|
|
display_name = EXCLUDED.display_name,
|
|
platform_code = EXCLUDED.platform_code,
|
|
source_kind = EXCLUDED.source_kind,
|
|
status = EXCLUDED.status,
|
|
enabled = EXCLUDED.enabled,
|
|
allowed_input_methods_json = EXCLUDED.allowed_input_methods_json,
|
|
quality_policy_json = EXCLUDED.quality_policy_json,
|
|
plain_note = EXCLUDED.plain_note,
|
|
updated_at = NOW();
|
|
|
|
GRANT ALL PRIVILEGES ON external_market_sources TO momo;
|
|
GRANT ALL PRIVILEGES ON external_offers TO momo;
|
|
GRANT USAGE, SELECT ON SEQUENCE external_market_sources_id_seq TO momo;
|
|
GRANT USAGE, SELECT ON SEQUENCE external_offers_id_seq TO momo;
|
|
|
|
DO $$
|
|
BEGIN
|
|
RAISE NOTICE 'Migration 044 complete: external market source and offer normalization is ready';
|
|
END $$;
|