最近自己个人业务有个客户一个大宗供应量销量预测与智能预测的需求。但是大宗供应链的场景,多半就是跟地缘政治因素强关联的,调研几个月,记录一下自己的思考
目录
项目概述
1.1 大宗商品企业的舆情风险
大宗商品供应链企业(钢铁、石油、粮食、矿产)面临四类核心风险:
| 风险维度 | 具体表现 | 影响度量 | 舆情指标 |
|---|---|---|---|
| A.物理物流中断 | 港口罢工、海峡阻塞、基础设施损坏 | 延迟天数 | 物流延迟指数(LDI) |
| B.监管贸易限制 | 出口禁令、制裁、关税变化 | 合规成本 | 合规风险指数(CRI) |
| C.政治安全不稳定 | 内战冲突、恐怖活动、政权更迭 | 保险费上升 | 地缘政治指数(GRI) |
| D.经济金融冲击 | 货币崩溃、主权违约、支付中断 | 交易对手风险 | 金融风险指数(FRI) |
舆情指标系统的核心价值:
- 非结构化新闻→定量决策指标 (<5分钟延迟)
- 按地理位置+商品种类的采购商特定风险评估
- 交易、物流、金融对冲的量化决策支持
1.2 系统设计目标
makefile
实时性: 事件→摄取→NLP→指标 <5分钟
准确性: NLP分类>95%, 指标相关性>0.8
覆盖: 150+采购商, 200+新闻源, 4类商品
可操作: 直接输入贸易/物流/金融系统
舆情指标体系
2.1 四层舆情指标框架
makefile
第1层: 基础采样指标
├─ 事件频率 (events/day)
├─ 新闻热度 (7day_volume)
├─ 网络舆论倾向 (sentiment)
└─ NLP置信度 (confidence)
第2层: 维度风险指标 (A/B/C/D四维)
├─ LDI: 物流延迟风险 (0-10)
├─ CRI: 合规监管风险 (0-10)
├─ GRI: 地缘政治风险 (0-10)
└─ FRI: 金融支付风险 (0-10)
第3层: 综合舆情指标
├─ 综合风险指数 (0-100)
├─ 采购商特定风险 (考虑货运量+商品敏感性)
└─ 舆情vs实际风险相关度 (-1~1)
第4层: 战略决策支持
├─ 预警等级 (Green/Yellow/Orange/Red)
├─ 建议应急措施
├─ 对冲对象
└─ 物流替代方案评分
2.2 维度指标具体定义
LDI: 物流延迟风险指数
erlang
公式: LDI = Σ(severity × location_proximity × commodity_multiplier)
severity_score (港口罢工:6-8, 海峡阻塞:8-9, 铁路破坏:4-6, 空域关闭:3-5)
location_proximity (距离衰减: <500km→1.0, 500-2000km→0.8, >5000km→0.2)
commodity_multiplier (易腐农产品:1.5, 冷链:2.0, 金属矿产:0.5)
示例: 上海采购商采购大豆, 巴拿马运河关闭
LDI = (8严重度 × 0.8距离系数 × 1.5易腐性) = 9.6/10 (红色告警)
CRI: 合规监管风险指数
ini
公式: CRI = Σ(impact_severity × enforcement_probability × exposure_ratio)
impact_severity (新增制裁:9-10, 出口禁令:7-8, 关税>15%:5-6, 关税5-15%:2-3)
enforcement_probability (立即:1.0, 30天:0.8, 90天:0.5, 讨论:0.2)
exposure_ratio (该地区采购额 / 总采购额)
示例: 俄罗斯铁矿石禁令, 采购商A占15%
CRI增量 = 8 × 0.8 × 0.15 = 0.96分
GRI: 地缘政治风险指数
erlang
公式: GRI = (severity × conflict_intensity × duration_decay) / redundancy × 操作乘数
event_severity (全面战争:9-10, 区域冲突:7-8, 大规模抗议:5-6)
conflict_intensity (ACLED数据: 死亡人数/事件频率)
duration_decay (刚发生:1.0, 1-7天:0.9, 1-4周:0.7, 1-3月:0.5, >3月:0.3)
infrastructure_redundancy (无替代:1.0, 1条:0.7, 2+条:0.4)
操作乘数 (劳动力撤出:1.5, 保险费上升:1.3, 工人安全:1.2)
FRI: 金融支付风险指数
ini
公式: FRI = Σ(financial_severity × counterparty_exposure × forward_duration)
financial_severity (SWIFT除名:9-10, 主权违约:8-9, 货币贬值>30%:7-8)
counterparty_exposure (该国交易额占比)
forward_duration (待结算<7天:1.0, 预期交易7-30天:0.8, 期货30-90天:0.6)
示例: 阿根廷比索危机, 采购商敞口12%, 待结算30天
FRI增量 = 8 × 0.12 × 0.8 = 0.77分
2.3 综合舆情指标
ini
综合风险指数 (CRI_Composite):
= 40% × (LDI + CRI)/2 + 60% × (GRI + FRI)/2
= 40% × 物流合规风险 + 60% × 地缘金融风险
范围: 0-100
采购商特定风险 (Buyer_Specific):
= 综合风险指数 × 货运量权重 × 商品敏感性乘数
商品敏感性:
├─ 农产品(谷物): 1.5 (高敏感)
├─ 石油: 1.3 (中高敏感)
├─ 金属矿产: 0.7 (低敏感)
└─ 煤炭: 0.9 (中敏感)
预警等级:
Green (0-25): 无实质风险, 常规监测
Yellow (25-50): 中等风险, 激活预案
Orange (50-75): 高风险, 立即采取措施
Red (75-100): 极危状态, 启动危机管理
技术架构
3.1 整体数据流
yaml
多源数据 → Kafka → Flink ETL → Paimon (湖) → Doris (分析) → 应用
├─ 新闻源
├─ 地缘政治数据
├─ 制裁名单
├─ 商品期货
├─ 港口状态
└─ 金融数据
关键特点:
├─ Paimon: 原始数据+标准化数据存储, 支持版本控制、时间旅行
├─ Doris: 实时OLAP分析, 秒级查询, 支持高并发
└─ Flink: 实时流处理, NLP分类, 指标计算
3.2 为何选择Paimon
markdown
1. 湖表一体:
├─ 原始数据保留在数据湖 (HDFS/S3)
├─ 实时指标在表结构中维护
└─ 降低数据重复存储成本
2. 时间旅行与版本控制:
├─ 查询历史快照 ("昨天下午2点的指标?")
├─ 完整审计日志 (舆情变化追溯)
└─ 风险回溯分析不可或缺
3. 高效增量更新:
├─ Flink直接流式写入
├─ Merge操作 (重复事件去重)
└─ 避免全量重新计算
4. ACID事务:
├─ 舆情指标一致性保证
├─ 多维度并发更新无脏数据
└─ 金融级别的准确性要求
Paimon表设计:
├─ mgir_events_raw: 原始事件 (90天保留)
├─ mgir_events_standardized: NLP后标准化事件
└─ mgir_dimension_scores: 维度指标增量表
3.3 为何选择Doris
markdown
1. 实时分析性能:
├─ 向量化执行+Pipeline模型
├─ 查询P95 <1秒 (百亿行数据)
├─ 支持1000+并发查询
└─ BI仪表板实时刷新
2. 多维聚合能力:
├─ Rollup和物化视图预计算
├─ 按(采购商, 商品, 小时)粒度聚合
├─ BI直接读取预聚合结果
└─ 特别适合舆情指标多维钻取
3. 数据新鲜度:
├─ Paimon数据实时导入 (5分钟)
├─ 准实时分析能力
└─ 舆情指标时效性保证
4. 低成本存储:
├─ 列式压缩率高 (8-10倍)
├─ 相比关系数据库成本 1/10
└─ 承载2年+历史数据
Doris表设计:
├─ mgir_indices_fact: 实时指标事实表
├─ mgir_indices_hourly_agg: 小时聚合表 (Rollup)
├─ buyer_dimension: 采购商维度表
└─ commodity_dimension: 商品维度表
数据模型
4.1 Paimon原始事件表
sql
-- mgir_events_raw_paimon
-- 存储所有摄取的原始新闻/事件 (未处理)
-- 分片: (risk_date, buyer_geo_key) hash
-- TTL: 90天 (用于模型再训练)
字段:
├─ event_id (UUID, PK)
├─ ingest_timestamp (TIMESTAMP)
├─ risk_date (DATE)
├─ source_type (STRING: news/api/...)
├─ source_name (STRING)
├─ source_url (STRING)
├─ raw_text (TEXT)
├─ language (STRING)
├─ detected_geo_keys (ARRAY<STRING>)
├─ keywords (ARRAY<STRING>)
├─ nlp_processing_flag (BOOLEAN)
├─ processing_version (INT)
└─ confidence_raw (FLOAT)
特性:
├─ 版本控制: 支持时间旅行查询
├─ ACID: 数据一致性保证
└─ 保留期: 90天
4.2 Paimon标准化事件表
sql
-- mgir_events_standardized_paimon
-- 存储NLP处理后的标准化事件
-- 支持Merge操作 (去重)
关键字段:
├─ event_id (UUID, PK)
├─ raw_event_id (UUID)
├─ risk_date (DATE)
├─ delivery_impact_dim (ENUM: A/B/C/D)
├─ severity_subcategory (STRING)
├─ impact_severity_score (FLOAT: 0-10)
├─ sentiment_score_nlp (FLOAT: -1~1)
├─ confidence_score_nlp (FLOAT: 0-1)
├─ primary_buyer_geo_key (STRING)
├─ affected_buyer_geo_keys (ARRAY<STRING>)
├─ commodity_type (STRING)
├─ geographic_locations (ARRAY<STRUCT>)
│ ├─ location_name, latitude, longitude
│ ├─ gis_type, relevance_score
├─ extracted_entities (ARRAY<STRUCT>)
│ ├─ entity_name, entity_type, confidence
├─ nlp_model_version (STRING)
├─ processing_timestamp (TIMESTAMP)
└─ update_version (INT)
特性:
├─ 去重: Merge按event_id, 保留最新版本
├─ ACID事务: 保证一致性
└─ 主键约束: event_id
4.3 Doris实时指标事实表
sql
-- mgir_indices_fact_doris
-- 存储计算后的实时舆情指标
-- 分区: risk_date | Bucket: 32 (按buyer_geo_key hash)
CREATE TABLE mgir_indices_fact_doris (
event_id UUID NOT NULL,
ingest_timestamp DATETIME NOT NULL,
risk_date DATE NOT NULL,
buyer_geo_key VARCHAR(50) NOT NULL,
commodity_type VARCHAR(50) NOT NULL,
delivery_impact_dim VARCHAR(10),
impact_severity_score FLOAT,
sentiment_score_nlp FLOAT,
confidence_score_nlp FLOAT,
ldi_score FLOAT, -- 物流延迟指标
cri_score FLOAT, -- 合规风险指标
gri_score FLOAT, -- 地缘政治指标
fri_score FLOAT, -- 金融风险指标
composite_risk_index FLOAT, -- 综合风险 (0-100)
buyer_specific_risk_score FLOAT, -- 采购商特定风险
sentiment_vs_risk_corr FLOAT, -- 舆情vs风险相关度
risk_alert_level VARCHAR(10), -- Green/Yellow/Orange/Red
source_url VARCHAR(500),
location_latitude DOUBLE,
location_longitude DOUBLE,
location_distance_to_buyer FLOAT,
forecast_impact_duration INT
)
ENGINE = OLAP
DUPLICATE KEY (event_id)
PARTITION BY RANGE (risk_date) ()
DISTRIBUTED BY HASH(buyer_geo_key) BUCKETS 32
PROPERTIES ("replication_num" = "3");
索引:
├─ PRIMARY KEY: event_id
├─ INDEX: (buyer_geo_key, risk_date)
├─ INDEX: (composite_risk_index)
└─ BLOOM FILTER: (buyer_geo_key, commodity_type)
4.4 Doris维度表
sql
-- buyer_dimension_doris (采购商维度)
CREATE TABLE buyer_dimension_doris (
buyer_geo_key VARCHAR(50) PRIMARY KEY,
buyer_name VARCHAR(200),
country_code VARCHAR(10),
region_code VARCHAR(50),
latitude DOUBLE,
longitude DOUBLE,
annual_volume_usd BIGINT,
main_commodities VARCHAR(500),
supply_chain_complexity VARCHAR(50),
alternative_suppliers_cnt INT,
risk_appetite VARCHAR(50),
contact_email VARCHAR(100),
risk_manager_name VARCHAR(100),
last_updated DATETIME
)
ENGINE = OLAP
DISTRIBUTED BY HASH(buyer_geo_key);
-- commodity_dimension_doris (商品维度)
CREATE TABLE commodity_dimension_doris (
commodity_code VARCHAR(50) PRIMARY KEY,
commodity_name VARCHAR(200),
commodity_category VARCHAR(50),
perishability_multiplier FLOAT, -- 0.5-2.0
storage_cost_per_day_usd FLOAT,
typical_transport_duration INT,
price_volatility_yearly FLOAT,
ldi_base_weight FLOAT,
cri_base_weight FLOAT,
gri_base_weight FLOAT,
fri_base_weight FLOAT,
last_updated DATETIME
)
ENGINE = OLAP
DISTRIBUTED BY HASH(commodity_code);
实时管道
5.1 数据源摄取
markdown
数据源分类:
1. 新闻与舆情源 (文本)
├─ 路透社、彭博社、CNBC、WSJ
├─ 区域主流新闻网站
├─ Twitter/X舆论情感
└─ 频率: 实时/5分钟
2. 地缘政治数据 (结构化)
├─ ACLED (冲突事件)
├─ ICG (危机分析报告)
├─ 官方声明 (国务院/外交部)
└─ 频率: 每天/每周
3. 制裁与监管 (结构化)
├─ OFAC制裁名单 (美国财政部)
├─ EU制裁名单 (欧盟理事会)
├─ 进出口禁令
└─ 频率: 实时/每天更新
4. 商品市场 (高频结构化)
├─ CME期货、LME金属、现货价格
├─ 交易所API
└─ 频率: 秒级更新
5. 港口与物流 (实时)
├─ 港口状态、船舶AIS、海峡通过
└─ 频率: 实时/10分钟
6. 金融与汇率 (高频)
├─ 外汇、国债收益率、CDS价差
└─ 频率: 秒-分钟级
Kafka主题设计:
├─ raw_news_events: 原始新闻 (32分区, 7天保留)
├─ geopolitical_conflict_events: 冲突数据 (8分区, 30天)
├─ sanctions_and_regulations: 制裁更新 (4分区, 90天)
├─ commodity_prices_stream: 商品价格 (16分区, 3天)
├─ port_logistics_status: 港口状态 (8分区, 7天)
└─ financial_indicators: 金融指标 (16分区, 1天)
5.2 Flink流处理任务
yaml
Flink任务拓扑:
Source: Kafka 6个Topic消费
↓
[数据清洗与标准化]
├─ 格式转换 (JSON/XML → Struct)
├─ 去重 (按source_url或content hash)
├─ 时间同步 (UTC时区)
├─ 质量检查 (null处理)
└─ 异常值处理
↓
[NLP处理] (Stateless)
├─ 语言检测与翻译
├─ 命名实体识别 (NER)
│ ├─ 地点识别 (港口、国家、海峡)
│ ├─ 采购商识别
│ └─ 商品识别
├─ 事件分类 (A/B/C/D四维)
│ └─ 微调BERT/RoBERTa模型
├─ 情感分析 (sentiment_score)
├─ 严重度评分 (impact_severity)
└─ 置信度评估 (confidence)
↓
[维度指标计算] (Stateful时间窗口)
├─ 窗口: 60分钟滑动, 30分钟步长
├─ 计算LDI: GroupBy (buyer_geo_key, commodity)
├─ 计算CRI: GroupBy (buyer_geo_key, region)
├─ 计算GRI: GroupBy (buyer_geo_key, conflict_zone)
├─ 计算FRI: GroupBy (buyer_geo_key, country)
└─ 应用衰减函数: 历史事件权重递减
↓
[综合指标合成] (Stateless)
├─ 综合风险指数 = 40%(LDI+CRI)/2 + 60%(GRI+FRI)/2
├─ 采购商特定风险 = 综合指数 × 货运量 × 商品敏感性
├─ 舆情vs风险相关度 (相关性分析)
└─ 预警等级转换 (Green/Yellow/Orange/Red)
↓
Sink: 并行输出
├─ Paimon表 1: mgir_events_raw
├─ Paimon表 2: mgir_events_standardized
├─ Paimon表 3: mgir_dimension_scores
├─ Doris表: mgir_indices_fact
├─ 告警系统 (即时处理Red级)
└─ 监控日志
并行度配置:
├─ 数据清洗: 32
├─ NLP处理: 16 (GPU场景)
├─ 指标计算: 32 (高计算密集)
├─ 数据输出: 16
└─ Checkpoint: 60秒间隔, RocksDB后端
指标计算
6.1 LDI计算示例
python
# Flink代码示例
from pyflink.datastream import StreamExecutionEnvironment
from pyflink.datastream.functions import MapFunction, WindowFunction
from pyflink.datastream.window import TumblingEventTimeWindow
class LDICalculator(WindowFunction):
def apply(self, key, window, elements):
buyer_geo, commodity = key
ldi_score = 0.0
for event in elements:
# 基础严重度
severity = self.get_severity_score(event['delivery_impact_dim'])
# 距离衰减系数
distance = self.get_distance_to_buyer(
event['location_lat'],
event['location_lon'],
buyer_geo
)
proximity_factor = self.distance_decay(distance)
# 商品易腐性乘数
commodity_multiplier = self.get_commodity_multiplier(commodity)
# LDI累加
event_ldi = severity * proximity_factor * commodity_multiplier
# 衰减函数: 事件发生越早权重越小
days_old = (now - event['risk_date']).days
decay_factor = math.exp(-0.1 * days_old) # 半衰期10天
ldi_score += event_ldi * decay_factor
# 归一化到0-10
ldi_normalized = min(10.0, ldi_score)
yield (buyer_geo, commodity, ldi_normalized)
6.2 综合风险指数计算
python
class CompositeRiskCalculator:
def calculate(self, ldi, cri, gri, fri,
volume_weight, commodity_sensitivity):
"""
综合风险指数 = 40% × (LDI+CRI)/2 + 60% × (GRI+FRI)/2
采购商特定风险 = 综合指数 × 货运量权重 × 商品敏感性
"""
# 第2层: 维度指标平均
logistics_compliance_avg = (ldi + cri) / 2.0
geopolitical_financial_avg = (gri + fri) / 2.0
# 综合舆情指数 (0-100)
composite_index = (
0.4 * logistics_compliance_avg +
0.6 * geopolitical_financial_avg
) * 10 # 缩放到0-100
# 采购商特定风险 (考虑货运量和商品敏感性)
buyer_specific_risk = (
composite_index *
volume_weight *
commodity_sensitivity
)
buyer_specific_risk = min(100.0, buyer_specific_risk)
# 预警等级
alert_level = self.get_alert_level(buyer_specific_risk)
return {
'composite_risk_index': composite_index,
'buyer_specific_risk_score': buyer_specific_risk,
'risk_alert_level': alert_level
}
def get_alert_level(self, risk_score):
if risk_score < 25:
return 'Green'
elif risk_score < 50:
return 'Yellow'
elif risk_score < 75:
return 'Orange'
else:
return 'Red'
6.3 舆情相关度计算
python
def calculate_sentiment_risk_correlation(
sentiment_scores: List[float],
risk_scores: List[float]
) -> float:
"""
计算舆情热度与实际风险的相关度
>0.7: 舆情热度与实际风险高度相关
0.3-0.7: 中等相关
<0.3: 低相关 (舆情为"噪音")
"""
import numpy as np
if len(sentiment_scores) < 2 or len(risk_scores) < 2:
return 0.0
s = np.array(sentiment_scores)
r = np.array(risk_scores)
# Pearson相关系数
correlation = np.corrcoef(s, r)[0, 1]
return float(correlation) if not np.isnan(correlation) else 0.0