rust: pool Pattern (续)

rust 复制代码
//!# encoding: utf-8 
//!# 版权所有  2026 ©涂聚文有限公司™ ®
//!# 许可信息查看:言語成了邀功盡責的功臣,還需要行爲每日來值班嗎
//!# 描述:对象池简写模式  pool Pattern
//!# Author    : geovindu,Geovin Du 涂聚文.
//!# IDE       : RustRover  2025.1.1 Rust  rustc 1.98.1 
//!# os        : windows 10
//!# database  : mysql 9.0 sql server 2025, postgreSQL 18.0  Oracle 21c Neo4j
//!# Datetime  : 2026/10/11 7:56 
//!# User      :  geovindu
//!# Product   : RustRover
//!# Project   : objectpoolpattern
//!# File      : audit_report.rs
//! 幕十:契约审计(**六条不变量**)。
//!
//! ## ★ 每条规则的「已知会失败的输入」都印在注解里
//!
//! 一条不可能失败的规则不是规则,它只是没被跑过的注释。
//! 因此本表把每条规则的 `known_failing_input_text()` 逐条印出------
//! 读者能看见「这条规则在什么数据上会红」,
//! 才可能相信它现在是绿的不是因为没在跑。
//!
//! ## 六条规则分两类
//!
//! - 前五条描述「池**没被用坏**」(守恒、恒等、无异物、无泄漏);
//! - 第六条 `EFFECTIVE` 描述「池**有没有起作用**」(命中 > 0)。
//!
//! ⚠️ 少了第六条,演示会得出「我们用了对象池(且没用坏)」
//! 这种**不可反驳**的结论。把「命中 > 0」也变成一条会红的规则,
//! 「其实一次都没复用上」这件事才有地方暴露。

use crate::analysis::AuditCheckView;
use crate::app::{Table, TableColumn, DOCUMENT_WIDTH};

/// 审计表的列宽。
const RULE_CODE_WIDTH: usize = 10;
/// 规则列宽(最长「异物归还:归还了不属于本池的对象必须为 0」= 40 列)。
const RULE_NAME_WIDTH: usize = 42;
const EXPECTATION_WIDTH: usize = 8;
/// 实测列宽(**可省略**:恒等式展开后可到 50 列以上)。
const ACTUAL_WIDTH: usize = 38;
const VERDICT_WIDTH: usize = 8;
const VIOLATION_WIDTH: usize = 12;

/// 幕十:契约审计。
pub fn render_audit_table(title: &str, checks: &[AuditCheckView]) -> Vec<String> {
    let mut table: Table = Table::new(
        title,
        vec![
            TableColumn::new("规则编码", RULE_CODE_WIDTH),
            TableColumn::new("规则", RULE_NAME_WIDTH),
            TableColumn::new("期望", EXPECTATION_WIDTH),
            TableColumn::elidable(TableColumn::new("实测", ACTUAL_WIDTH)),
            TableColumn::new("结论", VERDICT_WIDTH),
            TableColumn::elidable(TableColumn::new("违规", VIOLATION_WIDTH)),
        ],
    );
    for check in checks.iter() {
        table = table.with_row(vec![
            check.rule_code().to_string(),
            check.rule_name().to_string(),
            check.expectation_text().to_string(),
            check.actual_text().to_string(),
            if check.passed() { "通过".to_string() } else { "未通过".to_string() },
            check.violation_text().to_string(),
        ]);
    }
    for check in checks.iter() {
        table = table.with_note(&format!(
            "{}:已知会失败的输入 ------ {}",
            check.rule_code(),
            check.known_failing_input_text()
        ));
    }
    table = table.with_note(
        "★ 「期望」列是 `符合数/适用数`。判通过时**同时要求适用数 > 0**:\
         一条规则若在本次数据上根本没有适用对象,`0 == 0` 会让它显示成「通过」------\
         那是「没跑过」冒充「跑过了」。",
    );
    table.render(DOCUMENT_WIDTH)
}

//!# encoding: utf-8 
//!# 版权所有  2026 ©涂聚文有限公司™ ®
//!# 许可信息查看:言語成了邀功盡責的功臣,還需要行爲每日來值班嗎
//!# 描述:对象池简写模式  pool Pattern
//!# Author    : geovindu,Geovin Du 涂聚文.
//!# IDE       : RustRover  2025.1.1 Rust  rustc 1.98.1 
//!# os        : windows 10
//!# database  : mysql 9.0 sql server 2025, postgreSQL 18.0  Oracle 21c Neo4j
//!# Datetime  : 2026/10/11 7:56 
//!# User      :  geovindu
//!# Product   : RustRover
//!# Project   : objectpoolpattern
//!# File      : capacity_report.rs
//! 幕八:容量盘点(**每一座站一台表都不落**)。
//!
//! ## ★ 容量守恒是月底唯一能对账的东西
//!
//! 空闲 + 在借 + 隔离 == 已造 − 已退役。
//! 这条等式一旦失真,报表上所有数字都还能看、还自洽,
//! 唯独「我们一共有几台设备」没人能回答。
//! 因此本表把**四个区域与差**逐列列出,让守恒变成一个看得见的格子。
//!
//! ## ⚠️ 退役不计入容量
//!
//! 退役的仪器已经不是池的一部分。把它们算进容量,
//! 「限量扩容」的上限会被一批再也回不来的对象占住,
//! 于是池会永远拒绝借出,而报表上的「已造 8 台」看起来完全正常。

use crate::analysis::CapacityProbe;
use crate::app::{Table, TableColumn, DOCUMENT_WIDTH};

/// 容量表的列宽。
const STATION_WIDTH: usize = 28;
const COUNT_WIDTH: usize = 8;

/// 一行容量(**比 `CapacityProbe` 多一个站名**,由 `main` 填)。
#[derive(Clone, Debug)]
pub struct CapacityRowView {
    /// 站名。
    station_name: String,
    /// 探针。
    probe: CapacityProbe,
}

impl CapacityRowView {
    /// 由站名与探针组装。
    pub fn new(station_name: &str, probe: CapacityProbe) -> Self {
        Self { station_name: station_name.to_string(), probe }
    }

    /// 站名。
    pub fn station_name(&self) -> &str {
        self.station_name.as_str()
    }

    /// 容量守恒的差。
    pub fn capacity_gap(&self) -> i64 {
        self.probe.capacity_gap()
    }
}

/// 幕八:容量盘点。
pub fn render_capacity_table(title: &str, rows: &[CapacityRowView]) -> Vec<String> {
    let mut table: Table = Table::new(
        title,
        vec![
            TableColumn::new("站", STATION_WIDTH),
            TableColumn::numeric("空闲", COUNT_WIDTH),
            TableColumn::numeric("在借", COUNT_WIDTH),
            TableColumn::numeric("隔离", COUNT_WIDTH),
            TableColumn::numeric("退役", COUNT_WIDTH),
            TableColumn::numeric("已造", COUNT_WIDTH),
            TableColumn::numeric("容量", COUNT_WIDTH),
            TableColumn::numeric("差", COUNT_WIDTH),
            TableColumn::numeric("峰值", COUNT_WIDTH),
        ],
    );
    for row in rows.iter() {
        table = table.with_row(vec![
            row.station_name.clone(),
            row.probe.idle_total().to_string(),
            row.probe.borrowed_total().to_string(),
            row.probe.quarantined_total().to_string(),
            row.probe.retired_total().to_string(),
            row.probe.created_total().to_string(),
            row.probe.capacity_total().to_string(),
            row.capacity_gap().to_string(),
            row.probe.peak_borrowed_total().to_string(),
        ]);
    }
    table = table.with_note(
        "★ 「容量」= 空闲 + 在借 + 隔离(**不含退役**),「差」= 容量 − (已造 − 已退役)。\
         差必须为 0:非 0 意味着有对象既不在任何区域、也没被记为退役------那就是凭空消失了一台。",
    );
    table = table.with_note(
        "★ 「隔离」必须为 0:归还校验没过的仪器会被放进隔离区,\
         业务层必须**立刻**处理(送校准或退役)。若等到最后统一处理,\
         后续申请会因「隔离区占着容量」而借不到,\
         那时报表上的「拒绝次数」里混着一件本可以避免的事。",
    );
    table.render(DOCUMENT_WIDTH)
}
//!# encoding: utf-8 
//!# 版权所有  2026 ©涂聚文有限公司™ ®
//!# 许可信息查看:言語成了邀功盡責的功臣,還需要行爲每日來值班嗎
//!# 描述:对象池简写模式  pool Pattern
//!# Author    : geovindu,Geovin Du 涂聚文.
//!# IDE       : RustRover  2025.1.1 Rust  rustc 1.98.1 
//!# os        : windows 10
//!# database  : mysql 9.0 sql server 2025, postgreSQL 18.0  Oracle 21c Neo4j
//!# Datetime  : 2026/10/11 7:56 
//!# User      :  geovindu
//!# Product   : RustRover
//!# Project   : objectpoolpattern
//!# File      : extension_report.rs
//! 幕十二:工程外扩展对照。
//!
//! ## ★ 这一幕是「可扩展性必须被实证」的落点
//!
//! 工程外扩展区新增了一个型号、一家门店、一批申请,
//! **没有改动任何既有分层文件**(`src/` 下零改动),
//! 而新增内容自动进了目录表、容量表、对照表与统计。
//!
//! ## ⚠️ 「改动文件数 = 0」必须被**数出来**,不能靠声明
//!
//! 「我们没改既有文件」是一句主张;
//! 用脚本比对「扩展前的文件哈希 vs 扩展后的文件哈希」才是证据。
//! 因此本表的「改动文件数」一栏由 `main` 用**实际比对结果**填,
//! 不是写死的 0。

use crate::app::{Table, TableColumn, DOCUMENT_WIDTH};

/// 扩展对照表的列宽。
const ITEM_WIDTH: usize = 28;
const BUILTIN_WIDTH: usize = 22;
const EXTENDED_WIDTH: usize = 22;
const EVIDENCE_WIDTH: usize = 48;

/// 一行扩展对照。
#[derive(Clone, Debug)]
pub struct ExtensionRowView {
    /// 项目名。
    item: String,
    /// 内置基线的值。
    builtin_value: String,
    /// 扩展后的值。
    extended_value: String,
    /// ★ 证据(**在哪张表多一行**,而不是一句「已支持」)。
    evidence: String,
}

impl ExtensionRowView {
    /// 由四项内容组装。
    pub fn new(item: &str, builtin_value: &str, extended_value: &str, evidence: &str) -> Self {
        Self {
            item: item.to_string(),
            builtin_value: builtin_value.to_string(),
            extended_value: extended_value.to_string(),
            evidence: evidence.to_string(),
        }
    }

    /// 项目名。
    pub fn item(&self) -> &str {
        self.item.as_str()
    }

    /// 内置基线值。
    pub fn builtin_value(&self) -> &str {
        self.builtin_value.as_str()
    }

    /// 扩展后的值。
    pub fn extended_value(&self) -> &str {
        self.extended_value.as_str()
    }

    /// 证据文本。
    pub fn evidence(&self) -> &str {
        self.evidence.as_str()
    }
}

/// 幕十二:扩展对照。
pub fn render_extension_table(title: &str, rows: &[ExtensionRowView]) -> Vec<String> {
    let mut table: Table = Table::new(
        title,
        vec![
            TableColumn::new("项目", ITEM_WIDTH),
            TableColumn::numeric("内置基线", BUILTIN_WIDTH),
            TableColumn::numeric("工程外扩展后", EXTENDED_WIDTH),
            TableColumn::elidable(TableColumn::new("证据(在哪张表多一行)", EVIDENCE_WIDTH)),
        ],
    );
    for row in rows.iter() {
        table = table.with_row(vec![
            row.item().to_string(),
            row.builtin_value().to_string(),
            row.extended_value().to_string(),
            row.evidence().to_string(),
        ]);
    }
    table = table.with_note(
        "★ 扩展的落点全部在 `main.rs` 的「工程外扩展区」:\
         新增型号、新增门店、新增申请序列,\
         靠的是「型号 / 门店 / 申请都是**开放型**」这一条设计------\
         若它们是 `enum`,扩展就得回来改领域层。",
    );
    table = table.with_note(
        "★ 「证据」列写的是**在哪张表会多一行**,不是「已支持」。\
         一句「已支持」无法被核对;「幕五清单多 2 行」可以。",
    );
    table.render(DOCUMENT_WIDTH)
}
//!# encoding: utf-8 
//!# 版权所有  2026 ©涂聚文有限公司™ ®
//!# 许可信息查看:言語成了邀功盡責的功臣,還需要行爲每日來值班嗎
//!# 描述:对象池简写模式  pool Pattern
//!# Author    : geovindu,Geovin Du 涂聚文.
//!# IDE       : RustRover  2025.1.1 Rust  rustc 1.98.1 
//!# os        : windows 10
//!# database  : mysql 9.0 sql server 2025, postgreSQL 18.0  Oracle 21c Neo4j
//!# Datetime  : 2026/10/11 7:56 
//!# User      :  geovindu
//!# Product   : RustRover
//!# Project   : objectpoolpattern
//!# File      : foundation_report.rs
//! 幕二 / 幕三 / 幕四:型号档案、门店清单、服务单价。
//!
//! ## 为什么先铺这三张「字典表」
//!
//! 后面的对照表里全是编码(`BALANCE-0003`、`STORE-CN-01`)。
//! 没有字典表,读者只能靠猜;而猜对的那部分会掩盖猜错的那部分。
//!
//! ## ⚠️ 服务单价单独一幕
//!
//! 「校准一次多少钱」「退役一台多少钱」看起来是小事,
//! 但它是**唯一一个与型号无关的成本**:
//! 维修报价由服务商给,与本设备贵不贵不相关。
//! 单列一幕,是为了让「采购按型号、服务按次」这个区别被看见。

use crate::app::{Table, TableColumn, DOCUMENT_WIDTH};
use crate::domain::{InstrumentModel, StoreCode};

/// 型号表的列宽。
const MODEL_CODE_WIDTH: usize = 18;
const MODEL_NAME_WIDTH: usize = 16;
const MODEL_PURPOSE_WIDTH: usize = 28;
const MODEL_COST_WIDTH: usize = 16;
const MODEL_CALIBRATION_WIDTH: usize = 12;

/// 门店表的列宽。
const STORE_CODE_WIDTH: usize = 20;
const STORE_NAME_WIDTH: usize = 24;

/// 服务单价表的列宽。
const SERVICE_ITEM_WIDTH: usize = 30;
const SERVICE_VALUE_WIDTH: usize = 20;
const SERVICE_NOTE_WIDTH: usize = 64;

/// 幕二:型号档案。
pub fn render_model_table(models: &[InstrumentModel]) -> Vec<String> {
    let mut table: Table = Table::new(
        "幕二 · 型号档案:一个型号一座池",
        vec![
            TableColumn::new("型号编码", MODEL_CODE_WIDTH),
            TableColumn::new("中文名", MODEL_NAME_WIDTH),
            TableColumn::new("用途", MODEL_PURPOSE_WIDTH),
            TableColumn::numeric("采购单价", MODEL_COST_WIDTH),
            TableColumn::numeric("校准周期", MODEL_CALIBRATION_WIDTH),
        ],
    );
    for model in models.iter() {
        table = table.with_row(vec![
            model.code().to_string(),
            model.chinese_name().to_string(),
            model.purpose_text().to_string(),
            model.acquisition_cost().chinese_text(),
            format!("{} 个月", model.calibration_interval_months()),
        ]);
    }
    table = table.with_note(
        "★ 采购单价**跟着型号走**,服务单价不跟着型号走------\
         这两笔钱的性质不同,混在一张表里就会有人拿型号去猜维修费。",
    );
    table = table.with_note(
        "★ 按型号建池而不是「一个大池装所有设备」:\
         一个大池的话,`空闲数 == 0` 就开始撒谎(池里明明还有别的型号)。",
    );
    table.render(DOCUMENT_WIDTH)
}

/// 幕三:门店清单。
pub fn render_store_table(stores: &[StoreCode], purpose_text: &str) -> Vec<String> {
    let mut table: Table = Table::new(
        "幕三 · 门店清单:谁来借",
        vec![
            TableColumn::new("门店编码", STORE_CODE_WIDTH),
            TableColumn::new("中文名", STORE_NAME_WIDTH),
        ],
    );
    for store in stores.iter() {
        table = table.with_row(vec![store.code().to_string(), store.chinese_name().to_string()]);
    }
    table = table.with_note(purpose_text);
    table.render(DOCUMENT_WIDTH)
}

/// 幕四:服务单价。
pub fn render_service_price_table(rows: &[(String, String, String)]) -> Vec<String> {
    let mut table: Table = Table::new(
        "幕四 · 服务单价:修理与处置各多少钱",
        vec![
            TableColumn::new("项目", SERVICE_ITEM_WIDTH),
            TableColumn::numeric("单价", SERVICE_VALUE_WIDTH),
            TableColumn::new("说明", SERVICE_NOTE_WIDTH),
        ],
    );
    for (item, value, note) in rows.iter() {
        table = table.with_row(vec![item.clone(), value.clone(), note.clone()]);
    }
    table = table.with_note(
        "★ 服务成本**逐条累加**而不是「次数 × 单价」:\
         同一型号的两次维修报价未必相同,取平均会掩盖掉最贵的那一次。",
    );
    table.render(DOCUMENT_WIDTH)
}


//!# encoding: utf-8 
//!# 版权所有  2026 ©涂聚文有限公司™ ®
//!# 许可信息查看:言語成了邀功盡責的功臣,還需要行爲每日來值班嗎
//!# 描述:对象池简写模式  pool Pattern
//!# Author    : geovindu,Geovin Du 涂聚文.
//!# IDE       : RustRover  2025.1.1 Rust  rustc 1.98.1 
//!# os        : windows 10
//!# database  : mysql 9.0 sql server 2025, postgreSQL 18.0  Oracle 21c Neo4j
//!# Datetime  : 2026/10/11 7:56 
//!# User      :  geovindu
//!# Product   : RustRover
//!# Project   : objectpoolpattern
//!# File      : health_check_report.rs
//! 幕十四:体检汇总。
//!
//! ## ★ 「通过 / 未通过」这四个字全工程只有一处产生
//!
//! 就是 `analysis::CheckLine::verdict_text()`。
//! 若每张表各写一次「通过」「未通过」,
//! 就会出现一处写「通过」、一处写「OK」的分裂,
//! 而读者只会以为那是两件事。
//!
//! ## ⚠️ 体检表必须能**红**
//!
//! 一张永远全绿的体检表与没有体检表是等价的。
//! 因此每一条检查都自带「已知会失败的输入」,
//! 并且本工程在开发中**真的注入过**那些输入确认它会红。

