Metadata-Version: 2.5
Name: algpkg-sdk
Version: 0.1.0
Summary: 算法包（.mbank）开发、测试与打包工具链
Requires-Python: >=3.10
Requires-Dist: module-bank>=0.4.2
Requires-Dist: numpy>=1.26
Requires-Dist: orjson>=3.10
Provides-Extra: dev
Requires-Dist: pytest>=8; extra == 'dev'
Requires-Dist: sophon-arm>=3.11.1; extra == 'dev'
Provides-Extra: device
Requires-Dist: sophon-arm>=3.11.1; extra == 'device'
Description-Content-Type: text/markdown

# algpkg-sdk —— 算法包开发 SDK

独立项目：**算法作者只依赖本项目**，即可完成「写算法 → 本地跑通 → 打包 → 自检 → 交付 `.mbank`」，
不需要 worker 仓库，也不需要了解 worker 内部结构。

```text
算法包开发（本项目）                       设备运行（worker）
──────────────────────                    ──────────────────
写代码 / 本地测试 / 打包 / 签名    →  .mbank  →  上传 → 挂载 → 推理
   用的接口 = algpkg_sdk.api                     同一份接口（别名 video_algpkg_api）
```

## 1. 安装

> 本项目是**独立仓库**（2026-10-16 从 sophon-edge 仓库拆出）：
> `git clone <本仓库地址> && cd algpkg-sdk`（目录名即项目名；下文命令都在**本项目根**执行）。

```bash
# 从本项目根安装（开发态；也可打成 wheel 分发给第三方）
uv pip install -e .              # 只做静态检查/打包的机器
uv pip install -e '.[device]'    # 设备上（含 sophon-arm，可跑推理）
```

配套的 **worker（视频推理系统）仓库**只在使用 `algpkg` 工具链做集成联调时才需要：
worker 侧 `workers/video/pyproject.toml` 以**路径依赖**指向本项目（`path = "../../../algpkg-sdk"`），
交付打包（`deploy/pack_worker.py`）会把本项目打成 wheel 放进 worker 交付包的 `vendor/`；
路径可用环境变量 **`ALGPKG_SDK_DIR`** 覆盖（三个开发脚本与打包脚本都认它）。

依赖很轻：`module-bank`（`.mbank` 读写）、`orjson`、`numpy`。
**设备推理包（sophon-arm）是可选的** —— 没有 NPU 的机器照样能打包与做静态校验。

## 2. 三步上手

```bash
# ① 生成脚手架（目录骨架 + manifest 模板 + 可运行示例 + 自测说明）
algpkg init /tmp/smoke-detect --id demo.smoke-detect --name 抽烟检测 --version 1.0.0

# ② 实现 /tmp/smoke-detect/<包内代码目录>/__init__.py 的 load() / infer()
#    模型放 /tmp/smoke-detect/models/，或打包时用 --model 指定

# ③ 打包 + 自检（不需要设备）
algpkg build  /tmp/smoke-detect --out demo.smoke-detect-1.0.0.mbank
algpkg inspect demo.smoke-detect-1.0.0.mbank           # 清单/模型/快照/签名
algpkg verify  demo.smoke-detect-1.0.0.mbank           # 结构与模型校验
```

## 3. 本地测试（不需要 worker）

```bash
# 静态检查：manifest 合法性 / 包结构 / 模型文件 / 代码可导入（试加载）
algpkg check demo.smoke-detect-1.0.0.mbank
algpkg check demo.smoke-detect-1.0.0.mbank --deep      # 再实例化入口类（需设备）

# 设备上真跑：图片或视频，warmup 后统计单帧耗时，可画框存图
algpkg run demo.smoke-detect-1.0.0.mbank --source clip.mp4 --frames 5 --save-annotated out/
algpkg run a-1.0.0.mbank --source a.jpg --params '{"conf":0.3}'

# 两个包/版本对拍（逐帧检出数 + 逐位一致性）
algpkg compare a-1.0.0.mbank b-1.0.0.mbank --source clip.mp4
```

同样能力在 Python 里直接调（适合写进自己的 pytest）：

```python
from algpkg_sdk import testkit

report = testkit.check("demo.smoke-detect-1.0.0.mbank")          # 任意机器
runs = testkit.run("demo.smoke-detect-1.0.0.mbank", source="clip.mp4",
                   params={"conf": 0.3}, save_annotated="out/")   # 设备
print(runs.summary())
print(testkit.compare(runs, testkit.run("old.mbank", source="clip.mp4")))
```

## 4. 写算法：接口契约

包内代码只应导入 `video_algpkg_api`（接口契约名，= `algpkg_sdk.api` 的同一批对象）+ 设备框架：

> 装好本 SDK 后，这个导入名在**任何工具里都直接可用**：IDE 补全 / mypy / pyright /
> 本地 `python -c "import video_algpkg_api"`（SDK 自带同名真实模块 + PEP 561 `py.typed`）。
> 设备运行时 worker 仍注册同名别名，两条解析路径拿到的是同一批对象；
> **不要**自己 vendor/复制一份同名模块（两份实现分叉 = "本地过、设备挂"）。

