benchkitmap

    Offline kit readiness, stock allocation and purchase pack planning.

    bom
    inventory
    procurement
    offline
    Download zip
    Author
    Version
    0.1.0
    License
    Apache-2.0
    Last updated
    10 hours ago
    Downloads
    5

    No dependencies

    #BenchKitMap

    BenchKitMap 是一个离线实验套件备料工具。把零件库存、可嵌套的物料清单、供应包装和课次需求放进一张清单,就能得到每个课次今天能配齐几套、应该领多少零件,以及还要买哪些整包。

    例如,两节课共用传感器和线材,先给需要机壳的课次配整套。遇到机壳缺货时,工具保留余下传感器给后续课次,采购建议仍覆盖所有尚未满足的净需求。没有报价的缺口会单独标出。

    #运行

    安装 MoonBit 工具链,使用 moonc 0.10.16 或更高版本与 Node.js 22.16 或更高版本。核心只使用标准库,支持 Wasm GC 和 JavaScript。磁盘命令使用 Node.js。

    moon build --target js --release --deny-warn node _build/js/release/build/cmd/main/main.js plan examples/workshop.json node _build/js/release/build/cmd/main/main.js plan examples/workshop.json --format pick-csv --out pick.csv node _build/js/release/build/cmd/main/main.js plan examples/workshop.json --format buy-csv --out buy.csv

    样例里,现场实验的 6 套先配齐 4 套,随后桌面实验的 8 套能配齐 2 套。还缺 8 个传感器,3 件包和 5 件包各买一包共 1,790 分,比任一种包装单独采购便宜。已报价项合计 3,110 分,另有 2 个机壳缺报价。领料 CSV 中的 pick_units 是本次可领数量,required_units 是原始需求。采购 CSV 的同一零件可以有多条包装行,其中 shortage_units 是零件级缺口,无需按行相加。

    JSON 每个订单的 items 包含每套用量、订单开始时可用库存和 limits_order。当订单未配齐时,limits_order 标出限制本轮能配齐几套的零件;多种零件并列时会全部标出。

    #继续处理同一批备料

    # 假设所有已报价包装到货,看看各课次能配齐多少套 node _build/js/release/build/cmd/main/main.js project examples/workshop.json # 从盘点表更新指定库存,生成可继续规划的新清单 node _build/js/release/build/cmd/main/main.js refresh-stock examples/workshop.json examples/recount.csv --out recounted.json # 实际已领 3 套,生成包含扣减库存与待领数量的新清单 node _build/js/release/build/cmd/main/main.js issue examples/workshop.json field-session 3 --out next.json node _build/js/release/build/cmd/main/main.js plan next.json

    project 按原优先顺序重新分配假设到货的库存。遇到未报价的缺口,它保留待询价状态。靠后的订单有可能让出原来可领的部分库存,因为到货后靠前的订单开始能够配齐。

    refresh-stock 支持表头为 part,stock 的两列 CSV,可使用 LF 或 CRLF、UTF-8 BOM 和单行双引号。未列出的零件保留原盘点数。未知或重复 ID、非整数量、带嵌入分隔符或多行的单元格会报错。

    issue 以当前快照的优先分配为准,校验实际领用的整套数,扣减每种零件并更新待领订单。全部领完的订单会移除。每次确认实际领用后,下一次操作应使用刚生成的快照。同一份旧清单可生成不同的试算分支。

    默认输出 JSON 到 stdout。格式可选 json、pick-csv 或 buy-csv。退出码 0 表示计划已生成,报告可能包含缺料。输入或 I/O 错误返回 2,并写入 stderr。使用 --out 时,输出父目录须已存在并支持硬链接。程序先完成暂存写入再发布新文件,拒绝覆盖已有目标。强制结束进程可能留下隐藏的 .benchkitmap-* 暂存目录。

    #输入

    清单 schema 为 benchkitmap/v1,文件必须为 UTF-8,最多 2 MiB。字段见 完整样例。

    字段内容
    parts零件 id、可用库存 stock 和供应包装 packs。包装有 id、每包 quantity 和整包 cost_cents。未知报价用空数组。
    kits套件 id 及 items。每项的 item 可引用零件或子套件,quantity 是每套所需数量。
    orders课次或订单 id、所用 kit 和计划套数 quantity。数组顺序即领料优先级。完成全部待领订单后的快照可用空数组。

    ID 大小写敏感,长度 1 到 64,只允许 ASCII 字母、数字、下划线、点和连字符,首字符须为字母或数字。零件与套件共用 ID 空间。清单拒绝重复 JSON 键、未知字段、重复子件边、未知引用和循环依赖。数量和成本采用非负十进制整数。除库存与价格可为零外,输入数量须为正。使用同一币种的分作为价格单位,报告的总价字段用十进制字符串保留完整精度。

    计划先展开每个套件需要的实际零件,再按课次顺序从库存分配能配齐的整套。剩余需求减去剩余库存,得到采购缺口。对每个有报价的缺口,按最低总价、最少超买量、最少包数、报价顺序选定包装组合。采购建议为本次缺口的快照,可用于确认采购单和重新盘点。报价按不限量、同币种的整包计算,采购总价不含运费、税费或阶梯价。

    输入限制为最多 256 种零件、256 种套件、256 个订单、每套 256 项和每零件 16 种报价。单零件的库存、单件数量和展开后的累计需求上限为 1,000,000,每包价格上限为 1,000,000,000 分。多报价精确求解使用最多 100,000 个经最大公约数缩放后的状态,超限返回明确错误。

    #MoonBit SDK

    moon add Novanke/benchkitmap@0.1.0

    在使用方的 moon.pkg 导入 "Novanke/benchkitmap" @kit。使用 @kit.parse_manifest(text) 解析清单,@kit.plan(manifest) 生成计划,@kit.render_plan(plan, @kit.JsonReport) 导出。若调用方已有结构化数据,可直接构造 Manifest,规划入口仍会验证引用、数量和报价。纯核心示例见 examples/sdk。

    SDK 同时提供 project_after_purchase、refresh_stock、record_issue 和 encode_manifest。复现数据见 验证记录。

    moon test --target wasm-gc --deny-warn moon test --target js --deny-warn moon run examples/sdk --target wasm-gc --release --deny-warn moon run examples/sdk --target js --release --deny-warn python scripts/cli_smoke.py

    使用 Apache-2.0 许可证。样例使用合成物料和报价。

    PlanError

    pub(all) suberror PlanError {
    Invalid(String)
    }

    Allocation

    pub(all) struct Allocation {
    orders : Array[OrderPlan]
    parts : Array[PartBalance]
    }

    ItemNeed

    pub(all) struct ItemNeed {
    item : String
    quantity : Int
    }

    A component of a kit. The item can name a part or another kit.

    Kit

    pub(all) struct Kit {
    id : String
    items : Array[ItemNeed]
    }

    Manifest

    pub(all) struct Manifest {
    parts : Array[Part]
    kits : Array[Kit]
    orders : Array[Order]
    }

    An inventory snapshot and orders in descending priority.

    Order

    pub(all) struct Order {
    id : String
    kit : String
    quantity : Int
    }

    OrderPlan

    pub(all) struct OrderPlan {
    id : String
    kit : String
    requested : Int
    buildable : Int
    items : Array[PartIssue]
    }

    OutputFormat

    pub(all) enum OutputFormat {
    JsonReport
    PickCsv
    BuyCsv
    }

    PackCount

    pub(all) struct PackCount {
    id : String
    quantity : Int
    count : Int
    unit_cost_cents : Int
    }

    PackOption

    pub(all) struct PackOption {
    id : String
    quantity : Int
    cost_cents : Int
    }

    A vendor's fixed size pack, priced in cents in the manifest's currency.

    Part

    pub(all) struct Part {
    id : String
    stock : Int
    packs : Array[PackOption]
    }

    PartBalance

    pub(all) struct PartBalance {
    part : String
    stock : Int
    required : Int
    issued : Int
    remaining_stock : Int
    shortage : Int
    }

    PartIssue

    pub(all) struct PartIssue {
    part : String
    units_per_kit : Int
    available_before : Int
    required : Int
    issued : Int
    limits_order : Bool
    }

    Plan

    pub(all) struct Plan {
    allocation : Allocation
    purchases : Array[PurchaseLine]
    purchase_total_cents : Int64
    purchase_complete : Bool
    }

    ProjectedOrder

    pub(all) struct ProjectedOrder {
    id : String
    kit : String
    requested : Int
    buildable_now : Int
    buildable_after_priced_purchases : Int
    }

    Projection

    pub(all) struct Projection {
    orders : Array[ProjectedOrder]
    purchase_complete : Bool
    purchase_total_cents : Int64
    }

    Purchase

    pub(all) struct Purchase {
    shortage : Int
    ordered : Int
    cost_cents : Int64
    packs : Array[PackCount]
    }

    PurchaseLine

    pub(all) struct PurchaseLine {
    part : String
    shortage : Int
    purchase : Purchase?
    }

    A missing recommendation identifies a part with no purchasing options.

    allocate

    fn allocate(manifest : Manifest) -> Allocation raise PlanError

    Expand nested bills of materials and issue stock to whole kits in order. Balances use full requested demand, even when an order cannot be fulfilled.

    encode_manifest

    fn encode_manifest(manifest : Manifest) -> String

    Encode a validated snapshot using the same schema read by parse_manifest.

    parse_manifest

    fn parse_manifest(text : String) -> Manifest raise PlanError

    Decode the versioned worksheet. The planner separately checks identities, references, aggregate quantities and purchase feasibility.

    plan

    fn plan(manifest : Manifest) -> Plan raise PlanError

    Allocate today's stock and independently price each part's total shortage. Unpriced shortages remain explicit in the returned purchase lines.

    project_after_purchase

    fn project_after_purchase(manifest : Manifest) -> Projection raise PlanError

    Reallocate from the original stock plus all priced purchases, in the same order priority. An earlier order that becomes buildable can leave fewer parts for a later order. Unpriced shortages are never added to stock.

    recommend_purchase

    fn recommend_purchase(shortage : Int, packs : Array[PackOption]) -> Purchase? raise PlanError

    Minimize price, then excess units, then pack count. Equal solutions favor the earlier declared offers. Return None when nothing can be recommended. With multiple offers the exact search is limited to 100,000 reachable or intermediate states after dividing quantities by their greatest divisor.

    record_issue

    fn record_issue(manifest : Manifest, order_id : String, quantity : Int) -> Manifest raise PlanError

    Record the issue of complete kits already allocatable to the given order. The returned inventory and pending orders form a new planning snapshot.

    refresh_stock

    fn refresh_stock(manifest : Manifest, csv : String) -> Manifest raise PlanError

    Replace listed stock counts from a two-column part,stock CSV. Accepts LF or CRLF, an optional UTF-8 BOM, and optionally quoted cells. IDs and counts use a single-line ASCII alphabet; embedded quotes, delimiters, and multiline cells are rejected. Returns independent arrays.

    render_plan

    fn render_plan(result : Plan, format : OutputFormat) -> String

    Serialize a plan created by plan. JSON uses strings for aggregate cents.

    render_projection

    fn render_projection(result : Projection) -> String

    Export kit readiness now and after receiving all priced purchases.