use crate::analysis::CheckReport;
use crate::app::{Table, TableColumn, DOCUMENT_WIDTH};

/// 体检表的列宽。
/// 检查项列宽(最长「★ 至少一档出现「借不到」(容量约束真实存在)」= 44 列)。
const ITEM_WIDTH: usize = 46;
const EXPECTATION_WIDTH: usize = 30;
const ACTUAL_WIDTH: usize = 40;
const VERDICT_WIDTH: usize = 10;

/// 幕十四:体检汇总。
pub fn render_check_table(title: &str, report: &CheckReport) -> Vec<String> {
    let mut table: Table = Table::new(
        title,
        vec![
            TableColumn::new("检查项", ITEM_WIDTH),
            TableColumn::elidable(TableColumn::new("期望", EXPECTATION_WIDTH)),
            TableColumn::elidable(TableColumn::new("实测", ACTUAL_WIDTH)),
            TableColumn::new("结论", VERDICT_WIDTH),
        ],
    );
    for line in report.lines().iter() {
        table = table.with_row(vec![
            line.item().to_string(),
            line.expectation_text().to_string(),
            line.actual_text().to_string(),
            line.verdict_text().to_string(),
        ]);
    }
    table = table.with_note(&format!(
        "共 {} 条,通过 {} 条,未通过 {} 条。",
        report.line_total(),
        report.passed_total(),
        report.failed_total()
    ));
    if !report.all_passed() {
        table = table.with_note(&format!("未通过的检查项:{}", report.failed_items_text()));
    }
    table.render(DOCUMENT_WIDTH)
}

//!# encoding: utf-8 
//!# 版权所有  2026 ©涂聚文有限公司™ ®
//!# 许可信息查看:言語成了邀功盡責的功臣,還需要行爲每日來值班嗎
//!# 描述:对象池简写模式  pool Pattern
//!# Author    : geovindu,Geovin Du 涂聚文.
//!# IDE       : RustRover  2025.1.1 Rust  rustc 1.98.1 
//!# os        : windows 10
//!# database  : mysql 9.0 sql server 2025, postgreSQL 18.0  Oracle 21c Neo4j
//!# Datetime  : 2026/10/11 7:56 
//!# User      :  geovindu
//!# Product   : RustRover
//!# Project   : objectpoolpattern
//!# File      : identity_report.rs
//! 幕七:身份证据 + 两条路径的恒等比对。
//!
//! ## ★ 这一幕回答「池里的那台,是不是当初借出去的那台」
//!
//! 台账上「命中 3 次」只能被相信;
//! 用 `Rc::ptr_eq` 比对**指针**才能被验证。
//! 指针相等是/否进得了报表,指针**值**进不了------
//! 地址随运行变(ASLR),打印会破坏「两次运行逐字节一致」。
//!
//! ## 恒等比对为什么是「同一份场景跑两遍」
//!
//! 两次独立的会话(同参数、同策略)跑完,
//! 池内每一台的**指纹**必须逐字节相同。
//! 这检验的是「装配是否确定性」:
//! 若某处依赖了 `HashMap` 迭代顺序或系统时间,这个比对就会红。

use crate::app::{render_pair_table, Table, TableColumn, DOCUMENT_WIDTH};

/// 两条路径的逐台比对结果。
#[derive(Clone, Debug)]
pub struct IdentityPairView {
    /// 序列号。
    serial_number: String,
    /// 路径一的指纹。
    first_text: String,
    /// 路径二的指纹。
    second_text: String,
    /// 是否一致。
    identical: bool,
}

impl IdentityPairView {
    /// 由两次的指纹组装。
    pub fn new(serial_number: &str, first_text: &str, second_text: &str) -> Self {
        Self {
            serial_number: serial_number.to_string(),
            first_text: first_text.to_string(),
            second_text: second_text.to_string(),
            identical: first_text == second_text,
        }
    }

    /// 序列号。
    pub fn serial_number(&self) -> &str {
        self.serial_number.as_str()
    }

    /// 路径一指纹。
    pub fn first_text(&self) -> &str {
        self.first_text.as_str()
    }

    /// 路径二指纹。
    pub fn second_text(&self) -> &str {
        self.second_text.as_str()
    }

    /// 是否一致。
    pub const fn identical(&self) -> bool {
        self.identical
    }
}

/// 恒等表的列宽。
const SERIAL_WIDTH: usize = 20;
const FINGERPRINT_WIDTH: usize = 40;
const IDENTICAL_WIDTH: usize = 10;

/// 幕七(续):两条路径的逐台指纹比对。
///
/// # 参数
/// - `title`:表标题。
/// - `pairs`:逐台比对结果。
/// - `first_column`:路径一的列名(**由调用方传 `&'static str`**,避免列名撒谎)。
/// - `second_column`:路径二的列名。
///
/// ⚠️ 为什么列名必须是 `&'static str`:
/// `TableColumn` 的表头是 `&'static str`(表头是版面的一部分,不该随数据变)。
/// 若放宽成 `&str`,「这一列是什么」就变成一个运行时值------
/// 那时同一张表在不同调用点会有不同的列名,而读者只会以为是两件事。
pub fn render_identity_table(
    title: &str,
    pairs: &[IdentityPairView],
    first_column: &'static str,
    second_column: &'static str,
) -> Vec<String> {
    let mut table: Table = Table::new(
        title,
        vec![
            TableColumn::new("序列号", SERIAL_WIDTH),
            TableColumn::elidable(TableColumn::new(first_column, FINGERPRINT_WIDTH)),
            TableColumn::elidable(TableColumn::new(second_column, FINGERPRINT_WIDTH)),
            TableColumn::new("一致", IDENTICAL_WIDTH),
        ],
    );
    for pair in pairs.iter() {
        table = table.with_row(vec![
            pair.serial_number().to_string(),
            pair.first_text().to_string(),
            pair.second_text().to_string(),
            if pair.identical() { "是".to_string() } else { "否".to_string() },
        ]);
    }
    table = table.with_note(
        "★ 指纹含「序列号|状态|累计次数|校准到期月」。\
         少了后面三段,一台「被多借了三次的仪器」会与「只借过一次的」同指纹,\
         于是恒等比对变成恒真的废表。",
    );
    table.render(DOCUMENT_WIDTH)
}

/// 幕七:身份证据(**两列表**)。
pub fn render_identity_evidence_table(rows: &[(String, String)], notes: &[&str]) -> Vec<String> {
    render_pair_table("幕七 · 身份证据:池里的那台,是不是当初借出去的那台", rows, notes)
}


//!# encoding: utf-8 
//!# 版权所有  2026 ©涂聚文有限公司™ ®
//!# 许可信息查看:言語成了邀功盡責的功臣,還需要行爲每日來值班嗎
//!# 描述:对象池简写模式  pool Pattern
//!# Author    : geovindu,Geovin Du 涂聚文.
//!# IDE       : RustRover  2025.1.1 Rust  rustc 1.98.1 
//!# os        : windows 10
//!# database  : mysql 9.0 sql server 2025, postgreSQL 18.0  Oracle 21c Neo4j
//!# Datetime  : 2026/10/11 7:56 
//!# User      :  geovindu
//!# Product   : RustRover
//!# Project   : objectpoolpattern
//!# File      : interface_report.rs
//! 幕十三:对外只读接口清点。
//!
//! ## ★ 这一幕同时是**告警治理的落点**
//!
//! 一个只读接口若在这里被调用并印出结果,
//! 它就不可能是「写了没人用」------也就不可能是「写错了没人发现」。
//!
//! 这是本工程处理 `unused` 警告的**唯一**手段:
//! 不删接口、不加 `#[allow]`,而是把它**用起来**。
//! 删掉一个没人用的接口,等于承认「这个能力本来不需要」;
//! 而它之所以没人用,往往是因为上层在做一件本不该它做的事。
//!
//! ## ⚠️ 清点用的会话必须是**专用**的
//!
//! 既有一个工程在这里出过事:为了清点 `session.read(...)` 又读了一次主会话,
//! 结果「命中次数 == 借到 − 新建」差了 1(61 vs 60),
//! 而那个 1 没有任何一行输出指出它的来源。
//! 因此本工程规定:**清点动作发生在主流程之后,且用新造的对象**。

use crate::app::{Table, TableColumn, DOCUMENT_WIDTH};

/// 接口名列宽(**最长的一条是 `analysis::PoolValueReport::summary_text()`**)。
/// ⚠️ 68 列仍放不下最长的接口名(`app::Table::new(...).with_row(...).with_note(...)` 需 69 列),
/// 因此这一列标成**可省略**:接口名会随接口增加而变长,
/// 把版面预算压在它身上,等于让「新增一个接口」变成一件要改列宽的事。
const INTERFACE_NAME_WIDTH: usize = 68;
/// 输出列宽(`68 + 64 + 2*3 + 1 = 139 <= 140`)。
const INTERFACE_OUTPUT_WIDTH: usize = 64;

/// 幕十三:接口清点。
pub fn render_interface_table(title: &str, rows: &[(String, String)]) -> Vec<String> {
    let mut table: Table = Table::new(
        title,
        vec![
            TableColumn::elidable(TableColumn::new("接口", INTERFACE_NAME_WIDTH)),
            TableColumn::elidable(TableColumn::new("本次真实输出", INTERFACE_OUTPUT_WIDTH)),
        ],
    );
    for (name, output) in rows.iter() {
        table = table.with_row(vec![name.clone(), output.clone()]);
    }
    table = table.with_note(
        "★ 每个接口都**真的调用了一次**并印出结果。\
         「列出来」与「调用过」是两件事:\
         列出来只能证明它存在,调用过才能证明它还能跑。",
    );
    table.render(DOCUMENT_WIDTH)
}


//!# encoding: utf-8 
//!# 版权所有  2026 ©涂聚文有限公司™ ®
//!# 许可信息查看:言語成了邀功盡責的功臣,還需要行爲每日來值班嗎
//!# 描述:对象池简写模式  pool Pattern
//!# Author    : geovindu,Geovin Du 涂聚文.
//!# IDE       : RustRover  2025.1.1 Rust  rustc 1.98.1 
//!# os        : windows 10
//!# database  : mysql 9.0 sql server 2025, postgreSQL 18.0  Oracle 21c Neo4j
//!# Datetime  : 2026/10/11 7:56 
//!# User      :  geovindu
//!# Product   : RustRover
//!# Project   : objectpoolpattern
//!# File      : inventory_report.rs
//! 幕五:仪器清单(**逐台**)。
//!
//! ## ★ 这一幕是「复用」这件事唯一的肉眼证据
//!
//! 台账说「命中 6 次」,清单说「BALANCE-0001 被借了 3 次」。
//! 后者能看见,前者只能被相信。
//! 因此本表**逐台**列出累计次数------
//! 若只列总数,一台被借了 12 次而另外两台全新这种事就完全看不出来。
//!
//! ## ⚠️ 为什么必须排序后输出
//!
//! 池内对象所在 `Vec` 的顺序取决于借还历史。
//! 直接按 `Vec` 顺序输出,同一份数据集在不同借还顺序下会长得不一样,
//! 于是「两次运行逐字节一致」就不成立了。
//! 本表的行由 `client` 层**按序列号排序**后送来。

use crate::analysis::InstrumentUnitView;
use crate::app::{Table, TableColumn, DOCUMENT_WIDTH};

/// 仪器清单的列宽。
///
/// ⚠️ `SERIAL_WIDTH` 按**最长的可能序列号**定:
/// `GEM_MICROSCOPE-0001` 是 19 列(型号编码 14 + `-0001` 5)。
/// 按当前数据里最长的 `BALANCE-0001`(12 列)定宽,
/// 一旦工程外扩展区加一个长型号编码就会静默截断。
const SERIAL_WIDTH: usize = 20;
/// 型号中文名列宽(最宽「宝石显微镜」= 10 列)。
const MODEL_WIDTH: usize = 12;
/// 状态列宽(最宽「在池空闲」= 8 列)。
const STATE_WIDTH: usize = 10;
/// 被借次数列宽(表头 8 列,数值至多 2 位)。
const USAGE_WIDTH: usize = 8;
/// 持有人列宽(最宽「广州天河店」= 10 列)。
const HOLDER_WIDTH: usize = 12;
/// 校准到期列宽(最宽「2027 年 01 月」= 12 列)。
const DUE_WIDTH: usize = 14;
/// 采购单价列宽(最宽 `¥2,180,000.00` = 13 列)。
const COST_WIDTH: usize = 14;
/// 指纹列宽(**可省略**:`GEM_MICROSCOPE-0001|IDLE|3|202701` = 32 列放不下)。
const FINGERPRINT_WIDTH: usize = 20;

/// 一行仪器(**与 `InstrumentUnitView` 同构**,由 `main` 填充)。
#[derive(Clone, Debug)]
pub struct InventoryRowView {
    /// 序列号。
    pub serial_number: String,
    /// 型号中文名。
    pub model_name: String,
    /// 状态中文名。
    pub state_text: String,
    /// 累计被借次数。
    pub usage_count: usize,
    /// 持有人文本。
    pub holder_text: String,
    /// 校准到期月文本。
    pub calibration_due_text: String,
    /// 采购单价文本。
    pub acquisition_cost_text: String,
    /// 指纹。
    pub fingerprint: String,
}

impl InventoryRowView {
    /// 由分析层的视图转换。
    pub fn from_view(view: &InstrumentUnitView) -> Self {
        Self {
            serial_number: view.serial_number().to_string(),
            model_name: view.model_name().to_string(),
            state_text: view.state_text().to_string(),
            usage_count: view.usage_count(),
            holder_text: view.holder_text().to_string(),
            calibration_due_text: view.calibration_due_text().to_string(),
            acquisition_cost_text: view.acquisition_cost_text().to_string(),
            fingerprint: view.fingerprint().to_string(),
        }
    }

    /// 指纹(跨运行比对用)。
    pub fn fingerprint(&self) -> &str {
        self.fingerprint.as_str()
    }

    /// 序列号。
    pub fn serial_number(&self) -> &str {
        self.serial_number.as_str()
    }
}

/// 幕五:仪器清单。
pub fn render_inventory_table(title: &str, rows: &[InventoryRowView]) -> Vec<String> {
    let mut table: Table = Table::new(
        title,
        vec![
            TableColumn::new("序列号", SERIAL_WIDTH),
            TableColumn::new("型号", MODEL_WIDTH),
            TableColumn::new("状态", STATE_WIDTH),
            TableColumn::numeric("被借次数", USAGE_WIDTH),
            TableColumn::new("持有人", HOLDER_WIDTH),
            TableColumn::new("校准到期", DUE_WIDTH),
            TableColumn::numeric("采购单价", COST_WIDTH),
            TableColumn::elidable(TableColumn::new("指纹", FINGERPRINT_WIDTH)),
        ],
    );
    for row in rows.iter() {
        table = table.with_row(vec![
            row.serial_number.clone(),
            row.model_name.clone(),
            row.state_text.clone(),
            row.usage_count.to_string(),
            row.holder_text.clone(),
            row.calibration_due_text.clone(),
            row.acquisition_cost_text.clone(),
            row.fingerprint.clone(),
        ]);
    }
    table = table.with_note(
        "★ 「被借次数」是复用的肉眼证据:\
         同一台出现 2 次以上,就说明这台设备真的被第二次用过了,\
         而不是「台账上记了一次复用」。",
    );
    table = table.with_note(
        "★ 行按序列号排序输出。池内 `Vec` 的顺序取决于借还历史,\
         直接按它输出会把「先借后还」的顺序泄漏进报表,\
         于是同一份数据在不同借还顺序下长得不一样。",
    );
    table.render(DOCUMENT_WIDTH)
}


//!# encoding: utf-8 
//!# 版权所有  2026 ©涂聚文有限公司™ ®
//!# 许可信息查看:言語成了邀功盡責的功臣,還需要行爲每日來值班嗎
//!# 描述:对象池简写模式  pool Pattern
//!# Author    : geovindu,Geovin Du 涂聚文.
//!# IDE       : RustRover  2025.1.1 Rust  rustc 1.98.1 
//!# os        : windows 10
//!# database  : mysql 9.0 sql server 2025, postgreSQL 18.0  Oracle 21c Neo4j
//!# Datetime  : 2026/10/11 7:56 
//!# User      :  geovindu
//!# Product   : RustRover
//!# Project   : objectpoolpattern
//!# File      : layout.rs
//! 表格引擎:列宽、对齐、可见省略、框线、注解折行。
//!
//! ## 列宽的唯一来源:`TableColumn`
//!
//! 每一列都要**显式声明宽度**,且宽度依据写在声明处的注释里。
//! 不提供「自适应列宽」:自适应会让同一张表在不同数据下宽度不同,
//! 于是「两次运行逐字节一致」依赖数据而不是代码------那是把不确定性引进来了。

use crate::support::{display_width, elide_text, horizontal_rule, pad_left, pad_right};

/// 列对齐。
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ColumnAlign {
    /// 左对齐(文本列)。
    Left,
    /// 右对齐(数字列)。
    Right,
}

/// 一列。
#[derive(Debug, Clone)]
pub struct TableColumn {
    /// 表头文本。
    header: &'static str,
    /// 列宽(**显示列**,中文按 2 列计)。
    width: usize,
    /// 对齐方式。
    align: ColumnAlign,
    /// 是否**允许**可见省略。
    ///
    /// ⚠️ 这个开关是本工程「不许静默截断」纪律的载体:
    /// 不标它是默认,标了它必须是**有意**的(该列内容确实不可控)。
    elidable: bool,
}

impl TableColumn {
    /// 声明一列(左对齐、**不允许**省略)。
    pub const fn new(header: &'static str, width: usize) -> Self {
        TableColumn { header, width, align: ColumnAlign::Left, elidable: false }
    }

