feat(运营报表): AUG26-015 设备激活与套餐续费日报快照、查询趋势与受控导出
All checks were successful
构建并部署到测试环境(无 SSH) / build-and-deploy (push) Successful in 15m33s
All checks were successful
构建并部署到测试环境(无 SSH) / build-and-deploy (push) Successful in 15m33s
- 新增成对迁移 000231 与三张快照表:日级头行、设备粒度激活行、到期事件粒度续费行,以快照日期为唯一键 - 新增每日 03:30(Asia/Shanghai)日报快照生成任务与幂等整日替换,失败重试沿用同一目标日 - 新增六条受控入口:两张报表的汇总、日/月趋势与受控导出,配套查询层只读快照事实 - 新增 operations_activation 与 operations_renewal 两个导出场景,创建期冻结筛选与可见店铺范围、派发期冻结表头、执行期只按冻结值复核资格 - 采购数量口径按系统内未删除设备数实施并在 PRD 标注,附实测差额依据 - 同步证据链 requirement-evidence.json 与入口能力矩阵、ARCHITECTURE 与验证记录 - 归档 change add-operations-reports 并新建主 Spec openspec/specs/operations-report/spec.md
This commit is contained in:
32
migrations/000231_create_operations_report_snapshot.down.sql
Normal file
32
migrations/000231_create_operations_report_snapshot.down.sql
Normal file
@@ -0,0 +1,32 @@
|
||||
-- 回滚运营报表日报快照三张表,与 up 严格成对。
|
||||
-- 删除顺序与 up 的创建顺序严格倒序:先删索引,再按依赖无关的表逐一删除
|
||||
-- (三张表之间没有外键与视图,删除顺序不产生级联影响;表内 CHECK 约束随表一并移除)。
|
||||
--
|
||||
-- 不可逆说明:本迁移的 down 会删除全部已生成的日报快照。
|
||||
-- 快照是「截至某个上海自然日」的冻结读数,其源事实(实名状态可逆转、到期时间可被改写)
|
||||
-- 无法事后重算,因此 down 只在确认不需要历史报表时执行。
|
||||
--
|
||||
-- 守卫:存在已生成的快照行时直接阻断回滚,避免误删已经无法重建的历史报表事实。
|
||||
|
||||
DO $$
|
||||
BEGIN
|
||||
IF EXISTS (SELECT 1 FROM tb_operations_report_snapshot LIMIT 1) THEN
|
||||
RAISE EXCEPTION '已存在运营报表日报快照,拒绝回滚以避免丢失无法重建的历史报表事实';
|
||||
END IF;
|
||||
END $$;
|
||||
|
||||
LOCK TABLE tb_operations_report_snapshot IN ACCESS EXCLUSIVE MODE;
|
||||
LOCK TABLE tb_operations_report_activation_row IN ACCESS EXCLUSIVE MODE;
|
||||
LOCK TABLE tb_operations_report_renewal_row IN ACCESS EXCLUSIVE MODE;
|
||||
|
||||
DROP INDEX IF EXISTS idx_operations_report_renewal_series;
|
||||
DROP INDEX IF EXISTS idx_operations_report_renewal_shop;
|
||||
DROP INDEX IF EXISTS uk_operations_report_renewal_row;
|
||||
DROP TABLE IF EXISTS tb_operations_report_renewal_row;
|
||||
|
||||
DROP INDEX IF EXISTS idx_operations_report_activation_shop;
|
||||
DROP INDEX IF EXISTS uk_operations_report_activation_row;
|
||||
DROP TABLE IF EXISTS tb_operations_report_activation_row;
|
||||
|
||||
DROP INDEX IF EXISTS uk_operations_report_snapshot_date;
|
||||
DROP TABLE IF EXISTS tb_operations_report_snapshot;
|
||||
173
migrations/000231_create_operations_report_snapshot.up.sql
Normal file
173
migrations/000231_create_operations_report_snapshot.up.sql
Normal file
@@ -0,0 +1,173 @@
|
||||
-- 运营报表:新增设备激活与套餐续费日报快照的三张事实表(迭代 AUG26-015,设计 D2/D3)。
|
||||
-- 背景:本次报表的「累计激活数」依赖「截至统计日任一当前有效关联卡已实名」,
|
||||
-- 而 tb_iot_card.real_name_status 是可逆转的当前状态、first_realname_at 只记首次且不再更新,
|
||||
-- 仓库内不存在任何 as-of 实名查询,实时 Query 在事实层面不成立;
|
||||
-- 到期时间本身也非不可变事实(后台可改单条使用记录的到期时间),
|
||||
-- 因此报表的唯一样本是每日落盘、整日替换的本地快照。
|
||||
--
|
||||
-- 设计选择:
|
||||
-- 1. 采用设备粒度事实行(D3),不采用「快照日期 × 维度类型 × 维度值」预聚合立方:
|
||||
-- 维度立方无法支持「以维度 A 筛选、以维度 B 分组」,而 PRD 明确禁止回填历史,
|
||||
-- 一旦事后需要该能力即为永久缺口。设备粒度约 1.9 万行/日,每日一次整日删除+插入为秒级操作。
|
||||
-- 2. 三张表都不设软删除列(D2):快照是整日幂等替换的事实表,
|
||||
-- deleted_at 会与「快照日期唯一」冲突,整日替换需要物理删除。
|
||||
-- 3. 不设外键:与既有表一致,关联由应用层显式维护(本能力只写入、只按快照日期读取)。
|
||||
-- 4. 头行承担三件事:①该日是否有快照的唯一判定;②日/月趋势与新增指标的序列来源
|
||||
-- (O(1) 读取,不必扫明细行);③合计行与分组行不变量校验的权威值。
|
||||
-- 可验证不变量:同一快照日期、任一受支持维度下,分组行各指标之和等于头行对应指标;
|
||||
-- renewal_renewed_asset_count <= renewal_due_asset_count。
|
||||
-- 5. 明细行同时冻结归属的两个候选取值(D11):沿上级店铺上溯至根的一级代理店铺、
|
||||
-- 以及归属该店铺的 user_type=3 代理账号;查询侧映射为单一「代理」维度。
|
||||
-- 冻结时机是快照生成执行时刻(D12),历史行不因后续归属变化被改写。
|
||||
-- 6. 迁移资产与本地购买资产同口径(D9):迁移写入的主套餐记录含老系统到期时间,
|
||||
-- 会真实产生到期事件与续费事件,不额外排除。
|
||||
-- 7. 快照列的字符宽度按来源列宽度取值(如 virtual_no/asset_identifier 取 tb_device.virtual_no 的 100、
|
||||
-- series_name 取 tb_package_series.series_name 的 255),避免快照写入时静默截断来源事实。
|
||||
|
||||
CREATE TABLE tb_operations_report_snapshot (
|
||||
id BIGSERIAL PRIMARY KEY,
|
||||
snapshot_date DATE NOT NULL,
|
||||
purchased_device_count BIGINT NOT NULL DEFAULT 0,
|
||||
activated_device_count BIGINT NOT NULL DEFAULT 0,
|
||||
online_device_count BIGINT NOT NULL DEFAULT 0,
|
||||
active_device_count BIGINT NOT NULL DEFAULT 0,
|
||||
total_real_traffic_mb NUMERIC(20,2) NOT NULL DEFAULT 0,
|
||||
renewal_due_asset_count BIGINT NOT NULL DEFAULT 0,
|
||||
renewal_renewed_asset_count BIGINT NOT NULL DEFAULT 0,
|
||||
generated_at TIMESTAMP NOT NULL,
|
||||
created_at TIMESTAMP NOT NULL DEFAULT NOW(),
|
||||
updated_at TIMESTAMP NOT NULL DEFAULT NOW()
|
||||
);
|
||||
|
||||
-- 一日一行:既是「该日是否有快照」的唯一判定,也是整日替换的替换键。
|
||||
CREATE UNIQUE INDEX uk_operations_report_snapshot_date ON tb_operations_report_snapshot (snapshot_date);
|
||||
|
||||
CREATE TABLE tb_operations_report_activation_row (
|
||||
id BIGSERIAL PRIMARY KEY,
|
||||
snapshot_date DATE NOT NULL,
|
||||
device_id BIGINT NOT NULL,
|
||||
virtual_no VARCHAR(100) NOT NULL DEFAULT '',
|
||||
device_name VARCHAR(255) NOT NULL DEFAULT '',
|
||||
device_model VARCHAR(100) NOT NULL DEFAULT '',
|
||||
manufacturer VARCHAR(255) NOT NULL DEFAULT '',
|
||||
shop_id BIGINT,
|
||||
shop_name VARCHAR(100) NOT NULL DEFAULT '',
|
||||
root_shop_id BIGINT,
|
||||
root_shop_name VARCHAR(100) NOT NULL DEFAULT '',
|
||||
agent_account_id BIGINT,
|
||||
agent_account_name VARCHAR(255) NOT NULL DEFAULT '',
|
||||
business_owner_account_id BIGINT,
|
||||
business_owner_name VARCHAR(255) NOT NULL DEFAULT '',
|
||||
business_user_group_id BIGINT,
|
||||
business_user_group_name VARCHAR(100) NOT NULL DEFAULT '',
|
||||
purchased BOOLEAN NOT NULL DEFAULT FALSE,
|
||||
realnamed BOOLEAN NOT NULL DEFAULT FALSE,
|
||||
online BOOLEAN NOT NULL DEFAULT FALSE,
|
||||
active BOOLEAN NOT NULL DEFAULT FALSE,
|
||||
real_traffic_mb NUMERIC(20,2) NOT NULL DEFAULT 0,
|
||||
created_at TIMESTAMP NOT NULL DEFAULT NOW()
|
||||
);
|
||||
|
||||
CREATE UNIQUE INDEX uk_operations_report_activation_row ON tb_operations_report_activation_row (snapshot_date, device_id);
|
||||
CREATE INDEX idx_operations_report_activation_shop ON tb_operations_report_activation_row (snapshot_date, shop_id);
|
||||
|
||||
CREATE TABLE tb_operations_report_renewal_row (
|
||||
id BIGSERIAL PRIMARY KEY,
|
||||
snapshot_date DATE NOT NULL,
|
||||
asset_type VARCHAR(20) NOT NULL,
|
||||
asset_id BIGINT NOT NULL,
|
||||
asset_identifier VARCHAR(100) NOT NULL DEFAULT '',
|
||||
expired_usage_id BIGINT NOT NULL,
|
||||
package_id BIGINT NOT NULL,
|
||||
package_name VARCHAR(255) NOT NULL DEFAULT '',
|
||||
series_id BIGINT,
|
||||
series_name VARCHAR(255) NOT NULL DEFAULT '',
|
||||
shop_id BIGINT,
|
||||
shop_name VARCHAR(100) NOT NULL DEFAULT '',
|
||||
root_shop_id BIGINT,
|
||||
root_shop_name VARCHAR(100) NOT NULL DEFAULT '',
|
||||
agent_account_id BIGINT,
|
||||
agent_account_name VARCHAR(255) NOT NULL DEFAULT '',
|
||||
business_owner_account_id BIGINT,
|
||||
business_owner_name VARCHAR(255) NOT NULL DEFAULT '',
|
||||
business_user_group_id BIGINT,
|
||||
business_user_group_name VARCHAR(100) NOT NULL DEFAULT '',
|
||||
renewed BOOLEAN NOT NULL DEFAULT FALSE,
|
||||
created_at TIMESTAMP NOT NULL DEFAULT NOW(),
|
||||
CONSTRAINT ck_operations_report_renewal_row_asset_type CHECK (asset_type IN ('device', 'single_card'))
|
||||
);
|
||||
|
||||
-- 到期事件粒度:同一到期使用记录在同一快照日至多一行。
|
||||
CREATE UNIQUE INDEX uk_operations_report_renewal_row ON tb_operations_report_renewal_row (snapshot_date, expired_usage_id);
|
||||
CREATE INDEX idx_operations_report_renewal_shop ON tb_operations_report_renewal_row (snapshot_date, shop_id);
|
||||
CREATE INDEX idx_operations_report_renewal_series ON tb_operations_report_renewal_row (snapshot_date, series_id);
|
||||
|
||||
COMMENT ON TABLE tb_operations_report_snapshot IS '运营报表日级快照头行:一个上海自然日一行,承载「该日是否有快照」与全部指标的权威值,整日幂等替换';
|
||||
COMMENT ON COLUMN tb_operations_report_snapshot.id IS '主键';
|
||||
COMMENT ON COLUMN tb_operations_report_snapshot.snapshot_date IS '快照日期(上海自然日),唯一键;整日替换的替换键';
|
||||
COMMENT ON COLUMN tb_operations_report_snapshot.purchased_device_count IS '采购数量:截至快照日系统内未删除的设备数(口径收敛在单一函数内,见设计 D6)';
|
||||
COMMENT ON COLUMN tb_operations_report_snapshot.activated_device_count IS '累计激活数:任一当前有效关联卡已实名的设备数;实名可逆转,允许下降';
|
||||
COMMENT ON COLUMN tb_operations_report_snapshot.online_device_count IS '累计在网数:已实名且存在有效主套餐(主套餐、生效或已用完、未退款)的设备数';
|
||||
COMMENT ON COLUMN tb_operations_report_snapshot.active_device_count IS '活跃用户数:真流量合计大于零的设备数';
|
||||
COMMENT ON COLUMN tb_operations_report_snapshot.total_real_traffic_mb IS '累计用量:设备自身与其当前有效关联卡的当前有效使用记录的真已用量之和(MB)';
|
||||
COMMENT ON COLUMN tb_operations_report_snapshot.renewal_due_asset_count IS '到期资产数:未退款主套餐到期日为快照日的资产数(按资产去重)';
|
||||
COMMENT ON COLUMN tb_operations_report_snapshot.renewal_renewed_asset_count IS '续费资产数:到期资产中已续费的资产数(按资产去重,恒为到期资产数的子集)';
|
||||
COMMENT ON COLUMN tb_operations_report_snapshot.generated_at IS '快照生成时刻(本次整日替换的执行时刻)';
|
||||
COMMENT ON COLUMN tb_operations_report_snapshot.created_at IS '创建时间';
|
||||
COMMENT ON COLUMN tb_operations_report_snapshot.updated_at IS '最近更新时间';
|
||||
|
||||
COMMENT ON TABLE tb_operations_report_activation_row IS '运营报表设备激活快照行:设备粒度,一行对应一台未删除设备,冻结其生成时刻的归属与指标';
|
||||
COMMENT ON COLUMN tb_operations_report_activation_row.id IS '主键';
|
||||
COMMENT ON COLUMN tb_operations_report_activation_row.snapshot_date IS '快照日期(上海自然日)';
|
||||
COMMENT ON COLUMN tb_operations_report_activation_row.device_id IS '设备ID(tb_device.id)';
|
||||
COMMENT ON COLUMN tb_operations_report_activation_row.virtual_no IS '设备虚拟号快照';
|
||||
COMMENT ON COLUMN tb_operations_report_activation_row.device_name IS '设备名称快照(生成时刻取值,机型维度的分组列)';
|
||||
COMMENT ON COLUMN tb_operations_report_activation_row.device_model IS '设备型号快照(生成时刻取值)';
|
||||
COMMENT ON COLUMN tb_operations_report_activation_row.manufacturer IS '制造商快照(生成时刻取值)';
|
||||
COMMENT ON COLUMN tb_operations_report_activation_row.shop_id IS '生成时刻设备所属店铺ID,NULL-设备无店铺归属';
|
||||
COMMENT ON COLUMN tb_operations_report_activation_row.shop_name IS '生成时刻店铺名称快照';
|
||||
COMMENT ON COLUMN tb_operations_report_activation_row.root_shop_id IS '生成时刻沿上级店铺上溯至根的一级代理店铺ID,NULL-无店铺归属';
|
||||
COMMENT ON COLUMN tb_operations_report_activation_row.root_shop_name IS '生成时刻一级代理店铺名称快照';
|
||||
COMMENT ON COLUMN tb_operations_report_activation_row.agent_account_id IS '生成时刻归属该一级代理店铺的代理账号ID(user_type=3),NULL-无代理账号';
|
||||
COMMENT ON COLUMN tb_operations_report_activation_row.agent_account_name IS '生成时刻代理账号名称快照';
|
||||
COMMENT ON COLUMN tb_operations_report_activation_row.business_owner_account_id IS '生成时刻店铺业务员账号ID,NULL-店铺无业务员';
|
||||
COMMENT ON COLUMN tb_operations_report_activation_row.business_owner_name IS '生成时刻业务员账号名称快照';
|
||||
COMMENT ON COLUMN tb_operations_report_activation_row.business_user_group_id IS '生成时刻业务员所属业务用户组ID(按既有实时推导口径取值后冻结),NULL-无用户组';
|
||||
COMMENT ON COLUMN tb_operations_report_activation_row.business_user_group_name IS '生成时刻业务用户组名称快照';
|
||||
COMMENT ON COLUMN tb_operations_report_activation_row.purchased IS '采购标记:该设备计入采购数量(当前口径下未删除设备恒为 true,保留逐行标记以便口径切换时分组行之和仍等于头行值)';
|
||||
COMMENT ON COLUMN tb_operations_report_activation_row.realnamed IS '累计激活标记:任一当前有效关联卡(bind_status=1 且未删除)已实名(卡未删除且 real_name_status=1)';
|
||||
COMMENT ON COLUMN tb_operations_report_activation_row.online IS '累计在网标记:已实名且存在有效主套餐(主套餐、状态生效或已用完、未退款、未删除)';
|
||||
COMMENT ON COLUMN tb_operations_report_activation_row.active IS '活跃标记:该设备自身与其当前有效关联卡的当前有效使用记录真已用量合计大于零';
|
||||
COMMENT ON COLUMN tb_operations_report_activation_row.real_traffic_mb IS '真流量合计(MB):设备自身 + 当前有效关联卡的当前有效使用记录的 data_usage_mb,不含卡级全生命周期、自然月累计、通道读数、虚量与展示量';
|
||||
COMMENT ON COLUMN tb_operations_report_activation_row.created_at IS '创建时间';
|
||||
|
||||
COMMENT ON TABLE tb_operations_report_renewal_row IS '运营报表套餐续费快照行:到期事件粒度,一行对应一条在快照日到期的主套餐使用记录';
|
||||
COMMENT ON COLUMN tb_operations_report_renewal_row.id IS '主键';
|
||||
COMMENT ON COLUMN tb_operations_report_renewal_row.snapshot_date IS '快照日期(上海自然日),即该条主套餐的到期日';
|
||||
COMMENT ON COLUMN tb_operations_report_renewal_row.asset_type IS '资产类型 device-设备 single_card-单卡,沿用既有载体类型取值';
|
||||
COMMENT ON COLUMN tb_operations_report_renewal_row.asset_id IS '资产ID(tb_device.id 或 tb_iot_card.id)';
|
||||
COMMENT ON COLUMN tb_operations_report_renewal_row.asset_identifier IS '资产标识快照:设备取虚拟号,单卡取 ICCID';
|
||||
COMMENT ON COLUMN tb_operations_report_renewal_row.expired_usage_id IS '到期的主套餐使用记录ID(tb_package_usage.id),与快照日构成唯一键';
|
||||
COMMENT ON COLUMN tb_operations_report_renewal_row.package_id IS '到期主套餐商品ID';
|
||||
COMMENT ON COLUMN tb_operations_report_renewal_row.package_name IS '套餐名称快照(取自使用记录或商品)';
|
||||
COMMENT ON COLUMN tb_operations_report_renewal_row.series_id IS '套餐系列ID,NULL-无系列';
|
||||
COMMENT ON COLUMN tb_operations_report_renewal_row.series_name IS '套餐系列名称快照';
|
||||
COMMENT ON COLUMN tb_operations_report_renewal_row.shop_id IS '生成时刻资产所属店铺ID,NULL-无店铺归属';
|
||||
COMMENT ON COLUMN tb_operations_report_renewal_row.shop_name IS '生成时刻店铺名称快照';
|
||||
COMMENT ON COLUMN tb_operations_report_renewal_row.root_shop_id IS '生成时刻沿上级店铺上溯至根的一级代理店铺ID';
|
||||
COMMENT ON COLUMN tb_operations_report_renewal_row.root_shop_name IS '生成时刻一级代理店铺名称快照';
|
||||
COMMENT ON COLUMN tb_operations_report_renewal_row.agent_account_id IS '生成时刻归属该一级代理店铺的代理账号ID(user_type=3)';
|
||||
COMMENT ON COLUMN tb_operations_report_renewal_row.agent_account_name IS '生成时刻代理账号名称快照';
|
||||
COMMENT ON COLUMN tb_operations_report_renewal_row.business_owner_account_id IS '生成时刻店铺业务员账号ID';
|
||||
COMMENT ON COLUMN tb_operations_report_renewal_row.business_owner_name IS '生成时刻业务员账号名称快照';
|
||||
COMMENT ON COLUMN tb_operations_report_renewal_row.business_user_group_id IS '生成时刻业务员所属业务用户组ID(按既有实时推导口径取值后冻结)';
|
||||
COMMENT ON COLUMN tb_operations_report_renewal_row.business_user_group_name IS '生成时刻业务用户组名称快照';
|
||||
COMMENT ON COLUMN tb_operations_report_renewal_row.renewed IS '续费标记:该到期资产存在另一条未退款主套餐记录,其生效时间晚于本条到期时间或处于待生效状态';
|
||||
COMMENT ON COLUMN tb_operations_report_renewal_row.created_at IS '创建时间';
|
||||
|
||||
COMMENT ON INDEX uk_operations_report_snapshot_date IS '快照日期唯一键:一日一行,整日幂等替换的替换键';
|
||||
COMMENT ON INDEX uk_operations_report_activation_row IS '设备粒度激活行唯一键:同一快照日一台设备至多一行';
|
||||
COMMENT ON INDEX idx_operations_report_activation_shop IS '激活行按快照日期与店铺查询索引';
|
||||
COMMENT ON INDEX uk_operations_report_renewal_row IS '到期事件粒度续费行唯一键:同一快照日一条到期使用记录至多一行';
|
||||
COMMENT ON INDEX idx_operations_report_renewal_shop IS '续费行按快照日期与店铺查询索引';
|
||||
COMMENT ON INDEX idx_operations_report_renewal_series IS '续费行按快照日期与套餐系列分组索引';
|
||||
Reference in New Issue
Block a user