272 lines
13 KiB
Markdown
272 lines
13 KiB
Markdown
# batch_gxerp.py 批次统计计算规则
|
||
|
||
`apps/wpm/scripts/batch_gxerp.py` 用于按 **批次号 (batch)** 汇总该批次在各工序组下的生产/检验数据,写入 `BatchSt.data` (JSON),同时维护 `first_time` / `last_time`。
|
||
|
||
---
|
||
|
||
## 一、入参与前置条件
|
||
|
||
| 参数 | 说明 |
|
||
| --- | --- |
|
||
| `batch` | 批次号 |
|
||
| `mgroup_obj` | 工序组对象(当前实现未直接使用,仅作为入口签名) |
|
||
|
||
- 必须存在 `BatchSt.objects.get(batch=batch, version=1)`,否则报错并退出。
|
||
- 遍历 `Mgroup.objects.all().order_by("sort")`,对每个工序组分别统计。
|
||
- 仅统计已提交且已出库的报工单:`mlog.submit_time__isnull=False` 且 `material_out__isnull=False`,并要求 `need_inout=True`。
|
||
|
||
---
|
||
|
||
## 二、字段命名约定
|
||
|
||
输出 JSON `data` 的 key 形式如下(`<G>` 代表工序组名):
|
||
|
||
| Key 模式 | 含义 |
|
||
| --- | --- |
|
||
| `批次号` | 批次号 |
|
||
| `<G>_日期` | 该工序组所有报工日期,去重后 `;` 拼接 |
|
||
| `<G>_小日期` / `<G>_大日期` | 该工序组最早 / 最晚日期(`YYYY-MM-DD`) |
|
||
| `<G>_操作人` | 操作人姓名去重后 `;` 拼接 |
|
||
| `<G>_班次` | 班次名去重后 `;` 拼接 |
|
||
| `<G>_count_use` | 领用数(来自上游 `mlogb_from`) |
|
||
| `<G>_count_real` | 实际生产数 |
|
||
| `<G>_count_ok` | 合格数 |
|
||
| `<G>_count_notok` | 不合格数 |
|
||
| `<G>_count_ok_full` | 完全合格数 |
|
||
| `<G>_count_pn_jgqbl` | 加工前不良数(来自上游 `mlogb_from`) |
|
||
| `<G>_合格率` | 合格率(%) |
|
||
| `<G>_完全合格率` | 完全合格率(%) |
|
||
| `<G>_缺陷_<缺陷名>` | 该工序组的缺陷数量(按缺陷名汇总) |
|
||
| `<G>_缺陷_<缺陷名>_比例` | 缺陷数 / `count_real` × 100,单位 % |
|
||
| `<G>_加工前_缺陷_<缺陷名>` | 上游 `mlogb_from` 上的缺陷数量 |
|
||
| `<G>_加工前_缺陷_<缺陷名>_比例` | 加工前缺陷数 / `count_use` × 100,单位 % |
|
||
|
||
---
|
||
|
||
## 三、主流程:按工序组汇总(普通报工,`is_fix=False`)
|
||
|
||
数据源 `mlogb1_qs`:
|
||
|
||
```
|
||
Mlogb where mlog.submit_time IS NOT NULL
|
||
and material_out IS NOT NULL
|
||
and mlog.mgroup = <G>
|
||
and mlog.is_fix = False
|
||
and batch = <batch>
|
||
and need_inout = True
|
||
```
|
||
|
||
### 1. 累加规则
|
||
|
||
对每条 `Mlogb`:
|
||
|
||
| 字段 | 累加来源 |
|
||
| --- | --- |
|
||
| `count_use` | `mlogb_from.count_use`(上游报工子项;若无 `mlogb_from` 则跳过) |
|
||
| `count_pn_jgqbl` | `mlogb_from.count_pn_jgqbl` |
|
||
| `count_real` | 当前 `item.count_real` |
|
||
| `count_ok` | 当前 `item.count_ok` |
|
||
| `count_ok_full` | 当前 `item.count_ok_full or 0` |
|
||
| `count_notok` | 当前 `item.count_notok or 0` |
|
||
| `操作人` | `mlog.handle_user`(最终去重,按 `name` 拼接) |
|
||
| `日期` | `mlog.handle_date`(最终去重排序) |
|
||
| `班次` | `mlog.shift.name` |
|
||
|
||
并记录所有上游 `mlogb_from.id` 到 `mlogb_q_ids`,用于"加工前缺陷"统计。
|
||
|
||
### 2. 合格率计算
|
||
|
||
```
|
||
完全合格率 = round(count_ok_full / count_real * 100, 2)
|
||
合格率 = round(count_ok / count_real * 100, 2)
|
||
```
|
||
|
||
`decimal.InvalidOperation` 异常时(如分母为 0)记 `0`。
|
||
|
||
### 3. 缺陷统计
|
||
|
||
- 当前工序缺陷:`MlogbDefect.filter(mlogb__in=mlogb1_qs, count__gt=0).values('defect__name').annotate(total=Sum('count'))`
|
||
- `<G>_缺陷_<name>` = total
|
||
- `<G>_缺陷_<name>_比例` = `round(total / count_real * 100, 2)`
|
||
- 加工前(上游)缺陷:`MlogbDefect.filter(mlogb__id__in=mlogb_q_ids, count__gt=0)...`
|
||
- `<G>_加工前_缺陷_<name>` = total
|
||
- `<G>_加工前_缺陷_<name>_比例` = `round(total / count_use * 100, 2)`
|
||
|
||
### 4. 日期字段
|
||
|
||
```python
|
||
data[f"{G}_小日期"] = min(日期列表).strftime("%Y-%m-%d") # 最早日期
|
||
data[f"{G}_大日期"] = max(日期列表).strftime("%Y-%m-%d") # 最晚日期
|
||
```
|
||
|
||
最终 `<G>_日期` 被覆写为去重排序后的字符串(`;` 拼接)。
|
||
|
||
---
|
||
|
||
## 四、外观检验返修(`is_fix=True`)
|
||
|
||
数据源 `mlogb2_qs`:
|
||
|
||
```
|
||
Mlogb where mlog.submit_time IS NOT NULL
|
||
and material_out IS NOT NULL
|
||
and mlog.mgroup.name = '外观检验'
|
||
and mlog.is_fix = True
|
||
and batch = <batch>
|
||
and need_inout = True
|
||
```
|
||
|
||
输出字段:
|
||
|
||
| Key | 计算 |
|
||
| --- | --- |
|
||
| `外观检验_返修_日期` | 报工日期,去重 `;` 拼接 |
|
||
| `外观检验_返修_操作人` | 操作人姓名,去重 `;` 拼接 |
|
||
| `外观检验_返修_count_real` | Σ `count_real` |
|
||
| `外观检验_返修_count_ok` | Σ `count_ok` |
|
||
| `外观检验_返修_count_ok_full` | Σ `count_ok_full or 0` |
|
||
| `外观检验_返修_缺陷_<name>` | `MlogbDefect` 按缺陷名汇总 |
|
||
| `外观检验_返修_缺陷_<name>_比例` | `round(total / 外观检验_返修_count_real * 100, 2)` |
|
||
|
||
---
|
||
|
||
## 五、外观检验车间库存抽检
|
||
|
||
数据源 `ft_qs`:
|
||
|
||
```
|
||
FtestWork where type2 = TYPE2_SOME
|
||
and wm.mgroup.name = '外观检验'
|
||
and batch = <batch>
|
||
and submit_time IS NOT NULL
|
||
```
|
||
|
||
输出字段:
|
||
|
||
| Key | 计算 |
|
||
| --- | --- |
|
||
| `外观检验_车间库存抽检_日期` | `test_date` 去重 `;` 拼接 |
|
||
| `外观检验_车间库存抽检_操作人` | `test_user.name` 去重 `;` 拼接 |
|
||
| `外观检验_车间库存抽检_count_notok` | Σ `count_notok or 0` |
|
||
| `外观检验_车间库存抽检_缺陷_<name>` | `FtestworkDefect` 按缺陷名汇总(无比例字段) |
|
||
|
||
---
|
||
|
||
## 六、外观检验汇总指标(仅当存在 `外观检验_count_ok` 时计算)
|
||
|
||
| Key | 公式 |
|
||
| --- | --- |
|
||
| `外观检验_总合格数` | `外观检验_count_ok + 外观检验_返修_count_ok(默认0)` |
|
||
| `外观检验_总合格率` | `round(外观检验_总合格数 / 外观检验_count_real * 100, 2)` |
|
||
| `外观检验_完全总合格数` | `外观检验_count_ok_full + 外观检验_返修_count_ok_full(默认0)` |
|
||
| `外观检验_完全总合格率` | `round(外观检验_完全总合格数 / 外观检验_count_real * 100, 2)` |
|
||
| `外观检验_直通合格数` | `外观检验_总合格数 - 外观检验_车间库存抽检_count_notok(默认0)` |
|
||
|
||
### 直通合格率(依赖尺寸检验)
|
||
|
||
仅当 `尺寸检验_合格率` 存在时:
|
||
|
||
```
|
||
外观检验_直通合格率 = round(外观检验_总合格率 * 尺寸检验_合格率 / 100, 2)
|
||
外观检验_直通合格率2 = round(外观检验_直通合格数 / 尺寸检验_count_use * 100, 2)
|
||
```
|
||
|
||
仅当 `尺寸检验_完全合格率` 存在时:
|
||
|
||
```
|
||
外观检验_完全直通合格率 = round(外观检验_完全总合格率 * 尺寸检验_完全合格率 / 100, 2)
|
||
```
|
||
|
||
异常(`InvalidOperation` / `ZeroDivisionError`)回退为 `0`。
|
||
|
||
---
|
||
|
||
## 七、写回 BatchSt
|
||
|
||
1. `data` 经 `MyJSONEncoder` 序列化后回填 `batchst.data`。
|
||
2. 调 `get_f_l_date(data)`:扫描所有以 `_日期` 结尾的字段,按 `;` 拆开后逐个 `datetime.strptime("%Y-%m-%d")` 解析为 `date` 对象,再取最小/最大并转成上海时区的 `00:00:00` / `23:59:59`,得到 `first_time` / `last_time`。无法解析的片段会写日志并跳过。
|
||
3. 仅当现有 `first_time` 为空或新值更早时更新;`last_time` 反之。
|
||
4. 调用 `batchst.save()` 持久化。
|
||
|
||
---
|
||
|
||
## 八、关键依赖与字段含义速查
|
||
|
||
| 模型字段 | 中文释义 | 来源 |
|
||
| --- | --- | --- |
|
||
| `Mlogb.count_use` | 领用数 | 报工子项(来自 `mlogb_from`) |
|
||
| `Mlogb.count_real` | 实际生产数 | 报工子项 |
|
||
| `Mlogb.count_ok` | 合格数 | `count_real - count_notok` |
|
||
| `Mlogb.count_ok_full` | 完全合格数 | `count_real - count_notok_full` |
|
||
| `Mlogb.count_notok` | 不合格数 | 缺陷 `okcate=30` 求和 |
|
||
| `Mlogb.count_pn_jgqbl` | 加工前不良 | `MlogbDefect` 中加工前不良求和 |
|
||
| `Mlogb.need_inout` | 是否需出入库 | 仅 `True` 参与统计 |
|
||
| `Mlogb.mlogb_from` | 上游报工子项 | 用于追溯领用与加工前缺陷 |
|
||
|
||
---
|
||
|
||
## 九、已知潜在问题
|
||
|
||
1. **`mgroup_obj` 入参未使用**:当前实现忽略该参数,对所有 `Mgroup` 全量遍历。
|
||
|
||
---
|
||
|
||
## 十、大批直通良率(`main` 末尾自动触发)
|
||
|
||
### 定义
|
||
|
||
- **大批** = 拆出检验小批的那个批(如 `2605-BJ-0023`,退火后合批改名产生;外购半成品则为采购入库批)。由 `BatchLog` 拆合批关系确定,不依赖批次号命名和工艺路线。
|
||
- **直通良率** = 大批下所有子批次 `外观检验_直通合格数` 之和 ÷ 大批白料数(进炉前数量)× 100。
|
||
|
||
### 归属锁定(`BatchSt.zt_batch`)
|
||
|
||
批次统计完成后(`handle_zt`):
|
||
|
||
1. 当前批 `data` 含 `尺寸检验_count_real` / `外观检验_count_real` 时解析归属(`resolve_zt_big`):
|
||
- 唯一拆批来源(handover split)→ 该来源即大批;
|
||
- 合批复检批(如 `大批-A-1`)→ 所有合批来源归属同一大批时继承;不一致则记日志不归属;
|
||
- 无任何拆合批上游 → 自身即大批(未拆批直接检验、外购直检)。
|
||
2. 归属写入 `zt_batch`(锁定,已锁定的直接复用);随后重算大批(`cal_zt_big`)。
|
||
3. 大批自身重算时(有子批 `zt_batch` 指向它)也会触发重算。
|
||
|
||
### 分子
|
||
|
||
`BatchSt.filter(zt_batch=大批)` 各批 `外观检验_直通合格数` 之和(+大批自身若有)。子批发现从大批侧沿拆批边/同大批合批边展开(`cal_zt_big`,支持 `-A-1` 复检批、多级拆分)。检验前合并的子批无检验数据,天然不重复计数。
|
||
|
||
### 分母(白料数,`_zt_white_count` 回溯)
|
||
|
||
从大批沿 `BatchLog` 向上回溯(限深 `ZT_MAX_DEPTH=12`,按路径防环,跳过合并回自身的整理性边):
|
||
|
||
- **锚点**:批次存在锚点工段(`ZT_ANCHOR_MGROUPS=["黑化"]`)产出报工时停止,白料数 = 该批锚点工段 `mlogb_from.count_use`(进炉前领用数)。多次黑化的链取离大批最近的一次。
|
||
- **分摊**(`_zt_source_out_ratio`):来源批有多条**生产主线**流出边(报工改号/拆批/汇入大批格式 `ZT_BIG_BATCH_RE` 的合批)时,按各去向数量占比分摊;汇入不良集中批(如 `黑检片-00`)的旁路不占白料份额,使炉后不良计入生产主线分母。单一去向全额传递。
|
||
- **边数量**:拆批取 `Handoverb(batch=目标批)`,合批取 `Handoverb(batch=来源批)`,报工改号取目标产出行的 `mlogb_from.count_use`;无明细时兜底整单数量。
|
||
- **外购兜底**:无上游谱系且由入库创建 → 白料数 = 入库数量(`MIOItem.count`),口径记 `入库数量`。
|
||
- **老数据兜底**(`_zt_white_legacy`):BatchLog 上线前的批次无边,沿 `BatchSt.handover/mlog` 创建来源续上回溯,口径记 `谱系兜底`。
|
||
|
||
### 缓存与隔离
|
||
|
||
- 白料数算出后缓存在大批 `data`(含算不出的负缓存);子批触发的重算直接复用、只重聚合分子。大批自身重算时 `data` 重建、缓存失效并重新回溯。上游补改报工后需大批重算或全量回刷才会刷新。
|
||
- 总合格数 > 白料数(如白料池循环整理导致回溯不全)→ `直通_良率` 置空并在口径标注 `谱系异常`,记错误日志(2026-06 抽样 120 个大批:116 正常 / 2 异常隔离 / 2 谱系断头不可得)。
|
||
|
||
### 输出字段(写入大批 `data`)
|
||
|
||
| Key | 含义 |
|
||
| --- | --- |
|
||
| `直通_子批次` | 归属本大批的子批号 `;` 拼接 |
|
||
| `直通_总合格数` | 分子 |
|
||
| `直通_白料数` | 分母(不可得为 null) |
|
||
| `直通_良率` | 百分比(不可得/异常为 null) |
|
||
| `直通_口径` | 白料来源口径:`黑化领用` / `入库数量` / `谱系兜底` 及异常标注 |
|
||
|
||
### 部署
|
||
|
||
1. `python manage.py migrate wpm`(新增 `BatchSt.zt_batch`,迁移 `0133_batchst_zt_batch`)。
|
||
2. 历史回刷:`python scripts/correct_zt_gxerp.py [起始日期] [--fresh]`——只解析归属并重算各大批 `直通_*` 字段(每个大批仅算一次),不重建批次其他统计;`--fresh` 强制清缓存重新回溯白料。全量重建统计仍用 `scripts/correct_batchst.py`(其 `main` 末尾也会触发直通计算)。
|
||
|
||
## 十一、修订记录
|
||
|
||
- 修复 `小日期 / 大日期` 命名与含义反向,`小日期` 取最早日期、`大日期` 取最晚日期;同步修正 `batch_bxerp.py`、`batch_gzerp.py` 中所有同类字段。
|
||
- `get_f_l_date` 改为先 `datetime.strptime` 解析为 `date` 对象再比较,移除对字符串字典序的隐式依赖;非法日期片段记录日志后跳过。
|
||
- 所有合格率/直通率计算的兜底 `except` 同时捕获 `decimal.InvalidOperation` 与 `ZeroDivisionError`,以兼容 `Decimal` 与原生数值的除零场景;`batch_bxerp.py` 与 `batch_gzerp.py` 中仅捕获 `decimal.InvalidOperation` 的合格率分支也同步扩展。
|
||
- 新增大批直通良率统计(见第十节):`BatchSt` 加 `zt_batch` 归属锁定字段,`main` 末尾自动解析归属并重算大批的 `直通_白料数 / 直通_总合格数 / 直通_良率 / 直通_口径`。
|