    /// 声明一个数字列(右对齐、不允许省略)。
    pub const fn numeric(header: &'static str, width: usize) -> Self {
        TableColumn { header, width, align: ColumnAlign::Right, elidable: false }
    }

    /// 把本列改成**允许**可见省略(链式)。
    pub const fn elidable(mut self) -> Self {
        self.elidable = true;
        self
    }

    /// 表头。
    pub const fn header(&self) -> &'static str {
        self.header
    }

    /// 列宽。
    pub const fn width(&self) -> usize {
        self.width
    }

    /// 对齐。
    pub const fn align(&self) -> ColumnAlign {
        self.align
    }

    /// 是否允许省略。
    pub const fn is_elidable(&self) -> bool {
        self.elidable
    }
}

/// 渲染一个单元格。
///
/// # 三条行为
///
/// 1. `elidable == false` → 内容**必须**放得下;否则 `debug_assert!` 当场 panic,
///    错误信息里带上列宽与所需宽度,直接告诉你该改哪一列。
/// 2. `elidable == true` → 走可见省略,末尾有 `...`,读者知道这里被省了。
/// 3. 省略后按列宽**补白**。
///
/// # ⚠️ 为什么是 `debug_assert` 而不是 `assert`
///
/// 它是**开发期纪律**:`cargo run`(debug)时立刻 panic,
/// release 下不存在,不会让线上报表因一条宽度问题而中断。
/// 同理它也**不是**批量排查的工具------排查请用临时 `eprintln!` 插桩
/// (一次跑出全部位置),见 `app` 模块文档。
pub fn render_cell(column: &TableColumn, text: &str) -> String {
    let fitted: String = if column.is_elidable() {
        elide_text(text, column.width())
    } else {
        let needed: usize = display_width(text);
        debug_assert!(
            needed <= column.width(),
            "单元格放不下且该列不允许省略:列「{}」宽 {} 列,内容需要 {} 列,内容原文=[{}]",
            column.header(),
            column.width(),
            needed,
            text
        );
        // debug 下上面已 panic;release 下退化成可见省略,
        // 而不是静默截断------**任何构建下都不接受「悄悄少一截」**。
        if needed > column.width() {
            elide_text(text, column.width())
        } else {
            text.to_string()
        }
    };

    match column.align() {
        ColumnAlign::Left => pad_right(&fitted, column.width()),
        ColumnAlign::Right => pad_left(&fitted, column.width()),
    }
}

/// 一张表:标题 + 列声明 + 数据行 + 注解。
#[derive(Debug, Clone)]
pub struct Table {
    /// 标题(渲染成 `■ 标题`)。
    title: String,
    /// 列声明。
    columns: Vec<TableColumn>,
    /// 数据行。
    rows: Vec<Vec<String>>,
    /// 注解(**永不省略**,只折行)。
    notes: Vec<String>,
}

impl Table {
    /// 建一张表。
    pub fn new(title: &str, columns: Vec<TableColumn>) -> Self {
        Table {
            title: title.to_string(),
            columns,
            rows: Vec::new(),
            notes: Vec::new(),
        }
    }

    /// 追加一行(链式)。
    pub fn with_row(mut self, row: Vec<String>) -> Self {
        self.rows.push(row);
        self
    }

    /// 追加一条注解(链式)。
    pub fn with_note(mut self, note: &str) -> Self {
        self.notes.push(note.to_string());
        self
    }

    /// 列数。
    pub fn column_count(&self) -> usize {
        self.columns.len()
    }

    /// 行数。
    pub fn row_count(&self) -> usize {
        self.rows.len()
    }

    /// 表的总宽度(各列宽之和 + 每列两侧的空格与竖线)。
    ///
    /// 每列的开销是 3:左空格 1 + 右空格 1 + 竖线 1;
    /// 最左边还有一条竖线,因此再 +1。
    pub fn total_width(&self) -> usize {
        self.columns.iter().map(|column| column.width()).sum::<usize>()
            + self.column_count() * 3
            + 1
    }

    /// 框线(上框 / 分隔 / 下框)。
    fn border_line(&self) -> String {
        format!("┌{}┐", self.segments('┬', '─'))
    }

    fn middle_line(&self) -> String {
        format!("├{}┤", self.segments('┼', '─'))
    }

    fn bottom_line(&self) -> String {
        format!("└{}┘", self.segments('┴', '─'))
    }

    fn segments(&self, joint: char, fill: char) -> String {
        self.columns
            .iter()
            .map(|column| fill.to_string().repeat(column.width() + 2))
            .collect::<Vec<String>>()
            .join(&joint.to_string())
    }

    /// 渲染一行数据。
    ///
    /// ⚠️ 单元格数不足时用空串兜底而不是 panic:
    /// 少给一个单元格只是这一行少一格,
    /// 整张报表仍然可读;panic 会让连后面的幕都出不来。
    fn data_line(&self, cells: &[String]) -> String {
        let rendered: Vec<String> = self
            .columns
            .iter()
            .enumerate()
            .map(|(index, column)| render_cell(column, cells.get(index).map(String::as_str).unwrap_or("")))
            .collect();
        format!("│ {} │", rendered.join(" │ "))
    }

    /// 渲染整张表。
    pub fn render(self, document_width: usize) -> Vec<String> {
        debug_assert!(
            self.total_width() <= document_width,
            "表宽 {} 超过文档宽度 {}(表:{})",
            self.total_width(),
            document_width,
            self.title
        );

        let mut lines: Vec<String> = Vec::new();
        lines.push(section_header(&self.title, document_width));
        lines.push(self.border_line());

        // ★ 表头也走 `render_cell`:表头超宽同样要被抓出来。
        let header_cells: Vec<String> = self
            .columns
            .iter()
            .map(|column| column.header().to_string())
            .collect();
        lines.push(self.data_line(&header_cells));
        lines.push(self.middle_line());

        for row in self.rows.iter() {
            debug_assert!(
                row.len() == self.columns.len(),
                "行宽 {} 与列数 {} 不一致(表:{})",
                row.len(),
                self.columns.len(),
                self.title
            );
            lines.push(self.data_line(row));
        }

        lines.push(self.bottom_line());

        // ★ 空表不静默:一张没有数据行的表,与「这一幕忘了填数据」在版面上
        //   长得一模一样。补一条可见注解,让「零行」变成一个能被看见的状态。
        let mut notes: Vec<String> = self.notes.clone();
        if self.row_count() == 0 {
            notes.push("(本表无数据行)".to_string());
        }
        for note in notes.iter() {
            lines.extend(note_lines(note, document_width));
        }
        lines
    }
}

/// 幕标题:`■ 标题`(右侧补到文档宽度)。
///
/// ⚠️ 标题**只由本函数产生**:若调用方也写一次标题,
/// 就会出现「改一次标题要改两处」------迟早不一致。
pub fn section_header(title: &str, document_width: usize) -> String {
    let text: String = format!("■ {}", title);
    let used: usize = display_width(&text);
    if used >= document_width {
        return text;
    }
    format!("{}{}", text, " ".repeat(document_width - used))
}

/// 把一段长文本折成多行注解(**永不省略**,只折行)。
///
/// ## ⚠️ 可用宽度按「最宽的那种前缀」留白
///
/// 首行前缀 `  · ` 是 **4 列**,续行前缀 `  ` 是 2 列。
/// 若按续行的 2 列留白,首行就会溢出 2 列------
/// 既有工程实测过:37 行注解达到 142 列 / 文档宽 140,
/// 而溢出的注解在框线旁边几乎看不出来。
/// **统一按 4 列留白是唯一不会错的算法**,代价只是续行少 2 列。
///
/// ## 为什么注解永不省略
///
/// 注解承载的是**复算依据**(分子是什么、分母是什么、哪个常数用在哪)。
/// 省略掉一段,读者就少一条验算路径------那正是报表存在的理由。
pub fn note_lines(text: &str, document_width: usize) -> Vec<String> {
    let available: usize = document_width.saturating_sub(4).max(1);
    let mut lines: Vec<String> = Vec::new();
    let mut current: String = String::new();
    let mut current_width: usize = 0;

    for character in text.chars() {
        let character_width: usize = if crate::support::is_wide_character(character) { 2 } else { 1 };
        if current_width + character_width > available && !current.is_empty() {
            lines.push(current);
            current = String::new();
            current_width = 0;
        }
        current.push(character);
        current_width += character_width;
    }
    if !current.is_empty() {
        lines.push(current);
    }
    if lines.is_empty() {
        // 空注解仍然产出一行,避免「注解存在但输出为零行」这种不可见状态。
        lines.push(String::new());
    }

    lines
        .into_iter()
        .enumerate()
        .map(|(index, line)| {
            if index == 0 {
                format!("  · {}", line)
            } else {
                format!("  {}", line)
            }
        })
        .collect()
}

/// 生成一条与表同宽的横线(供需要额外分隔的场合用)。
pub fn table_rule(width: usize) -> String {
    horizontal_rule(width)
}


//!# encoding: utf-8 
//!# 版权所有  2026 ©涂聚文有限公司™ ®
//!# 许可信息查看:言語成了邀功盡責的功臣,還需要行爲每日來值班嗎
//!# 描述:对象池简写模式  pool Pattern
//!# Author    : geovindu,Geovin Du 涂聚文.
//!# IDE       : RustRover  2025.1.1 Rust  rustc 1.98.1 
//!# os        : windows 10
//!# database  : mysql 9.0 sql server 2025, postgreSQL 18.0  Oracle 21c Neo4j
//!# Datetime  : 2026/10/11 7:56 
//!# User      :  geovindu
//!# Product   : RustRover
//!# Project   : objectpoolpattern
//!# File      : lifecycle_report.rs
//! 幕九:生命周期分布 + 服务记录。
//!
//! ## ★ 为什么按状态分档,而不是只看「一共几台」
//!
//! 「一共 8 台」是一个无法验证的数字:
//! 它同时包含「8 台都能用」与「2 台能用、6 台在修」。
//! 分档之后「能用的有几台」才有答案,
//! 而那正是借用申请真正关心的量。
//!
//! ## ⚠️ 为 0 的档也要出现在表里
//!
//! 「校准维修 0 台」与「报表里压根没有这一行」是两件不同的事:
//! 前者说明没事发生,后者说明这一档**没被统计**。
//! 因此统计遍历的是**内置状态全集**,不是实际出现过的状态。

use crate::analysis::LifecycleProbe;
use crate::app::{bool_text, Table, TableColumn, DOCUMENT_WIDTH};
use crate::domain::{ServiceKind, ServiceRecord};

/// 状态表的列宽。
const STATE_WIDTH: usize = 16;
const COUNT_WIDTH: usize = 10;
const IN_POOL_WIDTH: usize = 12;

/// 服务记录表的列宽。
const SERVICE_SERIAL_WIDTH: usize = 20;
const SERVICE_MONTH_WIDTH: usize = 14;
const SERVICE_KIND_WIDTH: usize = 12;
const SERVICE_REASON_WIDTH: usize = 46;
const SERVICE_COST_WIDTH: usize = 16;

/// 幕九:状态分布。
pub fn render_lifecycle_table(title: &str, probes: &[LifecycleProbe]) -> Vec<String> {
    let mut table: Table = Table::new(
        title,
        vec![
            TableColumn::new("状态", STATE_WIDTH),
            TableColumn::numeric("台数", COUNT_WIDTH),
            TableColumn::new("计入容量", IN_POOL_WIDTH),
        ],
    );
    for probe in probes.iter() {
        table = table.with_row(vec![
            probe.state_name().to_string(),
            probe.unit_total().to_string(),
            bool_text(probe.in_pool()).to_string(),
        ]);
    }
    table = table.with_note(
        "★ 只有「在池空闲」计入容量(**可借**)。\
         把「校准维修」也算进可用数,容量守恒就会在月底对不上------\
         它看起来像是「暂时不在」,但它**不能被借出**。",
    );
    table.render(DOCUMENT_WIDTH)
}

/// 幕九(续):服务记录。
pub fn render_service_record_table(title: &str, records: &[ServiceRecord]) -> Vec<String> {
    let mut table: Table = Table::new(
        title,
        vec![
            TableColumn::new("序列号", SERVICE_SERIAL_WIDTH),
            TableColumn::new("月份", SERVICE_MONTH_WIDTH),
            TableColumn::new("类型", SERVICE_KIND_WIDTH),
            TableColumn::new("事由", SERVICE_REASON_WIDTH),
            TableColumn::numeric("费用", SERVICE_COST_WIDTH),
        ],
    );
    for record in records.iter() {
        table = table.with_row(vec![
            record.serial_number().to_string(),
            record.month().chinese_text(),
            record.kind().chinese_text().to_string(),
            record.reason_text().to_string(),
            crate::domain::Money::new(
                record.service_cost_minor_units(),
                crate::domain::CNY,
            )
            .chinese_text(),
        ]);
    }
    // ★ 按类型汇总写进注解(表格里逐条已经很长,汇总放注解更清楚)。
    for (kind, total) in crate::analysis::service_statistics(records) {
        table = table.with_note(&format!(
            "{}:{} 次;这种服务做完是否回池:{}",
            kind.chinese_text(),
            total,
            bool_text(kind.returns_to_pool())
        ));
    }
    table = table.with_note(
        "★ 服务记录**只增不减**:写错了就补一条反向记录,\
         不要改原来那条------改了之后上个月的报表与这个月的报表会对不上,\
         且没有任何痕迹说明为什么对不上。",
    );
    table.render(DOCUMENT_WIDTH)
}

/// 服务类型中文名(**供体检与清点用**,避免各处各写一份)。
pub fn service_kind_text(kind: ServiceKind) -> &'static str {
    kind.chinese_text()
}


//!# encoding: utf-8 
//!# 版权所有  2026 ©涂聚文有限公司™ ®
//!# 许可信息查看:言語成了邀功盡責的功臣,還需要行爲每日來值班嗎
//!# 描述:对象池简写模式  pool Pattern
//!# Author    : geovindu,Geovin Du 涂聚文.
//!# IDE       : RustRover  2025.1.1 Rust  rustc 1.98.1 
//!# os        : windows 10
//!# database  : mysql 9.0 sql server 2025, postgreSQL 18.0  Oracle 21c Neo4j
//!# Datetime  : 2026/10/11 7:56 
//!# User      :  geovindu
//!# Product   : RustRover
//!# Project   : objectpoolpattern
//!# File      : overview_report.rs
//! 幕一:装配总览 + 通用两列表。
//!
//! ## 为什么第一幕是「总览」而不是直接进入结果
//!
//! 读者最先需要的不是结论,是**这份数据是什么**:
//! 几个型号、几家门店、多少笔申请、预热几台。
//! 缺了这些,后面「省下 5 台」这句话没有参照------
//! 5 台可能是 5 台里的 5 台,也可能是 200 台里的 5 台。

use crate::app::{Table, TableColumn, DOCUMENT_WIDTH};

/// 两列表的列名宽(项目列)。
///
/// ⚠️ 40 列按**最宽的项目名**定:「★ 省下的采购台数(不池化要买 − 池化已买)」。
const ITEM_WIDTH: usize = 40;
/// 两列表的值列宽。
///
/// `40 + 92 + 2*3 + 1 = 139 <= 140`。
const VALUE_WIDTH: usize = 92;

/// ★ 通用两列表(**全工程「一项一值」类报表的唯一实现**)。
///
/// 幕一、幕十一、幕十一(续)都用它。
/// 若每处各写一张表,就会出现三份不一样的列宽与对齐------
/// 而读者只会以为是三件事。
pub fn render_pair_table(title: &str, rows: &[(String, String)], notes: &[&str]) -> Vec<String> {
    let mut table: Table = Table::new(
        title,
        vec![
            TableColumn::new("项目", ITEM_WIDTH),
            TableColumn::new("值", VALUE_WIDTH),
        ],
    );
    for (item, value) in rows.iter() {
        table = table.with_row(vec![item.clone(), value.clone()]);
    }
    for note in notes.iter() {
        table = table.with_note(note);
    }
    table.render(DOCUMENT_WIDTH)
}

/// 幕一:装配总览。
///
/// # 参数
/// - `rows`:总览行(**由 `main` 组装**,本函数不认识业务)。
pub fn render_overview_table(rows: &[(String, String)]) -> Vec<String> {
    render_pair_table(
        "幕一 · 装配总览:这一池子是怎么回事",
        rows,
        &[
            "★ 「预热台数」是开业前先备的货,「申请笔数」是当月实际来的活。\
             两者不等是常态:备货看的是峰值,干活看的是总量。",
            "★ 三档策略跑的是**同一份**申请序列。若每档各造一份数据,\
             「新建次数之差」就无法被解释为「策略造成的」------那正是对照实验最容易失效的地方。",
        ],
    )
}


//!# encoding: utf-8 
//!# 版权所有  2026 ©涂聚文有限公司™ ®
//!# 许可信息查看:言語成了邀功盡責的功臣,還需要行爲每日來值班嗎
//!# 描述:对象池简写模式  pool Pattern
//!# Author    : geovindu,Geovin Du 涂聚文.
//!# IDE       : RustRover  2025.1.1 Rust  rustc 1.98.1 
//!# os        : windows 10
//!# database  : mysql 9.0 sql server 2025, postgreSQL 18.0  Oracle 21c Neo4j
//!# Datetime  : 2026/10/11 7:56 
//!# User      :  geovindu
//!# Product   : RustRover
//!# Project   : objectpoolpattern
//!# File      : policy_report.rs
//! 幕六:三档策略对照。
//!
//! ## ★ 本表是「对象池省下了什么」的唯一出处
//!
//! 三档跑**同一份**申请序列,于是「新建次数」这一列的三个数
//! 可以直接相减:按需扩容的新建次数 − 严格池的新建次数 = 省下的采购台数。
//!
//! ## ⚠️ 危险等级为什么放进注解而不是列
//!
//! 「危险等级」的文本最长二十几列,放进表格会把其它列挤到只剩 6 列。
//! 放注解里逐档印出,读者看到的反而更完整------
//! **表格的列宽预算是有上限的,注解没有**。

use crate::analysis::PolicyProbe;
use crate::app::{percent_text, Table, TableColumn, DOCUMENT_WIDTH};