```python
from video_algpkg_api import Algorithm, AlgoContext, AlgoResult, AlgoError

class Algorithm(Algorithm):                 # 类名与 manifest.entry 一致
    kind = "detector"

    def load(self) -> None:
        data = self.ctx.model_bytes()       # 包内模型字节（不落盘，直接给引擎）
        conf = float(self.ctx.param("conf", 0.25))
        self._labels = self.ctx.read_asset("assets/labels.txt").decode().splitlines()
        self._engine = MyEngine(data, self.ctx.dev_id, conf)   # 例如 sail.Engine(bytes, …)

    def infer(self, frame) -> AlgoResult:   # 单帧；抽帧由运行方控制
        return AlgoResult(boxes=self._engine(frame))           # (N,6): x1,y1,x2,y2,score,cls

    def draw(self, frame, result):          # 可选：自定义叠加
        return None

    def close(self) -> None:                # 实例回收时调用
        self._engine = None
```

| 用法 | 说明 |
| ---- | ---- |
| `ctx.model_bytes(name)` / `ctx.read_asset(rel)` | 读**包内**文件（模型/类别表/阈值表），不落盘；不给名 = 选中模型，给名 = 任意已声明模型（多模型包，见下） |
| `ctx.param(key, default)` | 任务参数（`params_schema` 默认值已合并，越界在装载时被拒） |
| `ctx.dev_id` / `ctx.logger` / `ctx.meta` | 设备号 / 日志 / 选中模型的元信息（精度、分辨率…） |
| `infer` 返回 | `AlgoResult(boxes=(N,6), counts={"person": n}, value=n, labels=[...])` |
| `load()` 抛异常 | 任务转 failed，错误信息带包 id/版本（先在本地 `algpkg check --deep` 拦住） |

约定：同一实例不会被并发调用（运行方串行化），但 `infer` 不要长时间阻塞；
线程与内存预算见《算法包开发指南》§5。

**多模型包**（如"检测 + 二次判定/误报过滤"）：一个包声明多份模型（`manifest.models[]`），
代码里 `ctx.model_bytes("fp_filter")` 按名字取第二份——不必硬编码包内路径：

```python
def load(self):
    self._det = ctx.model_bytes()               # 选中模型（manifest 里 default 的那份）
    self._cls = ctx.model_bytes("fp_filter")    # 第二份：裁检测框 → 分类判误报
```

两级管线写在 `infer()` 里：返回**过滤后**的 boxes 即可，告警/统计/叠加图自动跟着走
（运行时不做级联，这就是"包内级联"的标准形态；细节与算力旋钮见《算法包开发指南》§5）。

## 5. 打包选项

```bash
algpkg build <源目录> --out x.mbank \
    --model <模型名>=<磁盘路径> \      # 模型不在源码目录时显式指定（包内路径以 manifest 为准）
    --encrypt <Fernet KEY> \          # 加密包内代码（明文源码会被删除；模型不加密）
    --pyarmor \                       # PyArmor 混淆（需授权）
    --sign <KEY> --key-id <ID>        # HMAC-SHA256 签名（对称；设备侧配相同密钥验签）
algpkg list <目录>                     # 列目录内的包（id/版本/大小）
```

## 6. 目录结构

```text
algpkg-sdk/
├── pyproject.toml            项目定义（scripts: algpkg）
├── src/algpkg_sdk/
│   ├── api.py                接口契约（Algorithm / AlgoContext / AlgoResult）+ 别名注册
│   ├── manifest.py           清单字段与校验（models / params_schema / alarm / legacy…）
│   ├── loader.py             AlgoPackage：读 svfs、挂代码（试加载）、构造实例
│   ├── builder.py            打包（module_bank 打代码 + svfs 放模型/资源 + 快照=版本 + 签名）
│   ├── testkit.py            本地测试台：check / run / compare（无需 worker）
│   └── cli.py                algpkg build|inspect|verify|init|list|check|run|compare
├── examples/person-count/    完整示例：人员计数（自带实现，只依赖本 SDK）
│   └── …
└── tests/                    pytest（无设备跑静态，有设备跑推理，自动跳过）
```

## 7. 自测

```bash
cd algpkg-sdk
uv run pytest                 # 静态用例；标记为 device 的用例在无 NPU 机器上自动跳过
uv run pytest -m device       # 设备上跑推理用例
```

## 8. 与设备的对接

1. `algpkg build` 产出单文件 `.mbank`；
2. 在设备「算法管理」页上传（或 `POST /api/v1/video/algorithm/install` 分片上传 → `commit`）；
3. 任务里选择该算法（参数表单由 `manifest.params_schema` 生成），可与其他算法并行；
4. 现场排障与完整字段表见仓库文档《算法包开发指南》；设计依据见《算法包与多算法任务设计》。
