factory/apps/wpm/scripts/batch_gxerp.md

278 lines
13 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 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` 指向它)也会触发重算。
4. **提交时纯继承**`services.inherit_zt_batch`):拆批交接、报工改号提交时,目标批直接继承来源批已有的 `zt_batch`(来源料子集式流转,永远安全);**合批不继承**——不良集中批(如 `黑检片-00`)等共享池会汇入多个大批的料,继承会造成错误归属,合批复检批(`-A-1`)的归属仍由统计侧"所有来源同大批"判定。已有归属冲突时不覆盖并记日志。
### 分子
`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 |
| `直通_口径` | 白料来源口径:`黑化领用` / `入库数量` / `谱系兜底` 及异常标注 |
### 接口(`/wpm/batchst/`
- 响应自带 `zt_batch`(所属大批号);`?with_zt_big=yes` 时每行附 `zt_big` = 所属大批的完整 BatchSt`直通_*`),自身即大批时为 null直通字段就在本行 data 里)。
- 过滤:`?zt_batch=<大批号>` 查某大批全部子批;`with_source_near=yes` 仍可附 BatchLog 直接来源批。
### 部署
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` 末尾自动解析归属并重算大批的 `直通_白料数 / 直通_总合格数 / 直通_良率 / 直通_口径`