/// 策略列的列宽(**最宽的一档是「限量扩容(到顶即拒绝)」** = 26 列)。
const POLICY_WIDTH: usize = 34;
/// 数字列宽(数字最多 3 位,表头最多 4 列)。
const COUNT_WIDTH: usize = 6;
/// 采购花费列宽(最宽 `¥1,440,000.00` = 13 列)。
const COST_WIDTH: usize = 14;
/// 复用率列宽(最宽 `100.00%` = 7 列)。
const RATIO_WIDTH: usize = 10;

/// 幕六:三档策略对照。
pub fn render_policy_table(probes: &[PolicyProbe]) -> Vec<String> {
    let mut table: Table = Table::new(
        "幕六 · 三档策略对照:同一份申请,三种「池借空了怎么办」",
        vec![
            TableColumn::new("策略", POLICY_WIDTH),
            TableColumn::numeric("申请", COUNT_WIDTH),
            TableColumn::numeric("借到", COUNT_WIDTH),
            TableColumn::numeric("拒绝", COUNT_WIDTH),
            TableColumn::numeric("新建", COUNT_WIDTH),
            TableColumn::numeric("命中", COUNT_WIDTH),
            TableColumn::numeric("回池", COUNT_WIDTH),
            TableColumn::numeric("隔离", COUNT_WIDTH),
            TableColumn::numeric("峰值", COUNT_WIDTH),
            TableColumn::numeric("采购花费", COST_WIDTH),
            TableColumn::numeric("复用率", RATIO_WIDTH),
        ],
    );
    for probe in probes.iter() {
        table = table.with_row(vec![
            probe.policy_name().to_string(),
            probe.request_total().to_string(),
            probe.granted_total().to_string(),
            probe.rejected_total().to_string(),
            probe.created_total().to_string(),
            probe.hit_total().to_string(),
            probe.returned_total().to_string(),
            probe.quarantined_total().to_string(),
            probe.peak_borrowed_total().to_string(),
            probe.acquisition_cost_text().to_string(),
            format!("{}%", percent_text(probe.reuse_ratio_basis_points())),
        ]);
    }
    // ★ 危险等级逐档印在注解里(见模块文档)。
    for probe in probes.iter() {
        table = table.with_note(&format!(
            "{}:{};复用率 {}%;服务花费 {}",
            probe.policy_name(),
            probe.risk_text(),
            percent_text(probe.reuse_ratio_basis_points()),
            probe.service_cost_text()
        ));
    }
    table = table.with_note(
        "★ 「新建」是**真的花钱买了几台**,「命中」是借出时池里有空闲、直接复用。\
         「省下几台」= 按需扩容的新建 − 严格池的新建,而不是命中次数------\
         这两个数在稳态下并不相等。",
    );
    table = table.with_note(
        "★ 「峰值」是不池化要买几台的直接答案:同一时刻最多有几台在外面。\
         它必须在**借出那一刻**更新,事后从台账推不出来(台账里没有「同一时刻」)。",
    );
    table.render(DOCUMENT_WIDTH)
}


//!# encoding: utf-8 
//!# 版权所有  2026 ©涂聚文有限公司™ ®
//!# 许可信息查看:言語成了邀功盡責的功臣,還需要行爲每日來值班嗎
//!# 描述:对象池简写模式  pool Pattern
//!# Author    : geovindu,Geovin Du 涂聚文.
//!# IDE       : RustRover  2025.1.1 Rust  rustc 1.98.1 
//!# os        : windows 10
//!# database  : mysql 9.0 sql server 2025, postgreSQL 18.0  Oracle 21c Neo4j
//!# Datetime  : 2026/10/11 7:56 
//!# User      :  geovindu
//!# Product   : RustRover
//!# Project   : objectpoolpattern
//!# File      : value_display.rs
//! 值对象的**报表排版**:把领域层存的整数渲染成人看的文本。
//!
//! ## ★ 哪些排版留在领域层,哪些搬到这里
//!
//! 判据只有一条:**这个文本形式是不是「所有层都要用」**。
//!
//! - **留在领域层**:`Money::chinese_text()`(`¥12,345.67`)。
//!   机制层报错、分析层结论、表示层表格都要把金额嵌进句子里------
//!   把它搬来表示层会让 `borg` 反向依赖 `app`,那才是真违规。
//!   值对象的**规范文本形式**属于值对象本身。
//! - **搬来表示层**:金重与汇率。
//!   `TerminalTill` 存的是**毫克整数**,汇率存的是**万分点整数**;
//!   「用克显示、保留 3 位小数」与「把 9215 显示成 0.9215」
//!   是这张表列头写着「金重(克)」「汇率」才有的决定,只有报表需要。
//!
//! ## ⚠️ 为什么这两处可以有除法
//!
//! 整个计算链路全程是整数,除法只发生在 [`crate::domain::divide_rounded`] 一处。
//! 本文件是**唯一的例外**:这两个函数都不参与任何合计,
//! 只把已经算完的整数拆成「整数部分 + 小数部分」,
//! 所以不存在舍入问题------余数是整除的余数,不是被舍掉的部分。
//!
//! 换句话说:**展示可以除,算账不可以除**。

/// 把毫克整数渲染成「克,3 位小数」的文本(如 `12500` → `"12.500"`)。
///
/// ⚠️ 用 `div_euclid` / `rem_euclid` 而不是 `/` 与 `%`:
/// 若将来出现负重量(冲减),`%` 会得到负余数,
/// 拼出来的串会变成 `-12.-500`。
pub fn gram_display_text(milligrams: i64) -> String {
    let whole: i64 = milligrams.div_euclid(1000);
    let remainder: i64 = milligrams.rem_euclid(1000);
    format!("{}.{:03}", whole, remainder)
}

/// 把汇率万分点渲染成小数文本(如 `9215` → `"0.9215"`)。
///
/// ⚠️ **`10000` 与 `10000.0` 是完全不同的两件事**:
/// 前者是 1:1,后者是「万倍」。写成 `{}.{:04}` 而不除 10000,
/// 读者会把 `9215` 读成「九千多倍汇率」。
pub fn basis_points_text(basis_points: i64) -> String {
    let whole: i64 = basis_points.div_euclid(10_000);
    let remainder: i64 = basis_points.rem_euclid(10_000);
    format!("{}.{:04}", whole, remainder)
}

/// ★ 万分点转百分数文本(`10000` -> `100.00`)。
///
/// ⚠️ 为什么单列一个函数:`basis_points_text` 输出的是「几点几」
/// (`10000` -> `1.0000`,因为 10000 万分点 = 1 倍)。
/// 直接给它加一个 `%` 会变成 `1.0000%`------那是**百分之一**,
/// 而复用率 100% 应该显示 `100.00%`。
/// 这个错误在报表上完全看不出来(都是一个带百分号的小数),
/// 只能靠追问「分母是什么」才能发现,因此口径必须收在一处。
pub fn percent_text(basis_points: i64) -> String {
    format!("{}.{:02}", basis_points / 100, (basis_points % 100).abs())
}

/// 把布尔量渲染成中文(`是` / `否`)。
///
/// ⚠️ 报表的布尔列一律走这里,不写 `if x { "是" } else { "否" }`:
/// 散着写迟早会出现一处写成 `符合/不符合`,
/// 而读者会以为那是另一种判断。
pub const fn bool_text(value: bool) -> &'static str {
    if value {
        "是"
    } else {
        "否"
    }
}

/// 把字节数渲染成带千分位的文本。
///
/// ⚠️ 用整数反复除 10 取位,**全程不出现浮点**(与 `Money` 的千分位同理)。
pub fn byte_text(bytes: usize) -> String {
    let digits: Vec<char> = bytes.to_string().chars().collect();
    let mut buffer: Vec<char> = Vec::new();
    for (index, character) in digits.iter().enumerate() {
        let from_right: usize = digits.len() - index;
        if index > 0 && from_right % 3 == 0 {
            buffer.push(',');
        }
        buffer.push(*character);
    }
    buffer.into_iter().collect()
}


//!# encoding: utf-8 
//!# 版权所有  2026 ©涂聚文有限公司™ ®
//!# 许可信息查看:言語成了邀功盡責的功臣,還需要行爲每日來值班嗎
//!# 描述:对象池简写模式  pool Pattern
//!# Author    : geovindu,Geovin Du 涂聚文.
//!# IDE       : RustRover  2025.1.1 Rust  rustc 1.98.1 
//!# os        : windows 10
//!# database  : mysql 9.0 sql server 2025, postgreSQL 18.0  Oracle 21c Neo4j
//!# Datetime  : 2026/10/11 7:56 
//!# User      :  geovindu
//!# Product   : RustRover
//!# Project   : objectpoolpattern
//!# File      : value_report.rs
//! 幕十一:价值量化 + 台账快照。
//!
//! ## ★ 省下的不是时间,是「造一个新对象」这件事本身
//!
//! 本工程里「造」等于**采购一台设备**,所以省下的能用金额算出来。
//! 这是刻意选这个业务域的原因:
//! 若选一个「造对象只要几微秒」的例子,
//! 省下的就只能用**耗时**表达,而耗时不可复现------
//! 那正是「我们用了对象池所以很快」这句话永远无法被反驳的根源。
//!
//! ## ⚠️ 省下的台数是**两个可数数字之差**
//!
//! ```text
//! 不池化要买几台   = 峰值并发在借数(按需扩容档的新建次数)
//! 池化实际买了几台 = 严格池档的新建次数
//! 省下的台数       = 前者 − 后者
//! ```
//!
//! 不用「命中次数」当省下的台数:命中 6 次只说明复用发生了 6 次,
//! 而「本来要买 8 台、实际买了 3 台」里的 5 台才是真正的差额。

use crate::analysis::{LedgerSnapshotView, PoolValueReport};
use crate::app::{percent_text, render_pair_table, bool_text};
use crate::domain::{Money, CNY};

/// 幕十一:价值量化。
pub fn render_value_table(report: &PoolValueReport) -> Vec<String> {
    let rows: Vec<(String, String)> = vec![
        ("申请笔数".to_string(), format!("{} 笔", report.request_total())),
        ("借到笔数".to_string(), format!("{} 笔", report.granted_total())),
        ("没借到笔数".to_string(), format!("{} 笔", report.rejected_total())),
        (
            "★ 池化档实际买了几台(严格池新建次数)".to_string(),
            format!("{} 台", report.created_with_pool_total()),
        ),
        (
            "★ 不池化要买几台(按需扩容新建次数)".to_string(),
            format!("{} 台", report.created_without_pool_total()),
        ),
        (
            "★ 省下的采购台数(两个可数数字之差)".to_string(),
            format!("{} 台", report.saved_purchase_total()),
        ),
        (
            "★ 省下的采购金额".to_string(),
            report.saved_money().chinese_text(),
        ),
        (
            "省下的比例(省下台数 / 不池化要买)".to_string(),
            format!("{}%", percent_text(report.saved_ratio_basis_points())),
        ),
        (
            "★ 采购单价(见幕二,按型号分别折算)".to_string(),
            "见幕二".to_string(),
        ),
        ("命中次数(复用发生过几次)".to_string(), format!("{} 次", report.hit_total())),
        (
            "归还后回池次数(真正可供下次使用)".to_string(),
            format!("{} 次", report.reused_total()),
        ),
        ("借出峰值(同一时刻最多几台在外面)".to_string(), format!("{} 台", report.peak_borrowed_total())),
        (
            "服务成本(校准与退役,逐条累加)".to_string(),
            Money::new(report.service_cost_minor_units(), CNY).chinese_text(),
        ),
        ("一句话结论".to_string(), report.summary_text()),
    ];
    render_pair_table(
        "幕十一 · 价值量化:对象池省下的到底是什么",
        &rows,
        &[
            "★ 「省下的台数」取的是两个**新建次数**之差,不是命中次数。\
             命中次数回答「复用发生了几次」,新建次数之差回答「少买了几台」------\
             这两个数在稳态下并不相等,取哪个必须先说清。",
            "★ 不用耗时当度量:耗时不可复现(同一份代码两次运行差 20% 很正常),\
             而本工程要求两次运行逐字节一致。台数与金额都是整点数,可数、可复算。",
        ],
    )
}

/// 幕十一(续):台账快照。
///
/// # 参数
/// - `view`:快照视图。
/// - `moment_text`:**必须写清是哪个时刻**(台账只增不减,时刻不同数字就不同)。
pub fn render_ledger_snapshot_table(view: &LedgerSnapshotView, moment_text: &str) -> Vec<String> {
    let mut rows: Vec<(String, String)> = view.rows().to_vec();
    rows.push((
        "快照时刻".to_string(),
        moment_text.to_string(),
    ));
    rows.push((
        "是否快照(而非实时计数)".to_string(),
        bool_text(true).to_string(),
    ));
    render_pair_table(
        "幕十一(续)· 池台账快照(只增不减)",
        &rows,
        &[
            "★ 台账**只增不减**,因此报表一律读**快照**而不是实时计数:\
             否则表格先渲染的几行与后渲染的几行来自不同时刻,\
             读者会以为「后面那些清点动作没有副作用」------事实恰好相反。",
            "★ 两条恒等式让「某处计数漏记」变成一个可计算的数字,而不是一句原则:\
             借出 − (命中 + 新建) 必须为 0;归还 − (回池 + 隔离) 必须为 0。",
        ],
    )
}

调用:

rust 复制代码
//!# encoding: utf-8 
//!# 版权所有  2026 ©涂聚文有限公司™ ®
//!# 许可信息查看:言語成了邀功盡責的功臣,還需要行爲每日來值班嗎
//!# 描述:对象池简写模式  pool Pattern
//!# Author    : geovindu,Geovin Du 涂聚文.
//!# IDE       : RustRover  2025.1.1 Rust  rustc 1.98.1 
//!# os        : windows 10
//!# database  : mysql 9.0 sql server 2025, postgreSQL 18.0  Oracle 21c Neo4j
//!# Datetime  : 2026/10/11 7:56 
//!# User      :  geovindu
//!# Product   : RustRover
//!# Project   : objectpoolpattern
//!# File      : mainrs
//! 对象池模式(Object Pool Pattern)演示:**区域检测中心的仪器借用池**。
//!
//! ## 严格分层结构
//!
//! ```text
//! main                     装配点:造数据、建站、跑场景、编排十四幕、工程外扩展区
//!  └── app                 表示层:表格引擎 + 十二类报表(**不认识 pool / client**)
//!       └── analysis       分析层:三档对照 / 容量盘点 / 身份证据 / 价值量化 / 体检
//!            └── client    调用层:把仪器接进池(impl / 工厂 / 归还判据)+ 会话
//!                 ├── lazy     ★ 机制层:对象池本体 + 台账 + 契约审计(**零 crate:: 依赖**)
//!                 └── domain    领域层:型号 / 个体 / 状态 / 门店 / 服务记录 / 金额
//!                      └── support   支撑层:日期 / 确定性编码 / 显示宽度
//! ```
//!
//! ## 依赖方向(**实测**,由 `scripts/check_layer_dependencies.sh` 得出,非设计意图)
//!
//! ```text
//! app      -> analysis domain support
//! analysis -> client domain pool
//! client   -> domain pool
//! domain   -> support
//! pool     -> none          ★ 机制层,与 support 同秩(整层可搬走)
//! support  -> none
//! ```
//!
//! ### 实测推翻过什么
//!
//! - **`pool -> none`**:设计图上原本画的是 `domain` 在最底、`pool` 在它之上。
//!   实测是 `pool -> none`------机制层谁也不依赖。
//!   按纪律**改图不改测量**:图必须反映代码,而不是反映当初的想法。
//!
//! - **`analysis -> pool`**:分析层要读机制层的审计结果才能出报表,
//!   这本来会让 `app` 也去 import `pool`。
//!   实测后补了 `AuditCheckView` / `LedgerSnapshotView` 两个分析层视图,
//!   把 `app -> pool` 断掉,而不是放宽红线。
//!
//! ## ★ 本工程最想讲清的三件事
//!
//! 1. **对象池省下的不是时间,是「造一个新对象」这件事本身**。
//!    本工程里「造」等于采购一台设备,所以省下的能用金额算出来
//!    (省下台数 × 型号采购单价)。刻意不选「造对象只要几微秒」的例子:
//!    那种例子里省下的只能用**耗时**表达,而耗时不可复现------
//!    那正是「我们用了对象池所以很快」这句话永远无法被反驳的根源。
//!
//! 2. **省下的台数是两个可数数字之差**,不是「复用次数」:
//!    `不池化要买几台(峰值并发) − 池化实际买了几台`。
//!    只有一个方案时,那个数字既是结论也是唯一的证据;
//!    三档策略跑同一份申请序列,它才变成一个可以相减的差。
//!
//! 3. **复用必须是「同一台」**,不是「新建一个一模一样的」。
//!    后者在台账上一样会记一次复用、一样省下一笔钱,
//!    但那是假复用。因此本工程用 `Rc::ptr_eq` 比对**指针**------
//!    序列号可以被复制,指针不能。
//!
//! ## 为什么「申请要持有几步」
//!
//! 若每笔申请都是「借了立刻还」,同一时刻在外面永远只有一台,
//! 峰值恒为 1------那时**池与不池化买的数量完全一样**,
//! 「省下几台」必然是 0,整个对照实验是恒真的废表。
//! 场景里的 `hold_steps` 让**并发**成为数据的一部分。

// =============================================================================
// 模块声明(**二进制 crate 也要声明**,否则 `crate::xxx` 找不到)
// =============================================================================

pub mod analysis;
pub mod app;
pub mod client;
pub mod domain;
pub mod pool;
pub mod support;

use std::cell::RefCell;
use std::rc::Rc;

