{"id":46392758,"url":"https://github.com/zensgit/cad-ml-platform","last_synced_at":"2026-03-05T09:09:37.788Z","repository":{"id":329229699,"uuid":"1094840420","full_name":"zensgit/cad-ml-platform","owner":"zensgit","description":null,"archived":false,"fork":false,"pushed_at":"2026-02-19T14:24:00.000Z","size":34923,"stargazers_count":0,"open_issues_count":9,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-02-19T17:51:42.869Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"Python","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/zensgit.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":"docs/SECURITY_MODEL_LOADING.md","support":null,"governance":"docs/GOVERNANCE_INDEX.md","roadmap":"docs/ROADMAP_PHASE2.md","authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":"AGENTS.md","dco":null,"cla":null}},"created_at":"2025-11-12T08:42:32.000Z","updated_at":"2026-02-19T14:24:03.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/zensgit/cad-ml-platform","commit_stats":null,"previous_names":["zensgit/cad-ml-platform"],"tags_count":2,"template":false,"template_full_name":null,"purl":"pkg:github/zensgit/cad-ml-platform","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/zensgit%2Fcad-ml-platform","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/zensgit%2Fcad-ml-platform/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/zensgit%2Fcad-ml-platform/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/zensgit%2Fcad-ml-platform/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/zensgit","download_url":"https://codeload.github.com/zensgit/cad-ml-platform/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/zensgit%2Fcad-ml-platform/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":30117552,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-03-05T08:19:04.902Z","status":"ssl_error","status_checked_at":"2026-03-05T08:17:37.148Z","response_time":93,"last_error":"SSL_connect returned=1 errno=0 peeraddr=140.82.121.5:443 state=error: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2026-03-05T09:09:36.722Z","updated_at":"2026-03-05T09:09:37.750Z","avatar_url":"https://github.com/zensgit.png","language":"Python","funding_links":[],"categories":[],"sub_categories":[],"readme":"# 🤖 CAD ML Platform - 智能CAD分析微服务平台\n![Stress \u0026 Metrics Validation](https://github.com/OWNER/REPO/actions/workflows/stress-tests.yml/badge.svg)\n\n## 目录\n- 项目概述\n- 系统架构\n- 快速开始\n- 评估与可观测性（健康检查、指标、PromQL）\n- CI \u0026 安全工作流\n- API 文档\n  - 接口迁移与废弃策略\n  - PromQL 示例\n  - Runbooks \u0026 告警规则\n  - 配置速查表\n  - 核心API端点\n\n\u003e 独立的、可扩展的CAD机器学习分析服务，为多个系统提供统一的智能分析能力\n\n[![Docker](https://img.shields.io/badge/docker-ready-blue)](https://www.docker.com/)\n[![Python](https://img.shields.io/badge/python-3.10+-green)](https://www.python.org/)\n[![FastAPI](https://img.shields.io/badge/FastAPI-0.100+-orange)](https://fastapi.tiangolo.com/)\n[![License](https://img.shields.io/badge/license-proprietary-red)](LICENSE)\n[![Evaluation](https://img.shields.io/badge/evaluation-passing-brightgreen)](docs/EVAL_SYSTEM_COMPLETE_GUIDE.md)\n[![Integrity](https://img.shields.io/badge/integrity-monitored-blue)](config/eval_frontend.json)\n\n---\n\n## 🎯 项目概述\n\nCAD ML Platform 是一个完全独立的微服务平台，专门为CAD图纸和工程图形提供机器学习增强的分析服务。它可以服务于多个业务系统，包括但不限于：\n\n- **DedupCAD**: CAD图纸查重系统\n- **Stainless Steel Cutting**: 不锈钢切割工艺系统\n- **ERP系统**: 企业资源规划\n- **MES系统**: 制造执行系统\n- **PLM系统**: 产品生命周期管理\n\n### 核心特性\n\n- 🔍 **零件识别**: 自动识别8种机械零件类型\n- 📊 **特征提取**: 95维深度特征向量\n- 🔄 **格式转换**: 支持DXF、STEP、IGES等多种格式\n- 🎯 **相似度分析**: 几何+语义双重分析 (支持 Top-K、材料/复杂度过滤、向量管理)\n- 📈 **质量评估**: 图纸质量自动评分\n- 🏭 **工艺推荐**: 智能加工工艺建议\n- 🔌 **多语言SDK**: Python、JavaScript、Java客户端\n- 🚀 **高性能**: 缓存、并发、分布式处理\n\n### 🆕 企业级功能 (P7-P10)\n\n| 功能模块 | 描述 | 文档 |\n|----------|------|------|\n| **🌐 Web UI** | 现代化前端界面，支持流式响应显示 | `web/` |\n| **📡 流式响应** | Server-Sent Events (SSE) 实时输出 | `src/core/assistant/streaming.py` |\n| **🔀 多模型支持** | 5种负载均衡策略，自动故障转移 | `src/core/assistant/multi_model.py` |\n| **👥 多租户** | 租户隔离、配额管理、层级权限 | `src/core/assistant/multi_tenant.py` |\n| **🔐 RBAC** | 细粒度角色权限控制 | `src/core/assistant/rbac.py` |\n| **☸️ K8s 部署** | Helm Chart、HPA、PDB 生产配置 | `deploy/helm/` |\n\n#### 多模型负载均衡策略\n\n```python\nfrom src.core.assistant.multi_model import LoadBalancingStrategy\n\n# 支持的策略\nLoadBalancingStrategy.ROUND_ROBIN    # 轮询\nLoadBalancingStrategy.WEIGHTED       # 加权随机\nLoadBalancingStrategy.LEAST_LATENCY  # 最低延迟\nLoadBalancingStrategy.PRIORITY       # 优先级\nLoadBalancingStrategy.RANDOM         # 随机\n```\n\n#### 租户层级配额\n\n| 层级 | 对话数 | 消息/天 | API调用/分钟 | 允许模型 |\n|------|--------|---------|--------------|----------|\n| FREE | 10 | 100 | 10 | offline |\n| BASIC | 100 | 1,000 | 30 | offline, qwen |\n| PROFESSIONAL | 1,000 | 10,000 | 100 | offline, qwen, openai |\n| ENTERPRISE | ∞ | ∞ | 500 | all |\n\n#### RBAC 角色继承\n\n```\nguest → user → engineer → manager → admin\n  │       │        │         │        │\n  └─ read └─ CRUD  └─ knowledge └─ user_manage └─ system_config\n```\n\n---\n\n## 🏗️ 系统架构\n\n```mermaid\ngraph TB\n    subgraph \"客户端系统\"\n        A[DedupCAD]\n        B[切割系统]\n        C[ERP系统]\n        D[其他系统]\n    end\n\n    subgraph \"CAD ML Platform\"\n        E[API网关]\n        F[分析服务]\n        G[模型服务]\n        H[适配器]\n        I[缓存层]\n        J[知识库]\n    end\n\n    A --\u003e E\n    B --\u003e E\n    C --\u003e E\n    D --\u003e E\n\n    E --\u003e F\n    E --\u003e G\n    F --\u003e H\n    F --\u003e I\n    G --\u003e J\n```\n\n### 技术栈\n\n| 组件 | 技术选型 | 用途 |\n|------|---------|------|\n| **API框架** | FastAPI | 高性能异步API |\n| **ML框架** | scikit-learn, TensorFlow | 机器学习模型 |\n| **CAD处理** | ezdxf, FreeCAD | CAD文件解析 |\n| **缓存** | Redis | 结果缓存 |\n| **消息队列** | RabbitMQ/Kafka | 异步处理 |\n| **容器化** | Docker | 部署标准化 |\n| **编排** | Kubernetes | 生产环境编排 |\n| **监控** | Prometheus + Grafana | 性能监控 |\n\n---\n\n## 🚀 快速开始\n\n### 前置要求\n\n- Python 3.9+\n- Docker 20.10+\n- Redis 6.0+ (可选)\n- CUDA 11.0+ (GPU加速，可选)\n\n### 安装步骤\n\n#### 1. 克隆仓库\n\n```bash\ngit clone https://github.com/your-org/cad-ml-platform.git\ncd cad-ml-platform\n```\n\n#### 2. 环境配置\n\n```bash\n# 创建Python虚拟环境\npython -m venv venv\nsource venv/bin/activate  # Windows: venv\\Scripts\\activate\n\n# 安装依赖\npip install -r requirements.txt\npip install -r requirements-dev.txt  # 开发工具（含 pytest-asyncio，用于异步OCR测试）\n```\n\n#### 3. 配置文件\n\n```bash\n# 复制配置模板\ncp config/config.example.yaml config/config.yaml\n\n# 编辑配置\nvim config/config.yaml\n```\n\n#### 4. 启动服务\n\n**开发环境**:\n```bash\n# 使用Docker Compose\ndocker-compose up -d\n\n# 或直接运行\npython src/main.py\n```\n\n**生产环境**:\n```bash\n# Kubernetes部署\nkubectl apply -f deployments/kubernetes/\n```\n\n### 相关文档\n\n- 2D 标准库: `docs/STANDARDS_LIBRARY.md`\n- 基线评测: `docs/BASELINE_EVALUATION.md`\n- 知识库运维: `docs/KNOWLEDGE_RULES_OPERATIONS.md`\n- 主动学习: `docs/ACTIVE_LEARNING_OPERATIONS.md`\n- 3D 训练: `docs/TRAINING_3D_PIPELINE.md`\n- 发布流程: `docs/RELEASE_PLAYBOOK.md`\n- 制造决策输出: `docs/MANUFACTURING_DECISION_OUTPUT.md`\n- 回放验证: `docs/REPLAY_VALIDATION.md`\n\n### 实验目录归档自动化\n\n为了避免 `reports/experiments` 持续膨胀，仓库提供了统一归档脚本与 Make 目标：\n\n```bash\n# 默认是 dry-run（只生成计划，不删除）\nmake archive-experiments\n\n# 实际归档并删除源目录（按保留窗口自动选择）\nmake archive-experiments \\\n  ARCHIVE_EXPERIMENTS_KEEP_DAYS=7 \\\n  ARCHIVE_EXPERIMENTS_EXTRA_ARGS=\"--delete-source\"\n\n# 指定目录归档（可重复 --dir）\npython3 scripts/ci/archive_experiment_dirs.py \\\n  --experiments-root reports/experiments \\\n  --archive-root \"$HOME/Downloads/cad-ml-platform-experiment-archives\" \\\n  --dir 20260217 --dir 20260219 \\\n  --delete-source \\\n  --manifest-json reports/archive_experiments_manifest.json\n```\n\n说明：\n- 脚本路径：`scripts/ci/archive_experiment_dirs.py`\n- 默认归档输出：`$HOME/Downloads/cad-ml-platform-experiment-archives`\n- 每次会输出 manifest（默认：`reports/archive_experiments_manifest.json`）\n- GitHub Actions:\n  - `Experiment Archive Dry Run`：每日 02:30 UTC 定时 dry-run，并上传 manifest/log artifact。\n  - `Experiment Archive Apply`：手动触发真实归档（`--delete-source`）；需输入确认短语。\n  - `Experiment Archive Apply` 使用 environment `experiment-archive-approval`，可在仓库设置中配置 required reviewers 形成人工审批门。\n\n#### gh workflow_dispatch（本地触发）\n\n当需要在本地通过 `gh` 触发归档工作流时，可使用 Make 封装目标（底层脚本：`scripts/ci/dispatch_experiment_archive_workflow.py`）：\n\n```bash\n# 触发 dry-run workflow_dispatch（仅调度，不做删除）\nmake archive-workflow-dry-run-gh \\\n  ARCHIVE_WORKFLOW_REF=main \\\n  ARCHIVE_WORKFLOW_EXPERIMENTS_ROOT=reports/experiments \\\n  ARCHIVE_WORKFLOW_KEEP_DAYS=7 \\\n  ARCHIVE_WORKFLOW_TODAY=20260221 \\\n  ARCHIVE_WORKFLOW_WATCH=1 \\\n  ARCHIVE_WORKFLOW_WAIT_TIMEOUT=120 \\\n  ARCHIVE_WORKFLOW_POLL_INTERVAL=3 \\\n  ARCHIVE_WORKFLOW_PRINT_ONLY=0\n\n# 触发 apply workflow_dispatch（会进入删除源目录流程）\nARCHIVE_APPROVAL_PHRASE=I_UNDERSTAND_DELETE_SOURCE \\\nmake archive-workflow-apply-gh \\\n  ARCHIVE_WORKFLOW_REF=main \\\n  ARCHIVE_WORKFLOW_EXPERIMENTS_ROOT=reports/experiments \\\n  ARCHIVE_WORKFLOW_KEEP_DAYS=7 \\\n  ARCHIVE_WORKFLOW_DIRS_CSV=20260217,20260219 \\\n  ARCHIVE_WORKFLOW_REQUIRE_EXISTS=true \\\n  ARCHIVE_WORKFLOW_WATCH=1 \\\n  ARCHIVE_WORKFLOW_WAIT_TIMEOUT=120 \\\n  ARCHIVE_WORKFLOW_POLL_INTERVAL=3 \\\n  ARCHIVE_WORKFLOW_PRINT_ONLY=0\n```\n\n说明：\n- `archive-workflow-dry-run-gh` 固定 `mode=dry-run`，支持覆盖 `ARCHIVE_WORKFLOW_REF`、`ARCHIVE_WORKFLOW_EXPERIMENTS_ROOT`、`ARCHIVE_WORKFLOW_KEEP_DAYS`、`ARCHIVE_WORKFLOW_TODAY`、`ARCHIVE_WORKFLOW_WATCH`、`ARCHIVE_WORKFLOW_WAIT_TIMEOUT`、`ARCHIVE_WORKFLOW_POLL_INTERVAL`、`ARCHIVE_WORKFLOW_PRINT_ONLY`。\n- `archive-workflow-apply-gh` 固定 `mode=apply`，额外支持 `ARCHIVE_WORKFLOW_DIRS_CSV`、`ARCHIVE_WORKFLOW_REQUIRE_EXISTS`、`ARCHIVE_WORKFLOW_WAIT_TIMEOUT`、`ARCHIVE_WORKFLOW_POLL_INTERVAL`，并强制从环境变量读取 `ARCHIVE_APPROVAL_PHRASE`。\n- `ARCHIVE_WORKFLOW_WAIT_TIMEOUT` 与 `ARCHIVE_WORKFLOW_POLL_INTERVAL` 主要在 `ARCHIVE_WORKFLOW_WATCH=1` 时生效：前者限制最长等待秒数，后者控制轮询间隔秒数。\n\n安全提示：\n- 建议先执行 `ARCHIVE_WORKFLOW_PRINT_ONLY=1` 或 dry-run，确认 dispatch 参数无误后再执行 apply。\n- `ARCHIVE_APPROVAL_PHRASE` 仅通过环境变量临时传入，不要写入仓库文件或脚本常量。\n- 使用 `ARCHIVE_WORKFLOW_DIRS_CSV` 做精确目录控制时，建议同时保留 `ARCHIVE_WORKFLOW_REQUIRE_EXISTS=true`，避免目录拼写错误被静默忽略。\n\n回归校验：\n```bash\nmake validate-archive-workflow-dispatcher\n```\n该目标会同时验证：\n- dispatcher 单测\n- workflow YAML 安全门回归测试\n- Make 目标参数透传与 `print-only` 输出行为\n\n#### 按提交 SHA 统一盯 CI\n\n当需要一次性跟踪某个提交触发的全部核心 CI 工作流（而不是手动逐条 `gh run watch`）时，可使用：\n\n```bash\n# 监控当前 HEAD（默认 push 事件）\nmake watch-commit-workflows\n\n# 先做 gh readiness 预检，再盯 CI\nmake watch-commit-workflows-safe\n\n# 非严格模式（预检失败也继续执行 watcher）\nmake watch-commit-workflows-safe CI_WATCH_PRECHECK_STRICT=0\n\n# 自动按解析后的提交 SHA 生成 readiness/watch 产物文件名\nmake watch-commit-workflows-safe-auto\n\n# 自定义产物文件名里的 SHA 长度（默认 12）\nmake watch-commit-workflows-safe-auto CI_WATCH_ARTIFACT_SHA_LEN=8\n\n# 预览命令（不执行）\nmake watch-commit-workflows CI_WATCH_PRINT_ONLY=1\n\n# 指定 SHA / 事件 / 必需工作流 / 超时\nmake watch-commit-workflows \\\n  CI_WATCH_SHA=9411c05568e11baeff28ef363fb464cfaab2195f \\\n  CI_WATCH_EVENTS=push \\\n  CI_WATCH_REQUIRED_WORKFLOWS=\"CI,CI Enhanced,CI Tiered Tests,Code Quality,Multi-Architecture Docker Build,Security Audit,Observability Checks,Self-Check,GHCR Publish,Evaluation Report\" \\\n  CI_WATCH_SUCCESS_CONCLUSIONS=\"success,skipped,neutral\" \\\n  CI_WATCH_MAX_LIST_FAILURES=3 \\\n  CI_WATCH_MISSING_REQUIRED_MODE=fail-fast \\\n  CI_WATCH_FAILURE_MODE=fail-fast \\\n  CI_WATCH_SUMMARY_JSON=reports/ci/watch_commit_summary.json \\\n  CI_WATCH_TIMEOUT=1800 \\\n  CI_WATCH_POLL_INTERVAL=20 \\\n  CI_WATCH_HEARTBEAT_INTERVAL=120 \\\n  CI_WATCH_LIST_LIMIT=100\n```\n\n说明：\n- 脚本路径：`scripts/ci/watch_commit_workflows.py`\n- `CI_WATCH_SHA` 支持 `HEAD`、短 SHA、完整 SHA；内部会通过 `git rev-parse` 解析为完整提交哈希后再匹配 workflow run。\n- 成功条件：观察到的工作流全部 `completed` 且结论均为 `success/skipped`，并满足 `CI_WATCH_REQUIRED_WORKFLOWS`。\n- 失败条件：出现非成功结论（如 `failure/cancelled/timed_out`）或超时。\n- `CI_WATCH_MISSING_REQUIRED_MODE` 支持：\n  - `fail-fast`：当已观察到的工作流都完成但必需工作流缺失时立即失败（默认）。\n  - `wait`：继续等待直到超时，适合需要等待延迟触发工作流的场景。\n- `CI_WATCH_FAILURE_MODE` 支持：\n  - `fail-fast`：检测到任意工作流出现非成功结论后立即失败（默认）。\n  - `wait-all`：等待所有工作流完成后再按最终结论返回失败。\n- `CI_WATCH_SUCCESS_CONCLUSIONS`：\n  - 逗号分隔的“视为成功”结论，默认 `success,skipped`。\n  - 如 workflow 会返回 `neutral`，可设置 `CI_WATCH_SUCCESS_CONCLUSIONS=success,skipped,neutral`。\n- `CI_WATCH_MAX_LIST_FAILURES`：\n  - 允许连续 `gh run list` 失败次数，默认 `3`。\n  - 网络抖动时 watcher 会先重试，超过阈值才失败。\n- `CI_WATCH_PRECHECK_STRICT`：\n  - `1`（默认）：`watch-commit-workflows-safe` 预检失败即终止。\n  - `0`：预检失败仅告警，继续执行 watcher（适合排障时保留后续日志）。\n- `CI_WATCH_ARTIFACT_SHA_LEN`：\n  - `watch-commit-workflows-safe-auto` 产物文件名使用的 SHA 前缀长度，默认 `12`。\n  - 设为 `0` 表示使用完整 40 位 SHA。\n- `CI_WATCH_SUMMARY_JSON`：\n  - 可选；设置后会输出机器可读 JSON 总结（包含最终 reason、counts、missing_required、runs 快照）。\n  - 适合与本地脚本或报告流水线联动。\n  - 默认建议写到 `reports/ci/`，该目录下的 `*.json` 被视为运行产物并默认忽略提交。\n- `CI_WATCH_HEARTBEAT_INTERVAL`：\n  - 默认 `120` 秒；当状态长时间无变化时输出心跳日志，避免误判“卡住”。\n  - 设为 `0` 可禁用心跳日志。\n- 对于可能按路径/条件触发的工作流（例如 `Stress and Observability Checks`），建议按需追加到 `CI_WATCH_REQUIRED_WORKFLOWS`，避免 docs-only 提交出现“缺失必需工作流”的误等待。\n- 若 watcher 报 `gh auth is not ready`，请先执行 `gh auth login -h github.com` 重新认证后再重试。\n\n回归校验：\n```bash\nmake validate-watch-commit-workflows\n```\n\n环境诊断（gh CLI / 认证 / Actions API）：\n```bash\nmake check-gh-actions-ready\n```\n- 软模式（总是返回 0，适合与非严格流水线结合）：\n```bash\nmake check-gh-actions-ready-soft\n```\n- `GH_READY_JSON`：预检 JSON 输出路径（默认 `reports/ci/gh_readiness_latest.json`）。\n- `GH_READY_SKIP_ACTIONS_API=1`：跳过 `gh run list` 连通性检查，仅验证 gh CLI + auth。\n\n清理 watcher 运行产物：\n```bash\nmake clean-ci-watch-summaries\nmake clean-gh-readiness-summaries\nmake clean-ci-watch-artifacts\n```\n\n生成 CI 验证报告（Markdown）：\n```bash\n# 自动选取最新 watch_commit_*_summary.json\nmake generate-ci-watch-validation-report\n\n# 指定 summary/readiness 与输出路径\nmake generate-ci-watch-validation-report \\\n  CI_WATCH_REPORT_SUMMARY_JSON=reports/ci/watch_commit_\u003csha\u003e_summary.json \\\n  CI_WATCH_REPORT_READINESS_JSON=reports/ci/gh_readiness_watch_\u003csha\u003e.json \\\n  CI_WATCH_REPORT_OUTPUT_MD=reports/DEV_CI_WATCHER_SAFE_AUTO_SUCCESS_VALIDATION_\u003cSHA7\u003e_YYYYMMDD.md\n```\n- `CI_WATCH_REPORT_SHA_LEN`：默认 `7`，用于自动输出文件名中的 SHA 前缀长度。\n- `CI_WATCH_REPORT_DATE`：默认当天（`YYYYMMDD`）。\n\n合并回归：\n```bash\nmake validate-ci-watchers\n```\n该目标会串行执行：\n- `make validate-check-gh-actions-ready`\n- `make validate-watch-commit-workflows`\n- `make validate-generate-ci-watch-validation-report`\n- `make validate-archive-workflow-dispatcher`\n\n---\n\n## 🔬 评估与可观测性\n\n### 完整评估系统\n\n我们构建了一个企业级的评估监控系统，提供全面的质量保证和可观测性：\n\n#### 核心功能\n- **联合评估**: Vision + OCR 加权评分系统\n- **数据完整性**: SHA-384 哈希验证，Schema v1.0.0 规范\n- **自动报告**: 静态/交互式 HTML 报告，Chart.js 可视化\n- **数据保留**: 5层保留策略（7天全量→30天每日→90天每周→365天每月→永久季度）\n- **版本监控**: 自动依赖更新检查，安全警报\n- **CI/CD集成**: GitHub Actions 自动化流水线\n\n#### Grafana Dashboard (CAD Analysis)\n新增仪表盘文件: `config/grafana/dashboard_cad_analysis_metrics.json`\n\n包含面板:\n- 成功率: `sum(analysis_requests_total{status='success'}) / sum(analysis_requests_total)`\n- 解析/特征提取阶段平均耗时 (ms) 使用 `rate(..._sum)/rate(..._count)`\n- 阶段耗时 p95: `histogram_quantile(0.95, sum by (le, stage)(rate(analysis_stage_duration_seconds_bucket[5m])))`\n- 实体数限制拒绝计数: `analysis_rejections_total{reason='entity_count_exceeded'}`\n- 特征向量维度分布: `analysis_feature_vector_dimension_bucket`\n- 错误码 TopK: `topk(10, rate(analysis_error_code_total[5m]))`\n- 材料使用速率: `sum by (material)(rate(analysis_material_usage_total[5m]))`\n\n导入步骤:\n1. Grafana UI -\u003e Dashboards -\u003e Import\n2. 粘贴 JSON 或选择文件 `dashboard_cad_analysis_metrics.json`\n3. 选择 Prometheus 数据源\n4. 保存并设置刷新间隔 (推荐 30s)\n\n建议告警规则:\n- 成功率 \u003c 90% 连续 5m\n- p95(parse) \u003e 500ms 连续 10m\n- 错误码 `INTERNAL_ERROR` rate \u003e 5/min 连续 5m\n- Rejections spike: `increase(analysis_rejections_total[10m]) \u003e 50`\n\n#### 快速开始\n\n```bash\n# 运行评估\nmake eval                    # 执行 Vision+OCR 联合评估\n\n---\n\n## 🔥 压力测试脚本 (Stress Test Scripts)\n\n- `scripts/stress_concurrency_reload.py`：并发触发模型重载，验证 `_MODEL_LOCK` 串行化与 `load_seq` 单调性。\n- `scripts/stress_memory_gc_check.py`：在合成负载下检查内存与 GC 稳定性。\n- `scripts/stress_degradation_flapping.py`：降级/恢复抖动观测；验证历史记录上限（≤10）与降级持续时间指标。\n\n## CI：压力与可观测性工作流\n\n- GitHub Actions 工作流 `/.github/workflows/stress-tests.yml` 会执行：\n  - 指标导出校验：`scripts/verify_metrics_export.py`，确保 `src/utils/analysis_metrics.py::__all__` 包含必需指标。\n  - Prometheus 规则验证：使用 `promtool` 检查 `prometheus/rules/cad_ml_phase5_alerts.yaml`。\n  - Grafana 仪表盘 JSON 语法校验：`grafana/dashboards/observability.json`。\n  - 目标测试集：v4 延迟指标、降级健康响应、迁移预览统计。\n\n## 恢复持久化与健康字段\n\n- 健康端点 (`GET /api/v1/health/faiss/health`) 暴露：\n  - `degraded`：是否处于降级状态\n  - `degradation_history_count`：历史事件数量（上限 10）\n  - `next_recovery_eta`：下一次自动恢复尝试的时间戳（epoch 秒）\n  - `manual_recovery_in_progress`：手动恢复协调标志\n- 恢复状态持久化支持文件与可选 Redis 后端：\n  - `FAISS_RECOVERY_STATE_BACKEND`：`file` 或 `redis`\n  - `FAISS_RECOVERY_STATE_PATH`：当为 `file` 时的持久化路径\n\n---\n\n## 📎 开发计划与报告\n\n参阅以下文档以获取分阶段路线图、变更日志与验收标准：\n- `docs/DETAILED_DEVELOPMENT_PLAN.md`\n- `docs/DEVELOPMENT_REPORT_FINAL.md`\n- `docs/DEVELOPMENT_SUMMARY_FINAL.md`\n\n## 🔥 压力测试脚本 (Stress Test Scripts)\n\n- `scripts/stress_concurrency_reload.py`: 并发触发 `/api/v1/model/reload` 验证 `_MODEL_LOCK` 串行化与 `load_seq` 单调性。\n- `scripts/stress_memory_gc_check.py`: 内存/GC 压力检测，输出 RSS 与 GC 时延概览。\n- `scripts/stress_degradation_flapping.py`: 降级状态翻转观测，检查历史上限（≤10）与降级持续时间指标。\n- 集成测试：`tests/integration/test_stress_stability.py` 提供端到端压力场景覆盖。\n\n## 📈 可观测性资产\n\n- Prometheus 告警规则：`prometheus/rules/cad_ml_phase5_alerts.yaml`（降级/恢复、安全、缓存、特征、压力、迁移）。\n- Grafana 仪表盘：`grafana/dashboards/observability.json`（向量存储健康、特征提取、缓存性能、模型安全、迁移面板）。\n- 指标一致性校验：`scripts/verify_metrics_export.py` 校验关键指标是否在 `src/utils/analysis_metrics.py::__all__` 中导出。\n\n## 🛠 恢复持久化与健康 ETA\n\n- 恢复状态后端：`FAISS_RECOVERY_STATE_BACKEND` 可选 `file`|`redis`，默认 `file`。\n- 文件落盘路径：`FAISS_RECOVERY_STATE_PATH` 持久化回退/ETA 状态。\n- 健康端点暴露 `next_recovery_eta` 与 `manual_recovery_in_progress`。\n- 抖动抑制参数：`FAISS_RECOVERY_FLAP_THRESHOLD`、`FAISS_RECOVERY_FLAP_WINDOW_SECONDS`、`FAISS_RECOVERY_SUPPRESSION_SECONDS`。\n\n### 恢复抑制 (Flapping Protection)\n\n当在 `FAISS_RECOVERY_FLAP_WINDOW_SECONDS` 时间窗口内降级事件次数 ≥ `FAISS_RECOVERY_FLAP_THRESHOLD`，系统进入抑制窗口（持续 `FAISS_RECOVERY_SUPPRESSION_SECONDS` 秒），跳过自动恢复以避免频繁重建与资源抖动。\n\n环境变量 (默认值)：\n\n| 变量 | 默认 | 说明 |\n|------|------|------|\n| `FAISS_RECOVERY_FLAP_THRESHOLD` | 3 | 在窗口内触发抑制的降级事件阈值 |\n| `FAISS_RECOVERY_FLAP_WINDOW_SECONDS` | 900 | 统计降级事件的滚动窗口 (秒) |\n| `FAISS_RECOVERY_SUPPRESSION_SECONDS` | 300 | 抑制窗口持续时间 |\n| `FAISS_RECOVERY_INTERVAL_SECONDS` | 300 | 正常恢复基础间隔 |\n| `FAISS_RECOVERY_MAX_BACKOFF` | 3600 | 恢复最大退避上限 |\n| `FAISS_RECOVERY_BACKOFF_MULTIPLIER` | 2 | 失败退避乘数 |\n| `FAISS_RECOVERY_STATE_BACKEND` | file | 状态持久化后端 (file 或 redis) |\n| `FAISS_RECOVERY_STATE_PATH` | data/faiss_recovery_state.json | file 后端存储路径 |\n\n关键指标：\n\n| 指标 | 类型 | 描述 |\n|------|------|------|\n| `faiss_recovery_suppressed_total{reason=\"flapping\"}` | Counter | 因抖动被抑制的恢复尝试次数 |\n| `faiss_recovery_attempts_total{result=\"suppressed\"}` | Counter | 被抑制跳过的恢复尝试（细分自 skipped） |\n| `faiss_next_recovery_eta_seconds` | Gauge | 下次计划自动恢复时间戳 (成功恢复或未调度时为 0) |\n| `faiss_degraded_duration_seconds` | Gauge | 当前降级持续秒数 (健康为 0) |\n\nRunbook: 详见 `docs/RUNBOOK_FLAPPING.md`（快速诊断、阈值调整、手动恢复、持久化校验）。\n\n### Redis 恢复后端配置\n\n将恢复状态持久化到 Redis 以在多副本之间共享：\n\n```bash\nexport FAISS_RECOVERY_STATE_BACKEND=redis\nexport REDIS_URL=redis://127.0.0.1:6379/0\n```\n\n当 Redis 不可用时自动回退到 `file` 后端（`FAISS_RECOVERY_STATE_PATH`）。\n\n# 生成报告\nmake eval-report-v2          # 生成交互式报告（推荐）\nmake eval-report            # 生成静态报告（备用）\n\n# 系统健康\nmake health-check           # 完整系统健康检查\nmake integrity-check        # 文件完整性验证\n\n# 数据管理\nmake eval-history           # 查看历史趋势\nmake eval-retention         # 应用保留策略\n```\n\n#### 评估公式\n```\nCombined Score = 0.5 × Vision + 0.5 × OCR_normalized\nOCR_normalized = OCR_Recall × (1 - Brier_Score)\n```\n\n#### 配置管理\n所有配置集中在 `config/eval_frontend.json`：\n- Chart.js 版本锁定 (4.4.0)\n- SHA-384 完整性校验\n- 5层数据保留策略\n- Schema 验证规则\n\n#### 测试套件\n\n```bash\n# 单元测试套件\npython3 scripts/test_eval_system.py --verbose\n\n# 完整集成测试\npython3 scripts/run_full_integration_test.py\n```\n\n详细文档：[评估系统完整指南](docs/EVALUATION_SYSTEM_COMPLETE.md)\n\n#### 健康检查与指标\n\n- 健康端点：`GET /health`\n\n---\n\n## 🧩 新增与扩展功能总览 (Recent Additions)\n\n### 🔢 特征版本枚举端点\n`GET /api/v1/features/versions`\n\n返回所有已知特征版本的维度与稳定性：\n```json\n{\n  \"status\": \"ok\",\n  \"versions\": [\n    {\"version\": \"v1\", \"dimension\": 7,  \"stable\": true,  \"experimental\": false},\n    {\"version\": \"v2\", \"dimension\": 12, \"stable\": true,  \"experimental\": false},\n    {\"version\": \"v3\", \"dimension\": 23, \"stable\": true,  \"experimental\": false},\n    {\"version\": \"v4\", \"dimension\": 25, \"stable\": false, \"experimental\": true}\n  ]\n}\n```\n说明：v4 为实验版本 (surface_count + shape_entropy)，仅在显式指定 `FEATURE_VERSION=v4` 或请求参数 `version=v4` 时生效。\n\n### 🧪 特征槽位查询\n`GET /api/v1/features/slots?version=v3`\n返回该版本所有槽位名称/类别/版本标签。失败示例（不支持版本）：HTTP 422 + `{ \"code\":\"INPUT_VALIDATION_FAILED\", \"stage\":\"feature_slots\" }`。\n\n### 🛡️ 模型健康与安全\n`GET /api/v1/health/model`\n示例响应：\n```json\n{\n  \"status\": \"ok\",\n  \"version\": \"v2\",\n  \"hash\": \"abcd1234ef567890\",\n  \"path\": \"models/classifier.pkl\",\n  \"loaded\": true,\n  \"loaded_at\": 1732464000.123,\n  \"uptime_seconds\": 12.45\n}\n```\n\n模型热重载安全流程 (`POST /api/v1/model/reload`)：\n1. 大小限制校验 (`MODEL_MAX_MB`)\n2. Magic Number / Pickle 协议验证\n3. Hash 白名单 (`ALLOWED_MODEL_HASHES`)\n4. Opcode 扫描（阻断 `GLOBAL` / `STACK_GLOBAL` / `REDUCE`）可通过 `MODEL_OPCODE_SCAN=0` 关闭；`MODEL_OPCODE_STRICT=1` 为严格模式\n5. 接口验证（必须存在 `predict` 方法）\n6. 失败回滚（一级/二级）\n\n安全失败指标：`model_security_fail_total{reason=\"magic_number_invalid|hash_mismatch|opcode_blocked|opcode_scan_error|forged_file\"}`。\n\n### 📦 批量相似度查询\n`POST /api/v1/vectors/similarity/batch`\n请求体：\n```json\n{\n  \"ids\": [\"vecA\",\"vecB\"],\n  \"top_k\": 5,\n  \"material\": \"steel\",\n  \"complexity\": \"high\",\n  \"format\": \"dxf\",\n  \"min_score\": 0.4\n}\n```\n限制：最大 ID 数量 `BATCH_SIMILARITY_MAX_IDS` (默认 200)，超过返回 422：\n```json\n{\"code\":\"INPUT_VALIDATION_FAILED\",\"stage\":\"batch_similarity\",\"message\":\"Batch size exceeds limit\",\"batch_size\":350,\"max_batch\":200}\n```\n指标：`vector_query_batch_latency_seconds{batch_size_range=\"small|medium|large\"}`，`analysis_rejections_total{reason=\"batch_too_large\"}`，`analysis_rejections_total{reason=\"batch_empty_results\"}`。\n响应字段：`fallback` 表示向量后端降级 (Faiss 不可用或处于 degraded)，与 `degraded` 一致。\n\n### 🔄 向量迁移摘要\n`GET /api/v1/vectors/migrate/summary`\n示例：\n```json\n{\n  \"counts\": {\"migrated\": 30, \"dry_run\": 5, \"downgraded\": 2, \"error\": 1, \"not_found\": 3, \"skipped\": 8},\n  \"total_migrations\": 49,\n  \"history_size\": 10,\n  \"statuses\": [\"dry_run\",\"downgraded\",\"error\",\"migrated\",\"not_found\",\"skipped\"]\n}\n```\n指标：`vector_migrate_total{status=\"migrated|dry_run|downgraded|error|not_found|skipped\"}`。\n\n### 🌊 Drift 基线监控与自动刷新\n端点：`/api/v1/analyze/drift`、`/api/v1/analyze/drift/baseline/status`、`/api/v1/analyze/drift/reset`\n自动刷新：基线年龄超出 `DRIFT_BASELINE_MAX_AGE_SECONDS` 且样本数 ≥ `DRIFT_BASELINE_MIN_COUNT` 时软刷新并记录 `drift_baseline_refresh_total{trigger=\"stale\"}`。\n启动首次访问标记：`drift_baseline_refresh_total{trigger=\"startup\"}`。\n手动重置：`drift_baseline_refresh_total{trigger=\"manual\"}`。\n\n### 🧪 新增指标 (Recent Metrics)\n| 名称 | 类型 | 说明 |\n|------|------|------|\n| feature_extraction_latency_seconds{version} | Histogram | 按版本特征提取延迟 |\n| vector_query_batch_latency_seconds{batch_size_range} | Histogram | 批量相似度延迟 |\n| model_security_fail_total{reason} | Counter | 模型安全校验失败原因 |\n| model_health_checks_total{status} | Counter | 模型健康端点访问统计 |\n| vector_store_reload_total{status} | Counter | 向量后端重载结果 |\n| drift_baseline_refresh_total{type,trigger} | Counter | Drift 基线刷新事件 |\n| vector_migrate_dimension_delta | Histogram | 迁移维度差 (新维度-旧维度) 分布监控 |\n| similarity_degraded_total{event} | Counter | Faiss 降级与恢复事件 (degraded|restored) |\n\nPromQL 示例：\n```promql\nhistogram_quantile(0.95, sum by (le,version)(rate(feature_extraction_latency_seconds_bucket[5m])))\nhistogram_quantile(0.99, sum by (le)(rate(vector_query_batch_latency_seconds_bucket[5m])))\nsum(rate(vector_migrate_total{status=\"migrated\"}[10m])) / sum(rate(vector_migrate_total[10m]))\nbaseline_material_age_seconds \u003e bool DRIFT_BASELINE_MAX_AGE_SECONDS\n```\n\n### 🧷 结构化 410 废弃端点错误\n```json\n{\n  \"code\": \"GONE\",\n  \"stage\": \"routing\",\n  \"message\": \"Endpoint moved. Please use GET /api/v1/vectors_stats/distribution\",\n  \"deprecated_path\": \"/api/v1/analyze/vectors/distribution\",\n  \"new_path\": \"/api/v1/vectors_stats/distribution\",\n  \"method\": \"GET\",\n  \"migration_date\": \"2024-11-24\"\n}\n```\n\n### 🧨 错误响应统一格式\n字段：`code`, `stage`, `message` (+上下文键)。常见 stage：`routing`/`batch_similarity`/`vector_migrate`/`feature_slots`/`model_reload`/`security`/`drift`。\n\n### 🛠 新增环境变量\n| 变量 | 用途 | 默认 |\n|------|------|------|\n| FEATURE_VERSION | 默认特征版本 | v1 |\n| BATCH_SIMILARITY_MAX_IDS | 批量相似度最大ID数 | 200 |\n| MODEL_MAX_MB | 模型文件大小上限(MB) | 50 |\n| ALLOWED_MODEL_HASHES | 模型哈希白名单 | 空 |\n| MODEL_OPCODE_SCAN | 是否执行 opcode 安全扫描 | 1 |\n| MODEL_OPCODE_STRICT | 扫描异常是否阻断 | 0 |\n| DRIFT_BASELINE_MIN_COUNT | Drift 基线最小样本数 | 100 |\n| DRIFT_BASELINE_MAX_AGE_SECONDS | Drift 基线最大年龄 | 86400 |\n| DRIFT_BASELINE_AUTO_REFRESH | 是否自动刷新过期基线 | 1 |\n| GRAPH2D_MIN_CONF | Graph2D 最小置信度门控 (低于阈值不参与融合) | 0.6 |\n| GRAPH2D_EXCLUDE_LABELS | Graph2D 融合排除标签（逗号分隔） | other |\n| GRAPH2D_ALLOW_LABELS | Graph2D 融合白名单标签（逗号分隔，优先生效） | 空 |\n\n### 🔐 安全建议\n- 生产环境配置并收敛 `ALLOWED_MODEL_HASHES`。\n- 高安全要求启用 `MODEL_OPCODE_STRICT=1`。\n- 监控 `model_security_fail_total` 异常增长（可能表示供应链或文件投毒）。\n\n### 📌 Roadmap 摘要\n- v4 特征真实化：精细 surface_count、熵计算优化。\n- 自适应缓存调优端点：推荐容量/TTL。\n- Opcode 白名单模式强化。\n- 批量相似度并行加速与 savings 指标。\n\n---\n  - `runtime.metrics_enabled`: Prometheus 导出是否启用\n  - `runtime.python_version`: 运行 Python 版本\n  - `runtime.vision_max_base64_bytes`: Vision Base64 输入大小上限（字节）\n  - `runtime.error_rate_ema.ocr|vision`: OCR/Vision 错误率的指数移动平均（0..1）\n  - `runtime.config.error_ema_alpha`: EMA 平滑系数，环境变量 `ERROR_EMA_ALPHA` 可配置\n\n- 关键指标（部分）：\n  - `vision_requests_total{provider,status}`、`vision_errors_total{provider,code}`\n  - `vision_processing_duration_seconds{provider}`\n  - `vision_input_rejected_total{reason}`、`vision_image_size_bytes`\n  - `ocr_requests_total{provider,status}`、`ocr_errors_total{provider,code,stage}`\n  - `ocr_input_rejected_total{reason}`、`ocr_image_size_bytes`\n    - 常见 OCR `reason`：`invalid_mime`、`file_too_large`、`pdf_pages_exceed`、`pdf_forbidden_token`\n  - `ocr_confidence_ema`、`ocr_confidence_fallback_threshold`\n\n统一错误模型：所有错误以 HTTP 200 返回 `{ success: false, code: ErrorCode, error: string }`。\n\n示例（输入过大）：\n```bash\ncurl -s http://localhost:8000/api/v1/vision/analyze \\\n  -H 'Content-Type: application/json' \\\n  -d '{\"image_base64\": \"\u003cvery_large\u003e\", \"include_description\": false}' | jq\n```\n\n### CI \u0026 安全工作流\n\n```yaml\n关键工作流：\n- `.github/workflows/ci.yml` 分离 `lint-type` 与测试矩阵 (3.10/3.11)\n- `.github/workflows/security-check.yml` 每周安全审计（基于 `scripts/security_audit.py` 退出码）\n- `.github/workflows/badge-review.yml` 每月自动阈值分析与建议 Issue\n - 新增非阻断 `lint-all-report`，上传全仓 flake8 报告工件\n```\n\n---\n\n## 📚 API文档\n\n### 🔄 接口迁移与废弃策略\n\n为提升系统可维护性，部分端点已迁移到新路径。旧端点返回 **HTTP 410 Gone** 状态码，并提供结构化迁移信息。\n\n#### 废弃端点列表\n\n| 废弃端点 (旧路径) | 新端点路径 | HTTP方法 | 迁移日期 | 状态码 |\n|------------------|-----------|---------|---------|--------|\n| `/api/v1/analyze/vectors/distribution` | `/api/v1/vectors_stats/distribution` | GET | 2024-11-24 | 410 |\n| `/api/v1/analyze/vectors/delete` | `/api/v1/vectors` (DELETE方法) | POST | 2024-11-24 | 410 |\n| `/api/v1/analyze/vectors` | `/api/v1/vectors` | GET | 2024-11-24 | 410 |\n| `/api/v1/analyze/vectors/stats` | `/api/v1/vectors_stats/summary` | GET | 2024-11-24 | 410 |\n| `/api/v1/analyze/features/diff` | `/api/v1/features/diff` | GET | 2024-11-24 | 410 |\n| `/api/v1/analyze/model/reload` | `/api/v1/model/reload` | POST | 2024-11-24 | 410 |\n| `/api/v1/analyze/features/cache` | `/api/v1/maintenance/stats` | GET | 2024-11-24 | 410 |\n| `/api/v1/analyze/faiss/health` | `/api/v1/health/faiss` | GET | 2024-11-24 | 410 |\n\n#### 错误响应格式\n\n废弃端点返回统一的结构化错误：\n\n```json\n{\n  \"detail\": {\n    \"code\": \"GONE\",\n    \"message\": \"Endpoint moved. Please use GET /api/v1/vectors_stats/distribution\",\n    \"stage\": \"routing\",\n    \"deprecated_path\": \"/api/v1/analyze/vectors/distribution\",\n    \"new_path\": \"/api/v1/vectors_stats/distribution\",\n    \"method\": \"GET\",\n    \"migration_date\": \"2024-11-24\"\n  }\n}\n```\n\n**错误码说明**：\n- `GONE`：资源已永久移除（对应 HTTP 410）\n- `severity`: INFO 级别（非错误，而是提示迁移）\n\n#### 迁移指南\n\n1. **立即行动**：更新客户端代码使用新端点路径\n2. **双写验证**：迁移后可暂时并行调用新旧端点比对响应（旧端点仅返回410元数据）\n3. **监控迁移**：使用 PromQL 监控 `rate(analysis_error_code_total{code=\"GONE\"}[5m])` 下降趋势，判断迁移完成度。\n\n### 🧩 模块结构与特征版本迁移\n\n主要API模块 (src/api/v1)：\n\n| 模块 | 功能 |\n|------|------|\n| analyze.py | 保留废弃端点的410结构化提示 |\n| vectors.py | 向量 CRUD / 更新 / 迁移 / 批量相似度 |\n| vectors_stats.py | 向量统计与分布查询 |\n| features.py | 特征差异比较 / 槽位枚举 `/features/slots` |\n| drift.py | 漂移基线与自动刷新控制 |\n| model.py | 模型热加载与版本/哈希查询、安全校验 |\n| maintenance.py | 孤儿向量清理 / 缓存管理 / 系统维护统计 |\n| process.py | 工艺规则审计与版本指标 |\n| health.py | Faiss / feature cache 健康状态 |\n| materials.py | 材料数据库查询 / 推荐 / 成本 / 兼容性 |\n\n材料 API 说明：\n- `GET /api/v1/materials/cost/search` 支持 `include_estimated=true`（按材料组估算成本）\n- `POST /api/v1/materials/cost/compare` 返回 `missing` 列表（未命中牌号）\n\n特征版本 (Feature Version) 演进：\n\n| 版本 | 新增槽位 | 描述 |\n|------|---------|------|\n| v1 | 基础7槽位 | 实体计数 + bbox尺寸/体积 + 图层/复杂度标志 |\n| v2 | 5槽位 | 归一化宽高深 + 宽高比 + 宽深比 |\n| v3 | 11槽位 | 几何增强 (solids/facets/比率/平均体积) + Top5实体类型频率 |\n| v4 | 2槽位 | surface_count（真实几何面数估计）+ shape_entropy（拉普拉斯平滑并归一化至[0,1]） |\n\n迁移端点：`POST /api/v1/vectors/migrate`\n\n\u003e v4 现已实现真实特征：`surface_count` 与 `shape_entropy`（拉普拉斯平滑并归一化）。仍建议在充分评估后再设为默认；设置 `FEATURE_VERSION=v4` 或迁移到 `to_version=\"v4\"` 将追加这两个槽位。\n\n请求示例（干运行 dry_run）：\n```bash\ncurl -X POST /api/v1/vectors/migrate \\\n  -H 'Content-Type: application/json' \\\n  -H 'x-api-key: test' \\\n  -d '{\"ids\":[\"id1\",\"id2\"],\"to_version\":\"v3\",\"dry_run\":true}'\n```\n\n响应示例：\n```json\n{\n  \"total\": 2,\n  \"migrated\": 0,\n  \"skipped\": 1,\n  \"items\": [\n    {\"id\": \"id1\", \"status\": \"dry_run\", \"from_version\": \"v1\", \"to_version\": \"v3\", \"dimension_before\": 7, \"dimension_after\": 23},\n    {\"id\": \"id2\", \"status\": \"skipped\", \"from_version\": \"v3\", \"to_version\": \"v3\"}\n  ],\n  \"migration_id\": \"...\",\n  \"started_at\": \"...\",\n  \"finished_at\": \"...\",\n  \"dry_run_total\": 1\n}\n```\n\n特征维度对照：\n\n| 版本 | 总维度 (geometric+semantic) |\n|------|---------------------------|\n| v1 | 7 |\n| v2 | 12 |\n| v3 | 23 |\n| v4 | 24 |\n\n降级与迁移状态说明：\n- `migrated`: 版本提升或同向调整\n- `skipped`: 已是目标版本\n- `dry_run`: 模拟迁移不写入\n- `downgraded`: 目标版本低于源版本（保留向后兼容）\n- `error`: 转换异常（长度不匹配等）\n- `not_found`: 向量ID不存在\n\n\n升级策略（无原始文档时 `upgrade_vector` 行为）：\n- v1→v2: 追加5个归一化与比率槽位（0填充）\n- v1→v3: 追加 v2 槽位 + 11 个增强槽位（0填充）\n- v2→v3: 追加 11 个增强槽位（0填充）\n- v3→v2: 截断 v3 扩展槽位（可能丢失信息）\n\n指标：\n- `vector_migrate_total{status=\"migrated|skipped|dry_run|error|not_found\"}`\n  - 现已扩展支持 `downgraded` 状态，用于版本降级（例如 v3 -\u003e v2 或 v2 -\u003e v1）。\n    监控示例：`sum(rate(vector_migrate_total{status=\"downgraded\"}[5m]))` 评估降级频率。\n- 干运行比率：`rate(vector_migrate_total{status=\"dry_run\"}[5m]) / rate(vector_migrate_total[5m])`\n- 历史记录扩展字段 `counts`（见响应示例）提供各状态精细统计：`migrated|skipped|dry_run|downgraded|error|not_found`\n\nPromQL p95 批量相似度延迟：\n```promql\nhistogram_quantile(0.95, sum by (le, batch_size_range)(rate(vector_query_batch_latency_seconds_bucket[5m])))\n```\n\n### 模型健康端点\n\n`GET /api/v1/health/model` 提供当前模型加载状态与元数据，包括回滚状态和错误追踪。\n\n**响应字段说明:**\n\n| 字段 | 类型 | 说明 |\n|------|------|------|\n| `status` | string | 健康状态: `ok` (正常), `absent` (未加载), `rollback` (已回滚), `error` (错误) |\n| `version` | string | 模型版本号 |\n| `hash` | string | 模型文件 SHA256 哈希值 (前16位) |\n| `path` | string | 模型文件路径 |\n| `loaded` | boolean | 是否已加载 |\n| `loaded_at` | float | 加载时间戳 (Unix时间) |\n| `uptime_seconds` | float | 模型运行时长（秒） |\n| `rollback_level` | int | 回滚级别: `0` (无回滚), `1` (一级回滚), `2` (二级回滚) |\n| `rollback_reason` | string\\|null | 回滚原因描述 |\n| `last_error` | string\\|null | 最近一次加载错误信息 |\n| `load_seq` | int | 单调递增的加载序列号（用于区分不同加载实例） |\n\n**示例响应 - 正常状态:**\n```json\n{\n  \"status\": \"ok\",\n  \"version\": \"v2.1.0\",\n  \"hash\": \"abcd1234ef567890\",\n  \"path\": \"models/classifier_v2.1.pkl\",\n  \"loaded\": true,\n  \"loaded_at\": 1732464000.123,\n  \"uptime_seconds\": 3600.5,\n  \"rollback_level\": 0,\n  \"rollback_reason\": null,\n  \"last_error\": null,\n  \"load_seq\": 5\n}\n```\n\n**示例响应 - 回滚状态:**\n```json\n{\n  \"status\": \"rollback\",\n  \"version\": \"v2.0.0\",\n  \"hash\": \"def567890abc1234\",\n  \"path\": \"models/classifier_v2.0.pkl\",\n  \"loaded\": true,\n  \"loaded_at\": 1732464100.456,\n  \"uptime_seconds\": 300.2,\n  \"rollback_level\": 1,\n  \"rollback_reason\": \"Rolled back to previous model after reload failure\",\n  \"last_error\": \"Security validation failed: disallowed pickle opcode REDUCE detected\",\n  \"load_seq\": 4\n}\n```\n\n**示例响应 - 二级回滚状态:**\n```json\n{\n  \"status\": \"rollback\",\n  \"version\": \"v1.9.0\",\n  \"hash\": \"ghi789012def3456\",\n  \"path\": \"models/classifier_v1.9.pkl\",\n  \"loaded\": true,\n  \"loaded_at\": 1732463800.789,\n  \"uptime_seconds\": 600.8,\n  \"rollback_level\": 2,\n  \"rollback_reason\": \"Rolled back to level 2 snapshot after consecutive failures\",\n  \"last_error\": \"Model missing predict method\",\n  \"load_seq\": 3\n}\n```\n\n**回滚机制:**\n- 系统维护3个模型快照：当前、前一版本(_PREV)、前两版本(_PREV2)\n- 当模型重载失败时，自动回滚到前一可用版本\n- 连续失败可回滚到二级快照（rollback_level=2）\n- 回滚后 `status` 变为 `\"rollback\"`，`last_error` 保留失败信息\n- `load_seq` 在回滚时保持不变（保留成功加载时的序列号）\n\n**指标:** `model_health_checks_total{status=\"ok|absent|rollback|error\"}`\n\n### 批量相似度查询限制\n\n环境变量 `BATCH_SIMILARITY_MAX_IDS` 控制单次批量查询最大 ID 数（默认 200）。\n超过限制返回 422：\n```json\n{\n  \"code\": \"INPUT_VALIDATION_FAILED\",\n  \"stage\": \"batch_similarity\",\n  \"message\": \"Batch size exceeds limit\",\n  \"batch_size\": 350,\n  \"max_batch\": 200\n}\n```\n拒绝计数：`analysis_rejections_total{reason=\"batch_too_large\"}`。\n\n特征槽位枚举：\n```bash\ncurl /api/v1/features/slots?version=v3 -H 'x-api-key: test'\n```\n示例响应：\n```json\n{\n  \"version\": \"v3\",\n  \"status\": \"ok\",\n  \"slots\": [\n    {\"name\": \"entity_count\", \"category\": \"geometric\", \"version\": \"v1\"},\n    {\"name\": \"bbox_width\", \"category\": \"geometric\", \"version\": \"v1\"},\n    {\"name\": \"norm_width\", \"category\": \"geometric\", \"version\": \"v2\"},\n    {\"name\": \"solids_count\", \"category\": \"geometric\", \"version\": \"v3\"}\n  ]\n}\n```\n2. **兼容期**：废弃端点将保持410响应至少6个月\n3. **监控**：通过 Prometheus 指标 `http_410_responses_total` 监控旧端点使用情况\n4. **测试覆盖**：所有废弃端点均有完整的测试覆盖（见 `tests/unit/test_deprecated_endpoints_410.py`）\n\n---\n\n### 📈 PromQL 示例（可直接用于 Grafana）\n\n- Vision 输入拒绝占比（5分钟窗）：\n  - sum(rate(vision_input_rejected_total[5m])) / sum(rate(vision_requests_total[5m]))\n\n- Vision 图像大小 P99（5分钟窗）：\n  - histogram_quantile(0.99, rate(vision_image_size_bytes_bucket[5m]))\n\n- OCR Provider Down 速率（每提供商）：\n  - sum by (provider) (rate(ocr_errors_total{code=\"provider_down\"}[5m]))\n\n- 错误率 EMA：\n  - vision_error_rate_ema\n  - ocr_error_rate_ema\n\nGrafana 面板示例：见 `docs/grafana/observability_dashboard.json`（导入到 Grafana 即可）。\n\n### 📟 Runbooks \u0026 Alerts\n\n- Prometheus 告警规则样例：`docs/ALERT_RULES.md`\n- 运行手册（排障指南）：\n  - 错误率 EMA 升高：`docs/runbooks/ocr_vision_error_rate_ema.md`\n  - 输入拒绝激增：`docs/runbooks/input_rejections_spike.md`\n  - Provider 宕机：`docs/runbooks/provider_down.md`\n  - 熔断器打开：`docs/runbooks/circuit_open.md`\n  - 分析结果落盘清理：`docs/runbooks/analysis_result_store_cleanup.md`\n\n### ⚙️ 配置速查表（.env）\n\n- `VISION_MAX_BASE64_BYTES`：Vision Base64 输入大小上限（字节，默认 1048576）。\n- `ERROR_EMA_ALPHA`：错误率 EMA 平滑因子（0\u003calpha\u003c=1，默认 0.2）。\n- `OCR_MAX_PDF_PAGES`：OCR PDF 最大页数（默认 20）。\n- `OCR_MAX_FILE_MB`：OCR 上传文件大小上限（MB，默认 50）。\n- `DEEPSEEK_HF_REVISION`：DeepSeek HF 模型固定版本（提交哈希，\u003e=7 位十六进制）。\n- `DEEPSEEK_HF_ALLOW_UNPINNED`：允许未固定版本下载（1=允许；默认禁用）。\n- `DEEPSEEK_HF_MODEL`：DeepSeek HF 模型仓库名（例如 `deepseek-ai/DeepSeek-OCR`）。\n- `TELEMETRY_MQTT_ENABLED`：是否启用 MQTT 遥测接入（默认 false）。\n- `MQTT_HOST`/`MQTT_PORT`/`MQTT_TOPIC`：MQTT Broker 连接参数。\n- `TELEMETRY_STORE_BACKEND`：遥测存储后端（memory|influx|timescale|none）。\n- `ANALYSIS_RESULT_STORE_DIR`：分析结果落盘目录（可选；启用后历史查询在缓存 miss 时回读）。\n- `ANALYSIS_RESULT_STORE_TTL_SECONDS`：分析结果落盘保留时长（秒，\u003c=0 表示不启用）。\n- `ANALYSIS_RESULT_STORE_MAX_FILES`：分析结果落盘最大保留数量（\u003c=0 表示不启用）。\n- `ANALYSIS_RESULT_CLEANUP_INTERVAL_SECONDS`：分析结果定时清理间隔（秒，0=关闭）。\n\n### 可选模块：Digital Twin / Telemetry\n\n`/api/v1/twin/*` 路由默认挂载，MQTT 接入与存储后端为可选能力：\n\n1. 配置上面的 Telemetry 环境变量，确保存储后端可用。\n2. 若启用 MQTT，请确保 Broker 可连接（测试依赖 `aiomqtt`）。\n\n### 基础端点\n\n服务启动后，访问以下地址查看交互式API文档：\n\n- Swagger UI: `http://localhost:8000/docs`\n- ReDoc: `http://localhost:8000/redoc`\n\n### 核心API\n\n#### 1. 分析CAD文件\n\n```http\nPOST /api/v1/analyze\nContent-Type: multipart/form-data\n\nfile: (binary)\noptions: {\n  \"extract_features\": true,\n  \"classify_parts\": true,\n  \"calculate_similarity\": false\n}\n```\n\n**响应示例**:\n```json\n{\n  \"id\": \"analysis_123456\",\n  \"timestamp\": \"2025-11-12T10:30:00Z\",\n  \"file_name\": \"demo.dxf\",\n  \"file_format\": \"DXF\",\n  \"results\": {\n    \"features\": {\n      \"geometric\": [12, 100.0, 50.0, 5.0, 25000.0],\n      \"semantic\": [3, 0],\n      \"dimension\": 7\n    },\n    \"classification\": {\n      \"part_type\": \"moderate_component\",\n      \"confidence\": 0.6,\n      \"characteristics\": [\"entities:12\", \"layers:3\", \"volume_estimate:25000.00\"]\n    },\n    \"quality\": {\"score\": 0.95, \"issues\": [], \"suggestions\": []},\n    \"process\": {\n      \"recommended_process\": \"cnc_machining\",\n      \"alternatives\": [\"casting\"],\n      \"parameters\": {\"est_volume\": 25000.0, \"entity_count\": 12, \"complexity\": \"medium\", \"material\": \"steel\"}\n    },\n    \"statistics\": {\"entity_count\": 12, \"layer_count\": 3, \"complexity\": \"medium\"}\n  },\n  \"cad_document\": {\n    \"format\": \"dxf\",\n    \"entity_count\": 12,\n    \"layers\": {\"LAYER1\": 5, \"LAYER2\": 7},\n    \"bounding_box\": {\"min_x\": 0.0, \"min_y\": 0.0, \"min_z\": 0.0, \"max_x\": 100.0, \"max_y\": 50.0, \"max_z\": 5.0},\n    \"complexity\": \"medium\",\n    \"metadata\": {\"material\": \"steel\"}\n  }\n}\n```\n\n#### 2. 批量相似度分析\n\n```http\nPOST /api/v1/similarity/batch\nContent-Type: application/json\n\n{\n  \"reference_id\": \"cad_001\",\n  \"candidates\": [\"cad_002\", \"cad_003\", \"cad_004\"],\n  \"threshold\": 0.75\n}\n```\n\n### Vision 分析响应（可选 CAD 特征统计）\nVision 请求可携带 `include_cad_stats` 与 `cad_feature_thresholds`：\n```json\n{\n  \"image_base64\": \"iVBORw0KGgoAAAANS...\",\n  \"include_description\": true,\n  \"include_ocr\": false,\n  \"include_cad_stats\": true,\n  \"cad_feature_thresholds\": {\"line_aspect\": 5.0, \"arc_fill_min\": 0.08}\n}\n```\n\n启用后响应将包含 `cad_feature_stats`：\n```json\n{\n  \"success\": true,\n  \"provider\": \"deepseek_stub\",\n  \"processing_time_ms\": 12.3,\n  \"description\": {\n    \"summary\": \"Mechanical part with cylindrical features\",\n    \"details\": [\"Main diameter: 20mm\"],\n    \"confidence\": 0.9\n  },\n  \"cad_feature_stats\": {\n    \"line_count\": 1,\n    \"circle_count\": 0,\n    \"arc_count\": 1,\n    \"line_angle_bins\": {\"0-30\": 1, \"30-60\": 0, \"60-90\": 0, \"90-120\": 0, \"120-150\": 0, \"150-180\": 0},\n    \"line_angle_avg\": 12.5,\n    \"arc_sweep_avg\": 180.0,\n    \"arc_sweep_bins\": {\"0-90\": 0, \"90-180\": 0, \"180-270\": 1, \"270-360\": 0}\n  }\n}\n```\n\n#### OCR / Drawing Base64 示例\n`image_base64` 支持 data URL 前缀或纯 base64；上传 PDF 可额外传 `content_type`.\n\n```bash\ncurl -s http://localhost:8000/api/v1/ocr/extract-base64 \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\"image_base64\":\"iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mP8/x8AAwMBAkYp9V0AAAAASUVORK5CYII=\",\"provider\":\"auto\",\"filename\":\"drawing.png\"}' | jq\n\ncurl -s http://localhost:8000/api/v1/drawing/recognize-base64 \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\"image_base64\":\"iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mP8/x8AAwMBAkYp9V0AAAAASUVORK5CYII=\",\"provider\":\"auto\",\"filename\":\"drawing.png\"}' | jq\n```\n\n#### cad_feature_thresholds 快速参考\n- `max_dim` (默认 256): 下采样最大边长（像素）\n- `ink_threshold` (默认 200): 像素阈值（灰度 \u003c 阈值视为线条）\n- `min_area` (默认 12): 连通域最小面积\n- `line_aspect` (默认 4.0): 线条长宽比阈值\n- `line_elongation` (默认 6.0): 线条延展比阈值（基于协方差特征）\n- `circle_aspect` (默认 1.3): 圆形长宽比阈值\n- `circle_fill_min` (默认 0.3): 圆形填充比最小值\n- `arc_aspect` (默认 2.5): 弧线长宽比阈值\n- `arc_fill_min` (默认 0.05): 弧线填充比最小值\n- `arc_fill_max` (默认 0.3): 弧线填充比最大值\n\n#### cad_feature_thresholds 调优方向\n| 参数 | 调高效果 | 调低效果 |\n|------|---------|---------|\n| `max_dim` | 更多细节、计算成本提升 | 更快但细节减少 |\n| `ink_threshold` | 更多像素被视为线条 | 更少像素被视为线条 |\n| `min_area` | 更少小噪声、可能漏检细线 | 更容易捕获细线、噪声增加 |\n| `line_aspect` | 更严格的线条判定 | 更容易把细长形状判为线 |\n| `line_elongation` | 更严格的延展判定 | 更容易判为线 |\n| `circle_aspect` | 更严格的圆形判定 | 更容易判为圆 |\n| `circle_fill_min` | 更少圆形（更保守） | 更容易判为圆 |\n| `arc_aspect` | 更严格的弧线判定 | 更容易判为弧 |\n| `arc_fill_min` | 更少弧线（更保守） | 更容易判为弧 |\n| `arc_fill_max` | 更严格限制弧线填充比 | 更宽松的弧线范围 |\n\n#### CAD 特征基准对比（benchmark）\n使用 `scripts/vision_cad_feature_benchmark.py` 评估阈值调整影响：\n```bash\n# 基准（建议加 --no-clients 避免外部依赖告警）\npython3 scripts/vision_cad_feature_benchmark.py \\\n  --no-clients \\\n  --input-dir /path/to/cad_images \\\n  --output-json /tmp/cad_baseline.json\n\n# 对比（输出 comparison 区块）\npython3 scripts/vision_cad_feature_benchmark.py \\\n  --no-clients \\\n  --input-dir /path/to/cad_images \\\n  --threshold line_aspect=6 \\\n  --threshold min_area=24 \\\n  --output-json /tmp/cad_tuned.json \\\n  --output-compare-csv /tmp/cad_tuned_compare_summary.csv \\\n  --compare-json /tmp/cad_baseline.json\n\n# 基准报告\npython3 scripts/vision_cad_feature_baseline_report.py \\\n  --input-json /tmp/cad_baseline.json \\\n  --output-md /tmp/cad_baseline_report.md\n```\n\n阈值文件与对比报告：\n```bash\n# 从文件加载阈值/网格组合\npython3 scripts/vision_cad_feature_benchmark.py \\\n  --no-clients \\\n  --threshold-file examples/cad_feature_thresholds.json \\\n  --output-json /tmp/cad_grid.json\n\n# 列表格式（variants）会按顺序运行\npython3 scripts/vision_cad_feature_benchmark.py \\\n  --no-clients \\\n  --threshold-file examples/cad_feature_thresholds_variants.json \\\n  --output-json /tmp/cad_variants.json\n\n# YAML 文件同样支持（需要安装 PyYAML）\npython3 scripts/vision_cad_feature_benchmark.py \\\n  --no-clients \\\n  --threshold-file examples/cad_feature_thresholds.yaml \\\n  --output-json /tmp/cad_grid.yaml.json\n\n# 生成对比摘要\npython3 scripts/vision_cad_feature_compare_report.py \\\n  --input-json /tmp/cad_tuned.json \\\n  --output-md /tmp/cad_tuned_report.md\n\n# 导出对比差异（JSON/CSV）\npython3 scripts/vision_cad_feature_compare_export.py \\\n  --input-json /tmp/cad_tuned.json \\\n  --output-json /tmp/cad_tuned_top.json \\\n  --output-csv /tmp/cad_tuned_top.csv \\\n  --top-samples 10\n\n# 若不指定输出文件，将输出 JSON 到 stdout\npython3 scripts/vision_cad_feature_compare_export.py \\\n  --input-json /tmp/cad_tuned.json \\\n  --top-samples 5\n\n# Filter a single combo index\npython3 scripts/vision_cad_feature_compare_export.py \\\n  --input-json /tmp/cad_tuned.json \\\n  --combo-index 2 \\\n  --output-json /tmp/cad_tuned_combo2.json \\\n  --output-csv /tmp/cad_tuned_combo2.csv\n```\n\n50 样本产物（示例）:\n- `reports/vision_cad_feature_grid_baseline_20260106_50.json`\n- `reports/vision_cad_feature_grid_baseline_report_20260106_50.md`\n- `reports/vision_cad_feature_grid_compare_20260106_50.json`\n- `reports/vision_cad_feature_grid_compare_summary_20260106_50.csv`\n- `reports/vision_cad_feature_grid_compare_report_20260106_50.md`\n- `reports/vision_cad_feature_grid_compare_top_20260106_50.json`\n- `reports/vision_cad_feature_grid_compare_top_20260106_50.csv`\n- `reports/vision_cad_feature_tuning_compare_20260106_50.json`\n- `reports/vision_cad_feature_tuning_compare_summary_20260106_50.csv`\n- `reports/vision_cad_feature_tuning_compare_report_20260106_50.md`\n- `reports/vision_cad_feature_tuning_compare_top_20260106_50.json`\n- `reports/vision_cad_feature_tuning_compare_top_20260106_50.csv`\n\n### Vision 错误响应规范\n所有 Vision 分析请求无论成功或失败返回 HTTP 200：\n```json\n{\n  \"success\": false,\n  \"provider\": \"deepseek_stub\",\n  \"processing_time_ms\": 5.1,\n  \"error\": \"Image too large (1.20MB) via base64. Max 1.00MB.\",\n  \"code\": \"INPUT_ERROR\"\n}\n```\n`code` 可能取值：`INPUT_ERROR`（输入校验失败）、`INTERNAL_ERROR`（内部异常）。\n\n### CAD 分析错误响应结构 (统一错误码集成)\n\n错误时返回标准结构（HTTP 状态码指示分类）：\n```json\n{\n  \"detail\": {\n    \"code\": \"INPUT_SIZE_EXCEEDED\",\n    \"source\": \"input\",\n    \"severity\": \"info\",\n    \"message\": \"File too large 15.2MB \u003e 10MB\"\n  }\n}\n```\n\n常见错误码：\n- `INPUT_SIZE_EXCEEDED`: 文件大小超限\n- `UNSUPPORTED_FORMAT`: 不支持的格式\n- `INPUT_ERROR`: 空文件 / 通用输入错误\n- `JSON_PARSE_ERROR`: 选项 JSON 解析失败\n- `BUSINESS_RULE_VIOLATION`: 实体数或业务规则超限\n- `DATA_NOT_FOUND`: 历史分析结果不存在\n- `INTERNAL_ERROR`: 未捕获的内部异常\n\n完整枚举参考 `src/core/errors_extended.py`。\n\n### 结构化错误响应格式 (build_error)\n\n所有后端 API 错误遵循统一的结构化格式（通过 `build_error()` 生成），嵌套在 HTTPException 的 `detail` 字段中：\n\n| 字段 | 类型 | 说明 |\n|------|------|------|\n| `code` | string | 错误代码 (SCREAMING_SNAKE_CASE，如 `INTERNAL_ERROR`, `VALIDATION_FAILED`) |\n| `stage` | string | 错误发生阶段 (如 `backend_reload`, `model_validation`, `similarity`) |\n| `message` | string | 人类可读的错误描述 |\n| `severity` | string | 严重程度: `error` (错误), `warning` (警告), `info` (信息) |\n| `context` | object | 上下文信息（可选，包含具体错误细节如建议、参数等） |\n| `suggestion` | string | 建议的修复措施（可选，通常在 context 中） |\n\n**示例 1 - 模型重载失败 (500 错误):**\n```json\n{\n  \"detail\": {\n    \"code\": \"INTERNAL_ERROR\",\n    \"stage\": \"backend_reload\",\n    \"message\": \"Vector store backend reload failed\",\n    \"severity\": \"error\",\n    \"context\": {\n      \"backend\": \"faiss\",\n      \"suggestion\": \"Check backend configuration and logs\"\n    }\n  }\n}\n```\n\n**示例 2 - 后端授权失败 (403 错误):**\n```json\n{\n  \"detail\": {\n    \"code\": \"FORBIDDEN\",\n    \"stage\": \"backend_reload_auth\",\n    \"message\": \"Admin token required for backend reload\",\n    \"severity\": \"error\",\n    \"context\": {\n      \"required_header\": \"X-Admin-Token\",\n      \"suggestion\": \"Provide valid admin token in X-Admin-Token header\"\n    }\n  }\n}\n```\n\n**示例 3 - 模型加载回滚 (200 响应，但含错误信息):**\n```json\n{\n  \"status\": \"rollback\",\n  \"error\": \"Security validation failed: disallowed pickle opcode REDUCE detected\",\n  \"previous_version\": \"v2.0.0\",\n  \"previous_hash\": \"abc123def456\"\n}\n```\n\n此格式确保所有错误响应包含足够的诊断信息，便于客户端处理和日志分析。\n\n### 相似度错误结构示例\n\n`/api/v1/analyze/similarity` 在错误 (向量缺失、维度不匹配) 时返回:\n```json\n{\n  \"reference_id\": \"abc\",\n  \"target_id\": \"def\",\n  \"score\": 0.0,\n  \"method\": \"cosine\",\n  \"dimension\": 0,\n  \"status\": \"reference_not_found\",\n  \"error\": {\n    \"code\": \"DATA_NOT_FOUND\",\n    \"source\": \"system\",\n    \"severity\": \"error\",\n    \"message\": \"Reference vector not found\",\n    \"stage\": \"similarity\"\n  }\n}\n```\n\n维度不一致示例 (`dimension_mismatch`): `code` 为 `VALIDATION_FAILED`。\n\n### OCR 错误响应规范\nOCR 提取端点统一 200 返回：\n```json\n{\n  \"success\": false,\n  \"provider\": \"auto\",\n  \"confidence\": null,\n  \"fallback_level\": null,\n  \"processing_time_ms\": 0,\n  \"dimensions\": [],\n  \"symbols\": [],\n  \"title_block\": {},\n  \"error\": \"Unsupported MIME type image/txt\",\n  \"code\": \"INPUT_ERROR\"\n}\n```\n前端只需依据 `success` 与 `code` 判断逻辑，不再依赖 HTTP 状态码。\n\n### Unified Error Code Examples (curl)\n\n#### Input Error Example\n```bash\n# Trigger INPUT_ERROR with invalid base64\ncurl -X POST http://localhost:8000/api/v1/vision/analyze \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\"image_base64\": \"invalid!!!\", \"include_description\": true}' \\\n  | jq '.code'\n# Output: \"INPUT_ERROR\"\n```\n\n#### Provider Timeout Example\n```bash\n# Simulate timeout (requires provider configuration)\ncurl -X POST http://localhost:8000/api/v1/ocr/extract \\\n  -F \"file=@large_file.pdf\" \\\n  -H \"X-Timeout-MS: 100\" \\\n  | jq '.code'\n# Output: \"PROVIDER_TIMEOUT\"\n```\n\n#### Model Load Error Example\n```bash\n# Trigger MODEL_LOAD_ERROR (when model not available)\ncurl http://localhost:8000/health | jq '.services.ml'\n# If \"down\", subsequent calls may return:\n# \"code\": \"MODEL_LOAD_ERROR\"\n```\n\n#### Resource Exhausted Example\n```bash\n# Trigger RESOURCE_EXHAUSTED with very large image\ncurl -X POST http://localhost:8000/api/v1/vision/analyze \\\n  -H \"Content-Type: application/json\" \\\n  -d \"{\\\"image_base64\\\": \\\"$(base64 -i 50mb_image.png)\\\"}\" \\\n  | jq '.code'\n# Output: \"RESOURCE_EXHAUSTED\"\n```\n\n### Strict Self-Check Mode\n\nThe self-check script now supports strict mode validation with environment variables:\n\n```bash\n# Basic self-check\npython scripts/self_check.py\n\n# Strict metrics validation\nSELF_CHECK_STRICT_METRICS=1 \\\nSELF_CHECK_MIN_OCR_ERRORS=5 \\\npython scripts/self_check.py\n\n# Remote endpoint check\nSELF_CHECK_BASE_URL=http://production:8000 \\\nSELF_CHECK_REQUIRE_EMA=1 \\\npython scripts/self_check.py\n\n# Full strict mode with counter increment\nSELF_CHECK_STRICT_METRICS=1 \\\nSELF_CHECK_INCREMENT_COUNTERS=1 \\\nSELF_CHECK_MIN_OCR_ERRORS=10 \\\npython scripts/self_check.py\n```\n\n#### Environment Variables:\n- `SELF_CHECK_BASE_URL`: Remote URL to check (default: local)\n- `SELF_CHECK_STRICT_METRICS`: Enable strict contract validation\n- `SELF_CHECK_MIN_OCR_ERRORS`: Minimum error counter threshold\n- `SELF_CHECK_REQUIRE_EMA`: Require EMA values in /health\n- `SELF_CHECK_INCREMENT_COUNTERS`: Make API calls to increment metrics\n\n#### Exit Codes:\n\n| Exit Code | Meaning | Description | CI Action |\n|-----------|---------|-------------|-----------|\n| `0` | Success | All checks passed | Continue pipeline |\n| `2` | API Failure | Health endpoint unreachable or API errors | Fail pipeline, alert team |\n| `3` | Health Check Failed | Service unhealthy or degraded | Block deployment |\n| `5` | Metrics Contract Violation | Required metrics missing or malformed | Fail quality gate |\n| `6` | Provider Mapping Gap | ErrorCode mapping incomplete | Warning, needs fix |\n\nExample CI usage:\n```bash\n# In GitHub Actions\n- name: Run Strict Self-Check\n  run: |\n    SELF_CHECK_STRICT_METRICS=1 python scripts/self_check.py\n  continue-on-error: false\n\n# In GitLab CI\nself-check:\n  script:\n    - export SELF_CHECK_STRICT_METRICS=1\n    - python scripts/self_check.py || exit_code=$?\n    - if [ $exit_code -eq 5 ]; then echo \"Metrics contract broken!\"; exit 1; fi\n```\n\n#### 3. 零件分类\n\n```http\nPOST /api/v1/classify\nContent-Type: multipart/form-data\n\nfile: (binary)\n```\n\n---\n\n## 🔧 客户端SDK\n\n### Python客户端\n\n```python\nfrom cad_ml_client import CADMLClient\n\n# 初始化客户端\nclient = CADMLClient(\n    base_url=\"http://localhost:8000\",\n    api_key=\"your_api_key\"\n)\n\n# 分析CAD文件\nwith open(\"drawing.dxf\", \"rb\") as f:\n    result = client.analyze(\n        file=f,\n        extract_features=True,\n        classify_parts=True\n    )\n\nprint(f\"零件类型: {result.part_type}\")\nprint(f\"置信度: {result.confidence}\")\n```\n\n### JavaScript客户端\n\n```javascript\nconst { CADMLClient } = require('cad-ml-client');\n\nconst client = new CADMLClient({\n    baseURL: 'http://localhost:8000',\n    apiKey: 'your_api_key'\n});\n\n// 分析文件\nconst result = await client.analyze({\n    file: fileBuffer,\n    options: {\n        extractFeatures: true,\n        classifyParts: true\n    }\n});\n\nconsole.log(`Part type: ${result.partType}`);\n```\n\n### Java客户端\n\n```java\nimport com.cadml.client.CADMLClient;\n\nCADMLClient client = new CADMLClient.Builder()\n    .baseUrl(\"http://localhost:8000\")\n    .apiKey(\"your_api_key\")\n    .build();\n\nAnalysisResult result = client.analyze(\n    file,\n    AnalysisOptions.builder()\n        .extractFeatures(true)\n        .classifyParts(true)\n        .build()\n);\n\nSystem.out.println(\"Part type: \" + result.getPartType());\n```\n\n---\n\n## 🔌 集成指南\n\n### 与DedupCAD集成\n\n```python\n# dedupcad/ml_integration.py\nfrom cad_ml_client import CADMLClient\n\nclass MLEnhancedDedup:\n    def __init__(self):\n        self.ml_client = CADMLClient(\n            base_url=os.getenv(\"CADML_URL\", \"http://cadml:8000\")\n        )\n\n    async def compare_with_ml(self, file1, file2):\n        # 获取ML特征\n        features1 = await self.ml_client.extract_features(file1)\n        features2 = await self.ml_client.extract_features(file2)\n\n        # 计算相似度\n        similarity = await self.ml_client.calculate_similarity(\n            features1, features2\n        )\n\n        return similarity\n```\n\n### 与切割系统集成\n\n```python\n# cutting_system/process_optimizer.py\nfrom cad_ml_client import CADMLClient\n\nclass ProcessOptimizer:\n    def __init__(self):\n        self.ml_client = CADMLClient()\n\n    async def optimize_cutting_process(self, dxf_file):\n        # 识别零件类型\n        analysis = await self.ml_client.analyze(dxf_file)\n\n        # 根据零件类型优化工艺\n        if analysis.part_type == \"plate\":\n            return self.optimize_plate_cutting(analysis)\n        elif analysis.part_type == \"shaft\":\n            return self.optimize_shaft_cutting(analysis)\n```\n\n---\n\n## 📊 性能指标 \u0026 自适应限流\n\n| 指标 | 目标值 | 当前值 | 状态 |\n|------|--------|--------|------|\n| **响应时间** | \u003c 500ms | 320ms | ✅ |\n| **吞吐量** | \u003e 100 req/s | 150 req/s | ✅ |\n| **准确率** | \u003e 90% | 94.5% | ✅ |\n| **可用性** | \u003e 99.9% | 99.95% | ✅ |\n| **缓存命中率** | \u003e 60% | 72% | ✅ |\n| **自适应降级响应时间回退** | \u003c +20% 基线 | +12% | ✅ |\n\n### 自适应限流指标\n\n核心 Prometheus 指标：\n\n```\nadaptive_rate_limit_tokens_current{service,endpoint}\nadaptive_rate_limit_base_rate{service,endpoint}\nadaptive_rate_limit_adjustments_total{service,reason}\nadaptive_rate_limit_state{service,state}  # 0=normal,1=degrading,2=recovery,3=clamped\nadaptive_rate_limit_error_ema{service}\nadaptive_rate_limit_latency_p95{service}\n```\n\n触发条件：\n- 错误 EMA \u003e error_threshold → 降级 (reason=error)\n- P95 延迟 \u003e baseline * multiplier → 降级 (reason=latency)\n- 拒绝率 \u003e reject_rate_threshold → 降级 (reason=reject)\n- 连续失败数 \u003e= max_failure_streak → 降级 (reason=failures)\n- 恢复条件满足（低错误 + 正常延迟 + 无拒绝）→ 渐进恢复 (reason=recover)\n- 抖动检测窗口内方向频繁交替 → 冷却 (进入 cooldown 抑制调整)\n\n环境变量：`ADAPTIVE_RATE_LIMIT_ENABLED=1` (默认开启)。关闭后仍保持基础令牌桶行为但不做动态调整。\n\nPromQL 示例：\n```\nincrease(adaptive_rate_limit_adjustments_total[15m]) \u003e 10\nadaptive_rate_limit_state{state=\"clamped\"} == 1\nadaptive_rate_limit_error_ema \u003e 0.05\n```\n\n告警建议：\n- CLAMPED 持续 \u003e10m：排查上游故障或资源瓶颈。\n- 冷却期触发频繁：调优 jitter_threshold 或调整最小样本参数。\n- error_ema 连续上升且未恢复：执行 provider 健康诊断脚本。\n\n### 性能优化\n\n1. **缓存策略**\n   - Redis缓存热点数据\n   - 特征向量缓存24小时\n   - 分类结果缓存7天\n\n2. **并发处理**\n   - 异步API处理\n   - 批量操作支持\n   - 工作队列并行处理\n\n3. **模型优化**\n   - 模型量化 (INT8)\n   - ONNX运行时加速\n   - GPU推理 (可选)\n\n---\n\n## 🛠️ 开发指南\n\n### 项目结构\n\n```\ncad-ml-platform/\n├── src/\n│   ├── api/              # API端点\n│   │   ├── v1/\n│   │   │   ├── analyze.py\n│   │   │   ├── similarity.py\n│   │   │   └── classify.py\n│   │   └── middleware.py\n│   ├── core/             # 核心算法\n│   │   ├── feature_extractor.py\n│   │   ├── classifier.py\n│   │   ├── similarity_engine.py\n│   │   └── quality_checker.py\n│   ├── adapters/         # 格式适配器\n│   │   ├── dxf_adapter.py\n│   │   ├── step_adapter.py\n│   │   └── iges_adapter.py\n│   ├── models/           # ML模型\n│   │   ├── part_classifier.pkl\n│   │   └── feature_model.h5\n│   └── utils/            # 工具函数\n├── clients/              # 客户端SDK\n│   ├── python/\n│   ├── javascript/\n│   └── java/\n├── tests/                # 测试套件\n│   ├── unit/\n│   ├── integration/\n│   └── e2e/\n├── docs/                 # 文档\n│   ├── api/\n│   ├── architecture/\n│   └── deployment/\n├── config/               # 配置文件\n│   ├── config.yaml\n│   └── logging.yaml\n├── scripts/              # 脚本工具\n│   ├── train_model.py\n│   ├── evaluate.py\n│   └── benchmark.py\n├── deployments/          # 部署配置\n│   ├── docker/\n│   │   ├── Dockerfile\n│   │   └── docker-compose.yml\n│   └── kubernetes/\n│       ├── deployment.yaml\n│       ├── service.yaml\n│       └── ingress.yaml\n└── knowledge_base/       # 领域知识\n    ├── part_types.json\n    ├── material_properties.json\n    └── process_rules.yaml\n```\n\n### 添加新功能\n\n1. **新增API端点**\n```python\n# src/api/v1/new_endpoint.py\nfrom fastapi import APIRouter, File, UploadFile\nfrom src.core import new_analyzer\n\nrouter = APIRouter()\n\n@router.post(\"/new-analysis\")\nasync def new_analysis(file: UploadFile = File(...)):\n    result = await new_analyzer.analyze(file)\n    return result\n```\n\n2. **新增适配器**\n```python\n# src/adapters/new_format_adapter.py\nfrom src.adapters.base import BaseAdapter\n\nclass NewFormatAdapter(BaseAdapter):\n    def convert(self, file_data: bytes) -\u003e Dict:\n        # 实现格式转换逻辑\n        pass\n```\n\n### 测试\n\n```bash\n# 运行单元测试\npytest tests/unit/\n\n# 运行集成测试\npytest tests/integration/\n\n# 运行端到端测试\npytest tests/e2e/\n\n# 运行 E2E smoke（需服务已启动；可设置 API_BASE_URL、DEDUPCAD_VISION_URL）\nmake e2e-smoke\n# 使用本地 stub（本地/离线开发备用）：python scripts/dedupcad_vision_stub.py\n# CI 默认使用 pinned GHCR 镜像，可覆盖：\n# DEDUPCAD_VISION_IMAGE=ghcr.io/zensgit/dedupcad-vision@sha256:9f7f567e3b0c1c882f9a363f1b1cb095d30d9e9b184e582d6b19ec7446a86251\n# GHCR 需 public 或 CI 开启 packages:read 权限\n# 若保持私有，建议 GHCR_TOKEN 仅授予 read:packages\n\n# 运行全量测试（需 DedupCAD Vision 服务已启动）\nDEDUPCAD_VISION_URL=http://localhost:58001 make test-dedupcad-vision\n\n# 生成覆盖率报告\npytest --cov=src --cov-report=html\n\n# Faiss 性能测试（默认只跑内存后端）\nRUN_FAISS_PERF_TESTS=1 pytest tests/perf/test_vector_search_latency.py -v\n# 如需强制失败（faiss 子进程崩溃时不跳过）\nREQUIRE_FAISS_PERF=1 RUN_FAISS_PERF_TESTS=1 pytest tests/perf/test_vector_search_latency.py -v\n# 注意：部分环境在 PYTHONWARNINGS=error::DeprecationWarning 下导入 faiss 会触发 segfault；\n# 测试已在子进程中隔离并过滤 swig 的 DeprecationWarning\n```\n\n#### 指标/metrics 测试说明\n\n- prometheus_client 未安装时，`/metrics` 返回 `app_metrics_disabled`；指标相关测试会自动跳过。\n- 运行指标契约测试：`pytest tests/test_metrics_contract.py -v`\n- 严格模式（检查最小错误计数/Provider 覆盖）：`STRICT_METRICS=1 pytest tests/test_metrics_contract.py -v`\n- 编写指标相关测试时可使用 `require_metrics_enabled` / `metrics_text` fixture（见 `tests/conftest.py`）。\n\n---\n\n## 🚢 部署\n\n### Docker部署\n\n```bash\n# 构建镜像\ndocker build -t cad-ml-platform:latest .\n\n# 运行容器\ndocker run -d \\\n  -p 8000:8000 \\\n  -e REDIS_URL=redis://redis:6379 \\\n  --name cad-ml \\\n  cad-ml-platform:latest\n```\n\n### Docker Compose部署\n\n```bash\n# 启动所有服务\ndocker-compose up -d\n\n# 查看日志\ndocker-compose logs -f\n\n# 停止服务\ndocker-compose down\n\n# 使用 GHCR 预构建镜像（无本地 build）\nCAD_ML_IMAGE=ghcr.io/zensgit/cad-ml-platform:main \\\\\n# 可选：arm64 主机使用 CAD_ML_PLATFORM=linux/arm64\n  docker compose -f deployments/docker/docker-compose.yml \\\\\n  -f deployments/docker/docker-compose.ghcr.yml up -d --no-build\n\n# 可选：DWG 渲染回退（建议本机运行 CAD render server）\nCAD_RENDER_FALLBACK_URL=http://host.docker.internal:18002 \\\\\n  docker compose -f deployments/docker/docker-compose.yml up -d --no-build\n```\n\n### Kubernetes部署\n\n```bash\n# 创建命名空间\nkubectl create namespace cad-ml\n\n# 应用配置\nkubectl apply -f deployments/kubernetes/ -n cad-ml\n\n# 检查部署状态\nkubectl get pods -n cad-ml\nkubectl get svc -n cad-ml\n```\n\n### 生产环境配置\n\n```yaml\n# config/production.yaml\nserver:\n  workers: 4\n  host: 0.0.0.0\n  port: 8000\n\nredis:\n  url: redis://redis.production:6379\n  ttl: 86400\n\nml:\n  model_path: /models\n  batch_size: 32\n  use_gpu: true\n\nmonitoring:\n  prometheus_enabled: true\n  metrics_port: 9090\n```\n\n---\n\n## 📈 监控与运维\n\n### Prometheus监控\n\n```yaml\n# prometheus.yml\nscrape_configs:\n  - job_name: 'cad-ml-platform'\n    static_configs:\n      - targets: ['cad-ml:9090']\n```\n\n### 健康检查\n\n```bash\n# 健康检查端点\ncurl http://localhost:8000/health\n\n示例响应:\n```json\n{\n  \"status\": \"healthy\",\n  \"services\": {\"api\": \"up\", \"ml\": \"up\", \"redis\": \"disabled\"},\n  \"runtime\": {\n    \"python_version\": \"3.11.2\",\n    \"metrics_enabled\": true,\n    \"vision_max_base64_bytes\": 1048576\n  }\n}\n```\n\nBase64 图像大小限制：超过 1MB 或空内容将被拒绝，并计入指标 `vision_input_rejected_total{reason=\"base64_too_large\"|\"base64_empty\"}`。\n\n触发超限示例:\n```bash\npython - \u003c\u003c'PY'\nimport base64, requests\nraw = b'x' * (1024 * 1200)  # \u003e1MB\npayload = {\"image_base64\": base64.b64encode(raw).decode(), \"include_description\": False, \"include_ocr\": False}\nr = requests.post('http://localhost:8000/api/v1/vision/analyze', json=payload)\nprint(r.status_code, r.json())\nPY\n```\n\n成功与拒绝请求后的部分指标示例 (Vision + OCR 双系统):\n```\nvision_requests_total{provider=\"deepseek_stub\",status=\"success\"} 1\nvision_input_rejected_total{reason=\"base64_too_large\"} 1\nocr_input_rejected_total{reason=\"validation_failed\"} 1\nocr_errors_total{provider=\"auto\",code=\"internal\",stage=\"endpoint\"} 1\nvision_processing_duration_seconds_bucket{provider=\"deepseek_stub\",le=\"0.1\"} ...\n```\n\n新增 OCR 输入与错误指标说明:\n- `ocr_input_rejected_total{reason}`: 上传文件验证失败（`validation_failed|mime_unsupported|too_large|pdf_forbidden` 等）。\n- `ocr_errors_total{provider,code,stage}`: 运行时错误分阶段统计（`code=internal|provider_down|rate_limit|circuit_open|input_error`）。\n- 统一错误响应：HTTP 200 + JSON `{\"success\": false, \"error\": \"...\", \"code\": \"INPUT_ERROR|INTERNAL_ERROR\"}`，便于前端与批处理流水线简化解析。\n\n# 就绪检查\ncurl http://localhost:8000/ready\n\n# 指标端点\ncurl http://localhost:8000/metrics\n```\n\n### 日志管理\n\n```python\n# 日志配置\nlogging:\n  level: INFO\n  format: json\n  outputs:\n    - console\n    - file: /var/log/cad-ml/app.log\n    - elasticsearch: http://elastic:9200\n```\n\n---\n\n## 🔒 安全性\n\n### API认证\n\n```python\n# 使用API密钥\nheaders = {\n    \"X-API-Key\": \"your_api_key\"\n}\n\n# 使用JWT令牌\nheaders = {\n    \"Authorization\": \"Bearer your_jwt_token\"\n}\n```\n\n### 速率限制\n\n```yaml\nrate_limiting:\n  enabled: true\n  requests_per_minute: 100\n  requests_per_hour: 5000\n```\n\n### 数据加密\n\n- HTTPS传输加密\n- 数据库字段加密\n- 文件存储加密\n\n---\n\n## 🤝 贡献指南\n\n### 开发流程\n\n1. Fork项目\n2. 创建特性分支 (`git checkout -b feature/AmazingFeature`)\n3. 提交更改 (`git commit -m 'Add some AmazingFeature'`)\n4. 推送到分支 (`git push origin feature/AmazingFeature`)\n5. 开启Pull Request\n\n### 代码规范\n\n- 遵循PEP 8 (Python)\n- 使用Black格式化代码\n- 编写单元测试\n- 更新文档\n\n---\n\n## 📝 许可证\n\n本项目为私有项目，版权所有 © 2025 Your Company\n\n---\n\n## 📞 联系支持\n\n- **技术支持**: tech-support@yourcompany.com\n- **商务合作**: business@yourcompany.com\n- **Issue追踪**: [GitHub Issues](https://github.com/your-org/cad-ml-platform/issues)\n\n---\n\n## 🔄 版本历史\n\n### v1.0.0 (2025-11-12)\n- 初始版本发布\n- 基础ML分析功能\n- 支持DXF格式\n- Python客户端SDK\n\n### 路线图\n\n- [ ] v1.1.0 - STEP/IGES格式支持\n- [ ] v1.2.0 - 深度学习模型集成\n- [ ] v1.3.0 - 实时流处理\n- [ ] v2.0.0 - 分布式处理集群\n\n---\n\n**最后更新**: 2025年11月12日\n### 文档导航\n- 关键能力与实现地图: docs/KEY_HIGHLIGHTS.md\n- CI 失败路由与响应: docs/CI_FAILURE_ROUTING.md\n\n### 路由前缀规范\n- 子路由仅包含资源级路径（src/api/v1/*）\n- 聚合路由统一挂载至 /api/v1，避免重复前缀\n- 有效路径示例：\n  - GET /api/v1/vision/health\n  - POST /api/v1/vision/analyze\n  - POST /api/v1/ocr/extract\n  - POST /api/v1/vision/analyze (错误路径测试: tests/test_ocr_errors.py)\n# 可选：环境变量覆盖\ncp .env.example .env\n# 根据需要编辑 .env（CORS、ALLOWED_HOSTS、REDIS 等）\n#### 2.1 预提交钩子（可选但推荐）\n\n```bash\npre-commit install\n# 运行全量检查\npre-commit run --all-files --show-diff-on-failure\n```\n\n提示：`tests/vision/test_vision_ocr_integration.py` 含非 UTF-8 内容，已在 pre-commit 与 Makefile 的格式化步骤中排除，不影响测试执行。\n\n### 质量配置文件\n- Flake8: `.flake8` (max-line-length=100, 忽略 E203/W503)\n- Mypy: `mypy.ini` (严格类型, metrics 模块宽松)\n- 新增 Vision 指标: `vision_requests_total`, `vision_processing_duration_seconds`, `vision_errors_total`\n```\n#### OCR 错误指标详细说明\n\n| Metric | Labels | Description | Example |\n|--------|--------|-------------|---------|\n| `ocr_errors_total` | `provider, code, stage` | 统计OCR各阶段错误次数 | `ocr_errors_total{provider=\"paddle\",code=\"rate_limit\",stage=\"preprocess\"} 3` |\n| `ocr_input_rejected_total` | `reason` | 输入验证拒绝 | `ocr_input_rejected_total{reason=\"validation_failed\"} 1` |\n\nStages 说明:\n- `validate`: 上传文件读取与验证（MIME/大小/PDF安全）\n- `preprocess`: 预处理与速率限制\n- `infer`: Provider推理或回退逻辑\n- `parse`: 结构化解析阶段\n- `manager`: 管理器路由与回退判定\n- `endpoint`: 最外层端点包装/未知异常\n\n常见错误代码 (`code`): `internal`, `provider_down`, `rate_limit`, `circuit_open`, `input_error`。\n#### 自检脚本 (CI Smoke)\n\n运行快速自检以验证健康、核心指标与基础端点：\n```bash\npython scripts/self_check.py || echo \"Self-check failed\"\n```\n可配置项：\n- `SELF_CHECK_METRICS=0` 可在 Prometheus 未启用或未挂载 `/metrics` 时跳过指标检查：\n  ```bash\n  SELF_CHECK_METRICS=0 python scripts/self_check.py\n  ```\n- `SELF_CHECK_ERROR=0` 可跳过最小错误路径契约检查（默认开启）。\n  ```bash\n  SELF_CHECK_ERROR=0 python scripts/self_check.py\n  ```\n退出码含义：\n- 0: 所有检查通过\n- 2: 关键端点不可用或严重错误\n- 3: 指标缺失 (核心计数器未暴露)\n- 4: 错误响应契约异常\n### Prometheus告警规则示例\n\n参见 `docs/ALERT_RULES.md` 获取 OCR/Vision 错误突增、Provider Down、输入拒绝与速率记录规则示例。\n### 相似度检索 (Top-K)\n\n单个向量相似度查询:\n```bash\ncurl -X POST http://localhost:8000/api/v1/analyze/similarity/topk \\\n  -H \"X-API-Key: test\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\"target_id\": \"\u003canalysis_id\u003e\", \"k\": 5, \"material_filter\": \"steel\", \"complexity_filter\": \"medium\"}'\n```\n\n响应示例:\n```json\n{\n  \"target_id\": \"123e4567-e89b-12d3-a456-426614174000\",\n  \"k\": 5,\n  \"results\": [\n    {\"id\": \"...\", \"score\": 0.9923, \"material\": \"steel\", \"complexity\": \"medium\", \"format\": \"dxf\"}\n  ]\n}\n```\n\n**批量相似度查询** (新增):\n```bash\ncurl -X POST http://localhost:8000/api/v1/vectors/similarity/batch \\\n  -H \"X-API-Key: test\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\n    \"ids\": [\"vec1\", \"vec2\", \"vec3\"],\n    \"top_k\": 5,\n    \"material\": \"steel\",\n    \"min_score\": 0.7\n  }'\n```\n\n批量查询响应:\n```json\n{\n  \"total\": 3,\n  \"successful\": 3,\n  \"failed\": 0,\n  \"batch_id\": \"550e8400-e29b-41d4-a716-446655440000\",\n  \"duration_ms\": 12.34,\n  \"items\": [\n    {\n      \"id\": \"vec1\",\n      \"status\": \"success\",\n      \"similar\": [\n        {\"id\": \"vec2\", \"score\": 0.9512, \"material\": \"steel\", \"complexity\": \"high\", \"format\": \"step\", \"dimension\": 128}\n      ]\n    }\n  ]\n}\n```\n\n批量查询特性:\n- 支持批量查询多个向量的相似向量\n- 可选过滤: `material`, `complexity`, `format`\n- 最小相似度阈值: `min_score` (0.0-1.0)\n- 自动记录 Prometheus 指标: `vector_query_batch_latency_seconds` (按批量大小分桶: small/medium/large)\n- 批量上限: 由环境变量 `BATCH_SIMILARITY_MAX_IDS` 控制 (默认 200)\n- 超出上限: 返回 422 结构化错误 (code=INPUT_VALIDATION_FAILED, stage=batch_similarity) 并计数 `analysis_rejections_total{reason=\"batch_too_large\"}`\n- 若所有成功项的相似结果列表为空 (过滤或阈值导致) 计数 `analysis_rejections_total{reason=\"batch_empty_results\"}` 方便调优阈值\n\n### 向量管理\n\n列出已注册向量:\n```bash\ncurl -H \"X-API-Key: test\" http://localhost:8000/api/v1/analyze/vectors | jq\n```\n\n删除向量:\n```bash\ncurl -X POST http://localhost:8000/api/v1/analyze/vectors/delete \\\n  -H \"X-API-Key: test\" -H \"Content-Type: application/json\" \\\n  -d '{\"id\": \"123e4567-e89b-12d3-a456-426614174000\"}'\n```\n\n更新向量 (替换或追加 + 元数据更新):\n```bash\ncurl -X POST http://localhost:8000/api/v1/analyze/vectors/update \\\n  -H \"X-API-Key: test\" -H \"Content-Type: application/json\" \\\n  -d '{\"id\": \"123e4567-e89b-12d3-a456-426614174000\", \"append\": [0.12, 0.34], \"material\": \"steel\"}'\n```\n\n向量统计:\n```bash\ncurl -H \"X-API-Key: test\" http://localhost:8000/api/v1/analyze/vectors/stats | jq\n```\n示例响应:\n```json\n{\n  \"backend\": \"memory\",\n  \"total\": 42,\n  \"by_material\": {\"steel\": 20, \"aluminum\": 12, \"unknown\": 10},\n  \"by_complexity\": {\"low\": 30, \"medium\": 10, \"high\": 2},\n  \"by_format\": {\"dxf\": 25, \"step\": 10, \"stl\": 7}\n}\n```\n\n### 复杂度与限制\n通过环境变量控制:\n```bash\nexport ANALYSIS_MAX_FILE_MB=15          # 最大文件大小 (MB)\nexport ANALYSIS_MAX_ENTITIES=50000      # 最大实体数\nexport PROCESS_RULES_FILE=config/process_rules.yaml  # 工艺规则文件路径\nexport ANALYSIS_PARSE_P95_TARGET_MS=250  # 解析阶段 p95 目标 (用于内部预算)\nexport ANALYSIS_VECTOR_DIM_CHECK=1       # 开启向量维度一致性检查 (未来扩展)\nexport CLASSIFICATION_RULE_VERSION=v1    # 分类规则版本标记 (观测变更影响)\nexport VECTOR_STORE_BACKEND=memory       # 向量存储后端 memory|redis\nexport VECTOR_TTL_SECONDS=0              # 向量TTL(秒) 0表示禁用\nexport VECTOR_LIST_LIMIT=200             # 向量列表接口最大返回数量\nexport VECTOR_LIST_SCAN_LIMIT=5000       # 列表 Redis 扫描上限 (0=无限制)\nexport VECTOR_STATS_SCAN_LIMIT=5000      # 统计 Redis 扫描上限 (0=无限制)\nexport VECTOR_PRUNE_INTERVAL_SECONDS=30  # 后台清理间隔(秒)\nexport PROCESS_RULE_VERSION=v1           # 工艺规则版本 (Prometheus计数 + 响应暴露)\nexport ENABLE_PROCESS_AUDIT_ENDPOINT=1   # 开启 /api/v1/analyze/process/rules/audit 审计端点 (默认开启)\nexport ANALYSIS_MAX_FILE_MB=15           # 最大文件大小 (MB)\nexport ANALYSIS_MAX_ENTITIES=50000       # 最大实体数\nexport ANALYSIS_PARSE_P95_TARGET_MS=250  # 解析阶段 p95 目标 (用于预算比率计算)\nexport CLASSIFICATION_RULE_VERSION=v1    # 分类规则版本 (Prometheus + 响应识别)\nexport VECTOR_STORE_BACKEND=memory       # 向量存储后端 memory|redis\nexport PROCESS_RULES_FILE=config/process_rules.yaml  # 工艺规则文件路径\nexport VECTOR_TTL_SECONDS=0              # 向量 TTL (秒)\nexport PROCESS_RULE_VERSION=v1           # 工艺规则版本\nexport ANALYSIS_VECTOR_DIM_CHECK=1       # 开启向量维度一致性检查\nexport PROMETHEUS_MULTIPROC_DIR=/tmp     # 可选: 多进程指标导出目录\nexport FEATURE_FLAG_SIMILARITY=1         # 未来扩展: 相似度功能开关\nexport FEATURE_FLAG_OCR=0                # 未来扩展: OCR 功能开关\nexport FEATURE_VERSION=v1               # 特征版本 v1|v2 (v2 启用归一化与额外几何比率)\n```\n\n超限时返回 422 并在指标 `analysis_rejections_total{reason=\"entity_count_exceeded\"}` 中增加。\n\n新增延迟指标:\n```\nclassification_latency_seconds            # 分类阶段 wall clock 延迟直方图\nprocess_recommend_latency_seconds         # 工艺推荐阶段 wall clock 延迟直方图\nvector_store_material_total{material=\"steel\"}  # 向量按材料分布计数\nvector_dimension_rejections_total{reason=\"dimension_mismatch_*\"} # 向量维度拒绝次数\nanalysis_parallel_enabled                 # 并行执行 classify/quality/process 时为1, 否则0\nanalysis_cache_hits_total / analysis_cache_miss_total  # 分析缓存命中与未命中次数\nfeatures: { feature_version: v1 }       # 响应特征版本标记\n```\n\n录制规则新增:\n```\ncad_ml:classification_p95_5m\ncad_ml:process_recommend_p95_5m\n```\n\n### 工艺规则热加载\n规则文件路径可通过 `PROCESS_RULES_FILE` 环境变量指定。默认示例: `config/process_rules.yaml`。\n结构示例:\n```yaml\nsteel:\n  low:\n    - max_volume: 10000\n      primary: cnc_machining\n      alternatives: [sheet_metal]\n```\n\n审计端点:\n```bash\n# 完整返回 (包含原始规则结构)\ncurl -H \"X-API-Key: test\" \"http://localhost:8000/api/v1/analyze/process/rules/audit\" | jq\n\n# 精简返回 (不包含 raw 规则体)\ncurl -H \"X-API-Key: test\" \"http://localhost:8000/api/v1/analyze/process/rules/audit?raw=0\" | jq\n```\n修改后文件 mtime 变化会触发下一次请求自动重载。\n\n### 📟 Runbooks \u0026 Alerts\n常见告警处置:\n- HighErrorRate: 检查最近发布与入口流量激增 (查看 `cad_ml:api_error_rate_5m` 与 `cad_ml:api_request_rate_5m`)，滚动重启有无失败；若大量 5xx 来自单一路径，执行局部熔断。\n- LowOCRSuccessRate: 对应 Provider 通道质量下降，降级到备用 Provider (`ocr_provider=deepseek_hf` 或 `paddle`)；比对 `ocr_processing_duration_seconds_*` 是否超时导致失败。\n- HighResourceUsage: 登录节点查看 `top` / `iostat`; 若 CPU 高且 parse 阶段耗时上升，考虑临时扩容或提升 `ANALYSIS_PARSE_P95_TARGET_MS` 后调整预算。\n- ParseLatencyBudget(未来): 关注 `analysis_parse_latency_budget_ratio` \u003e1.0 持续窗口，可能是大文件或 adapter 回退路径性能问题。\n\nRunbook 链接由 Prometheus alert `runbook` 注解指向此节。\n\n### 特征版本 (Feature Versioning)\n当前支持:\n- v1: 基础几何 (实体数, bbox宽高深, 体积) + 语义 (层数, 高复杂度标记)\n- v2: 在 v1 基础上追加归一化宽/高/深与宽高、宽深比率 (需 `FEATURE_VERSION=v2`)\n- v3: 在 v2 基础上追加 STEP/IGES 几何增强 (solids, facets, 平均体积/实体占比) + 前5实体类型频率向量 (固定5槽位); 适配 `FEATURE_VERSION=v3`\n\n维度升级建议流程:\n1. 暂时关闭维度检查 (`ANALYSIS_VECTOR_DIM_CHECK=0`)\n2. 重新注册或批量重建旧向量为新版本\n3. 开启维度检查并固定新版本 (版本回滚只需切回 v1 并保持检查开启)\n\n后续规划: STEP 几何细化 (edges/surfaces/solids)、层名称频率向量、OCR 文本嵌入扩展到 v3/v4。\nv3 已初步包含 solids/facets 与实体类型频率 (Top-5 正规化)，未来 v4 计划加入边/面数量、B-Rep 拓扑特征、OCR文本嵌入。\n向量元数据已存储：`geometric_dim` / `semantic_dim` / `total_dim` / `feature_version`，用于迁移与一致性校验；分布接口 `/api/v1/analyze/vectors/distribution` 提供 `average_dimension` 与版本频率 `versions`。\n严格格式与矩阵校验: `FORMAT_STRICT_MODE=1` + `FORMAT_VALIDATION_MATRIX=config/format_validation_matrix.yaml` 支持动态 token/尺寸规则与项目豁免 (`exempt_projects`)。\nFaiss 持久化: `FAISS_EXPORT_INTERVAL_SECONDS` 定期导出到 `FAISS_INDEX_PATH` (默认 `data/faiss_index.bin`)，指标 `faiss_export_total{status}` / `faiss_export_duration_seconds`。\nML 分类指标: `classification_model_load_total`, `classification_model_inference_seconds`, `classification_prediction_distribution{label,version}`。\n\n新增指标与增强:\n- `analysis_cache_hits_total` / `analysis_cache_miss_total`：缓存命中率监控 (告警 CacheHitRateLow \u003c30%)\n  - 录制规则: `cad_ml:analysis_cache_hit_ratio_30m` / `cad_ml:analysis_cache_hit_ratio_6h` 提供中短期趋势对比\n- `feature_cache_hits_last_hour` / `feature_cache_miss_last_hour`：特征缓存近1小时滑窗命中与未命中事件计数 (告警 FeatureCacheHitRateLowSlidingHour \u003c30%)\n- `material_drift_ratio`：主导材料占比漂移监控 (\u003e0.85 触发告警 MaterialDistributionDrift)\n- `signature_validation_fail_total{format}`：文件签名与声明格式不匹配的次数\n- `format_validation_fail_total{format,reason}`：严格模式下深度格式验证失败次数\n- `strict_mode_enabled`：严格格式校验模式开关状态 (Gauge)\n - `faiss_auto_rebuild_total{status}`：Faiss 延迟删除达到阈值自动重建结果统计 (success|error)\n\n签名校验 (Signature Validation):\n- 针对 STEP / STL / IGES 做轻量首部校验，失败返回 415 与告警指标递增，并在错误体中包含 `signature_prefix` (前32字节十六进制) 与 `expected_signature`。\n\nFeature Slots (feature_slots):\n- 分析结果 `results.features.feature_slots` 提供特征槽位定义，含 `name/category/version`，避免客户端硬编码索引。\n示例: `[{\"name\":\"entity_count\",\"category\":\"geometric\",\"version\":\"v1\"}, {\"name\":\"norm_width\",\"category\":\"geometric\",\"version\":\"v2\"}]`\n\n向量迁移批次:\n- 迁移响应包含 `migration_id`, `started_at`, `finished_at`, `dry_run_total`。\n- 状态端点 `/api/v1/vectors/migrate/status` 返回最近批次与 `history` (最多10条)，便于审计迁移活动。\n\nFaiss 自动导入与重建:\n- 启动尝试从 `FAISS_INDEX_PATH` 导入 (日志显示维度与大小)。\n- 达到 `FAISS_MAX_PENDING_DELETE` 触发自动重建并记录指标 `faiss_auto_rebuild_total{status}`。\n- 新增退避指标 `faiss_rebuild_backoff_seconds`，失败指数退避 (初始 `FAISS_REBUILD_BACKOFF_INITIAL`，最大 `FAISS_REBUILD_BACKOFF_MAX`)。\n- 配置保持宽松以避免早期误报；可在安全加固阶段收紧匹配逻辑\n\n深度格式校验 (Deep Format Validation / Strict Mode):\n- 设置 `FORMAT_STRICT_MODE=1` 启用严格校验；失败使用扩展错误码 `INPUT_FORMAT_INVALID` + 415。\n- 失败原因示例: `missing_step_header`, `missing_step_HEADER_section`, `stl_too_small`, `iges_section_markers_missing`, `dxf_section_missing`。\n- 运行手册: `docs/runbooks/format_validation_fail.md`。\n\nTTL 行为说明:\n- 向量 TTL (`VECTOR_TTL_SECONDS`) 过期由后台循环与查询时 prune 双路径处理，可能产生重复删除 (幂等安全)\n- 竞态条件不会导致错误，仅可能多次尝试删除相同 key\n- 监控 `vector_store_material_total` 与 `material_drift_ratio` 组合评估数据新鲜度与分布均衡\n\n漂移处置 Runbook: `docs/runbooks/material_distribution_drift.md`\n\n### 向量后端路线 (ANN Roadmap)\n协议 `VectorStoreProtocol` 支持可插拔后端:\n- InMemoryVectorStore: 内存 + 可选 Redis 落地\n- FaissVectorStore (占位): 未安装 faiss 时查询为空，安装后方法抛 `NotImplementedError` 等待后续实现\n  - 已实现基础 IndexFlatIP 增加 / 查询，支持归一化余弦近似、延迟删除 + 手动重建接口 `/api/v1/analyze/vectors/faiss/rebuild`\n  - 延迟删除：`/vectors/delete` 在 Faiss 后端仅标记待删除集合，重建时一次性重新构建索引\n  - 指标：`faiss_index_size`、`faiss_index_age_seconds`、`faiss_rebuild_total{status}`、`faiss_rebuild_duration_seconds`、`vector_query_latency_seconds{backend}`\n  - 老化监控：`faiss_index_age_seconds` 超过阈值(如 3600s) 触发索引刷新或重建告警\n  - 环境：`VECTOR_STORE_BACKEND=faiss`、`FEATURE_COSINE_NORMALIZE=1`\n\n未来: 提供 `VECTOR_STORE_BACKEND=faiss` 真正启用 ANN；增加相似度 Top-K 过滤条件与批量查询性能优化。\n#### 特征差异对比 (Features Diff Endpoint)\n使用 `/api/v1/features/diff?id_a=\u003cid1\u003e\u0026id_b=\u003cid2\u003e` 返回逐槽位差异: `[{index,name,category,version,a,b,delta,percent_change,normalized_delta}, ...]`。\n新增字段说明:\n- `percent_change`: `(b - a) / (|a| + 1e-9)`\n- `normalized_delta`: `|b - a| / max(|a|, |b|, 1e-9)` 范围 [0,1]\n指标: `features_diff_requests_total{status}`、`feature_slot_delta_magnitude`。\n示例响应:\n```\n{\n  \"id_a\": \"A123\", \"id_b\": \"B456\", \"dimension\": 12, \"status\": \"ok\",\n  \"diffs\": [\n     {\"index\":0, \"name\":\"entity_count\", \"a\":10, \"b\":12, \"delta\":2, \"percent_change\":0.2, \"normalized_delta\":0.1667},\n     {\"index\":1, \"name\":\"bbox_width\", \"a\":100.0, \"b\":95.0, \"delta\":-5.0, \"percent_change\":-0.05, \"normalized_delta\":0.05}\n  ]\n}\n```\n用于相似性检索后定位差异来源与幅度。\n\n#### 模型热重载 (Model Hot Reload)\n端点: `POST /api/v1/analyze/model/reload`\n请求示例:\n```\n{\n  \"path\": \"models/new_classifier.pkl\",\n  \"expected_version\": \"v2\",\n  \"force\": false\n}\n```\n响应示例 (成功):\n```\n{\n  \"status\": \"success\",\n  \"model_version\": \"v2\",\n  \"hash\": \"a1b2c3d4e5f6a7b8\"\n}\n```\n失败 (`not_found`): 返回扩展错误体。\n指标: `model_reload_total{status,version}`。\n\n#### 漂移监控 (Drift Monitoring)\n端点: `GET /api/v1/analyze/drift`\n字段:\n- `material_drift_score` / `prediction_drift_score`: PSI 近似分布漂移分数 (0-1)。\n- `baseline_min_count`: 建立基线需要的最小样本数 (env `DRIFT_BASELINE_MIN_COUNT`, 默认100)。\n- `status`: `baseline_pending` 或 `ok`。\n指标: `material_distribution_drift_score`、`classification_prediction_drift_score` (Histogram)，`baseline_material_age_seconds`、`baseline_prediction_age_seconds` (Gauge)，`drift_baseline_created_total{type}` (Counter)。\n建议告警: 15m 移动平均 \u003e0.3 连续三周期。\nRunbook: `docs/runbooks/drift_monitoring.md` (需后续补充)。\n\n#### 解析阶段超时保护 (Parse Timeout)\n环境变量: `PARSE_TIMEOUT_SECONDS` (默认10)。\n超时返回 504 + 扩展错误码 `TIMEOUT`，计数: `parse_timeout_total` 与 `analysis_errors_total{stage=\"parse\",code=\"timeout\"}`。\n\n#### 向量孤儿扫描 (Orphan Vector Scan)\n后台任务周期 (`VECTOR_ORPHAN_SCAN_INTERVAL_SECONDS`, 默认300s) 检测无对应 `analysis_result:{id}` 缓存的向量。\n指标: `vector_orphan_total` (按批次增量) 与 `vector_orphan_scan_last_seconds` (距离最近扫描秒数)。\n用于发现缓存清理或迁移异常导致的引用失配。发现孤儿比例过高可触发自动清理策略。\n\n### 模型热重载回滚与限制 (新增)\n环境变量: `MODEL_MAX_MB` (默认 50MB)。模型重载过程将校验文件大小与 `predict` 方法存在性，失败自动回滚旧模型。\n重载端点: `POST /api/v1/analyze/model/reload`\n新增状态:\n- `size_exceeded`: 文件大小超限。\n- `rollback`: 加载失败已恢复旧模型。\n指标扩展: `model_reload_total{status=\"size_exceeded\"|\"rollback\"}`。\n\n### 特征向量缓存 (Feature Cache)\n环境变量:\n- `FEATURE_CACHE_CAPACITY` (默认 256)\n- `FEATURE_CACHE_TTL_SECONDS` (默认 0 = 不过期)\n缓存命中跳过特征提取；响应 `features.cache_hit=true`。\n指标: `feature_cache_hits_total`、`feature_cache_miss_total`、`feature_cache_evictions_total`、`feature_cache_size`。\n命中率记录规则示例:\n```\nrecord: feature_cache_hit_ratio\nexpr: sum(rate(feature_cache_hits_total[5m])) / (sum(rate(feature_cache_hits_total[5m])) + sum(rate(feature_cache_miss_total[5m])))\n```\n\n### 孤儿向量清理端点 (Orphan Cleanup)\n端点: `DELETE /api/v1/analyze/vectors/orphans?threshold=\u003cn\u003e\u0026force=\u003cbool\u003e\u0026dry_run=\u003cbool\u003e`\n当孤儿数量 \u003e= threshold 或使用 `force=true` 时执行清理；`dry_run=true` 仅统计不删除。\n返回: `{\"status\":\"cleaned|skipped|dry_run\",\"cleaned\":\u003c数量\u003e,\"total_orphans_detected\":\u003c总数\u003e}`。\n指标: `vector_cold_pruned_total{reason=\"orphan_cleanup\"}`。\n### Faiss 健康检查与降级模式\n\n新增端点 `GET /api/v1/health/faiss/health` 返回 Faiss 索引状态和降级信息:\n\n**正常状态示例:**\n```json\n{\n  \"available\": true,\n  \"index_size\": 120,\n  \"dim\": 12,\n  \"age_seconds\": 3600,\n  \"pending_delete\": 3,\n  \"max_pending_delete\": 100,\n  \"normalize\": true,\n  \"status\": \"ok\",\n  \"degraded\": false,\n  \"degraded_reason\": null,\n  \"degraded_duration_seconds\": null,\n  \"degradation_history_count\": 0,\n  \"degradation_history\": null\n}\n```\n\n**降级状态示例 (Faiss 不可用，降级到内存):**\n```json\n{\n  \"available\": false,\n  \"index_size\": null,\n  \"dim\": null,\n  \"age_seconds\": null,\n  \"pending_delete\": null,\n  \"max_pending_delete\": null,\n  \"normalize\": null,\n  \"status\": \"degraded\",\n  \"degraded\": true,\n  \"degraded_reason\": \"Faiss library unavailable\",\n  \"degraded_duration_seconds\": 3600.5,\n  \"degradation_history_count\": 2,\n  \"degradation_history\": [\n    {\n      \"timestamp\": 1732460400.123,\n      \"reason\": \"Faiss library unavailable\",\n      \"backend_requested\": \"faiss\",\n      \"backend_actual\": \"memory\"\n    },\n    {\n      \"timestamp\": 1732464000.456,\n      \"reason\": \"Faiss initialization failed: ModuleNotFoundError\",\n      \"backend_requested\": \"faiss\",\n      \"backend_actual\": \"memory\",\n      \"error\": \"ModuleNotFoundError: No module named 'faiss'\"\n    }\n  ]\n}\n```\n\n**降级模式说明:**\n- 当 `VECTOR_STORE_BACKEND=faiss` 但 Faiss 库不可用或初始化失败时，系统自动降级到内存向量存储\n- `degraded=true` 标志表示当前处于降级模式\n- `degraded_reason` 说明降级原因（库不可用 / 初始化失败）\n- `degraded_duration_seconds` 显示降级持续时间\n- `degradation_history` 记录最近10次降级事件，包含时间戳、原因、请求/实际后端、错误信息\n- `status` 优先级: `degraded` \u003e `unavailable` \u003e `ok`\n\n用于运维查看索引向量规模、维度、距离上次导入/导出时间、待删除向量数量阈值情况以及降级状态监控。\n\n### Feature Cache 统计\n\n端点 `GET /api/v1/features/cache` 返回缓存大小、命中率、TTL 等信息，辅助调优 `FEATURE_CACHE_CAPACITY` 与 `FEATURE_CACHE_TTL_SECONDS`。\n### 漂移基线状态 / 过期\n\n新增端点 `GET /api/v1/drift/baseline/status` 返回基线年龄、创建时间以及是否过期 (`stale=true/false`)。当达到 `DRIFT_BASELINE_MAX_AGE_SECONDS` 配置阈值会触发告警 `DriftBaselineStale`，参考运行手册 `docs/runbooks/drift_baseline_stale.md`。\n\n### 模型安全模式与 Opcode 审计\n\n- 环境变量 `MODEL_OPCODE_MODE` 控制模型重载的安全扫描模式：\n  - `blacklist`（默认）：阻止已知危险 opcode（如 GLOBAL/STACK_GLOBAL/REDUCE）。\n  - `audit`：仅记录观测到的 opcode，不阻止；用于生产审计期。\n  - `whitelist`：只允许白名单 opcode；任何未知 opcode 将被阻止。\n\n- 审计查询端点：`GET /api/v1/model/opcode-audit`（需要 `X-API-Key` 与 `X-Admin-Token`）\n\n  示例响应：\n\n  {\n    \"opcodes\": [\"GLOBAL\", \"BINUNICODE\", \"TUPLE\"],\n    \"counts\": {\"GLOBAL\": 3, \"BINUNICODE\": 12, \"TUPLE\": 12},\n    \"sample_count\": 15\n  }\n\n- 相关指标：\n  - `model_opcode_audit_total{opcode}`：观测到的 opcode 计数（审计/白名单/黑名单模式均采集）。\n  - `model_opcode_whitelist_violations_total{opcode}`：白名单拒绝次数。\n\n### Faiss 自动恢复与降级指标\n\n- 端点：\n  - `GET /api/v1/health/faiss/health`：包含 `degraded`、`degraded_reason`、`degraded_duration_seconds`、`degradation_history`。\n  - `POST /api/v1/faiss/recover`：手动触发恢复尝试（遵循退避）。\n\n- 指标：\n  - `similarity_degraded_total{event=\"degraded|restored\"}`：降级/恢复事件计数。\n  - `faiss_recovery_attempts_total{result=\"success|skipped|error\"}`：自动/手动恢复尝试结果。\n  - `faiss_degraded_duration_seconds`：当前降级持续时间（健康时为 0）。\n  - `faiss_next_recovery_eta_seconds`：下次自动恢复计划时间戳（成功恢复或未计划时为 0）。\n  - `faiss_recovery_suppressed_total{reason=\"flapping\"}`：由于抖动被抑制的恢复次数。\n  - `faiss_recovery_state_backend{backend}`：当前恢复状态持久化后端（`file` 或 `redis`）。\n  - `process_start_time_seconds`：进程启动时间（用于告警静默窗口条件）。\n\n- 建议 Prometheus 规则（示例）：\n\n  - alert: VectorStoreDegraded\n    expr: faiss_degraded_duration_seconds \u003e 300\n    for: 5m\n    labels:\n      severity: warning\n    annotations:\n      summary: \"Vector store degraded for \u003e 5min\"\n\n  - alert: OpcodeWhitelistViolations\n    expr: increase(model_opcode_whitelist_violations_total[10m]) \u003e 0\n    for: 10m\n    labels:\n      severity: warning\n    annotations:\n      summary: \"Model reload whitelist violations detected\"\n\n### Feature Cache 运行时调优与预热\n\n- 统计端点: `GET /api/v1/features/cache` 返回缓存大小、容量、TTL、命中/未命中/驱逐等指标以及命中率。\n- 调优建议: `GET /api/v1/features/cache/tuning` 提供容量与TTL建议和原因。\n- 应用新配置: `POST /api/v1/features/cache/apply` 需要 `X-Admin-Token`，支持 5 分钟回滚窗口，返回快照信息：\n\n  示例响应:\n\n  {\n    \"status\": \"applied\",\n    \"applied\": {\"capacity\": 1024, \"ttl_seconds\": 3600, \"evicted\": 0},\n    \"snapshot\": {\n      \"previous_capacity\": 256,\n      \"previous_ttl\": 0,\n      \"applied_at\": \"2025-11-25T10:30:45.123Z\",\n      \"can_rollback_until\": \"2025-11-25T10:35:45.123Z\"\n    }\n  }\n\n- 回滚旧配置: `POST /api/v1/features/cache/rollback` 需要 `X-Admin-Token`，在窗口内恢复之前的容量/TTL。\n- 预热缓存: `POST /api/v1/features/cache/prewarm?strategy=auto\u0026limit=50` 需要 `X-Admin-Token`，以 LRU 触碰方式预热，返回触碰条目数量。\n\n安全: 以上三个写端点均要求双重认证（`X-API-Key` + `X-Admin-Token`）。\n#### 后端重载\n\n强制重新选择向量存储后端（例如切换为 Faiss 后需要热重载）:\n```bash\ncurl -X POST http://localhost:8000/api/v1/maintenance/vectors/backend/reload -H \"X-API-Key: test\"\n```\n响应:\n```json\n{\"status\":\"ok\",\"backend\":\"memory\"}\n```\n指标: `vector_store_reload_total{status=\"success|error\"}`\n\n---\n\n## 🔥 压力测试脚本 (Stress Test Scripts)\n\n位于 `scripts/` 目录的压力测试脚本用于验证系统在高并发和故障场景下的稳定性。\n\n### stress_concurrency_reload.py\n\n并发模型重载压力测试，验证 `_MODEL_LOCK` 有效性和 `load_seq` 单调递增。\n\n```bash\n# 基本用法\npython scripts/stress_concurrency_reload.py --threads 10 --iterations 10\n\n# 环境变量配置\nexport STRESS_API_URL=http://localhost:8000\nexport STRESS_API_KEY=your-api-key\nexport STRESS_ADMIN_TOKEN=your-admin-token\n\n# 严格模式（任何异常即失败）\npython scripts/stress_concurrency_reload.py --strict\n```\n\n输出示例：\n```\nSTRESS TEST RESULTS\nTotal time: 15.2s | Throughput: 6.6 req/s\nLoad sequence monotonicity: monotonic (1 -\u003e 100)\nVERDICT: PASS - No concurrency issues detected\n```\n\n### stress_memory_gc_check.py\n\n内存泄漏检测脚本，监控 RSS 内存增长和 GC 回收效率。\n\n```bash\n# 基本用法\npython scripts/stress_memory_gc_check.py --iterations 50 --allocation-mb 10\n\n# 环境变量配置\nexport STRESS_API_URL=http://localhost:8000\nexport STRESS_API_KEY=your-api-key\n```\n\n### stress_degradation_flapping.py\n\n降级状态翻转观测脚本，监控 Faiss 可用性切换时的指标一致性。\n\n\u003e **数据源说明**: 脚本优先使用健康端点 (`/api/v1/health/faiss/health`) 的 `degradation_history_count` 字段（权威来源，限制 ≤10），Prometheus `/metrics` 用于获取 `similarity_degraded_total` 计数器、`faiss_degraded_duration_seconds` 时长指标，以及 `faiss_next_recovery_eta_seconds` ETA（成功恢复后应重置为 0）。\n\n```bash\n# 基本用法\npython scripts/stress_degradation_flapping.py --cycles 20 --interval 1.0\n\n# 环境变量配置\nexport STRESS_API_URL=http://localhost:8000\nexport STRESS_API_KEY=your-api-key\n\n# 自定义参数\npython scripts/stress_degradation_flapping.py --url http://staging:8000 --cycles 50 --interval 0.5\n```\n\n**验证内容：**\n- `similarity_degraded_total{event=\"degraded|restored\"}` 计数器递增\n- `faiss_degraded_","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fzensgit%2Fcad-ml-platform","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fzensgit%2Fcad-ml-platform","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fzensgit%2Fcad-ml-platform/lists"}