vault backup: 2026-05-26 00:23:07

This commit is contained in:
Willie
2026-05-26 00:23:07 +08:00
parent 896265cad6
commit 495caa2780
5 changed files with 959 additions and 4 deletions

View File

@@ -197,6 +197,10 @@
},
"active": "849c5ff8936a2b67",
"lastOpenFiles": [
"prop-acc/scenarios/meter/generate-bill-with-multiplier.md",
"prop-acc/scenarios/meter/generate-bill-tiered-pricing.md",
"prop-acc/scenarios/meter/read-with-photo-proof.md",
"prop-acc/scenarios/meter/read-via-iot-remote-source.md",
"prop-acc/scenarios/meter/read-batch-via-excel-import.md",
"prop-acc/scenarios/meter/read-single-meter-manual.md",
"prop-acc/scenarios/meter/decommission-without-replacement.md",
@@ -221,10 +225,6 @@
"prop-acc/scenarios/prepaid/freeze-suspected-fraud.md",
"prop-acc/scenarios/prepaid/refund-partial-after-consume.md",
"prop-acc/scenarios/prepaid/refund-full-resident-moveout.md",
"prop-acc/scenarios/prepaid/consume-batch-auto-monthly.md",
"prop-acc/scenarios/prepaid/consume-meter-bill.md",
"prop-acc/scenarios/prepaid/consume-multiple-bills-priority.md",
"prop-acc/scenarios/prepaid/consume-monthly-property-bill.md",
"prop-acc/scenarios/prepaid",
"prop-acc/concepts/prepaid",
"prop-acc/scenarios/deposit",

View File

@@ -0,0 +1,246 @@
---
title: prop-acc · meter · 场景 - 阶梯水电价生成账单(progressive 累进)
aliases:
- 阶梯计价账单
- tiered pricing 算例
- generate-bill-tiered-pricing
- 场景-阶梯计价生成账单
tags:
- 场景
- prop-acc
- 计量表
- 账单生成
audience:
- 业务人员
- 财务
- 业户
status: 已发布
sub_feature: meter
last_review: 2026-05-26
code_version: 2026-05-22
---
# 场景:阶梯水电价生成账单(progressive 累进)
业户**用量超过低阶梯**进入更高阶梯时,系统按 **progressive 累进** 算账单(每段用量按各自单价,**不是**整月按最高阶梯)。本场景完整演示算例。
## 典型情境
> [!example] 真实情境
> 张阿姨 5 月用水 35 吨(平时 12-15 吨,本月浇花 + 装修),嘉禾花园的水费阶梯如下:
>
> | 阶梯 | 月用水(吨)| 单价(元/吨)|
> |---|---|---|
> | 第一阶梯 | 0-20 | 3.0 |
> | 第二阶梯 | 21-30 | 4.5 |
> | 第三阶梯 | 30+ | 6.0 |
>
> 张阿姨 5 月用 35 吨,本月水费应是多少?
**Progressive 累进算法**(本系统采用):
```
段 1(0-20 吨):20 × 3.0 = 60 元
段 2(21-30 吨):10 × 4.5 = 45 元
段 3(31-35 吨): 5 × 6.0 = 30 元
合计: 135 元 ✅
```
**Full-tier 简陋实现**(错!本系统未采用):
```
35 吨 × 6.0(整月按最高阶梯) = 210 元 ❌
```
差额 ¥75。简陋实现对业户极不公平,**是市场上劣质系统的常见 bug**。本系统 `MeterBillCalculator::calculateTiered()` 实现的是 progressive,业户收到的账单准确。
## 系统流程
```mermaid
sequenceDiagram
participant 抄表完成
participant Action[GenerateBillsFromMeterReadingsAction]
participant Service[MeterBillGenerationService]
participant Calc[MeterBillCalculator]
participant 数据库
抄表完成->>Action: handle([5月 reading,consumption=35])
Action->>Service: generateBillForReading(reading)
Service->>数据库: 查 meter.fee_type=水费 → 查 RatePlan + RateTier(3 个 tier)
Service->>Calc: calculate(consumption=35, ratePlan, min, max)
Calc->>Calc: calculateTiered(35, [tier1, tier2, tier3])
Note over Calc: 段 1:min(35,20)-0=20 → 20*3=60
Note over Calc: 段 2:min(35,30)-20=10 → 10*4.5=45
Note over Calc: 段 3:35-30=5 → 5*6=30
Note over Calc: 总和 135
Calc-->>Service: 135
Service->>Service: clamp(135, min, max) = 135
Service->>数据库: 建 Bill(amount=135, sourceable=reading)
Service->>数据库: 更新 reading.bill_id
Service-->>Action: ok
```
## `calculateTiered()` 算法(伪代码)
```php
function calculateTiered(float $consumption, Collection $tiers): float
{
$amount = 0.0;
$remaining = $consumption;
foreach ($tiers as $tier) {
if ($remaining <= 0) break;
$tierRange = $tier->upper ? $tier->upper - $tier->lower : INF;
$consumedInTier = min($remaining, $tierRange);
$amount += $consumedInTier * $tier->unit_price;
$remaining -= $consumedInTier;
}
return $amount;
}
```
详见 [[multiplier-and-tiered-pricing|倍率与阶梯计价]] 概念。
## 业户视角
### 您收到的账单(简化版)
```
5 月水费账单
用水量:35 吨
明细:
0-20 吨段:20 × 3.0 = 60 元
21-30 吨段:10 × 4.5 = 45 元
31+ 吨段: 5 × 6.0 = 30 元
合计:135 元
应付:¥135.00
```
> [!info] 账单明细的展示
> 当前 Bill / Receipt 是否展示阶梯明细取决于模板设计。**强烈推荐展示**:
>
> - 业户能看清"为什么收 135 不是 105"
> - 政策合规(国家阶梯水电价要求公开透明)
> - 减少业户疑问
### 用得越多越贵的教育意义
阶梯计价**鼓励节约**:
| 用量 | 总水费 | 平均单价 |
|---|---|---|
| 15 吨(低用)| 45 | 3.0 |
| 35 吨(本场景)| 135 | 3.86 |
| 60 吨(浪费)| 270 | 4.5 |
业户**用越多平均单价越高**,符合"超额消费多付费"的政策导向。
## 业务人员视角
### 配置阶梯
阶梯定义在 `RatePlan` + `RateTier`(不在 meter 子模块,通常运营 / 财务总监配):
- 后台 → 费率管理 → 选水费 → 编辑 RatePlan
- 加 RateTier:`tier=1, lower=0, upper=20, unit_price=3.0`
- 加 RateTier:`tier=2, lower=20, upper=30, unit_price=4.5`
- 加 RateTier:`tier=3, lower=30, upper=null, unit_price=6.0`(upper=null 表示无上限)
> [!warning] 阶梯配置要严谨
> 配置错的常见症状:
> - 段不连续(`tier1 upper=20`, `tier2 lower=22`)→ 21 吨用量无法分配
> - 段重叠(`tier1 upper=20`, `tier2 lower=18`)→ 18-20 吨段算两次
> - 缺最高段(没有 `tier_max`)→ 超过最高阶梯的用量无单价
>
> 业务人员配置完后**用极端值算例**验证(0 / 1 / 20 / 21 / 30 / 31 / 100 / 1000 吨各算一遍看是否合理)。
### 月度账单生成
抄表完成 → 业务人员触发 `GenerateBillsFromMeterReadingsAction`(或自动)→ 系统调 `MeterBillCalculator` → 每张表算金额 → 建 Bill。
### 异常处理
阶梯计价的常见异常:
| 异常 | 处置 |
|---|---|
| 业户用量极高(> 100 吨) | [[exception-high-consumption|高用量预警]] → 排查是否漏水 / 设备故障 |
| 业户用量极低(0 吨)| `min_amount` 兜底,详见 [[generate-bill-min-max-cap]] |
| 业户用量倒走(reading 错)| 走 [[exception-readings-locked-after-bill|修正流程]] |
## 财务视角
### 账面会计
阶梯账单的**总金额**仍归"水费收入"科目。无需按阶梯拆分入账。
阶梯只影响**业户感知**(单价不同)和**业户行为引导**(鼓励节约),不影响会计核算。
### 报表统计
业务可能想看"本月各阶梯段用量分布"(政策报告 / 节约成效),需要单独的报表 SQL:
```sql
-- 本月各阶梯段用量分布(简化版,真实算法更复杂)
SELECT
SUM(LEAST(consumption, 20)) AS tier1_volume,
SUM(GREATEST(LEAST(consumption, 30) - 20, 0)) AS tier2_volume,
SUM(GREATEST(consumption - 30, 0)) AS tier3_volume
FROM acc_meter_readings
WHERE meter_id IN (SELECT id FROM acc_meters WHERE community_id=? AND fee_type_id=)
AND read_at BETWEEN '2026-05-01' AND '2026-05-31';
```
## 常见问题
> [!question] 阶梯按月 / 按年?
> 看物业政策:
> - **按月**(常见):每月 reset,从段 1 开始算
> - **按年**(部分地区):全年累计,跨段更慢
>
> 当前系统**按月**(单次抄表 = 单段计价)。按年的话需要不同算法(累加去年 12 月以来的用量,再分阶梯)。
> [!question] 不同物业 / 不同社区可以有不同阶梯吗?
> 可以。`RatePlan` 按 `community_id` + `fee_type_id` 隔离。每个社区独立配置。
> [!question] 阶梯改了,历史 Bill 怎么办?
> 历史 Bill 不变(`Bill.amount` 是当时算出的,不动态查 RatePlan)。新 Bill 按新阶梯算。
>
> 这是正确做法(已发账单不应因配置变化追溯改金额)。
> [!question] 业户对算法有疑问怎么解释?
> 给业户看明细(段 1 + 段 2 + 段 3)+ 阶梯单价表。绝大多数业户看懂后接受。
> [!question] progressive 算法的边界用量(如 20 吨整)算哪段?
> 看实现细节:
> - `consumption=20`:段 1 全部(20 × 3 = 60),段 2 / 3 不进入
> - `consumption=20.01`:段 1(20 × 3 = 60)+ 段 2(0.01 × 4.5 ≈ 0.045)
> - 边界是 inclusive 还是 exclusive 看 RateTier 配置(`lower`/`upper` 字段语义)
> [!question] 阶梯计价对工业表(multiplier > 1)的影响?
> 倍率 + 阶梯叠加:`consumption = (current - previous) × multiplier`,然后这个 consumption 走阶梯。例如三相工业表 multiplier=10:
>
> - 物理表头读数差 28 度 → consumption = 280 度
> - 280 度走阶梯计算
>
> 详见 [[generate-bill-with-multiplier]]。
## 异常分支
- 工业表倍率参与 → [[generate-bill-with-multiplier]]
- 异常用量触发 min/max → [[generate-bill-min-max-cap]]
- 用量异常高(漏水)→ [[exception-high-consumption]]
## 相关文档
- [[multiplier-and-tiered-pricing]]
- [[bill-generation-pipeline]]
- [[meter-vs-meter-reading]]
- [[generate-bill-with-multiplier]]
- [[generate-bill-min-max-cap]]

View File

@@ -0,0 +1,234 @@
---
title: prop-acc · meter · 场景 - 工业表 10x 倍率生成账单
aliases:
- 工业表账单
- multiplier 计算
- generate-bill-with-multiplier
- 场景-倍率表生成账单
tags:
- 场景
- prop-acc
- 计量表
- 账单生成
- 倍率
audience:
- 业务人员
- 财务
- 抄表员
status: 已发布
sub_feature: meter
last_review: 2026-05-26
code_version: 2026-05-22
---
# 场景:工业表 10x 倍率生成账单
商铺 / 集团表 / 三相工业表的物理表头**只显示用量的 1/10 或 1/100**,需要乘以倍率(multiplier)才是实际用量。系统在 `consumption = (current - previous) × multiplier` 公式中自动处理。
## 典型情境
> [!example] 真实情境
> 嘉禾花园 1 楼商铺(中式餐厅)装的是**三相工业电表**(multiplier=10)。本月抄表:
>
> - 上月 reading.current_reading = 280
> - 本月 reading.current_reading = 308
> - 物理表头读数差 = 28
>
> **实际用电量** = 28 × **10** = **280 度**(不是 28 度!)
>
> 按电费阶梯(0-200 度 0.8 元,200+ 度 1.0 元)算:
>
> ```
> 段 1:200 × 0.8 = 160 元
> 段 2:80 × 1.0 = 80 元
> 合计: 240 元
> ```
>
> 商铺老板看到账单 ¥240 / 280 度。
如果**没有 multiplier**:
```
consumption = (308 - 280) × 1 = 28 度
amount = 28 × 0.8 = 22.4 元 ❌
```
少收 ¥217.6,商铺白用 252 度电。**物业必须用倍率算账,这是工业表的天然属性**。
## 系统流程
```mermaid
sequenceDiagram
participant 抄表员
participant Filament
participant Calc[MeterBillCalculator]
participant 数据库
抄表员->>Filament: 录入 reading current=308(物理表头)
Filament->>Filament: 查 meter.multiplier=10
Filament->>Filament: 查 previous_reading=280(上月)
Filament->>Filament: consumption = (308 - 280) × 10 = 280 度
Filament->>数据库: 建 MeterReading(consumption=280)
Note over 数据库: 后续 GenerateBills
Filament->>Calc: calculate(consumption=280, ratePlan)
Calc->>Calc: 阶梯算法 200*0.8 + 80*1 = 240
Calc-->>Filament: 240
Filament->>数据库: 建 Bill(amount=240)
```
## 抄表员视角(李师傅)
### 抄表录入
抄表员看到物理表头是 **308**(不是 3080),录入时填 **308**
> [!warning] 抄表员不应自己乘倍率
>
> **错误做法**:抄表员看到 308 + 知道倍率 10 → 录入 3080
>
> **后果**:系统再乘一次 10 → consumption = (3080 - 2800) × 10 = 2800 度 → 收业户 2800 度电费 → 业户疯狂投诉
>
> **正确做法**:**严格录入物理表头数字**(308),倍率由系统自动处理。
Form 上应显示 meter 的 multiplier 提醒抄表员:
```
表编号:E-COMMERCIAL-1
倍率:10x(工业表)
上次读数:280
本次读数:[___] (请录入物理表头数字)
```
### 与家用表的对比
| 维度 | 家用表(multiplier=1)| 工业表(multiplier=10/100/...)|
|---|---|---|
| 抄表员录入 | 表头数字 = 实际用量 | 表头数字 ≠ 实际用量 |
| 是否需要换算 | 不需要 | 需要(系统自动)|
| 抄表员错误风险 | 低 | **中**(可能录乘 / 不乘 10) |
| 业务培训 | 简单 | **需培训**(说清楚"录原始数字")|
## 业务人员视角
### 配置倍率
建表时 `MeterForm.multiplier` 字段填:
| 表类型 | 推荐 multiplier |
|---|---|
| 家用单相电表 | 1 |
| 家用水表 | 1 |
| 三相工业电表 | 10(常见)|
| 大型工业电表(高压侧) | 100 / 1000 |
| 工业大流量水表 | 10 / 100 |
具体看表的物理铭牌 + 设计图纸。
### 倍率改了如何处理
> [!warning] 倍率改动影响极大
> `multiplier` 改了不会重算历史 reading 的 consumption(那些数据已经存了)。但**会影响后续抄表**。
>
> **何时可改 multiplier**:
> - 表是 `is_active=true`(退役表不可改,见 [[decommission-and-locking]])
> - 表**没有任何已生成 Bill 的 reading**(若有,改 multiplier 让历史 vs 现在的算法不一致,审计困难)
>
> **推荐做法**:倍率配错 → 退役旧表 → 建新表用正确 multiplier(不走更换链,因为不是物理换表)。详见 [[decommission-without-replacement]]。
## 业户视角(商铺老板)
### 您看到的账单
```
2026 年 5 月电费账单
用电量:280 度
(本月表头读数 308,上月 280,差 28 × 倍率 10)
明细:
0-200 度段:200 × 0.8 = 160 元
201+ 度段: 80 × 1.0 = 80 元
合计:240 元
```
> [!info] 账单展示倍率信息
> 强烈推荐账单 / Receipt 展示"原始读数 + 倍率 + 计算公式",让业户看明白:
>
> - 业户看到差 28 度,但收 240 元,会疑惑(单价怎么算?)
> - 展示"28 × 倍率 10 = 280 度,按 280 算"业户秒懂
### 商铺老板的特殊关注
| 业户疑问 | 应答 |
|---|---|
| "为什么我家是工业表不是家用表?" | 商铺用电量大,法规要求工业表 |
| "倍率是物业定的吗?" | 不是,是物理表的属性,出厂时定 |
| "可以换成家用表吗?" | 不能,商铺合规上必须用工业表 |
## 倍率与阶梯的叠加
倍率**先算**,得到 consumption;然后 consumption 走阶梯。两者完全独立、按顺序应用。
完整公式:
```
consumption = (current - previous) × multiplier # 第 1 层
amount = sum(段 i 用量 × 段 i 单价) # 第 2 层(阶梯)
amount = clamp(amount, min, max) # 第 3 层(min/max 封顶)
```
详见 [[multiplier-and-tiered-pricing]] 完整说明。
## 不同 multiplier 表的算例
| 表类型 | multiplier | 表头读数差 | consumption | 单价 0.8 | 账单 |
|---|---|---|---|---|---|
| 家用 | 1 | 280 | 280 | 224 | 224 |
| 三相工业 | 10 | 28 | 280 | 224 | 224 |
| 大工业(高压侧) | 100 | 2.8 | 280 | 224 | 224 |
**同样 280 度,账单相同**,只是物理表头数字大小不同。系统的 multiplier 字段把这层差异隐藏在算法里。
## 常见问题
> [!question] multiplier 必须是整数吗?
> 不必须。decimal(10,4) 精度,支持 0.5 / 1.25 等小数。但 1.0 / 10.0 / 100.0 是市场常见的"整数倍率"。
> [!question] 同一表的 multiplier 会随时间变吗?
> 物理上**不会**。表的物理参数(变压比)是出厂时确定的,使用过程中不变。
>
> 系统层面**可改字段**(若 `is_active=true` 且无 Bill,Policy 允许),但**不推荐**改 —— 历史与现在的算法不一致,审计困难。
> [!question] 抄表员录入时如何区分"录物理读数"vs"录乘倍率后读数"?
> Form 上**明确显示倍率**+ 标注"请录入物理表头数字"。培训抄表员**严格遵守**。系统统一存物理表头数字。
> [!question] 集抄系统推过来的数据是物理读数还是乘了倍率?
> 看集抄运营商。**通常是物理读数**(IoT 设备读表头,不知道倍率)。本系统接收时按 `meter.multiplier` 算 consumption。
>
> 如果集抄推已乘倍率的数据 → 集抄端**降级处理**(本系统 multiplier 应该设为 1,避免重复乘)。需对接时讲清楚。
> [!question] 配置错倍率(应该 10 配成 1)会怎样?
> 系统按 multiplier=1 算 → consumption 缩小 10 倍 → 业户账单缩小 10 倍 → 物业损失收入(直到发现)。
>
> 发现后修复:
> - 改 multiplier=10(若 Policy 允许)
> - 修复历史已欠收(走"补开账单"业务流程,系统不直接支持)
> - 通知业户 + 退还 / 补收差额
## 异常分支
- 阶梯计价(本场景叠加)→ [[generate-bill-tiered-pricing]]
- min/max 封顶 → [[generate-bill-min-max-cap]]
- 倍率配错想改 → 复杂,通常退役旧表新建([[decommission-and-locking]])
- 抄表员录错乘倍率 → [[exception-readings-locked-after-bill]] 修正
## 相关文档
- [[multiplier-and-tiered-pricing]]
- [[bill-generation-pipeline]]
- [[meter-vs-meter-reading]]
- [[generate-bill-tiered-pricing]]
- [[generate-bill-min-max-cap]]

View File

@@ -0,0 +1,246 @@
---
title: prop-acc · meter · 场景 - 集抄系统自动推送(source=remote)
aliases:
- 集抄系统
- IoT 抄表
- read-via-iot-remote-source
- 场景-集抄自动抄表
tags:
- 场景
- prop-acc
- 计量表
- 抄表
- IoT
audience:
- 业务人员
- 架构师
- 抄表员
status: 已发布
sub_feature: meter
last_review: 2026-05-26
code_version: 2026-05-22
---
# 场景:集抄系统自动推送(source=remote)
集抄系统(IoT)通过 RS485 / NB-IoT / LoRa 等技术**远程读取**物理表读数,定时(每天 / 每小时 / 每月)推送给本系统。本系统收到后建 `MeterReading(source=remote)`,**完全自动,无人工介入**。是现代化物业的标配。
## 典型情境
> [!example] 真实情境
> 嘉禾花园 2 年前升级了集抄系统,1,200 张水电气表全部接入 IoT 网关。集抄运营商每月 1 日凌晨**统一上传上月用量数据**到本系统。
>
> 物业财务王主管早上来上班,集抄数据已经全部到位 + 账单已经自动生成。她只需要:
> - 看 dashboard 异常告警(高用量 / 集抄掉线表)
> - 手工处理少数异常情况
## 系统流程
```mermaid
sequenceDiagram
participant Meter[物理表]
participant Gateway[IoT 网关]
participant Vendor[集抄运营商平台]
participant API[本系统集抄 API]
participant 数据库
participant GenerateBills
Note over Meter: 物理表每月 1 日凌晨自报
Meter->>Gateway: 读数推送(RS485 / NB-IoT)
Gateway->>Vendor: 上传(GSM / 4G / 有线)
Vendor->>Vendor: 整合 + 校验 + 归档
Note over Vendor: 月度批量推送(可能是每天 / 每月)
Vendor->>API: HTTP POST(批量推 readings)
API->>API: 校验签名 + 防重放
API->>API: 校验 meter 是否存在 + 是否 active
loop 每条 reading
API->>数据库: 建 MeterReading(source=remote, operated_by=null, photo_url=null)
end
API->>GenerateBills: handle(刚建的 readings)
GenerateBills->>数据库: 批量建 Bill + 回写 reading.bill_id
API-->>Vendor: 响应(成功 N,失败 M)
```
## 业务人员视角
### 日常(集抄正常运行)
业务人员**几乎不操作**:
-`MeterDashboard`(自动跳出异常告警)
- 月度对账(`audit-meters-needing-reading.md` 类似审计)
- 不需要手动抄表 / 录入
### 月初(集抄推数完成后)
通常上午到岗时:
- 所有 reading 已经在 `MeterReading` 表里
- 所有 Bill 已经生成
- Dashboard 显示:本月已抄 N 张 / 应抄 M 张
-`HighConsumptionReadingsListWidget` 异常告警 → 处理([[exception-high-consumption]])
-`MetersNeedingReadingListWidget` 显示掉线 / 漏抄表(本场景的兜底:走 [[read-single-meter-manual]] 个别补抄)
### 异常(集抄掉线)
| 掉线规模 | 处置 |
|---|---|
| 个别表(< 5%)| 抄表员补抄([[read-single-meter-manual]])|
| 大面积(网关故障)| 联系集抄运营商修复 + 物业短期手抄兜底 |
| 长期掉线(> 2 周)| 重新评估集抄设备的可靠性 |
## 集抄 API 设计(待文档化 / 实现细节)
> [!info] 当前实施状态
> 集抄 API 的**具体实现细节**(endpoint、签名、数据格式、防重放)需要看代码或与集抄运营商对接文档,本场景描述**业务流程层**。
### 接口要求
| 要求 | 说明 |
|---|---|
| 接收端 | 本系统提供 HTTP POST endpoint |
| 数据格式 | JSON 数组,每条 reading 字段:meter_code / read_at / current_reading / community_id / vendor_signature |
| 签名校验 | 防伪造,通常 HMAC-SHA256 或非对称签名 |
| 防重放 | 同 meter + 同 read_at 不重复建(unique index)|
| 错误响应 | 详细告知哪条失败、原因 |
| 失败重试 | 集抄运营商按响应决定重试逻辑 |
### MeterReading 字段对照
集抄推送的 reading,数据库存的 MeterReading:
| 字段 | 集抄数据 | 系统存 |
|---|---|---|
| `meter_id` | 集抄推 meter_code → 系统查 | meter ID |
| `read_at` | 集抄推时间(通常表自身上报) | datetime |
| `current_reading` | 物理表读数 | decimal(12,2) |
| `previous_reading` | (系统自动取上次)| decimal |
| `consumption` | (系统自动算)| decimal |
| **`source`** | (系统设)| **`remote`** |
| `operated_by` | null(集抄无人)| null |
| `photo_url` | null(集抄无照片)| null |
| `bill_id` | null(初始)→ 后续 GenerateBills 回写 | 可空 |
| `memo` | 选填(集抄可能写型号 / 网关 ID)| text |
## 集抄 vs 手抄 数据差异
| 维度 | manual(手抄) | **remote(集抄)** |
|---|---|---|
| 录入速度 | 慢(几天到几周)| **几小时全社区** |
| 准确性 | 中(手抖 / 看错)| **高**(机器直读)|
| 拍照存证 | 强烈推荐 | 无(IoT 设备自身就是凭证)|
| operated_by | 抄表员 ID | null |
| 业户感知 | 抄表员上门 | **无感**(无人上门)|
| 异常处理 | 抄表员现场判断 | 系统后台告警 → 人工介入 |
| 成本 | 抄表员工资 | IoT 设备 + 网关 + 月度服务费 |
| 业户家在不在影响 | 影响(开门才能抄) | 无影响 |
## 业户视角
业户**完全无感** —— 集抄系统的存在业户可能都不知道(除非物业告知)。
唯一感知:
- 账单出来更准时(每月固定日期到账)
- 没人上门抄表(隐私感更好)
- 用量明细可能更详细(集抄能支持小时 / 天级别 granularity,虽然账单仍是月度)
## 集抄设备与本系统的关系
```mermaid
flowchart TB
subgraph "物理层"
M1[家用水表]
M2[家用电表]
M3[工业表]
end
subgraph "网关层"
G1[楼栋网关<br/>RS485 → 4G]
end
subgraph "运营商层(第三方)"
V[集抄运营商平台<br/>设备管理 / 数据归档 / API 输出]
end
subgraph "本系统(prop-acc)"
API[集抄 API endpoint]
DB[(MeterReading)]
GenerateBills[GenerateBillsFromMeterReadingsAction]
Bill[(Bill)]
end
M1 --> G1
M2 --> G1
M3 --> G1
G1 --> V
V -->|每月推送| API
API --> DB
DB --> GenerateBills
GenerateBills --> Bill
```
**本系统只**管接收 + 存 + 生成账单。**不管**物理设备、网关、信号质量。这些都是集抄运营商的事。
## 常见问题
> [!question] 集抄数据准确吗?会不会比手抄更容易出错?
> 集抄数据**理论上更准**(机器直读,没手抖)。但:
>
> - IoT 设备本身可能故障(读数漂移、传输错)
> - 信号不好可能漏传(掉线)
> - 集抄运营商平台可能 bug(数据传错)
>
> 准确性强烈依赖**集抄运营商的可靠性**。选大牌运营商 + 完善 SLA。
> [!question] 集抄推过来的数据能改吗?
> 不能(MeterReading 不可变,见 [[decommission-and-locking]])。如果集抄推错:
>
> - 在系统侧建错误 reading
> - 走作废 Bill → 删 reading(若 Policy 允许)→ 集抄重推
> - 或运维 tinker 介入
> [!question] 集抄运营商收费贵不贵?
> 看市场,通常:
> - 设备 + 安装一次性几十到几百每点
> - 月度服务费 几元 / 表 / 月
>
> 中型社区(1,000 表)月度成本可能几千到几万。物业自行评估"省抄表员工资 vs 集抄费用" ROI。
> [!question] 集抄系统对接需要多久?
> 看运营商:
> - 大牌(华为 / 海康)平台标准 API → 1-2 周(主要是数据格式映射 + 测试)
> - 小厂自研协议 → 1-3 个月(需自定义对接代码)
> [!question] 集抄推数据时本系统有问题(数据库挂 / 网络断)?
> 集抄运营商应有重试机制(看 SLA)。本系统应:
> - API 幂等(同 meter + 同 read_at 不重复建)
> - 失败响应详细(让运营商知道哪条没收到)
> - 重要数据有补传机制(运营商手动重推)
> [!question] 集抄数据与业户预存款自动扣的关系?
> 集抄推数 → 生成 Bill → 触发 [[../prepaid/auto-deduction-design|预存款自动抵扣 job]](待实现)→ 业户预存款余额自动扣 + Bill 翻 Paid。
>
> 整条链**完全无人介入**,业户次日推送:"5 月电费 ¥168 已自动扣,余额 ¥X"。
## 异常分支
- 集抄掉线个别表 → [[read-single-meter-manual]] 兜底
- 集抄数据异常用量 → [[exception-high-consumption]]
- 集抄推错数据需修正 → [[exception-readings-locked-after-bill]]
- 集抄 API 故障 → 运维 / 集抄运营商支持
## 相关文档
- [[meter-vs-meter-reading]]
- [[reading-source-and-photo-proof]]
- [[bill-generation-pipeline]]
- [[read-single-meter-manual]]
- [[read-batch-via-excel-import]]
- [[exception-high-consumption]]

View File

@@ -0,0 +1,229 @@
---
title: prop-acc · meter · 场景 - 抄表拍照存证(物理表头照片)
aliases:
- 抄表拍照
- 照片存证
- read-with-photo-proof
- 场景-抄表拍照存证
tags:
- 场景
- prop-acc
- 计量表
- 抄表
- 存证
audience:
- 业户
- 业务人员
- 抄表员
status: 已发布
sub_feature: meter
last_review: 2026-05-26
code_version: 2026-05-22
---
# 场景:抄表拍照存证(物理表头照片)
抄表员**录入读数同时上传现场拍的物理表头照片**(`photo_url`),作为**业户事后质疑账单时的关键凭证**。配合 [[read-single-meter-manual|单录]] 流程使用。
## 典型情境
> [!example] 真实情境
> 张阿姨 6 月初收到 5 月电费账单 ¥168(280 度),她声称"我家平时就 100 多度,不可能这么多":
>
> - 王主管打开后台 → 找 E-501 的 5 月 reading → 看 `photo_url`
> - 照片清晰显示:**表头数字 5,280**(日期戳 2026-05-26,环境信息可识别为张阿姨家配电箱)
> - 拿照片给张阿姨看 → **业户无话可说**(确实读数是 5,280)
> - 王主管协助分析:可能近期家里某电器异常耗电(空调 24 小时开?旧冰箱故障?)
> - 业户回去自查 → 发现儿子用了电烤箱 + 空气炸锅一个月 → 接受账单
>
> 拍照存证**避免了一次纠纷**。
## 抄表员视角(李师傅)
### 拍照要点
每张表抄表时,**拍 1-2 张照片**:
| 角度 | 内容 |
|---|---|
| **正面特写** | 表头数字清晰可读(关键!)|
| **环境照** | 表 + 周围环境(配电箱 / 房号牌),证明是哪家的表 |
照片要素:
- **清晰**(对焦,光线足够)
- **包含表头读数全部位数**(不要遮挡)
- **时间戳**(手机 EXIF 元数据自动带)
- **GPS**(若手机 App 支持,更好)
### 录入
后台 / 抄表 App 上的 reading 录入 form 含 `photo_url` 字段:
| 字段 | 填什么 |
|---|---|
| 当前读数 | 5280 |
| 拍照 | 上传刚才的照片(或 App 直接拍照上传)|
| 备注 | 选填,如"电表正常,环境无异常" |
上传后照片存储到对象存储(S3 / OSS),`photo_url` 字段存对外可访问 URL。
> [!info] 强制度看物业政策
> - **严格物业**:Form 上 `photo` 字段 `->required()`,无照片不能提交
> - **建议物业**:Form 可空,UI 上有"建议拍照"提示
> - **宽松物业**:Form 可空,无提示
>
> 当前实现看 `MeterReadingsRelationManager` 的 form 配置。**生产环境强烈推荐严格模式**。
## 业务人员视角
### 查照片
后台 → 计量表 → ViewMeter → Reading 列表(`MeterReadingsRelationManager`):
- 每条 reading 显示"照片"图标(若 photo_url 不为空)
- 点击图标 → 弹出照片预览
- 可下载原图
### 业户争议处理
业户来电话 / 上门质疑账单:
1. 王主管打开对应 reading
2. 调出 photo_url 照片
3. 与业户当面 / 视频核对
4. 业户接受 → 案件了结
5. 业户仍质疑 →
- 物业派人**现场再读一次**(若表还在)
- 若现场读数与照片一致 → 业户更难反驳
- 若现场读数与照片不一致 → 表可能故障 / 数据有问题,走 [[replace-broken-meter|换表]] / [[exception-readings-locked-after-bill|修正流程]]
## 拍照流程图
```mermaid
sequenceDiagram
participant 李师傅
participant 物理表
participant 手机App
participant ObjectStorage[对象存储 S3/OSS]
participant 数据库
李师傅->>物理表: 现场观察读数
李师傅->>物理表: 拍照
李师傅->>手机App: 录入读数 + 选刚拍的照片
手机App->>ObjectStorage: 上传照片
ObjectStorage-->>手机App: 返回 URL
手机App->>数据库: 建 MeterReading + photo_url
```
## 业户视角
### 您会感受到什么
通常**不感知** —— 抄表员拍照是物业内部流程。
唯一影响:
- 抄表员上门时可能要求"打开配电箱看表"(配合)
- 极端情况:业户怀疑抄表员拍假照(看清照片是否真的是自己家表 = 看背景环境)
### 您的权利
对账单有异议时**有权要求**:
- 看抄表照片
- 派人现场再读一次
- 提交业户自己拍的反证照片(若业户当时也拍了)
## 拍照的额外用途
除业户争议外,照片还有其他价值:
| 用途 | 怎么用 |
|---|---|
| **审计追溯**(年审 / 监管)| 抽查某月某些 reading 的 photo_url,验证抄表真实性 |
| **抄表员考核** | 检查抄表员是否真去现场(对比 GPS / 时间 / 环境)|
| **培训新人** | 给新抄表员看老抄表员的照片范例 |
| **争议预防** | 业户知道有照片在,主动质疑减少 |
## 拍照的成本
| 成本 | 说明 |
|---|---|
| 抄表员时间 | 每张表 +5-10 秒 → 1,000 张表 +1-2 小时 |
| 手机存储 | 每张照片 1-5 MB → 1,000 张 = 几 GB / 月 |
| 对象存储 | 几 GB / 月 ≈ 几元 / 月(阿里云 OSS / S3) |
| 长期累积 | 5 年 = TB 级,需归档策略 |
| 培训 | 抄表员要学会拍合格照片(对焦、清晰、含表头全部数字)|
整体**成本可控**,但需要持续投入。
## 拍照的法律意义
> [!info] 业户法律纠纷时
>
> 在物业 vs 业户法律纠纷中,拍照照片是**关键物证**:
>
> - 照片有**时间戳 + GPS + 环境元数据**(EXIF) → 可证明真实性
> - 照片清晰显示读数 + 表号 → 可证明读数正确
> - 法院通常**采信** 这类专业现场记录
>
> 物业**完全没有拍照** → 只有业户口供 vs 物业账面数据 → 物业举证困难。
## 集抄(remote)与拍照的关系
集抄 reading(`source=remote`)**通常无拍照**:
- IoT 设备直接传数据,无相机
- IoT 设备本身就是凭证(机器读数 → 通常比人工准)
- 集抄掉线的兜底 [[read-single-meter-manual]] 仍要拍照
详见 [[reading-source-and-photo-proof]]"拍照存证"段。
## 常见问题
> [!question] 业户拒绝抄表员进屋拍照怎么办?
> 公共部位的表(楼道电表、燃气表)通常装在外面,不用进屋。
>
> 入户表(水表常在厨房 / 卫生间)若业户拒绝:
> - 协商上门时间(业户在家时)
> - 业户**自己拍照**发给物业(部分物业接受)
> - 长期拒绝 → 物业政策决定(警告 / 法律手段 / 估算用量)
> [!question] 照片上传失败怎么办?
> 网络问题 / 对象存储故障:
> - App 应有**本地缓存**机制(离线时存手机,联网时上传)
> - 上传失败的 reading 应**仍能提交**(photo_url 为空但有备注"上传失败")
> - 后续重传(若设计支持)
> [!question] 照片存多久?
> 法律 / 业务规定:
> - 中国通常 3-5 年(看物业法规)
> - 与账单同周期(账单存多久,照片存多久)
> - 长期归档(冷存储,如阿里云 OSS Archive)成本极低
> [!question] 一张照片能证明哪张表是谁家的?
> 照片本身不能(看不出房号)。需配合:
> - **环境识别**(房号牌、门口装饰、邻居环境)
> - **抄表员手写标注**(若 App 支持)
> - **GPS 坐标**(若 App 支持)
> - **数据库 meter_id 关联**(reading 关联 meter,meter 关联 asset)
> [!question] 业户对照片质疑"那不是我家的表"怎么办?
> 调取**原始 EXIF 数据**(时间戳、GPS、设备型号)→ 证明照片真实性。物业可派人**现场对照**确认表号物理一致。
## 异常分支
- 单录(本场景前置)→ [[read-single-meter-manual]]
- 批量导入(无拍照)→ [[read-batch-via-excel-import]]
- 集抄(无拍照)→ [[read-via-iot-remote-source]]
- 拍照后业户仍质疑高用量 → [[exception-high-consumption]]
## 相关文档
- [[reading-source-and-photo-proof]]
- [[read-single-meter-manual]]
- [[read-batch-via-excel-import]]
- [[read-via-iot-remote-source]]
- [[exception-high-consumption]]
- [[decommission-and-locking]]