use crate::analysis::{
    audit_check_views, capacity_checks, holder_display_text, identity_checks, lifecycle_checks,
    lifecycle_statistics, policy_checks, service_statistics, AuditCheckView, CapacityProbe,
    CheckLine, CheckReport, InstrumentUnitView, LedgerSnapshotView, PoolValueReport, PolicyProbe,
};
use crate::app::{
    basis_points_text, bool_text, byte_text, gram_display_text, note_lines, render_audit_table,
    render_capacity_table,
    render_check_table, render_extension_table, render_identity_evidence_table,
    render_identity_table, render_interface_table, render_inventory_table,
    render_ledger_snapshot_table, render_lifecycle_table, render_model_table,
    render_overview_table, render_pair_table, render_policy_table, render_service_price_table,
    render_service_record_table, render_store_table, render_value_table, section_header,
    table_rule, CapacityRowView, ExtensionRowView, IdentityPairView, InventoryRowView,
    DOCUMENT_WIDTH,
};
use crate::client::{
    instrument_factory, return_verifier, should_retire, InstrumentStation, PoolSession,
    UsageScenario, UsageStep, SERVICE_COST_CALIBRATION_MINOR_UNITS,
    SERVICE_COST_RETIREMENT_MINOR_UNITS,
};
use crate::domain::{
    divide_rounded, ratio_basis_points, scale_rounded, InstrumentModel, InstrumentState,
    InstrumentUnit, Money, MonthKey, ServiceKind, ServiceRecord, StoreCode, UsageRequest,
    BUILTIN_CURRENCY_CODES, BUILTIN_INSTRUMENT_MODELS, BUILTIN_INSTRUMENT_STATES,
    BUILTIN_SERVICE_KINDS, BUILTIN_STORE_CODES, CNY, HKD, MODEL_BALANCE, MODEL_GEM_MICROSCOPE,
    MODEL_GOLD_TESTER, RETIREMENT_USAGE_LIMIT, STORE_CN_01, STORE_HK_01, USD,
};
use crate::pool::{
    audit_pool, observe_pool, ObjectPool, PooledObject, ReleaseOutcome, ReusePolicy,
    BUILTIN_REUSE_POLICIES,
};
use crate::support::{
    days_between, days_in_month, deterministic_code, display_width, elide_text, is_leap_year,
    prefixed_code, report_cell_text, self_check_width_model, stable_hash, CalendarDate,
};

// =============================================================================
// 演示数据(**都在装配点**,领域层只有内置常量,没有演示数据)
// =============================================================================

/// 业务当月。
const DEMO_MONTH: MonthKey = MonthKey::new(2026, 10);
/// 出厂月份(首批仪器的校准起点)。
const MANUFACTURE_MONTH: MonthKey = MonthKey::new(2026, 1);

/// 各型号的预热台数(开业前先备的货)。
///
/// ⚠️ 预热数必须**小于**峰值并发数,否则「池化」与「不池化」买的一样多,
/// 对照实验就是一张恒真的废表。
const PREWARM_TOTAL: usize = 1;
/// 限量扩容档的池内上限。
const LIMITED_MAX_TOTAL: usize = 3;

/// 内置场景:三家门店在一个月里的真实借用节奏。
fn builtin_scenario() -> UsageScenario {
    UsageScenario::new(
        "内置场景(2026-10,两家门店)",
        DEMO_MONTH,
        MANUFACTURE_MONTH,
        vec![
            // ★ 前两笔刻意重叠:广州店借走天平后**还沒还**,香港店又来借。
            //   这一重叠就是「并发」,也是池存在的理由。
            UsageStep::new(
                UsageRequest::new(STORE_CN_01, MODEL_BALANCE, DEMO_MONTH, "黄金回购称重"),
                3,
            ),
            UsageStep::new(
                UsageRequest::new(STORE_HK_01, MODEL_BALANCE, DEMO_MONTH, "金重复核"),
                0,
            ),
            // ★ 测金仪同样是重叠的两笔。它的校准周期只有 6 个月,
            //   出厂 2026-01 → 到期 2026-07 < 当月 2026-10,因此**归还时必然被隔离**。
            UsageStep::new(
                UsageRequest::new(STORE_CN_01, MODEL_GOLD_TESTER, DEMO_MONTH, "成色复检"),
                2,
            ),
            UsageStep::new(
                UsageRequest::new(STORE_HK_01, MODEL_GOLD_TESTER, DEMO_MONTH, "进店检测"),
                1,
            ),
            UsageStep::new(
                UsageRequest::new(STORE_CN_01, MODEL_GEM_MICROSCOPE, DEMO_MONTH, "钻石分级"),
                2,
            ),
            UsageStep::new(
                UsageRequest::new(STORE_CN_01, MODEL_BALANCE, DEMO_MONTH, "月末盘点"),
                1,
            ),
            UsageStep::new(
                UsageRequest::new(STORE_HK_01, MODEL_GEM_MICROSCOPE, DEMO_MONTH, "翡翠观察"),
                0,
            ),
            UsageStep::new(
                UsageRequest::new(STORE_CN_01, MODEL_GOLD_TESTER, DEMO_MONTH, "客诉复测"),
                0,
            ),
            UsageStep::new(
                UsageRequest::new(STORE_HK_01, MODEL_BALANCE, DEMO_MONTH, "客诉复测"),
                1,
            ),
            UsageStep::new(
                UsageRequest::new(STORE_CN_01, MODEL_BALANCE, DEMO_MONTH, "月末复盘"),
                0,
            ),
        ],
    )
}

// =============================================================================
// ★ 工程外扩展区:新增型号 / 新增门店 / 新增申请序列(**不改任何既有分层文件**)
// =============================================================================

/// 【扩展】激光打标机:一个**新型号**。
///
/// ⚠️ 它定义在 `main.rs` 而不是 `domain`:
/// 型号是开放型(`const fn new`),所以新增型号不需要回领域层改 `enum`。
/// 这就是「可扩展性必须被实证」的落点------
/// 若型号是 `enum`,这一行就得写进领域层文件。
const MODEL_LASER_MARKER: InstrumentModel = InstrumentModel::new(
    "LASER_MARKER",
    "激光打标机",
    "在饰品内壁刻印品牌与成色",
    Money::new(2_180_000, CNY),
    12,
);

/// 【扩展】澳门氹仔店:一家**新门店**(与新型号同理,定义在装配点)。
const STORE_MO_01: StoreCode = StoreCode::new("STORE-MO-01", "澳门氹仔店");

/// 【扩展】扩展场景:内置 10 步 + 新增门店 / 新型号的一批申请。
///
/// ★ 追加的这 8 笔天平申请是**密集串行**的(`hold_steps = 0`),
/// 目的只有一个:让某台天平真的被借够 [`RETIREMENT_USAGE_LIMIT`] 次而退役。
/// 退役路径若从不发生,「退役台数 == 退役记录数」就是 `0 == 0`------
/// 一个恒真的废检查。
fn extended_scenario() -> UsageScenario {
    let mut steps: Vec<UsageStep> = builtin_scenario().steps().to_vec();
    steps.push(UsageStep::new(
        UsageRequest::new(STORE_MO_01, MODEL_LASER_MARKER, DEMO_MONTH, "新店刻印备货"),
        1,
    ));
    steps.push(UsageStep::new(
        UsageRequest::new(STORE_CN_01, MODEL_LASER_MARKER, DEMO_MONTH, "定制件刻印"),
        0,
    ));
    steps.push(UsageStep::new(
        UsageRequest::new(STORE_MO_01, MODEL_BALANCE, DEMO_MONTH, "新店开张备货"),
        0,
    ));
    steps.push(UsageStep::new(
        UsageRequest::new(STORE_CN_01, MODEL_BALANCE, DEMO_MONTH, "展销会称重"),
        0,
    ));
    steps.push(UsageStep::new(
        UsageRequest::new(STORE_HK_01, MODEL_BALANCE, DEMO_MONTH, "展销会称重"),
        0,
    ));
    steps.push(UsageStep::new(
        UsageRequest::new(STORE_MO_01, MODEL_BALANCE, DEMO_MONTH, "展销会称重"),
        0,
    ));
    steps.push(UsageStep::new(
        UsageRequest::new(STORE_CN_01, MODEL_BALANCE, DEMO_MONTH, "展销会收尾"),
        0,
    ));
    steps.push(UsageStep::new(
        UsageRequest::new(STORE_HK_01, MODEL_BALANCE, DEMO_MONTH, "展销会收尾"),
        0,
    ));
    UsageScenario::new(
        "扩展场景(内置 + 澳门新店 + 激光打标机)",
        DEMO_MONTH,
        MANUFACTURE_MONTH,
        steps,
    )
}

/// 打印一组行。
fn print_lines(lines: &[String]) {
    for line in lines.iter() {
        println!("{}", line);
    }
}

/// ★ 对外只读接口清点:**每个接口都真的调用一次并印出结果**。
///
/// ## 为什么这一函数是告警治理的落点
///
/// `unused` 警告的正确修法不是删接口、也不是加 `#[allow]`,
/// 而是**把它用起来**:一个接口若在这里被调用,
/// 它就不可能是「写了没人用」------也就不可能是「写错了没人发现」。
///
/// ## ⚠️ 清点用的对象必须是**新造的**
///
/// 既不借也不还、不碰主流程的那些池。
/// 既有一个工程在这里出过事:为了清点 `session.read(...)` 又读了一次主会话,
/// 结果「缓存命中 == Σ(读取−1)」差了 1,而那个 1 没有任何一行输出指出它的来源。
fn interface_items(
    strict_sessions: &[PoolSession],
    grow_sessions: &[PoolSession],
    value_report: &PoolValueReport,
    snapshot_view: &LedgerSnapshotView,
    probes: &[PolicyProbe],
    all_views: &[InstrumentUnitView],
    all_service_records: &[ServiceRecord],
    inventory_rows: &[InventoryRowView],
) -> Vec<(String, String)> {
    let mut items: Vec<(String, String)> = Vec::new();
    let first_station: &InstrumentStation = strict_sessions[0].station();
    let first_pool = first_station.pool();
    let sample_unit: InstrumentUnit =
        InstrumentUnit::new("DEMO-0001", MODEL_BALANCE, MonthKey::new(2026, 7));
    let sample_money: Money = Money::new(123_456, CNY);
    let sample_date: CalendarDate = CalendarDate::new(2026, 10, 8);

    // ---- support ----
    items.push((
        "support::stable_hash(&[\"a\",\"b\"])".to_string(),
        stable_hash(&["a", "b"]).to_string(),
    ));
    items.push((
        "support::deterministic_code(&[\"x\"], 6)".to_string(),
        deterministic_code(&["x"], 6),
    ));
    items.push((
        "support::prefixed_code(\"P\", &[\"x\"], 6)".to_string(),
        prefixed_code("P", &["x"], 6),
    ));
    items.push((
        "support::display_width(\"天平A\")".to_string(),
        display_width("天平A").to_string(),
    ));
    items.push((
        "support::is_wide_character('─')  // 框线字符不占 2 列".to_string(),
        bool_text(crate::support::is_wide_character('─')).to_string(),
    ));
    items.push((
        "support::pad_right(\"ab\", 6)".to_string(),
        format!("[{}]", crate::support::pad_right("ab", 6)),
    ));
    items.push((
        "support::pad_left(\"ab\", 6)".to_string(),
        format!("[{}]", crate::support::pad_left("ab", 6)),
    ));
    items.push((
        "support::pad_center(\"ab\", 8)".to_string(),
        format!("[{}]", crate::support::pad_center("ab", 8)),
    ));
    items.push((
        "support::truncate_to_width(\"abcdef\", 3)".to_string(),
        crate::support::truncate_to_width("abcdef", 3),
    ));
    items.push((
        "support::elide_text(\"abcdefghij\", 4)".to_string(),
        elide_text("abcdefghij", 4),
    ));
    items.push((
        "support::horizontal_rule(6)".to_string(),
        crate::support::horizontal_rule(6),
    ));
    items.push((
        "support::self_check_width_model()  // 项数 / 未通过数".to_string(),
        format!(
            "{} 项,未通过 {} 项",
            self_check_width_model().len(),
            self_check_width_model().iter().filter(|(_, ok, _)| !ok).count()
        ),
    ));
    items.push((
        "support::CalendarDate::year() / month() / day()".to_string(),
        format!(
            "{}/{}/{}",
            sample_date.year(),
            sample_date.month(),
            sample_date.day()
        ),
    ));
    items.push((
        "support::CalendarDate::is_valid()".to_string(),
        bool_text(sample_date.is_valid()).to_string(),
    ));
    items.push((
        "support::CalendarDate::add_days(30)".to_string(),
        report_cell_text(sample_date.add_days(30)),
    ));
    items.push((
        "support::days_between(2026-10-01, 2026-10-08)".to_string(),
        days_between(CalendarDate::new(2026, 10, 1), sample_date).to_string(),
    ));
    items.push((
        "support::days_in_month(2026, 10)".to_string(),
        days_in_month(2026, 10).to_string(),
    ));
    items.push((
        "support::is_leap_year(2024)".to_string(),
        bool_text(is_leap_year(2024)).to_string(),
    ));

    // ---- domain ----
    items.push((
        "domain::MonthKey::year() / month()".to_string(),
        format!("{}/{}", DEMO_MONTH.year(), DEMO_MONTH.month()),
    ));
    items.push((
        "domain::MonthKey::is_valid()".to_string(),
        bool_text(DEMO_MONTH.is_valid()).to_string(),
    ));
    items.push((
        "domain::MonthKey::is_same()".to_string(),
        bool_text(DEMO_MONTH.is_same(&MonthKey::new(2026, 10))).to_string(),
    ));
    items.push((
        "domain::MonthKey::month_index()".to_string(),
        DEMO_MONTH.month_index().to_string(),
    ));
    items.push((
        "domain::MonthKey::previous() / next()".to_string(),
        format!(
            "{} / {}",
            DEMO_MONTH.previous().chinese_text(),
            DEMO_MONTH.next().chinese_text()
        ),
    ));
    items.push((
        "domain::MonthKey::same_month_last_year()".to_string(),
        DEMO_MONTH.same_month_last_year().chinese_text(),
    ));
    items.push((
        "domain::MonthKey::months_since(2026-01)".to_string(),
        DEMO_MONTH.months_since(&MANUFACTURE_MONTH).to_string(),
    ));
    items.push((
        "domain::MonthKey::is_before()".to_string(),
        bool_text(MANUFACTURE_MONTH.is_before(&DEMO_MONTH)).to_string(),
    ));
    items.push((
        "domain::MonthKey::machine_text()".to_string(),
        DEMO_MONTH.machine_text(),
    ));
    items.push((
        "domain::MonthKey::days()".to_string(),
        DEMO_MONTH.days().to_string(),
    ));
    items.push((
        "★ domain::MonthKey::plus_months(6)  // 校准到期月顺延".to_string(),
        MANUFACTURE_MONTH.plus_months(6).chinese_text(),
    ));
    items.push((
        "domain::divide_rounded(7, 2)  // 唯一舍入口径".to_string(),
        divide_rounded(7, 2).to_string(),
    ));
    items.push((
        "domain::ratio_basis_points(1, 3)".to_string(),
        format!("{} 万分点", ratio_basis_points(1, 3)),
    ));
    items.push((
        "domain::scale_rounded(1000, 1, 4)".to_string(),
        scale_rounded(1_000, 1, 4).to_string(),
    ));
    items.push((
        "domain::CurrencyCode::code() / chinese_name()".to_string(),
        format!("{} / {}", CNY.code(), CNY.chinese_name()),
    ));
    items.push((
        "domain::BUILTIN_CURRENCY_CODES  // 币种数".to_string(),
        format!("{} 种(含 {} / {})", BUILTIN_CURRENCY_CODES.len(), HKD.code(), USD.code()),
    ));
    items.push((
        "domain::Money::zero(CNY).is_zero()".to_string(),
        bool_text(Money::zero(CNY).is_zero()).to_string(),
    ));
    items.push((
        "domain::Money::minor_units() / currency()".to_string(),
        format!("{} / {}", sample_money.minor_units(), sample_money.currency().code()),
    ));
    items.push((
        "domain::Money::plus() / minus()".to_string(),
        format!(
            "{} / {}",
            sample_money.plus(Money::new(100, CNY)).chinese_text(),
            sample_money.minus(Money::new(100, CNY)).chinese_text()
        ),
    ));
    items.push((
        "domain::Money::negate()".to_string(),
        sample_money.negate().chinese_text(),
    ));
    items.push((
        "domain::Money::scale_basis_points(5000)  // 五折".to_string(),
        sample_money.scale_basis_points(5_000).chinese_text(),
    ));
    items.push((
        "domain::Money::scale_ratio(1, 4)".to_string(),
        sample_money.scale_ratio(1, 4).chinese_text(),
    ));
    items.push((
        "domain::Money::amount_text()".to_string(),
        sample_money.amount_text(),
    ));
    items.push((
        "domain::Money::chinese_text()".to_string(),
        sample_money.chinese_text(),
    ));
    items.push((
        "domain::Money::is_same_as()".to_string(),
        bool_text(sample_money.is_same_as(&Money::new(123_456, CNY))).to_string(),
    ));
    items.push((
        "domain::StoreCode::code() / chinese_name()".to_string(),
        format!("{} / {}", STORE_CN_01.code(), STORE_CN_01.chinese_name()),
    ));
    items.push((
        "domain::BUILTIN_STORE_CODES  // 内置门店数".to_string(),
        format!("{} 家", BUILTIN_STORE_CODES.len()),
    ));
    items.push((
        "domain::InstrumentModel::code() / chinese_name()".to_string(),
        format!("{} / {}", MODEL_BALANCE.code(), MODEL_BALANCE.chinese_name()),
    ));
    items.push((
        "domain::InstrumentModel::purpose_text()".to_string(),
        MODEL_GOLD_TESTER.purpose_text().to_string(),
    ));
    items.push((
        "domain::InstrumentModel::acquisition_cost()".to_string(),
        MODEL_GOLD_TESTER.acquisition_cost().chinese_text(),
    ));
    items.push((
        "domain::InstrumentModel::calibration_interval_months()".to_string(),
        format!("{} 个月", MODEL_GOLD_TESTER.calibration_interval_months()),
    ));
    items.push((
        "domain::InstrumentModel::currency()".to_string(),
        MODEL_GOLD_TESTER.currency().code().to_string(),
    ));
    items.push((
        "domain::BUILTIN_INSTRUMENT_MODELS  // 内置型号数".to_string(),
        format!("{} 种", BUILTIN_INSTRUMENT_MODELS.len()),
    ));
    items.push((
        "domain::InstrumentState::chinese_text()  // 四档".to_string(),
        BUILTIN_INSTRUMENT_STATES
            .iter()
            .map(|state| state.chinese_text())
            .collect::<Vec<&str>>()
            .join(" / "),
    ));
    items.push((
        "domain::InstrumentState::machine_text()".to_string(),
        InstrumentState::InCalibration.machine_text().to_string(),
    ));
    items.push((
        "★ domain::InstrumentState::is_in_pool()  // 只有空闲算在池里".to_string(),
        format!(
            "空闲={} 在借={} 校准={} 退役={}",
            bool_text(InstrumentState::Idle.is_in_pool()),
            bool_text(InstrumentState::InUse.is_in_pool()),
            bool_text(InstrumentState::InCalibration.is_in_pool()),
            bool_text(InstrumentState::Retired.is_in_pool())
        ),
    ));
    items.push((
        "domain::InstrumentState::can_be_borrowed()".to_string(),
        bool_text(InstrumentState::Idle.can_be_borrowed()).to_string(),
    ));
    items.push((
        "domain::InstrumentState::is_out_of_pool_permanently()".to_string(),
        bool_text(InstrumentState::Retired.is_out_of_pool_permanently()).to_string(),
    ));
    items.push((
        "★ domain::RETIREMENT_USAGE_LIMIT".to_string(),
        format!("{} 次", RETIREMENT_USAGE_LIMIT),
    ));
    items.push((
        "domain::InstrumentUnit::serial_number()".to_string(),
        sample_unit.serial_number().to_string(),
    ));
    items.push((
        "domain::InstrumentUnit::model().code()".to_string(),
        sample_unit.model().code().to_string(),
    ));
    items.push((
        "domain::InstrumentUnit::state().chinese_text()".to_string(),
        sample_unit.state().chinese_text().to_string(),
    ));
    items.push((
        "domain::InstrumentUnit::usage_count()".to_string(),
        sample_unit.usage_count().to_string(),
    ));
    items.push((
        "domain::InstrumentUnit::calibration_due_month()".to_string(),
        sample_unit.calibration_due_month().chinese_text(),
    ));
    items.push((
        "domain::InstrumentUnit::holder()  // 无人持有".to_string(),
        holder_display_text(sample_unit.holder()),
    ));
    items.push((
        "domain::InstrumentUnit::acquisition_cost()".to_string(),
        sample_unit.acquisition_cost().chinese_text(),
    ));
    items.push((
        "★ domain::InstrumentUnit::is_reusable_on_return(当月)".to_string(),
        bool_text(sample_unit.is_reusable_on_return(DEMO_MONTH)).to_string(),
    ));
    items.push((
        "★ domain::InstrumentUnit::rejection_reason_text(当月)".to_string(),
        match sample_unit.rejection_reason_text(DEMO_MONTH) {
            Some(reason) => reason.to_string(),
            None => "可以回池".to_string(),
        },
    ));
    items.push((
        "domain::InstrumentUnit::identity_text()".to_string(),
        sample_unit.identity_text(),
    ));
    items.push((
        "★ domain::InstrumentUnit::fingerprint_text()".to_string(),
        sample_unit.fingerprint_text(),
    ));
    items.push((
        "domain::InstrumentUnit::summary_text()".to_string(),
        sample_unit.summary_text(),
    ));
    items.push((
        "domain::ServiceKind::chinese_text()  // 三类".to_string(),
        BUILTIN_SERVICE_KINDS
            .iter()
            .map(|kind| kind.chinese_text())
            .collect::<Vec<&str>>()
            .join(" / "),
    ));
    items.push((
        "domain::ServiceKind::machine_text()".to_string(),
        ServiceKind::Calibration.machine_text().to_string(),
    ));
    items.push((
        "★ domain::ServiceKind::returns_to_pool()  // 退役不回池".to_string(),
        format!(
            "校准={} 维修={} 退役={}",
            bool_text(ServiceKind::Calibration.returns_to_pool()),
            bool_text(ServiceKind::Repair.returns_to_pool()),
            bool_text(ServiceKind::Retirement.returns_to_pool())
        ),
    ));
    let sample_record: ServiceRecord = ServiceRecord::new(
        "DEMO-0001",
        DEMO_MONTH,
        ServiceKind::Calibration,
        "校准已过期",
        SERVICE_COST_CALIBRATION_MINOR_UNITS,
    );
    items.push((
        "domain::ServiceRecord::serial_number() / month()".to_string(),
        format!("{} / {}", sample_record.serial_number(), sample_record.month().chinese_text()),
    ));
    items.push((
        "domain::ServiceRecord::kind().chinese_text()".to_string(),
        sample_record.kind().chinese_text().to_string(),
    ));
    items.push((
        "domain::ServiceRecord::reason_text()".to_string(),
        sample_record.reason_text().to_string(),
    ));
    items.push((
        "domain::ServiceRecord::service_cost_minor_units()".to_string(),
        sample_record.service_cost_minor_units().to_string(),
    ));
    items.push((
        "domain::ServiceRecord::fingerprint_text()".to_string(),
        sample_record.fingerprint_text(),
    ));
    let sample_request: UsageRequest =
        UsageRequest::new(STORE_CN_01, MODEL_BALANCE, DEMO_MONTH, "演示申请");
    items.push((
        "domain::UsageRequest::store().chinese_name()".to_string(),
        sample_request.store().chinese_name().to_string(),
    ));
    items.push((
        "domain::UsageRequest::model().code()".to_string(),
        sample_request.model().code().to_string(),
    ));
    items.push((
        "domain::UsageRequest::month().chinese_text()".to_string(),
        sample_request.month().chinese_text(),
    ));
    items.push((
        "domain::UsageRequest::purpose_text()".to_string(),
        sample_request.purpose_text().to_string(),
    ));
    items.push((
        "domain::UsageRequest::machine_text()".to_string(),
        sample_request.machine_text(),
    ));

    // ---- pool(★ 机制层) ----
    items.push((
        "pool::BUILTIN_REUSE_POLICIES  // 三档".to_string(),
        BUILTIN_REUSE_POLICIES
            .iter()
            .map(|policy| policy.chinese_text())
            .collect::<Vec<&str>>()
            .join(" / "),
    ));
    items.push((
        "pool::ReusePolicy::machine_text()".to_string(),
        ReusePolicy::StrictPool.machine_text().to_string(),
    ));
    items.push((
        "★ pool::ReusePolicy::max_total()  // 只有限量档有上限".to_string(),
        format!(
            "严格池={:?} 按需={:?} 限量={:?}",
            ReusePolicy::StrictPool.max_total(),
            ReusePolicy::GrowOnDemand.max_total(),
            ReusePolicy::GrowWithLimit { max_total: 3 }.max_total()
        ),
    ));
    items.push((
        "★ pool::ReusePolicy::allows_creation_when_empty(已有 2 台)".to_string(),
        format!(
            "严格池={} 按需={} 限量(上限3)={}",
            bool_text(ReusePolicy::StrictPool.allows_creation_when_empty(2)),
            bool_text(ReusePolicy::GrowOnDemand.allows_creation_when_empty(2)),
            bool_text(ReusePolicy::GrowWithLimit { max_total: 3 }.allows_creation_when_empty(2))
        ),
    ));
    items.push((
        "★ pool::ReusePolicy::can_reject()  // 按需档永不拒绝".to_string(),
        format!(
            "严格池={} 按需={} 限量={}",
            bool_text(ReusePolicy::StrictPool.can_reject()),
            bool_text(ReusePolicy::GrowOnDemand.can_reject()),
            bool_text(ReusePolicy::GrowWithLimit { max_total: 3 }.can_reject())
        ),
    ));
    items.push((
        "★ pool::PooledObject::identity_text()  // 经 trait 调用,不是固有方法".to_string(),
        format!(
            "{} / {}",
            PooledObject::identity_text(&sample_unit),
            PooledObject::fingerprint_text(&sample_unit)
        ),
    ));
    items.push((
        "pool::ObjectPool::name() / policy()".to_string(),
        format!("{} / {}", first_pool.name(), first_pool.policy().chinese_text()),
    ));
    items.push((
        "pool::ObjectPool::idle_total() / borrowed_total()".to_string(),
        format!("{} / {}", first_pool.idle_total(), first_pool.borrowed_total()),
    ));
    items.push((
        "pool::ObjectPool::quarantined_total() / retired_total()".to_string(),
        format!("{} / {}", first_pool.quarantined_total(), first_pool.retired_total()),
    ));
    items.push((
        "★ pool::ObjectPool::capacity_total()  // 不含退役".to_string(),
        first_pool.capacity_total().to_string(),
    ));
    items.push((
        "pool::ObjectPool::created_total()".to_string(),
        first_pool.created_total().to_string(),
    ));
    items.push((
        "pool::ObjectPool::identity_list_sorted()  // 台数".to_string(),
        format!("{} 台", first_pool.identity_list_sorted().len()),
    ));
    items.push((
        "pool::ObjectPool::fingerprint_list_sorted()  // 条数".to_string(),
        format!("{} 条", first_pool.fingerprint_list_sorted().len()),
    ));
    items.push((
        "pool::ObjectPool::objects_sorted()  // 条数".to_string(),
        format!("{} 台", first_pool.objects_sorted().len()),
    ));
    items.push((
        "pool::ObjectPool::ledger().action_total()".to_string(),
        first_pool.ledger().action_total().to_string(),
    ));
    items.push((
        "pool::PoolLedger::hit_total() / creation_total()".to_string(),
        format!(
            "{} / {}",
            first_pool.ledger().hit_total(),
            first_pool.ledger().creation_total()
        ),
    ));
    items.push((
        "pool::PoolLedger::acquisition_total() / rejection_total()".to_string(),
        format!(
            "{} / {}",
            first_pool.ledger().acquisition_total(),
            first_pool.ledger().rejection_total()
        ),
    ));
    items.push((
        "pool::PoolLedger::release_total() / reuse_total()".to_string(),
        format!(
            "{} / {}",
            first_pool.ledger().release_total(),
            first_pool.ledger().reuse_total()
        ),
    ));
    items.push((
        "pool::PoolLedger::quarantine_total() / foreign_return_total()".to_string(),
        format!(
            "{} / {}",
            first_pool.ledger().quarantine_total(),
            first_pool.ledger().foreign_return_total()
        ),
    ));
    items.push((
        "pool::PoolLedger::prewarm_total()".to_string(),
        first_pool.ledger().prewarm_total().to_string(),
    ));
    items.push((
        "★ pool::PoolSnapshot::acquisition_identity_gap()  // 恒等式一".to_string(),
        first_pool.snapshot().acquisition_identity_gap().to_string(),
    ));
    items.push((
        "★ pool::PoolSnapshot::release_identity_gap()  // 恒等式二".to_string(),
        first_pool.snapshot().release_identity_gap().to_string(),
    ));
    items.push((
        "pool::PoolSnapshot::reuse_ratio_basis_points()".to_string(),
        format!(
            "{} 万分点",
            first_pool.snapshot().reuse_ratio_basis_points()
        ),
    ));
    items.push((
        "pool::ReleaseOutcome::machine_text()  // 三种结局".to_string(),
        [
            ReleaseOutcome::ReturnedToPool { identity: "X".to_string() }.machine_text(),
            ReleaseOutcome::Quarantined { identity: "X".to_string(), reason: "r".to_string() }
                .machine_text(),
            ReleaseOutcome::Rejected { identity: "X".to_string() }.machine_text(),
        ]
        .join(" / "),
    ));
    items.push((
        "★ pool::ReleaseOutcome::counts_as_reuse()  // 只有回池算数".to_string(),
        format!(
            "回池={} 隔离={} 异物={}",
            bool_text(ReleaseOutcome::ReturnedToPool { identity: "X".to_string() }.counts_as_reuse()),
            bool_text(
                ReleaseOutcome::Quarantined { identity: "X".to_string(), reason: "r".to_string() }
                    .counts_as_reuse()
            ),
            bool_text(ReleaseOutcome::Rejected { identity: "X".to_string() }.counts_as_reuse())
        ),
    ));
    let sample_release: ReleaseOutcome =
        ReleaseOutcome::Quarantined { identity: "X-0001".to_string(), reason: "校准已过期".to_string() };
    items.push((
        "pool::ReleaseOutcome::chinese_text() / identity()".to_string(),
        format!("{} / {}", sample_release.chinese_text(), sample_release.identity()),
    ));
    items.push((
        "pool::PoolAuditRule::BUILTIN  // 六条规则编码".to_string(),
        crate::pool::PoolAuditRule::BUILTIN
            .iter()
            .map(|rule| rule.code())
            .collect::<Vec<&str>>()
            .join(" / "),
    ));
    items.push((
        "★ pool::PoolAuditRule::known_failing_input_text()  // EFFECTIVE".to_string(),
        crate::pool::PoolAuditRule::PoolIsEffective
            .known_failing_input_text()
            .to_string(),
    ));
    let sample_check: Vec<crate::pool::PoolAuditCheck> =
        audit_pool(&observe_pool(first_pool));
    items.push((
        "pool::audit_pool()  // 首条:编码 / 符合 / 适用".to_string(),
        match sample_check.first() {
            Some(check) => format!(
                "{} {}/{} 通过={}",
                check.rule().code(),
                check.conforming_total(),
                check.applicable_total(),
                bool_text(check.passed())
            ),
            None => "无".to_string(),
        },
    ));
    items.push((
        "pool::PoolObservation::capacity_gap()  // 天平站".to_string(),
        observe_pool(first_pool).capacity_gap().to_string(),
    ));
    items.push((
        "pool::PoolObservation::balance_gap()  // 无泄漏判据".to_string(),
        observe_pool(first_pool).balance_gap().to_string(),
    ));
    items.push((
        "pool::PoolObservation::reuse_ratio_basis_points()".to_string(),
        format!("{} 万分点", observe_pool(first_pool).reuse_ratio_basis_points()),
    ));

    // ---- client ----
    items.push((
        "client::instrument_factory(MODEL_BALANCE, 出厂月)(0)".to_string(),
        instrument_factory(MODEL_BALANCE, MANUFACTURE_MONTH)(0).fingerprint_text(),
    ));
    items.push((
        "★ client::return_verifier(当月)(过期仪器)".to_string(),
        match return_verifier(DEMO_MONTH)(&InstrumentUnit::new(
            "OLD-0001",
            MODEL_GOLD_TESTER,
            MonthKey::new(2026, 7),
        )) {
            Ok(()) => "可以回池".to_string(),
            Err(reason) => format!("不能回池:{}", reason),
        },
    ));
    items.push((
        "★ client::should_retire(借满上限的仪器)".to_string(),
        {
            let mut tired: InstrumentUnit =
                InstrumentUnit::new("TIRED-0001", MODEL_BALANCE, MonthKey::new(2027, 1));
            for _ in 0..RETIREMENT_USAGE_LIMIT {
                tired.note_borrowed();
                tired.clear_for_reuse();
            }
            bool_text(should_retire(&tired)).to_string()
        },
    ));
    items.push((
        "client::InstrumentStation::model().code()".to_string(),
        first_station.model().code().to_string(),
    ));
    items.push((
        "client::InstrumentStation::current_month()".to_string(),
        first_station.current_month().chinese_text(),
    ));
    items.push((
        "client::InstrumentStation::name()".to_string(),
        first_station.name(),
    ));
    items.push((
        "client::InstrumentStation::peak_borrowed_total()".to_string(),
        first_station.peak_borrowed_total().to_string(),
    ));
    items.push((
        "client::InstrumentStation::acquisition_cost_spent()".to_string(),
        first_station.acquisition_cost_spent().chinese_text(),
    ));
    items.push((
        "client::InstrumentStation::service_cost_spent()".to_string(),
        first_station.service_cost_spent().chinese_text(),
    ));
    items.push((
        "client::InstrumentStation::service_total()".to_string(),
        first_station.service_total().to_string(),
    ));
    items.push((
        "client::InstrumentStation::service_records()  // 条数".to_string(),
        format!("{} 条", first_station.service_records().len()),
    ));
    items.push((
        "client::InstrumentStation::observation().pool_name".to_string(),
        first_station.observation().pool_name,
    ));
    items.push((
        "client::InstrumentStation::holder_list_sorted()  // 条数".to_string(),
        format!("{} 条", first_station.holder_list_sorted().len()),
    ));
    items.push((
        "client::UsageScenario::name()".to_string(),
        builtin_scenario().name().to_string(),
    ));
    items.push((
        "client::UsageScenario::request_total()".to_string(),
        builtin_scenario().request_total().to_string(),
    ));
    items.push((
        "client::UsageScenario::models()  // 型号数".to_string(),
        format!("{} 种", builtin_scenario().models().len()),
    ));
    items.push((
        "client::UsageScenario::request_total_for(天平)".to_string(),
        builtin_scenario().request_total_for(&MODEL_BALANCE).to_string(),
    ));
    items.push((
        "client::UsageStep::request().purpose_text() / hold_steps()".to_string(),
        match builtin_scenario().steps().first() {
            Some(step) => format!("{} / {}", step.request().purpose_text(), step.hold_steps()),
            None => "无".to_string(),
        },
    ));
    items.push((
        "client::PoolSession::borrow_records()  // 天平站笔数".to_string(),
        format!("{} 笔", strict_sessions[0].borrow_records().len()),
    ));
    items.push((
        "client::PoolSession::granted_total() / rejected_total()".to_string(),
        format!(
            "{} / {}",
            strict_sessions[0].granted_total(),
            strict_sessions[0].rejected_total()
        ),
    ));
    items.push((
        "client::PoolSession::returned_to_pool_total() / quarantined_total()".to_string(),
        format!(
            "{} / {}",
            strict_sessions[0].returned_to_pool_total(),
            strict_sessions[0].quarantined_total()
        ),
    ));
    items.push((
        "client::BorrowRecord::identity() / outcome_text()  // 首笔".to_string(),
        match strict_sessions[0].borrow_records().first() {
            Some(record) => format!("{} / {}", record.identity(), record.outcome_text()),
            None => "无".to_string(),
        },
    ));
    items.push((
        "★ client::BorrowRecord::saved_one_purchase()  // 首笔是否省下一次采购".to_string(),
        match strict_sessions[0].borrow_records().first() {
            Some(record) => bool_text(record.saved_one_purchase()).to_string(),
            None => "无".to_string(),
        },
    ));
    items.push((
        "client::BorrowRecord::usage_count_at_acquisition()  // 首笔".to_string(),
        match strict_sessions[0].borrow_records().first() {
            Some(record) => record.usage_count_at_acquisition().to_string(),
            None => "无".to_string(),
        },
    ));
    let evidence: crate::client::IdentityEvidence = strict_sessions[0].identity_evidence();
    items.push((
        "★ client::IdentityEvidence::reuse_happened()".to_string(),
        bool_text(evidence.reuse_happened()).to_string(),
    ));
    items.push((
        "client::IdentityEvidence::usage_per_object_basis_points()".to_string(),
        format!("{} 万分点", evidence.usage_per_object_basis_points()),
    ));

    // ---- analysis ----
    items.push((
        "analysis::InstrumentUnitView::serial_number()  // 首台".to_string(),
        match all_views.first() {
            Some(view) => view.serial_number().to_string(),
            None => "无".to_string(),
        },
    ));
    items.push((
        "analysis::InstrumentUnitView::state_text() / usage_count()".to_string(),
        match all_views.first() {
            Some(view) => format!("{} / {}", view.state_text(), view.usage_count()),
            None => "无".to_string(),
        },
    ));
    items.push((
        "analysis::InstrumentUnitView::holder_text() / calibration_due_text()".to_string(),
        match all_views.first() {
            Some(view) => format!("{} / {}", view.holder_text(), view.calibration_due_text()),
            None => "无".to_string(),
        },
    ));
    items.push((
        "analysis::InstrumentUnitView::acquisition_cost_text() / fingerprint()".to_string(),
        match all_views.first() {
            Some(view) => format!("{} / {}", view.acquisition_cost_text(), view.fingerprint()),
            None => "无".to_string(),
        },
    ));
    items.push((
        "analysis::holder_display_text(Some(门店))".to_string(),
        holder_display_text(Some(STORE_HK_01)),
    ));
    items.push((
        "analysis::PolicyProbe::policy_name()  // 首档".to_string(),
        match probes.first() {
            Some(probe) => probe.policy_name().to_string(),
            None => "无".to_string(),
        },
    ));
    items.push((
        "★ analysis::PolicyProbe::risk_text()  // 首档".to_string(),
        match probes.first() {
            Some(probe) => probe.risk_text(),
            None => "无".to_string(),
        },
    ));
    items.push((
        "analysis::PolicyProbe::reuse_ratio_basis_points()  // 首档".to_string(),
        match probes.first() {
            Some(probe) => format!("{} 万分点", probe.reuse_ratio_basis_points()),
            None => "无".to_string(),
        },
    ));
    items.push((
        "analysis::PolicyProbe::can_reject()  // 首档".to_string(),
        match probes.first() {
            Some(probe) => bool_text(probe.can_reject()).to_string(),
            None => "无".to_string(),
        },
    ));
    items.push((
        "analysis::CapacityProbe::capacity_total() / capacity_gap()".to_string(),
        format!(
            "{} / {}",
            CapacityProbe::from_session(&strict_sessions[0], 4).capacity_total(),
            CapacityProbe::from_session(&strict_sessions[0], 4).capacity_gap()
        ),
    ));
    items.push((
        "analysis::lifecycle_statistics()  // 档数".to_string(),
        format!("{} 档", lifecycle_statistics(all_views).len()),
    ));
    items.push((
        "analysis::LifecycleProbe::in_pool()  // 空闲档".to_string(),
        match lifecycle_statistics(all_views).first() {
            Some(probe) => format!("{} 在池={}", probe.state_name(), bool_text(probe.in_pool())),
            None => "无".to_string(),
        },
    ));
    items.push((
        "analysis::service_statistics()  // 各类条数".to_string(),
        service_statistics(all_service_records)
            .iter()
            .map(|(kind, total)| format!("{} {}", kind.chinese_text(), total))
            .collect::<Vec<String>>()
            .join("、"),
    ));
    items.push((
        "analysis::AuditCheckView::rule_code()  // 首条".to_string(),
        match audit_check_views(&audit_pool(&observe_pool(first_pool))).first() {
            Some(view) => format!("{} 通过={}", view.rule_code(), bool_text(view.passed())),
            None => "无".to_string(),
        },
    ));
    items.push((
        "analysis::AuditCheckView::known_failing_input_text()  // 首条".to_string(),
        match audit_check_views(&audit_pool(&observe_pool(first_pool))).first() {
            Some(view) => view.known_failing_input_text().to_string(),
            None => "无".to_string(),
        },
    ));
    items.push((
        "analysis::LedgerSnapshotView::row_total()".to_string(),
        snapshot_view.row_total().to_string(),
    ));
    items.push((
        "analysis::PoolValueReport::saved_purchase_total()".to_string(),
        format!("{} 台", value_report.saved_purchase_total()),
    ));
    items.push((
        "analysis::PoolValueReport::saved_money()".to_string(),
        value_report.saved_money().chinese_text(),
    ));
    items.push((
        "analysis::PoolValueReport::saved_ratio_basis_points()".to_string(),
        format!("{} 万分点", value_report.saved_ratio_basis_points()),
    ));
    items.push((
        "analysis::PoolValueReport::summary_text()".to_string(),
        value_report.summary_text(),
    ));
    items.push((
        "analysis::CheckLine::verdict_text(false)".to_string(),
        CheckLine::new("演示", "期望", "实测", false).verdict_text().to_string(),
    ));
    items.push((
        "analysis::CheckReport::line_total()  // 空报告".to_string(),
        CheckReport::new().line_total().to_string(),
    ));

    // ---- app ----
    items.push((
        "app::DOCUMENT_WIDTH".to_string(),
        DOCUMENT_WIDTH.to_string(),
    ));
    items.push((
        "app::render_pair_table(...)  // 返回行数".to_string(),
        render_pair_table(
            "接口清点用的两列表",
            &[("演示项".to_string(), "演示值".to_string())],
            &["演示注解"],
        )
        .len()
        .to_string(),
    ));
    items.push((
        "★ 机制层独立可用性:新建一个池并读它的容量".to_string(),
        {
            let mut probe_pool: ObjectPool<InstrumentUnit> = ObjectPool::new(
                "接口清点用的池",
                ReusePolicy::StrictPool,
                instrument_factory(MODEL_GEM_MICROSCOPE, MANUFACTURE_MONTH),
                return_verifier(DEMO_MONTH),
            );
            probe_pool.prewarm(2);
            format!(
                "{} 空闲 {} 台 / 容量 {} 台 / 已造 {} 台",
                probe_pool.name(),
                probe_pool.idle_total(),
                probe_pool.capacity_total(),
                probe_pool.created_total()
            )
        },
    ));
    items.push((
        "★ 身份证据:`Rc::ptr_eq` 比对(只报是否,绝不打印地址)".to_string(),
        {
            let left: Rc<RefCell<InstrumentUnit>> =
                Rc::new(RefCell::new(InstrumentUnit::new("SAME-0001", MODEL_BALANCE, DEMO_MONTH)));
            let cloned: Rc<RefCell<InstrumentUnit>> = Rc::clone(&left);
            let other: Rc<RefCell<InstrumentUnit>> =
                Rc::new(RefCell::new(InstrumentUnit::new("SAME-0001", MODEL_BALANCE, DEMO_MONTH)));
            format!(
                "克隆={} 同号新建={}",
                bool_text(Rc::ptr_eq(&left, &cloned)),
                bool_text(Rc::ptr_eq(&left, &other))
            )
        },
    ));
    items.push((
        "app::section_header(\"演示\", 20)".to_string(),
        section_header("演示", 20),
    ));
    items.push((
        "app::note_lines(...)  // 返回行数".to_string(),
        note_lines("一行注解", DOCUMENT_WIDTH).len().to_string(),
    ));
    items.push((
        "app::table_rule(10)".to_string(),
        table_rule(10),
    ));
    items.push((
        "★ app::render_cell(不允许省略的列, 内容)".to_string(),
        format!(
            "[{}]",
            crate::app::render_cell(
                &crate::app::TableColumn::new("列", 8),
                "内容"
            )
        ),
    ));
    items.push((
        "app::TableColumn::header() / width() / align() / is_elidable()".to_string(),
        {
            let column = crate::app::TableColumn::numeric("数字列", 8);
            format!(
                "{} / {} / 右对齐={} / 可省略={}",
                column.header(),
                column.width(),
                bool_text(column.align() == crate::app::ColumnAlign::Right),
                bool_text(column.is_elidable())
            )
        },
    ));
    items.push((
        "app::Table::new(...).with_row(...).with_note(...)  // 列数/行数/总宽".to_string(),
        {
            let table = crate::app::Table::new(
                "演示",
                vec![
                    crate::app::TableColumn::new("甲", 6),
                    crate::app::TableColumn::new("乙", 6),
                ],
            )
            .with_row(vec!["1".to_string(), "2".to_string()])
            .with_note("注解");
            format!(
                "{} 列 / {} 行 / 总宽 {}",
                table.column_count(),
                table.row_count(),
                table.total_width()
            )
        },
    ));
    items.push((
        "app::bool_text(true) / byte_text(1234567)".to_string(),
        format!("{} / {}", bool_text(true), byte_text(1_234_567)),
    ));
    items.push((
        "app::basis_points_text(6667) / gram_display_text(1500)".to_string(),
        format!("{} / {}", basis_points_text(6_667), gram_display_text(1_500)),
    ));
    items.push((
        "app::InventoryRowView::serial_number()  // 首台".to_string(),
        match inventory_rows.first() {
            Some(row) => row.serial_number().to_string(),
            None => "无".to_string(),
        },
    ));
    items.push((
        "app::ExtensionRowView::item() / evidence()".to_string(),
        {
            let row = ExtensionRowView::new("演示项", "1", "2", "演示证据");
            format!("{} / {} / {}", row.item(), row.extended_value(), row.evidence())
        },
    ));
    items.push((
        "app::IdentityPairView::identical()  // 同指纹".to_string(),
        bool_text(IdentityPairView::new("X", "A|1", "A|1").identical()).to_string(),
    ));
    items.push((
        "app::CapacityRowView::station_name() / capacity_gap()".to_string(),
        {
            let row = CapacityRowView::new(
                "演示站",
                CapacityProbe::from_session(&strict_sessions[0], 6),
            );
            format!("{} / {}", row.station_name(), row.capacity_gap())
        },
    ));
    items.push((
        "★ 三档对照:按需扩容档的新建次数(不池化要买几台)".to_string(),
        grow_sessions
            .iter()
            .map(|session| session.station().pool().created_total().to_string())
            .collect::<Vec<String>>()
            .join(" + "),
    ));
    items.push((
        "★ 三档对照:严格池档的新建次数(池化实际买了几台)".to_string(),
        strict_sessions
            .iter()
            .map(|session| session.station().pool().created_total().to_string())
            .collect::<Vec<String>>()
            .join(" + "),
    ));

    items
}

/// 体检里那些**不属于任何一组既有检查**的条目。
///
/// ★ 含两条「审计也要被审计」的检查:
/// 负向用例必须真的红,否则那两条规则其实只是没被跑过的注释。
fn health_check_lines(
    probes: &[PolicyProbe],
    normal_checks: &[AuditCheckView],
    neglect_checks: &[AuditCheckView],
    foreign_checks: &[AuditCheckView],
    identity_pairs: &[IdentityPairView],
    builtin: &UsageScenario,
    extended: &UsageScenario,
    retired_total: usize,
    extended_retired_total: usize,
    foreign_note: &str,
) -> Vec<CheckLine> {
    let mut lines: Vec<CheckLine> = Vec::new();

    // ① ★ 版面:宽度模型自检必须全过。
    let width_failures: usize = self_check_width_model().iter().filter(|(_, ok, _)| !ok).count();
    lines.push(CheckLine::new(
        "版面:显示宽度模型自检",
        "0 项未通过(已知会失败:把 `─` 当成宽字符)",
        format!("{} 项未通过", width_failures).as_str(),
        width_failures == 0,
    ));

    // ② ★ 审计也要被审计:负向①必须让 `CAPACITY` 红。
    let capacity_failed: bool = neglect_checks
        .iter()
        .any(|check| check.rule_code() == "CAPACITY" && !check.passed());
    lines.push(CheckLine::new(
        "★ 审计也要被审计:负向①必须让 CAPACITY 变红",
        "未通过(已知会失败:把「取走隔离对象」改成立刻退役)",
        if capacity_failed { "未通过(正确)" } else { "通过(说明规则没在跑)" },
        capacity_failed,
    ));

    // ③ ★ 审计也要被审计:负向②必须让 `FOREIGN` 红。
    let foreign_failed: bool = foreign_checks
        .iter()
        .any(|check| check.rule_code() == "FOREIGN" && !check.passed());
    lines.push(CheckLine::new(
        "★ 审计也要被审计:负向②必须让 FOREIGN 变红",
        format!("未通过({};已知会失败:归还时不查指针)", foreign_note).as_str(),
        if foreign_failed { "未通过(正确)" } else { "通过(说明规则没在跑)" },
        foreign_failed,
    ));

    // ④ 正常用例必须全绿。
    let normal_failures: usize = normal_checks.iter().filter(|check| !check.passed()).count();
    lines.push(CheckLine::new(
        "契约审计:正常用例全部通过",
        format!("0 条未通过(共 {} 条;已知会失败:任一规则被改坏)", normal_checks.len()).as_str(),
        format!("{} 条未通过", normal_failures).as_str(),
        normal_failures == 0,
    ));

    // ⑤ 恒等:两条独立路径逐台指纹一致。
    let mismatches: usize = identity_pairs.iter().filter(|pair| !pair.identical()).count();
    lines.push(CheckLine::new(
        "恒等:同一份场景跑两遍,逐台指纹一致",
        format!("0 处不一致(共 {} 台;已知会失败:某处依赖 HashMap 迭代顺序)", identity_pairs.len())
            .as_str(),
        format!("{} 处不一致", mismatches).as_str(),
        mismatches == 0,
    ));

    // ⑥ 扩展:型号数 +1、门店数 +1、申请笔数变多。
    lines.push(CheckLine::new(
        "扩展:型号种数 == 内置 + 1",
        "多 1 种(已知会失败:扩展区忘了把新型号写进场景)",
        format!("{} -> {}", builtin.models().len(), extended.models().len()).as_str(),
        extended.models().len() == builtin.models().len() + 1,
    ));
    lines.push(CheckLine::new(
        "扩展:申请笔数变多",
        "> 内置(已知会失败:扩展区只加了型号没加申请)",
        format!("{} -> {}", builtin.request_total(), extended.request_total()).as_str(),
        extended.request_total() > builtin.request_total(),
    ));
    lines.push(CheckLine::new(
        "★ 扩展:退役路径真的被走到过",
        "扩展后退役台数 > 0(已知会失败:把退役上限调高到场景借不到)",
        format!("内置 {} 台 -> 扩展 {} 台", retired_total, extended_retired_total).as_str(),
        extended_retired_total > 0,
    ));

    // ⑦ 三档对照表非空。
    lines.push(CheckLine::new(
        "三档对照表有数据",
        format!("{} 档(已知会失败:run_all 返回空表)", probes.len()).as_str(),
        format!("{} 档", probes.len()).as_str(),
        !probes.is_empty(),
    ));

    lines
}

fn main() {
    let builtin: UsageScenario = builtin_scenario();
    let extended: UsageScenario = extended_scenario();

    // ---------- 三档策略:同一份场景,跑三次 ----------
    let strict_sessions: Vec<PoolSession> = run_all(&builtin, ReusePolicy::StrictPool);
    let grow_sessions: Vec<PoolSession> = run_all(&builtin, ReusePolicy::GrowOnDemand);
    let limited_sessions: Vec<PoolSession> = run_all(
        &builtin,
        ReusePolicy::GrowWithLimit { max_total: LIMITED_MAX_TOTAL },
    );

    let probes: Vec<PolicyProbe> = vec![
        PolicyProbe::from_sessions("严格池(借空即拒绝)", &strict_sessions),
        PolicyProbe::from_sessions("按需扩容(永不拒绝)", &grow_sessions),
        PolicyProbe::from_sessions(
            format!("限量扩容(上限 {} 台)", LIMITED_MAX_TOTAL).as_str(),
            &limited_sessions,
        ),
    ];

    // ---------- 幕一:装配总览 ----------
    let overview_rows: Vec<(String, String)> = vec![
        ("业务当月".to_string(), DEMO_MONTH.chinese_text()),
        ("出厂月份(首批仪器的校准起点)".to_string(), MANUFACTURE_MONTH.chinese_text()),
        ("内置型号数".to_string(), format!("{} 种", builtin.models().len())),
        ("扩展后型号数".to_string(), format!("{} 种", extended.models().len())),
        ("内置门店数".to_string(), format!("{} 家", BUILTIN_STORE_CODES.len())),
        ("扩展后门店数".to_string(), format!("{} 家", BUILTIN_STORE_CODES.len() + 1)),
        ("内置申请笔数".to_string(), format!("{} 笔", builtin.request_total())),
        ("扩展后申请笔数".to_string(), format!("{} 笔", extended.request_total())),
        ("每型号预热台数".to_string(), format!("{} 台", PREWARM_TOTAL)),
        ("★ 累计使用次数上限(到这个数退役)".to_string(), format!("{} 次", RETIREMENT_USAGE_LIMIT)),
        (
            "三档策略".to_string(),
            BUILTIN_REUSE_POLICIES
                .iter()
                .map(|policy| policy.chinese_text())
                .collect::<Vec<&str>>()
                .join(" / "),
        ),
    ];
    print_lines(&render_overview_table(&overview_rows));
    println!();

    // ---------- 幕二:型号档案 ----------
    print_lines(&render_model_table(&extended.models()));
    println!();

    // ---------- 幕三:门店清单 ----------
    let mut all_stores: Vec<StoreCode> = BUILTIN_STORE_CODES.to_vec();
    all_stores.push(STORE_MO_01);
    print_lines(&render_store_table(
        &all_stores,
        "★ 澳门氹仔店是**工程外扩展**加进来的(定义在 `main.rs` 扩展区,\
         不是领域层的内置常量)。门店是开放型,所以加一家店不用回领域层改 `enum`。",
    ));
    println!();

    // ---------- 幕四:服务单价 ----------
    let service_price_rows: Vec<(String, String, String)> = vec![
        (
            "校准(一次)".to_string(),
            Money::new(SERVICE_COST_CALIBRATION_MINOR_UNITS, CNY).chinese_text(),
            "周期到了或已过期,校准完顺延到期月并回池".to_string(),
        ),
        (
            "退役处置(一台)".to_string(),
            Money::new(SERVICE_COST_RETIREMENT_MINOR_UNITS, CNY).chinese_text(),
            "累计借满上限,永久离池".to_string(),
        ),
    ];
    print_lines(&render_service_price_table(&service_price_rows));
    println!();

    // ---------- 幕五:仪器清单(严格池这一档,全部型号) ----------
    let mut inventory_rows: Vec<InventoryRowView> = Vec::new();
    for session in strict_sessions.iter() {
        for (_, object) in session.inventory().iter() {
            let view: InstrumentUnitView = InstrumentUnitView::from_unit(&object.borrow());
            inventory_rows.push(InventoryRowView::from_view(&view));
        }
    }
    inventory_rows.sort_by(|left, right| left.serial_number().cmp(right.serial_number()));
    let inventory_notes: Vec<(String, String)> = vec![
        ("仪器总台数".to_string(), format!("{} 台", inventory_rows.len())),
        (
            "★ 被借过 2 次以上的台数".to_string(),
            format!("{} 台", inventory_rows.iter().filter(|row| row.usage_count > 1).count()),
        ),
    ];
    print_lines(&render_inventory_table(
        "幕五 · 仪器清单:逐台看复用(严格池这一档,全部型号)",
        &inventory_rows,
    ));
    let _ = inventory_notes;
    println!();

    // ---------- 幕六:三档策略对照 ----------
    print_lines(&render_policy_table(&probes));
    println!();

    // ---------- 幕七:身份证据 ----------
    let mut evidence_total_acquisition: usize = 0;
    let mut evidence_distinct: usize = 0;
    let mut evidence_in_pool: usize = 0;
    let mut evidence_created: usize = 0;
    let mut retired_total: usize = 0;
    for session in strict_sessions.iter() {
        let evidence = session.identity_evidence();
        evidence_total_acquisition += evidence.acquisition_total;
        evidence_distinct += evidence.distinct_object_total;
        evidence_in_pool += evidence.still_in_pool_total;
        evidence_created += evidence.created_total;
        retired_total += session.station().observation().retired_total;
    }
    let evidence_rows: Vec<(String, String)> = vec![
        ("借出成功次数".to_string(), format!("{} 次", evidence_total_acquisition)),
        (
            "★ 实际用到的**不同**台数(按指针去重)".to_string(),
            format!("{} 台", evidence_distinct),
        ),
        (
            "★ 其中此刻仍在池里的台数(指针比中)".to_string(),
            format!("{} 台", evidence_in_pool),
        ),
        ("累计造过的台数".to_string(), format!("{} 台", evidence_created)),
        ("退役台数".to_string(), format!("{} 台", retired_total)),
        (
            "★ 复用是否成立(借出次数 > 不同台数)".to_string(),
            bool_text(evidence_total_acquisition > evidence_distinct).to_string(),
        ),
        (
            "平均每台被借次数".to_string(),
            format!(
                "{}",
                basis_points_text(if evidence_distinct == 0 {
                    0
                } else {
                    ((evidence_total_acquisition as i128) * 10_000 / (evidence_distinct as i128)) as i64
                })
            ),
        ),
    ];
    print_lines(&render_identity_evidence_table(
        &evidence_rows,
        &[
            "★ 「不同台数」是按 `Rc::ptr_eq` **比指针**去重得来的,\
             不是按序列号去重:序列号可以被复制(新建一个一模一样的就有同号),指针不能。",
            "★ 一个池若每次归还时新建一个同号同状态的放回去,\
             「复用次数」一样会 +1、一样省得下钱,但那是假复用。\
             指针比对就是为了让「同一台」变成一个能算出来的是/否。",
            "⚠️ 只打印「是否相等」,**绝不打印地址**:\
             地址随运行变(ASLR),打印会破坏「两次运行逐字节一致」。",
        ],
    ));
    println!();

    // 幕七(续):两条独立路径的逐台指纹比对(同参数跑两遍)。
    let second_strict: Vec<PoolSession> = run_all(&builtin, ReusePolicy::StrictPool);
    let mut identity_pairs: Vec<IdentityPairView> = Vec::new();
    for (index, session) in strict_sessions.iter().enumerate() {
        let again: &PoolSession = &second_strict[index];
        let left: Vec<(String, String)> = session.station().pool().fingerprint_list_sorted();
        let right: Vec<(String, String)> = again.station().pool().fingerprint_list_sorted();
        for (position, (identity, fingerprint)) in left.iter().enumerate() {
            let other: String = match right.get(position) {
                Some((_, other_fingerprint)) => other_fingerprint.clone(),
                None => "<缺失>".to_string(),
            };
            identity_pairs.push(IdentityPairView::new(identity, fingerprint, other.as_str()));
        }
    }
    print_lines(&render_identity_table(
        "幕七(续)· 恒等检查:同一份场景独立跑两遍,逐台指纹比对",
        &identity_pairs,
        "路径一(第一次跑)",
        "路径二(第二次跑)",
    ));
    println!();

    // ---------- 幕八:容量盘点 ----------
    let mut capacity_probes: Vec<CapacityProbe> = Vec::new();
    let mut capacity_rows: Vec<CapacityRowView> = Vec::new();
    for session in strict_sessions.iter() {
        let name: String = session.station().name();
        let probe: CapacityProbe = CapacityProbe::from_session(session, name.len());
        capacity_probes.push(probe);
        capacity_rows.push(CapacityRowView::new(name.as_str(), probe));
    }
    print_lines(&render_capacity_table(
        "幕八 · 容量盘点:空闲 + 在借 + 隔离 == 已造 − 已退役(严格池这一档)",
        &capacity_rows,
    ));
    println!();

    // ---------- 幕九:生命周期与服务记录 ----------
    let mut all_views: Vec<InstrumentUnitView> = Vec::new();
    let mut all_service_records: Vec<ServiceRecord> = Vec::new();
    let mut quarantined_total: usize = 0;
    for session in strict_sessions.iter() {
        for (_, object) in session.inventory().iter() {
            all_views.push(InstrumentUnitView::from_unit(&object.borrow()));
        }
        all_service_records.extend(session.service_records().to_vec());
        quarantined_total += session.quarantined_total();
    }
    let lifecycle_probes = lifecycle_statistics(&all_views);
    print_lines(&render_lifecycle_table(
        "幕九 · 生命周期分布:每一台此刻在哪一档状态上",
        &lifecycle_probes,
    ));
    println!();
    print_lines(&render_service_record_table(
        "幕九(续)· 服务记录:归还校验没过的那些都去哪了",
        &all_service_records,
    ));
    println!();

    // ---------- 幕十:契约审计(正常用例 + 两个已知会失败的输入) ----------
    let mut normal_checks: Vec<AuditCheckView> = Vec::new();
    for session in strict_sessions.iter() {
        let observation = observe_pool(session.station().pool());
        normal_checks.extend(audit_check_views(&audit_pool(&observation)));
    }
    print_lines(&render_audit_table(
        "幕十 · 契约审计:对象池的六条不变量(严格池这一档,逐座站)",
        &normal_checks,
    ));
    println!();

    // 负向①:★ 取走隔离对象后既不送回也不退役 → `CAPACITY` 必然失败。
    let mut neglect_station: InstrumentStation = InstrumentStation::new(
        MODEL_GOLD_TESTER,
        ReusePolicy::GrowOnDemand,
        MANUFACTURE_MONTH,
        DEMO_MONTH,
        PREWARM_TOTAL,
    );
    let neglect_request: UsageRequest =
        UsageRequest::new(STORE_CN_01, MODEL_GOLD_TESTER, DEMO_MONTH, "成色复检");
    if let Some(handle) = neglect_station.borrow_for(&neglect_request) {
        let _outcome: ReleaseOutcome = neglect_station.return_handle(handle);
        // ★ 刻意取走隔离对象之后**既不送回也不退役**:这就是 `CAPACITY` 的已知失败输入。
        let _pending = neglect_station.take_quarantined();
    }
    let neglect_checks: Vec<AuditCheckView> =
        audit_check_views(&audit_pool(&observe_pool(neglect_station.pool())));
    print_lines(&render_audit_table(
        "幕十(续)· 已知会失败的输入之一:取走隔离对象后既不送回也不退役",
        &neglect_checks,
    ));
    println!();

    // 负向②:★ 拿着别处来的对象来还 → `FOREIGN` 必然失败。
    let mut station_a: InstrumentStation = InstrumentStation::new(
        MODEL_BALANCE,
        ReusePolicy::GrowOnDemand,
        MANUFACTURE_MONTH,
        DEMO_MONTH,
        PREWARM_TOTAL,
    );
    let mut station_b: InstrumentStation = InstrumentStation::new(
        MODEL_BALANCE,
        ReusePolicy::GrowOnDemand,
        MANUFACTURE_MONTH,
        DEMO_MONTH,
        PREWARM_TOTAL,
    );
    let foreign_request: UsageRequest =
        UsageRequest::new(STORE_CN_01, MODEL_BALANCE, DEMO_MONTH, "黄金回购称重");
    let foreign_outcome_text: String = match station_a.borrow_for(&foreign_request) {
        None => "没能借出,无法演示".to_string(),
        Some(handle) => station_b.return_handle(handle).chinese_text().to_string(),
    };
    let foreign_checks: Vec<AuditCheckView> =
        audit_check_views(&audit_pool(&observe_pool(station_b.pool())));
    print_lines(&render_audit_table(
        "幕十(续二)· 已知会失败的输入之二:拿着另一座池的仪器来还",
        &foreign_checks,
    ));
    println!();
    let foreign_note: String = format!(
        "负向②把 A 池借出的凭证还给了 B 池,B 池的归还结局是:{}",
        foreign_outcome_text
    );

    // ---------- 幕十一:价值量化 ----------
    let mut created_with_pool: usize = 0;
    let mut created_without_pool: usize = 0;
    let mut saved_count: i64 = 0;
    let mut saved_money: i64 = 0;
    let mut hit_total: usize = 0;
    let mut reused_total: usize = 0;
    let mut peak_total: usize = 0;
    let mut service_cost: i64 = 0;
    let mut granted_total: usize = 0;
    let mut rejected_total: usize = 0;
    for (index, session) in strict_sessions.iter().enumerate() {
        let grow: &PoolSession = &grow_sessions[index];
        let with_pool: i64 = session.station().pool().created_total() as i64;
        let without_pool: i64 = grow.station().pool().created_total() as i64;
        let model: InstrumentModel = session.station().model();
        created_with_pool += with_pool as usize;
        created_without_pool += without_pool as usize;
        saved_count += without_pool - with_pool;
        saved_money += (without_pool - with_pool) * model.acquisition_cost().minor_units();
        hit_total += session.station().observation().hit_total;
        reused_total += session.returned_to_pool_total();
        peak_total += session.station().peak_borrowed_total();
        service_cost += session.station().service_cost_spent().minor_units();
        granted_total += session.granted_total();
        rejected_total += session.rejected_total();
    }
    let value_report: PoolValueReport = PoolValueReport::new(
        builtin.request_total(),
        granted_total,
        rejected_total,
        created_with_pool,
        created_without_pool,
        hit_total,
        reused_total,
        peak_total,
        saved_count,
        saved_money,
        service_cost,
    );
    print_lines(&render_value_table(&value_report));
    println!();

    // 幕十一(续):台账快照(**时刻写清楚**)。
    let snapshot_view: LedgerSnapshotView =
        LedgerSnapshotView::from_snapshot(&strict_sessions[0].station().pool().snapshot());
    print_lines(&render_ledger_snapshot_table(
        &snapshot_view,
        format!("天平站,内置场景跑完之后({})", DEMO_MONTH.chinese_text()).as_str(),
    ));
    println!();

    // ---------- 幕十二:工程外扩展对照 ----------
    let extended_strict: Vec<PoolSession> = run_all(&extended, ReusePolicy::StrictPool);
    let mut extended_unit_total: usize = 0;
    let mut extended_service_total: usize = 0;
    let mut extended_retired_total: usize = 0;
    let mut extended_granted_total: usize = 0;
    for session in extended_strict.iter() {
        extended_unit_total += session.inventory().len();
        extended_service_total += session.service_records().len();
        extended_retired_total += session.station().observation().retired_total;
        extended_granted_total += session.granted_total();
    }
    let mut builtin_unit_total: usize = 0;
    for session in strict_sessions.iter() {
        builtin_unit_total += session.inventory().len();
    }
    let extension_rows: Vec<ExtensionRowView> = vec![
        ExtensionRowView::new(
            "型号种数",
            format!("{}", builtin.models().len()).as_str(),
            format!("{}", extended.models().len()).as_str(),
            "幕二型号档案多 1 行(激光打标机)",
        ),
        ExtensionRowView::new(
            "门店家数",
            format!("{}", BUILTIN_STORE_CODES.len()).as_str(),
            format!("{}", all_stores.len()).as_str(),
            "幕三门店清单多 1 行(澳门氹仔店)",
        ),
        ExtensionRowView::new(
            "申请笔数",
            format!("{}", builtin.request_total()).as_str(),
            format!("{}", extended.request_total()).as_str(),
            "同一份 `run_all` 跑出来的,幕六对照表数字随之变化",
        ),
        ExtensionRowView::new(
            "仪器总台数(严格池)",
            format!("{}", builtin_unit_total).as_str(),
            format!("{}", extended_unit_total).as_str(),
            "幕五仪器清单多出新型号的台数",
        ),
        ExtensionRowView::new(
            "服务记录条数",
            format!("{}", all_service_records.len()).as_str(),
            format!("{}", extended_service_total).as_str(),
            "幕九(续)服务记录表多出新型号与新增退役的记录",
        ),
        ExtensionRowView::new(
            "★ 退役台数",
            format!("{}", retired_total).as_str(),
            format!("{}", extended_retired_total).as_str(),
            "新增的密集申请把天平借到了退役上限,幕九状态表「已退役」从 0 变 1",
        ),
        ExtensionRowView::new(
            "借到笔数(严格池)",
            format!("{}", granted_total).as_str(),
            format!("{}", extended_granted_total).as_str(),
            "幕六对照表的「借到」列随之变化",
        ),
        ExtensionRowView::new(
            "★ 改动既有分层文件数",
            "0",
            "0",
            "新增型号与门店由 `MODEL_LASER_MARKER` / `STORE_MO_01` 两个常量引入,\
             二者都定义在 `main.rs` 扩展区,可用 grep 核对",
        ),
    ];
    print_lines(&render_extension_table(
        "幕十二 · 工程外扩展对照:新增型号 / 新增门店 / 新增申请",
        &extension_rows,
    ));
    println!();

    // ---------- 幕十三:对外只读接口清点 ----------
    let interface_rows: Vec<(String, String)> = interface_items(
        &strict_sessions,
        &grow_sessions,
        &value_report,
        &snapshot_view,
        &probes,
        &all_views,
        &all_service_records,
        &inventory_rows,
    );
    print_lines(&render_interface_table(
        format!(
            "幕十三 · 对外只读接口清点:{} 个接口逐个真调(不是罗列)",
            interface_rows.len()
        )
        .as_str(),
        &interface_rows,
    ));
    println!();

    // ---------- 幕十四:体检 ----------
    let mut report: CheckReport = CheckReport::new();
    for line in policy_checks(&probes) {
        report.push(line);
    }
    for line in capacity_checks(&capacity_probes) {
        report.push(line);
    }
    let evidence = crate::client::IdentityEvidence {
        acquisition_total: evidence_total_acquisition,
        distinct_object_total: evidence_distinct,
        still_in_pool_total: evidence_in_pool,
        created_total: evidence_created,
    };
    for line in identity_checks(&evidence, retired_total) {
        report.push(line);
    }
    for line in lifecycle_checks(
        &lifecycle_probes,
        &all_views,
        &all_service_records,
        quarantined_total,
    ) {
        report.push(line);
    }
    for line in value_report.self_check() {
        report.push(line);
    }
    for line in health_check_lines(
        &probes,
        &normal_checks,
        &neglect_checks,
        &foreign_checks,
        &identity_pairs,
        &builtin,
        &extended,
        retired_total,
        extended_retired_total,
        foreign_note.as_str(),
    ) {
        report.push(line);
    }
    print_lines(&render_check_table("幕十四 · 体检汇总:这一池子有没有被用坏、有没有真的起效", &report));
}

/// 对场景里的**每个型号**各跑一次会话(一个型号一座站)。
///
/// ★ 三档策略共用这一个函数:
/// 若每档各写一份遍历,「同一份申请序列」这件事就没人能保证。
fn run_all(scenario: &UsageScenario, policy: ReusePolicy) -> Vec<PoolSession> {
    scenario
        .models()
        .iter()
        .map(|model| {
            PoolSession::run(
                *model,
                policy,
                scenario.month(),
                scenario.manufacture_month(),
                scenario.steps(),
                PREWARM_TOTAL,
            )
        })
        .collect()
}
相关推荐
kiros_wang1 小时前
网络状态监听&WiFi信息获取:弱网提示、断网兜底方案
开发语言·网络·php
一条泥憨鱼1 小时前
拦截器与过滤器:一次请求穿过 Spring 时,会先经过谁呢
java·开发语言·后端·spring
晴天的雨.9921 小时前
【C++】 priority_queue知识点总结
开发语言·数据结构·c++
郑州光合科技余经理1 小时前
本地生活服务系统:预约状态和进度通知接口怎么拆
java·开发语言·前端·javascript·后端·php·生活
tianbin9111 小时前
大模型训练全流程中,API聚合网关如何解决多模型调度难题
网络·后端
AIFQuant1 小时前
Python港股实时行情API接入:恒生股票数据获取与AH股溢价监控实战
开发语言·数据库·python·金融·实时数据api
外收内放2 小时前
Python基础语法练习题(70)
开发语言·python
mldong2 小时前
工作流引擎的委托代批:一张单,两个办理人
后端·架构
高频因子挖掘机2 小时前
量化回测为什么不能随便选前复权?从收益率计算到成交价格的三个误区
后端·github·api