Files
FacereDataset/log.md
Knowit 6aa72faf84 docs: std corpus 2026-05 snapshot + batch-1000/4000/remaining log
Snapshot of full oshwhub std corpus delivery:
- 12,493 projects total, 12,166 (97.4%) with editor source
- 4 sweep batches + 1 early-mixed = 5 zip artifacts in COS GZ + SG buckets
- 30-day SG-region presigned URLs for downstream pickup

log.md tracks the multi-batch sweep including driver bug postmortem
(bash heredoc python3 missed httpx → 26-min run wasted on empty zips,
recovered by switching to uv run).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-03 10:56:09 +08:00

77 KiB
Raw Permalink Blame History

FacereDataset 执行日志

时间倒序,最新在顶部。


2026-05-03 07:17 batch-remaining-std扫完所有未抓 std 项目7,381双桶副本

Claude 会话

收尾批:把 unfetched std 池子7,381 项)一次扫完。拆 A/B 两批 ~3690 each按 rank 50/50 切A 头部、B 长尾),取消单作者上限 2(这是 sweep 全量,不需要再做多样性约束)。两批走完整 1-4 步 + zip + 双桶推送。

候选筛选

  • Atop 3691 by rankgrade 0-4 都有likes p50=8 / p90=29 / max=275
  • Bbottom 3690几乎全 grade 0/1likes p50=1 / p90=4 / max=21
  • 候选 jsonldata/state/oshwhub_remaining_{a,b}_std_candidates.jsonl

第一轮 driver bug 全军覆没(必读)

症状driver 跑了 26 min "顺利完成",但所有 7,381 项 source 都 0——source_documents=[] 全空。两个 zip 才 7 MB / 6.8 MB只 metadata + description没 source

根因driver 里 Step 4 用 python3 - <<PYEOF 而不是 uv run python -u -

  • Step 1uv run -m crawlers.oshwhub)走的是项目 venvhttpx 在
  • Step 4 用 system python3crawlers.oshwhub.crawler 第一行 import httpx 直接 ImportError
  • set -uo pipefail 没设 -e,每个 Step 的 stderr 写到日志后继续走,外面看 zip 也成功 / COS 也成功
  • driver 不会 fail但源工程 0 拿到

教训bash heredoc + 多 venv 项目里,所有依赖项目代码的 Python 调用必须 uv run,仅 stdlib + 系统 pip 包(qcloud_coszipfile)的可以 system python3。下次写 driver 检查时把每个 PYEOF 块对应的 import 列出来对照。

Recovery driver

重写 /tmp/recover_driver.sh:所有 Python 调用改成 cat > /tmp/step_X.py <<PYEOF ... && uv run python -u /tmp/step_X.py(避免 heredoc-stdin 路径文件式更稳。Step 1/Step 2 已经成功就跳过,只做 Step 4 + retry + zip + COS。

阶段 时间
RECOVERY START 06:18:44
batch_remaining_a 收工 06:48:5530 min
batch_remaining_b 收工 07:17:1128 min
整 recovery 走时 58 min

完成度

batch meta with_source attach_only docs src bytes
remaining_a 3,691 3,641 (98.6%) 50 8,877 3,317 MB
remaining_b 3,690 3,570 (96.7%) 120 8,231 2,783 MB

attach_only 比例A 1.4% / B 3.3%)比 batch-1000 (3.7%)、batch-4000 (0.05%) 没明显异常D/E tier 项目被废弃 / upstream 删的比例本来就偏高,正常。

双桶最终状态

两个桶现在内容完全一致:

对象 大小
batch1000_std.zip 471 MB
batch4000_std.zip 1.38 GB
batch_remaining_a.zip 1.06 GB
batch_remaining_b.zip 891 MB
每桶合计 ~3.78 GB

走 GZ→SG 服务端 cross-region copy 链路,完全不碰 dev1↔SG 公网丢包链路。100 GB 套餐占 ~7.6 GB双桶

整体 corpus 落幕

  • listing 里 origin=std 总数:12,493
  • corpus 里 std 项目12,4935,112 旧 + 7,381 本批)—— 100% 覆盖
  • corpus 总目录数(含 Pro / 早期混抓12,523
  • dev1 占盘13 GB40 GB 总,余 13 GB / 33% free
  • license 主流:约 60% GPL 3.0 + 18% Public Domain + 5% MIT + 5% NC variants混合 batch-1-4 趋势一致)

决策Why

  • 不设作者上限sweep 全量批次,多样性已在 batch-1000 / batch-4000 阶段保证;这里要的是 "全部",不能掉作者。
  • batch-A vs batch-B 按 rank 切而不是随机:万一空间不够,优先保 A更高质量实际两批都顺利完成但万一中断 A 是先做的更稳。
  • 不删 GZ 桶副本:用户有 100 GB 套餐,~3.78 GB 双桶舒适;多副本对未来跨区拉取友好。

下一步建议

  • crawler 加 --backfill-uuids-file <path> 选项替代 --uuids 字符串,下次大批量不用绕路写 driver 内嵌 Python参考 /tmp/backfill_4000.py 模式可以直接搬进 crawler
  • driver 模板加 sanity check跑完每个 batch 用 du -sh /tmp/${SHORT}.zip 与 raw bytes 比,如果压缩比 < 5% 报警(这次的 7 MB zip 用这个能立即抓到)
  • 全 std corpus 已落地,下次扩量目标应该是 Pro 项目93 项飞控 Pro 候选还没动)或扩到 oshwhub 之外站点

2026-05-03 03:58 batch-4000-stdStep 1-4 + zip + COS 链路落地 SG box

Claude 会话

接 batch-1000-std。再扩 4000 项 std → corpus 142 → 1142 → 5142。本批走完整 1-4 步zip 后通过 COS 跨区链路拉到 SG box 本地(绕开 dev1↔SG 公网丢包链路)。

候选筛选

  • 数据源同上
  • A 档grade≥3 & like≥10剩 396 项不够 4000下沉到 unfetched 全池 11,381 按 rank score 倒排,单作者 ≤ 2取前 4000
  • 候选 jsonl 落 dev1 data/state/oshwhub_batch4000_std_candidates.jsonl
  • 质量分布grade 4: 16 / 3: 448 / 2: 1798 / 1: 1319 / 0: 419likes p50=10 / p90=42A 档第三梯队 + B + C 头部)

抓取dev1concurrency=5

  • Step 1 详情扫 license~24 min3989/4000 OK + 11 fail全 "Server disconnected"),重抓 concurrency=2 全过 → 4000/4000 metadata
  • Step 2 license 盘点56% GPL 3.0、20% PD、4.8% MIT、4.8% NC-SA、4.3% unknownunknown 比例比 batch-1000 高B/C tier 项目 license 标注更随便)
  • Step 4 std-source backfill~31 min3983 OK + 17 fail
    • 15 项 "Server disconnected" 瞬态,重抓全过
    • 2 项 upstream 真实问题1× 404 文档未找到doc 被删1× code 104001(项目封)。这 2 项保留 metadata-only
  • 最终4000/4000 metadata · 3998 含完整 sch/pcb 源工程 · 2 metadata-only

关键修:--uuids 撞 ARG_MAX

  • backfill 路径用 --uuids "$(jq ... | paste -sd,)"4000 UUID × 33B ≈ 132 KB > ARG_MAX (128 KiB)
  • 现象:bash: /usr/bin/nohup: Argument list too long,进程没启动,但 pgrep 误匹配 stale shell
  • 修:临时脚本 /tmp/backfill_4000.py,直接 import crawlers.oshwhub.crawler 内部函数(_run_backfill_concurrent / fetch_std_sourceUUID 集从 candidates jsonl 读,绕开命令行
  • 长期crawler 应加 --backfill-uuids-file <path> 选项,下次扩量再改

ZIP 打包dev1

  • 4000 dirs26,098 文件4,489 MB raw → 1,445 MB zip,压缩比 32.2%99 秒完成
  • 用 Python zipfile + compresslevel=1dev1 没装 zip 二进制)

传输链路COS 三段0 字节走公网丢包链路)

时长 速度 路径
dev1 → facere-gz-1321068335 (ap-guangzhou) 17s 166 MB/s 同区内网
GZ 桶 → facere-1321068335 (ap-singapore) 23s COS 服务端 copyVM 不参与)
SG 桶 → SG box 8.5s 201 MB/s 同区内网
整 1.4 GB 跨地域 ~50 秒

哈希校验穿三段:a79a87e4a3f5dfbad80d9ba94f557b09010e104f6e0c968ea87eba2267b262b3

决策Why

  • rank-score top-4000 不设硬阈值硬过滤太挑会漏rank score 已综合 like*3 + grade*50 + views/100 + comments*2 + fork*2 + star自动平衡。最低 like=0 也进了几百项(多数是 grade≥1 但社区互动少的),可接受。
  • 不传统 scp走 COS 三段:之前飞控-77 33 MB scp 走 dev1↔SG 6.5%-loss 链路要 3 min这次 1.4 GB 走 COS 50 s 完事,提速 ~150×。COS 跨区复制流量计费 ~¥0.5/GB × 1.4 GB ≈ ¥0.7,零头。
  • zip vs tar.gzzip 选 level=1速度 vs 体积平衡tar.gz 单线程 deflate 跟 zip-l1 体积相近但慢 2-3×

完成度

  • corpus 由 1142 项扩到 5142 项+4000
  • License: 56% GPL 3.0 主流不变unknown 比例从 0.4% (batch-1000) 涨到 4.3% (batch-4000),与 B/C tier 项目低标注度一致
  • editor 版本从 6.3 全谱系到 6.5.42 都覆盖到了
  • 源文件体积dev1 上 data/raw/oshwhub/ 占 ~10 GB含全部历史 + 本批)

下一步建议

  • corpus 已经达 5142 项,足够下游 EPRO2/Std → KiCad 训练数据规模
  • 真要继续扩A+B+C 档已基本吃完头部,下沉 D 档grade=0 或 like=0质量回报递减可暂缓
  • crawler 加 --backfill-uuids-file 选项,避免下次 ARG_MAX 撞墙
  • COS GZ 桶里的 batch1000_std.zip + batch4000_std.zip 用完可以删SG 桶副本足够),节省 ~¥0.2/月 存储费

2026-05-03 batch-1000-stdStep 1-41000 块标准板源工程入库dev1

Claude 会话

走 batch-200 脚手架抓 1000 项 std A 档剩量。用户指令"只走 1-4 步":不抓附件、不传 SG、不 push gitea数据留 dev1。

候选筛选

  • 数据源:data/state/oshwhub_listing_full.jsonl33,695 项)
  • 过滤:origin=std AND grade≥3 AND like≥10A 档),减去已抓 142 项 → 池子 1,396 项
  • 排序rank score 倒序;单作者 ≤ 2 → 取前 1000791 唯一作者)
  • 候选 jsonl 落 dev1 data/state/oshwhub_batch1000_std_candidates.jsonl(不入 git可重算
  • like p50=43 / p90=170 / max=420A 档第三梯队,吃掉 1,396 池子的 ~72%

抓取dev1 Guangzhouconcurrency=5

  • Step 1 详情扫 license~80s996/1000 OK + 4 "Server disconnected" 瞬态 fail
  • 4 项重抓 (concurrency=2) 全 OK → 1000/1000 metadata
  • Step 2 license 盘点57% GPL 3.0、19% PD、8.6% CC-BY-NC-SA、5.4% MIT、其它 < 2%。形态与 batch-50 / batch-200 一致。
  • Step 3 SKIP本批 std-only没有 Pro 候选
  • Step 4 std-source backfill~6 分钟1000/1000 OK0 fail

完成度

  • 1000/1000 metadata
  • 963 项有完整 std 源工程2,853 个 sch+pcb doc平均 2.96 docs/proj
  • 37 项 upstream attachments-onlysource_documents=[] 真实状态,跟飞控-77 4/77 同形态)
  • 源文件体积1.47 GB on dev1按 batch-50 估算 12 MB/项 偏高,实测 1.5 MB/项——A 档第三梯队项目体量比头部小)
  • editor 版本194 项 6.4.25 / 91 项 6.5.5 / 77 项 6.5.16.4-6.5 全谱系覆盖
  • corpus 由 142 项扩到 1,142 项(+1000

决策Why

  • 不传 SG / 不 push gitea:用户指明只走 1-4 步。数据 1.47 GB 走 dev1↔SG 6.5%-loss link 估 ~3 hr 单 scp没必要现在花。要传时再走 COS 跨区或 split-parallel-scp。
  • concurrency=5 全程:飞控-77 验证过 std doc endpoint 这个并发安全;实测 Step 1 ~12s/100项、Step 4 ~36s/100项零限流告警。
  • Step 3 跳过:候选池纯 std-originPro backfill 没数据可处理。

下一步建议

  • 真要消费这批数据:(a) 在 dev1 直接 push giteaSSH transport~10 min @ 1.5 GB或 (b) 走 COS 跨区同步到 SG。
  • 该批的 metadata-only 部分37 项 attachments-only若想补 sch/pcb需要回头单独扫 attachment ZIP 看里面是否 bundled 了 EasyEDA 工程,那得改 crawler。
  • A 档剩余只有 396 项了;下次再扩可以下沉到 B 档grade≥2 & like≥53,884 项 unfetched

2026-04-30 19:10 飞控-77主题定向抓 77 块标准飞控板

Claude 会话

走完整 pipeline本地索引筛 → dev1 抓 → tar+scp 回 SG → push gitea。

候选筛选

  • 数据源:data/state/oshwhub_listing_full.jsonl33,695 项)
  • 过滤:origin=std AND ('飞控' in name OR '飞控' in introduction) → 79 hits
  • 减去已抓的 2 项 → 77 个新候选
  • 工具:临时脚本,候选 jsonl 落 dev1 data/state/oshwhub_feikong_candidates.jsonl(不入 git可重算

抓取dev1 Guangzhouconcurrency=5

  • Step 1 详情扫 license: ~12s, 74/77 OK + 3 fail
  • 3 fail 都是同一个 buglisting entry 的 count dict 缺 like 字段crawler 直接 count["like"] 抛 KeyError
  • 修:rank_score / pick_top / metadata builder 全改 count.get("like", 0) 形式commit 29530e0
  • 重抓 3 项 → 全 OK
  • Step 4 std-source backfill: ~80s, 73/77 拉到源工程文档4 项 upstream 就是 attachments-only没编辑器 sessionsource_documents=[] 是真实状态)

传输tar+scp 而非 dev1 push gitea

  • dev1 → SG 同样吃 6.5% 丢包 link单 TCP cwnd 压扁
  • 33 MB tarball 走 scp ~3 min与之前 dev1 push gitea 同量级)
  • 落 SG 后从 SG 直推 gitea同区低延迟秒级完成
  • rebasedev1 端有人手动推了 74-项 commit (c199840),本地 77-项 superset rebase 上去conflicts 仅 projects.mdregen 一遍即解)

完成度

  • 79/79 飞控 std 项目都有 metadata
  • 73 项有完整 std 源工程
  • 4 项是真实 attachments-onlyupstream API 返空)
  • License 分布65% GPL 3.011% PD11% MIT~6% CC variants与 batch-50 同形态)
  • corpus 由 65 项扩到 142 项(+77

下一步建议

  • 跨区传输优化tencent-cloud COS 同 cloud 跨区复制走骨干网,比 scp 快几倍;下次大批量再装。或者 split + 并行 scp 也能拉 3-5x。
  • 清理 stash 里那两份 .decrypted.txtpre-existing 调试残留)
  • 可以再试一波 Pro 飞控93 hitsorigin=pro

2026-04-29 04:30 std/ writer 翻 Option 2raw objects dump + mapping doc

Claude 会话

fe6971f。下游同学回了具体规格:选 Option 2objects dict 直接 dump不要我们做 tilde 串映射,他自己写 ~100 LoC adapter 翻。把之前 Option 3full Std shape: ["TRACK~...", ...])那套删了,重写。

下游确认的规格

  1. shape[] 不要保留——adapter 从 objects 重建,空占位反而误导
  2. 全 mil——不转 mmBBox 也 milStd canvas 那一行 ~mil~ 已经写了)
  3. head 必带
    • editorVersionfacere-epro2/0.1 (epro2 <doc.head.editVersion>)Wokwi 据此选解析分支
    • docType"3" (PCB) / "1" (SCH)
    • units"mil"
  4. layers[] 沿用 Std 53-layer 字符串数组格式("1~TopLayer~#FF0000~..."inner SIGNAL 有用到才追加
  5. 保留空 stub preference / netColors / DRCRULE——失败时 grep 路径稳定
  6. per-doc 一个 .json,平铺,不合并

写完三件

tools/epro2/std/pcb_writer.py 重写

40 行核心逻辑(之前 Option 3 是 500 行):

  • _gather_bbox_points:扫所有 op 的 x/y/startX/... 等坐标字段min/max
  • _used_inner_signal_layers:找真正有 primitive 的 SIGNAL 内层 id
  • envelope 直接 dump dict(doc.objects)

tools/epro2/std/sch_writer.py 重写

更短——schematic 没 copper layerlayers=[]。其它结构跟 PCB 一样。

docs/sources/epro2_to_std_mapping.md 新增

这是给下游 adapter 的关键文档——他写 100 LoC adapter 时按这个表查。内容:

  • EPRO2 layer id → Std layer id 重映射表最坑5↔7 mask/paste 反着、11→10 outline、12→11 multi、SIGNAL 15+→Std 21+
  • PCB OPTYPE → Std verb 全表TRACK / VIA / COPPERAREA / SOLIDREGION / CIRCLE / LIB+#@$PAD+TEXT
  • SCH OPTYPE → Std verb 全表W / N / T / LIB+#@$P—— 标了 best-effort没 Std SCH 实样
  • COMPONENT placement 旋转 + 平移公式footprint-local → PCB-absolute
  • 5-Voltage 占位符 pid8a0e77bacb214e 的 Global Net Name → 额外 N flag 的 trick

mapping doc 直接从 fe6971f 那个 Option 3 writer 提炼出来——他不用读我们代码,照表填就行。

实测

ESP-VoCat 6 PCB + 9 SCH = 15 JSON

  • 一个典型 PCB: objects=1719, layers=17, BBox=(-2293, -900, 2997×1899)
  • head.units = "mil"head.editorVersion = "facere-epro2/0.1 (epro2 3.2.91)"head.docType = "3"
  • shape 字段已确认不存在
  • objects 原 payload 1:1 保留(含 LAYER ops 的 activeColor 等所有字段)

决策Why

  • 不留空 shape: []——下游说"误导 adapter"。明确不存在,比空数组更诚实
  • head.editorVersion 加前缀 facere-epro2/0.1——区分我们的输出 vs lceda 真实 Stdadapter 看到这个能猜出是 EPRO2 转过来的
  • 保留 preference/netColors/DRCRULE 空 stub——下游说失败 grep 排查方便
  • mapping doc 单独成文不混在 README——adapter 作者一个文件就够,不用读源码

测试

82 → 84 单测全过:原 Option 3 的 11 个测试改成验 Option 2envelope 必带 key / 无 shape / head units&version / objects 1:1 / BBox min-max / 内层 SIGNAL append / docType reject

Push

fe6971f 的 Option 3 已被这次 commit 覆盖 / 简化掉。下游回来如果说 mapping table 有错位再修——但他自己拿表填 adapter跑通后给反馈我们再迭代。


2026-04-29 04:00 Std-format JSON 转换器EPRO2 → 下游同学 Wokwi pipeline 的输入格式

Claude 会话

KiCad 那条路下游同学不需要——他们的 Wokwi pipeline 吃 oshwhub Std (lceda) 的 JSON dict-format。EPRO2 解密我们已经搞定per-doc 流就在 data/raw/<uuid>/source/),现在缺的是把 EPRO2 op-stream 翻成 Std 的 dataStr.shape 字符串数组。

新增 tools/epro2/std/(跟 kicad/ 平级,旧的不动)

参照 data/raw/oshwhub/3e2f893d.../25931ddab8.json 一个 Std PCB 实样反推协议:

  • 信封:{success, code, result: {uuid, puuid, title, docType, components, dataStr: {head, canvas, shape, layers, ...}}}
  • shape 字符串:VERB~field1~field2~...~ 分隔
  • LIBfootprint placement下面挂 PAD/TEXT 用 #@$ 分隔器嵌套

已实现 verb 映射

PCBdocType=3高保真对照实样

EPRO2 op Std verb 备注
LINE TRACK layer 单独映射
VIA VIA 字段顺序 x~y~outerD~net~innerD~uuid~lock
POUR COPPERAREA path 转成 SVG M..L..Z
FILL SOLIDREGION 同 SVG path
POLY (CIRCLE) CIRCLE
COMPONENT + FOOTPRINT.PADs LIB...#@$PAD...#@$PAD... 内层 PAD 坐标做了 placement rotate + translate

SCHdocType=1best-effort无实样

  • LINE → Wwire 段)
  • LINE.lineGroup → WIRE.NET → 在端点放一个 Nnet flag
  • COMPONENT → LIB...#@$P...(嵌 PIN/TEXT包括我们之前发现的 5-Voltage 电源占位符的 Global Net Name
  • TEXT → T

重要 caveat:我们 corpus 里所有 Std 项目都只有 PCBdocType=3没有 SCHdocType=1实样。SCH 的 verb 字段顺序是按 EasyEDA Std 公开 spec 写的,可能跟下游 parser 实际期望的字段顺序有出入。下游同学 review 后给反馈,错的位移修一下就行。

Layer 映射(重要,跟 KiCad 不一样)

EPRO2 跟 Std 的 layer id 不完全对齐:

  • EPRO2 layer 5 (TOP_SOLDER_MASK) → Std 7
  • EPRO2 layer 7 (TOP_PASTE_MASK) → Std 5 ← 跟 5 互换!
  • EPRO2 layer 11 (OUTLINE) → Std 10 (BoardOutLine)
  • EPRO2 layer 12 (MULTI) → Std 11 (Multi-Layer)

inner SIGNAL 层EPRO2 15+ → Std 21+ (Inner1 起步)。

CLI 平铺输出

uv run python -m tools.epro2.std <project_dir> --all --out <dst>

输出按 Std 习惯平铺<dst>/<doc_uuid>.json,不分 board 子目录。三个互斥模式:--all-pcb / --all-sch / --all

ESP-VoCat 实测

15 个 doc → 15 个 JSON

类型 数量 实测产物
PCBdocType=3 6 tracks 2K+, vias 700+, copperareas 19, libs 206, pads 807
SCHdocType=1 9 wires 814, libs 477, netflags 838 (含 power-port), texts 71

libs_unresolved=0 全过——FOOTPRINT/SYMBOL doc 跨文档解析全部命中。

JSON 信封跟 Std 实样对比top-level keys 一致(success/code/resultresultmaster/owner/created_at/... 这些爬取层 metadata(不是数据本体,下游应该不需要);dataStr.shape/layers/canvas/head 全有。

决策Why

  • 不替换 KiCad 那套:用户说"原先那套页不要换"——保留 tools/epro2/kicad/,新写 tools/epro2/std/ 平级,命令行也独立 python -m tools.epro2.std vs python -m tools.epro2.kicad
  • json.dumpsseparators=(",",":") 不缩进:实样 Std 文件就是单行紧凑 JSON没换行也没缩进节省空间也方便 diff。
  • 数字格式 _num():实样 Std 输出整数不带 .04303 不是 4303.0),用 math.isclose(f, int(f)) 判断后选择 int repr跟 Std 风格对齐。

测试

71 → 82 单测全过std_writers 11 个新(信封 / TRACK 字段顺序 / VIA 字段顺序 / COPPERAREA SVG path / LIB 嵌 PAD via #@$ / docType=1 / W+N 配对 / power-port netflag / json.dumps round-trip

下游交付

15 个 ESP-VoCat JSON 已经在 /tmp/std_json/。要给下游同学的最小 deliverable

data/processed/std_json/<project_uuid>/<doc_uuid>.json

下一步:跑剩 4 块 Pro 项目X86主板 / 220V电源 / 泰山派 / 梁山派)—— Pro 2.x 那两块仍然不行,需要 Pro 2.x JSON 解析器。


2026-04-29 03:30 rate-limit benchmark 整理成正式报告

Claude 会话

把零散跑出来的 rate-limit ladder 探针结果整理成 docs/sources/probe_rate_limit_results.md,从临时增量笔记升级成正式 benchmark 文档。

5 个 host 全部探完

Host 旧 sleep 新 sleep 加速
pro.lceda.cn/api/v4/... 5.0s 0.5s 10×
lceda.cn/api/documents/... 5.0s 0.5s 10×
oshwhub.com/<owner>/<path> 2.0s 1.0s 2×
oshwhub.com/api/project 2.0s 1.0s 2×
modules.lceda.cn/... 0.2s 0.2s — (已优化)

关键发现:原 5s/req 完全是出于"Pro 要登录、被封号最痛"的心理顾虑而设没有实测依据。Pro API 实测 25 distinct UUID 连发 0/25 badmedian 410ms latencyQPS=2 完全经得住。Std doc endpoint 同样的故事。

对 batch-50 的净效sleep 总时间 32 min → 3 min约 10x整批 walltime 估算 ~2h → ~10-15 min。

报告结构TL;DR 总表 → 方法论(包括安全约束 + 限制)→ 5 个 host 各自详细数据 → 最终设置 → 复现指南 → 后续考虑。

下一步:直接跑 batch-50 计划的 Step 1详情页扫 license就行。


2026-04-29 03:00 跑完 5 块 Pro 项目 export发现并修两个 --all 崩溃路径

Claude 会话

3c00edf。给下游同学打包5 块 Wokwi pipeline 不吃的 Pro 项目X86主板 / 220V电源 / ESP-VoCat / 泰山派 / 梁山派)跑 --all 一键 export。

实战结果

项目 EPRO2 类型 --all 结果 备注
ESP-VoCat Pro 3.x AES 6 boards 之前已验证
220V 电源 Pro 3.x AES 7 boards 修了两个 bug 之后
X86 主板 Pro 3.x AES ⚠️ 4/5 boards 7374 docsCPU 板 PCB 写到 OOM swap death-spiral14 min 还没完,杀掉保剩 4 板
泰山派 (RK3566) Pro 2.x JSON no BOARDs 我们的 EPRO2 pipeline 不识别 Pro 2.x docType
梁山派 Pro 2.x JSON no BOARDs 同上

220V 跑出来发现的两个崩溃路径

Bug A: KiCad 拒收奇数铜层数

12:45:56 AM: Error: 3 is not a valid layer count in '...kicad_pcb', line 31

220V 电源里的板子有 1 个内层 SIGNAL 用到GND 一层),加 F.Cu+B.Cu 总共 3 层。KiCad 8 要求铜层数偶数2/4/6/...)。修法:奇数时 pad 一个空的 In{N+1}.Cu总层数凑偶。

Bug B: 同标题 BOARD 互覆盖

220V 项目里有两块都叫 "显示板" 的不同 BOARD不同 uuid。我之前的 --all 用 title 推 basename两块争同一个目录第二块写完就把第一块覆盖。修法title 撞名时给所有撞名实例都加 BOARD uuid 前 8 位后缀(显示板_52e8cc76 / 显示板_55d32906);只一份的还是干净 basename。

Pro 2.x 没拿下

泰山派 / 梁山派的 source 文件是纯 JSON<uuid>.json),不是 .epro2 二进制流。我们的 replay_project 读得了文件但 doc_type=None——head 里没 docType 字段,整个识别链路断掉,_group_by_board 拿不到 SCH/PCB 分组。

要补这 2 块得写单独的 Pro 2.x → KiCad writerplaintext dataStr 解析跟 EPRO2 是两套不一样的对象模型;也许能跟 Std json 共用)。本轮范围外。

决策Why

  • 基板撞名一律加 uuid:第一份保 clean basename 的方案有歧义性,"哪个是真的"不可靠。所有撞名一律带 uuid 后缀虽然多两个字符但绝对安全。
  • 不在 ESP-VoCat 上重跑验证奇数层 fixESP-VoCat 的板要么 2 层(无 inner要么 4 层2 inner不会触发奇数层路径重跑没意义。

测试

71 → 73test_odd_inner_signal_count_padded_to_even_total + test_duplicate_board_titles_get_distinct_basenames

X86 OOM 真因

跑到 14 分钟还没出 CPU 板 .kicad_pcb看进程状态

  • VmRSS 1.96 GB + VmSwap 1.41 GB = 实占 3.4 GB
  • 系统 3.3 GB RAM + 4 GB swapfree 120 Mi、swap free 434 Mi——死循环 swap 抖
  • read_bytes 24 GB远超数据本身—— 全是 swap-in/swap-out
  • State: Duninterruptible disk sleep

CPU 板 PCB doc 是 X86 项目里最大的(>8K objects + 35+ 子 SCH 页),我们的 pcb_writer 把整个输出 list 在内存里建好再 to_sexpr 一次性序列化,加上 35+ 次 write_sch_page(每次 Relations.build 加 lib_symbols 嵌入)累积爆 RAM。

杀掉,保已经写完的 4 块。要修得做 streaming 输出(边算边写文件,不在内存里建大 list。下一轮独立改动。

下游交付

来源 板数 状态
ESP-VoCat 6
220V 电源 7
X86 主板 4/5 部分CPU 板 SCHEMATIC1_1 缺 .kicad_pcb子页 SCH 都在)
泰山派 0 Pro 2.x本轮不支持
梁山派 0 Pro 2.x本轮不支持
合计 17 32/32 文件 kicad-cli 解析通过

2026-04-29 02:30 KiCad 工程文件 + --all 一键导:双击 .kicad_pro 打开 GUI

Claude 会话

adc5dc5。两件小事一起做:发 .kicad_pro + CLI 加 --all。目标是让消费者(咱们的 ML 训练代码 / 下游同学)双击就能在 KiCad GUI 里同时打开 schematic + PCB 配对。

1. tools/epro2/kicad/pro_writer.py

KiCad 8 的 .kicad_pro 是 JSON。最小可用集

{
  "meta": {"filename": "<basename>.kicad_pro", "version": 1},
  "sheets": [["<root_sheet_uuid>", ""]],   // 绑 schematic 根 sheet
  "board": {...}, "schematic": {...}, "net_settings": {...}, ...
}

只要 .kicad_pro / .kicad_sch / .kicad_pcb 三个文件同名同目录KiCad 自动配对——双击 .kicad_pro 同时弹两个编辑器。sheets 数组里的 uuid 必须和 .kicad_sch 的 (uuid ...) 对得上,所以 write_root_sheet 加了 root_uuid 参数让调用方注入确定值。

2. CLI --all

按 BOARD uuid 分组 SCH 和 PCB两边的 META 都带 board: <uuid>,自动 1:1 对应),每个 BOARD 一个目录:

out/
├── EchoEar-BaseBoard-V1_0/
│   ├── EchoEar-BaseBoard-V1_0.kicad_pro
│   ├── EchoEar-BaseBoard-V1_0.kicad_sch       (root)
│   ├── EchoEar-BaseBoard-V1_0.kicad_pcb
│   └── P1_45092758.kicad_sch                  (子页)
├── EchoEar-CoreBoard-V1_0/
│   ├── EchoEar-CoreBoard-V1_0.kicad_pro
│   ├── EchoEar-CoreBoard-V1_0.kicad_sch
│   ├── EchoEar-CoreBoard-V1_0.kicad_pcb
│   ├── Overview_dc13d6d2.kicad_sch            (4 个子页)
│   ├── MCU_510cff33.kicad_sch
│   ├── Interface_b336a7c7.kicad_sch
│   └── codec_0b0163fa.kicad_sch
... (5 boards total)

basename 从 SCH/PCB title 剥掉 SCH-/PCB- 前缀(SCH-EchoEar-CoreBoard-V1_0EchoEar-CoreBoard-V1_0schematic 和 PCB 自然 collapse 到一个项目。

ESP-VoCat 实测

6 个 BOARD含 LCD-BD 那个 SCH 被删的——只有 PCB照样生成 .kicad_pro.kicad_pcbsheets:[]

项目 sch 解析 pcb 渲染
EchoEar-BaseBoard-V1_0 ✓ ERC 485
EchoEar-CoreBoard-V1_0 ✓ ERC 1205
EchoEar-Rotating-Base-LCD-BD-V1_0 (no sch)
EchoEar-Rotating-Base-Mainboard-V1_0 ✓ ERC 412
ESP-VoCat-MicBoard-V1_0 ✓ ERC 133
ESP-VoCat-Rotating-Base-Sub-board-V1_0 ✓ ERC 30

pro.sheets[0][0].kicad_sch 第一行 (uuid ...) 字符串相等(验证过 CoreBoard366d3e53-5167-4e17-9325-c2fccbe4330b)。

决策Why

  • basename 对齐 = KiCad pairing 的全部KiCad 不读 .kicad_pro 里的 boards/schematic 字段去找文件,纯靠同目录同 basename自动配对。其它字段都是项目 settings缺了 KiCad 用 default 兜底。
  • sheets: [] 兜底 LCD-BDSCH 被 DELETE_DOC 的板子没 schematic rootpro 里空数组兜底KiCad 打开时只显示 pcbnew不会卡。
  • --all 不替代 --all-sch / --all-pcb:保留两个细粒度命令——只想看 schematic 或只想 batch-export PCB 时不必生成 .kicad_pro 噪音。

测试

68 → 71 单测全过pro_writer 3 个filename + root uuid 绑定 / sheets 空数组 fallback / KiCad 8 顶层 key 完整性)。

下游交付

下游同学的 Wokwi pipeline 不吃的 5 个 Pro 项目3 块 EPRO2 AES + 2 块 Pro 2.x现在一条命令就能转

uv run python -m tools.epro2.kicad data/raw/oshwhub/<uuid> --all --out <dst>

每个项目目录拷贝到他们 corpus 即可。


2026-04-29 02:00 PCB Phase-2POUR → KiCad zoneCoreBoard unconnected -43%

Claude 会话

e614044。Phase-1 PCB 6 板都解析了但 DRC 报很多 unconnected——大头是 GND/AGND 走 POUR 覆铜没导出来。补 zone 导出。

做的

pcb_writer.py_decode_zone_path 处理 EPRO2 POUR.path 三种形态:

  • [['R', x, y, w, h, ...]] — 矩形实测最常见CoreBoard/MicBoard 全是这个)
  • [['CIRCLE', cx, cy, r]] — 圆形(按 36 段近似成 polygon
  • [[x1, y1, 'L', x2, y2, ...]] — polylineARC token 跳过 4 个 token按弦近似

每个 POUR 输出 (zone (net N) (net_name "..." ) (layer "F.Cu") (polygon (pts ...)) (filled_polygon (pts ...)))

关键坑:必须 emit (filled_polygon)

第一次只发 (zone)(polygon) 边界的版本DRC 完全不变——kicad-cli pcb drc 自动跑 zone fill只有 GUI 的"填充覆铜"会跑。所以 file 必须自己声明已填充。简单做法:用 boundary polygon 当 filled_polygon= "整个 pour 区域都是铜",忽略 pad clearance/thermal

ESP-VoCat 6 板 DRC 对比unconnected_items count

before zones after zones Δ
BaseBoard 227 227 0
CoreBoard 358 205 -43%
MicBoard 75 75 0
LCD-BD 43 43 0
Mainboard 179 179 0
Sub-board 1 1 0
TOTAL 883 730 -17%

为什么只 CoreBoard 改善明显

抽样 MicBoard 残留 75 unconnected

  • AGND 94 个 item 里很多 pad 在 zone boundary 之外——POUR 矩形是 (72.3, 112.3)→(126.8, 126.0),但 AGND pad 在 y=107 上方
  • 大量 $1N1865 这种内部网——根因是 via 没绑对网(不是 POUR 能解决的)

EPRO2 用户画 POUR 时通常只覆元件密集区,不覆全板;外围 trace 自己接。zone 解决"靠 pour 接到 GND"的 pad但解不了"trace 路由不通"或"via 网漂移"。

CoreBoard zones=74-layerGND+POWER+AGND 各一对),覆盖面广,效果明显。其它板 zones 多是 2 个,覆盖小。

决策Why

  • filled_polygon = boundary 不做 clearance/thermal 计算:自己实现 zone fill 算法工作量爆炸KiCad 实现是 C++ 几千行。boundary fill 是"连通性正确clearance 不精确"——KiCad GUI 一键 refill 即可矫正。这条路保留 EPRO2 user-drawn boundary 作为 single source of truth。
  • 不读 POURED.pourFillPOURED 是 EPRO2 自己 fill 算法的输出path 含 ARC 难解析、坐标系跟 POUR 不一定对齐。boundary 直接用更可靠。
  • ARC 在 polyline 里按弦近似:跟 Phase-1 ARC 处理一致KiCad 解析得了,几何稍偏(不会比 POUR 不导更糟)。
  • 不强行优化 MicBoard 那种 zone 之外的 pad:那是 EPRO2 source 本身的连通方式trace + via不是 zone 能修的。

测试

65 → 68 单测全过rectangle path → 4 corners + filled_polygon mirror / circle → 36-seg polygon / 非 copper layer skip。

下一步建议

  • ARC 圆心反推(中等工作量):消 invalid_outline 警告 + zone polyline 里 ARC 段更准。需要三点定圆。
  • schematic + PCB 同时跑小工作量CLI 加 --all 同时输出两套,目录配对。
  • .kicad_pro 项目文件(小工作量):双击就能打开 KiCad GUIschematic 和 PCB 自动配对。

2026-04-29 01:30 KiCad 导出 Phase 3 PCB6/6 .kicad_pcb 全部 kicad-cli 通过

Claude 会话

ff5553f 后续。Forge 投影最后一块——schematic 已经够用,做 .kicad_pcb 导出。一上来就铺了 Phase-1 最小可解析 scope要先有"能在 KiCad 8 里打开 + kicad-cli 处理"的底线),不追全保真。

做的

  1. tools/epro2/kicad/pcb_writer.py — 单 PCB doc → .kicad_pcb 主入口

    • 数据驱动的层映射 _build_layer_map:扫所有 primitive 的 layerId,把真正用到的 SIGNAL 内层id 15+)按 EPRO2-id 顺序铺成 In1.Cu/In2.Cu/...F.Cu 永远 ordinal 0、B.Cu 永远 ordinal 31KiCad 硬约定)。
    • net 表:从 NET op 取名字,从 1 开始分配 id0 留给 KiCad 的 "no net")。
    • 走 LINEcopper 层 → (segment ...)layer 11 OUTLINE → (gr_line layer Edge.Cuts);走 VIA → (via);走 layer-11 POLY → 把 path 拆 polyline 段 emit 成 Edge.Cuts 上的 gr_line 链(实测板子轮廓全在 POLY 不在 LINE少这步 edge=0
    • ARC 现在按弦近似Phase 1 不解 EPRO2 ARC.path 的圆心/半径表示KiCad 收得下DRC 报 invalid_outline——下一轮改
  2. tools/epro2/kicad/footprint_writer.py — inline (footprint ...)

    • 走 FOOTPRINT.PADRECT/ELLIPSE/OVAL/POLYGON → KiCad rect/circle/oval/customhole 存在则 SMD→thru_hole + (drill ...)SLOT 走 (drill oval w h) 二参形式。
    • layer 映射layerId 1 → F.Cu+F.Mask+F.Paste、2 → B.Cu+B.Mask+B.Paste、12 (MULTI/THT) → *.Cu+*.Mask
    • net 解析借 pcb_rel.pad_nets_by_pad[pad_id](之前 ProjectRelations 已经为 PCB→FOOTPRINT cross-doc 攒过的索引现在派上用场)。
    • bottom-side COMPONENT (layerId=2) 整个 footprint 走 (layer B.Cu)Reference 标签同步落到 B.SilkS
  3. CLI 加 --all-pcbtools/epro2/kicad/__main__.py):每个 PCB doc 一文件,文件名取 META.title。

ESP-VoCat 实测6 个 PCB

nets footprints segments vias edge_cuts
BaseBoard 38 59 391 126 4
CoreBoard 84 87 1131 262 4
MicBoard 13 17 88 47 16
LCD-BD 7 5 66 35 2
Mainboard 24 33 340 181 4
Sub-board 4 5 10 0 4

6/6 都过 kicad-cli pcb export svg——文件解析无 error、SVG 输出正常。footprint/track/via/edge cuts 全部可见。

kicad-cli pcb drc 对最小的 MicBoard 跑145 violations + 75 unconnected。分布

  • 28 clearancetrace/pad 间距)+ 19 track_dangling + 39 via_dangling + 75 unconnected — 真连接问题,部分是 EPRO2 源里靠 POUR 解决但我们 Phase 1 没导
  • 21 silk_overlap + 8×2 edge_clearance + 3 invalid_outline — 边角 + silk 美化
  • 17 lib_footprint_issues — facere 库没注册cosmetic跟 schematic 一样)

Phase 1 砍掉的 / 下一轮再做

  • POUR / POURED — 铜皮覆铜,导了 75 unconnected 的大头会消掉GND 大面积在 POUR 里走,没导出来 trace 当然报 unconnected
  • FILL — 元件下方手工 fill 块
  • ARC 圆心/半径解析 — 现在按弦近似invalid_outline 警告就这来的
  • TEARDROP — pad/via 接 trace 处的圆角,纯美化
  • STRING / IMAGE — 板上文字和 logo 图

决策Why

  • 板子原点平移到 (100, 100) mmEPRO2 板能在任意坐标含负KiCad 画布原点在 (0,0),不平移开 KiCad 时板子可能根本不在视野里。100mm 是经验值,留够 silkscreen 边距。
  • footprint inline 不走外部 .kicad_mod:跟 schematic lib_symbol 同思路,自包含,能直接 kicad-cli 处理;缺点是同款 footprint 重复写多份,但 EPRO2 一个 SCH 内的 FOOTPRINT 数量级(几十)摊开来文件膨胀也不大。
  • ELLIPSE 当 circle 不当 oval:实测 ELLIPSE 的 width/height 经常相等(=圆形 padKiCad 没有真椭圆 pad typecircle 取 max(w,h) 比 oval 更接近圆。
  • ARC 暂时按弦:算 EPRO2 ARC 圆心要先反推三点定圆浮点精度敏感。Phase 2 单独处理。

测试

52 → 65 单测全过pcb_writer 8 个layer map / net id / segment / via / outline POLY / 零长 skip / 非 PCB doc reject+ footprint_writer 5 个SMD pad layers / 圆 vs slot drill / pad net 跨 doc 解析 / bottom-layer 翻转 / unresolved skip

下一步建议

  • POUR/POURED 导 zone(中工作量):消掉大部分 75 unconnectedCoreBoard 这种 4-layer + 大量 GND/POWER 覆铜的板真实连通性会贴近完整。
  • schematic + PCB 同时跑 一个 export 命令(--all),生成完整 KiCad project 目录。
  • Phase 4: KiCad project 文件 .kicad_pro:让用户双击就能在 KiCad GUI 里打开 schematic + PCB 配对。

2026-04-29 01:00 科普文档:爬取 per-doc .epro2 vs 网页端 .epro2 ZIP 整包

Claude 会话

接 chain replay sleep 优化commit 1e06ba6)后续。同事看到 data/raw/oshwhub/<uuid>/source/ 下面躺着 278 个 .epro2 而不是 1 个,会直觉以为抓错了——他们认知里的 .epro2 是网页端"下载工程包"那个 1.4 MB 单文件。

实际上:

  • 网页端 .epro2 = ZIP 容器(扩展名误导),里面 .epru(拼成一坨的 EPRO2 流)+ project2.json + IMAGE/ 6 张组件预览图
  • 爬取 .epro2 = 工程内每个文档SYMBOL / FOOTPRINT / DEVICE / SCH_PAGE / PCB ……)自己的 EPRO2 消息流per-doc 一文件
  • 两者信息量基本等价ESP-VoCat 我们 278 vs 网页 266多的 12 个是 history chain 上演化掉的容器层旧版本);唯一真实差异是 IMAGE/ 二进制图(我们 blob 引用爬到了但没拉本体——已知 gap
  • 我们走 per-doc 不走 ZIP 的硬约束:ZIP 那条路服务端没有公开端点,是纯前端 JS 现拼现压(三份 HAR 验证:导出按钮零 HTTP 流量)

写到 docs/sources/pro_crawl_vs_export.md给同事看。结构TL;DR → ESP-VoCat 实例 → docType 分布对比表 → 数量差异解释 → 体积对比 → 选型决策表。


2026-04-29 00:30 KiCad 导出 Phase 3 hierarchicalroot + global_label + 5-Voltage 电源端口

Claude 会话

54f0173。Handoff #3 多 sheet hierarchicalkicad-cli sch erc 在 project 视角而不是单 sheet 视角下校验,理论上能把 single-sheet ERC 看不见跨 sheet 的 248+111 残留压下去。

三件事一起做

  1. (label)(global_label)tools/epro2/kicad/sch_writer.py EPRO2 的 NET 是项目全局——一个 GND 名字横跨整个 schematic 而且通过 PCB 走线到隔壁板子。KiCad 的 (label) 是 sheet-scoped单 sheet ERC 看到一个名字只出现一次就报 dangling(global_label) 才是项目级hierarchical ERC 在 root 上能跨 sheet 解。

  2. tools/epro2/kicad/root_sch_writer.py 新模块 按 EPRO2 的 SCH_PAGE.META.schematic 把页分组,每组 emit 一个 root .kicad_sch,里面 N 个 (sheet ...) 引子页。子页的 (sheet_instances (path "/<assigned_uuid>" ...)) 必须回引 root 给它分配的 uuid——少了这一步 ERC 把子页当孤岛走。

  3. 5-Voltage 电源端口识别sch_writer.py COMPONENT 循环里加判断) 实测 ESP-VoCat 有 365 个 partId=pid8a0e77bacb214e 的 COMPONENT——挖了下发现这是 EPRO2 内部的 "Voltage" 占位符号,对应 KiCad 的 (power_symbol)网络名不在 PART 里,而是 placement 自己的 Global Net Name ATTR101 个有名字、264 个还是空的草稿态)。每次有名字就在 placement 位置 emit 一个 (global_label)

CLI 改造

uv run python -m tools.epro2.kicad <project> --all-sch 现在默认 hierarchical 输出:每个 SCH 一个目录,里面 root + 子页。--flat 兜回老行为(一张图一个文件)。DELETE_DOC.isDelete=True 的 SCH 直接跳——LCD-BD 那个被删了的没生成。

ESP-VoCat 实测

kicad-cli sch erc <each-root> 跨 5 个 root 累加:

Type flat baseline hier 后 Δ
wire_dangling 52 52 0
pin_not_connected 196 190 -6
label_dangling → global_label_dangling 111 105 -6
pin_not_driven 23 21 -2
endpoint_off_grid 1372 1340 -32 (LCD-BD 移除带走的)
lib_symbol_issues 571 557 -14 (同上)
TOTAL 2325 2265 -60

修得不够多——为什么EPRO2 的 6 个 SCH 里 5 个只有 1 pagehierarchical 对它们没用;只有 CoreBoard 是真 4-page 多 sheet。CoreBoard 自己里面也只有 GND / MCU_3V3 / VCC_3V3 是真跨 sheet 共享的网其它GPIO4, CHIP_PU, AUDIO_I2C_, I2S_)都是 sheet-local——名字虽然 unique 但只在一个 sheet 上有 wire 引用hierarchical ERC 也救不了,依然 dangling。

剩下 190 PNC 的真原因:抽样发现 C44/C45/R19/R20 这类 0402 元件wire 从一个 pin 出来直直穿过另一个 pin但 pin 落在 wire 中段而不是端点。KiCad ERC 要求 pin 落在 wire 端点或 explicit junction 处才认连接wire 中段穿过的 pin 不自动连。EPRO2 源里这种连接合法但导出时丢了——要修需要做 wire-pin 几何相交,在中段 pin 位置 split wire 或 emit (junction)。下一轮再做。

决策Why

  • 不走单 root 包全部 9 page6 块 PCB 是物理独立板子merge 进一个 root 会把 BaseBoard 的 GND 和 CoreBoard 的 GND 误判成同一个网。EPRO2 已经按 SCH 分好组,按 SCH 拆 root 是结构对齐做法。
  • (global_label (shape passive))不知道方向input/output/bidirectional 都会触发更多 ERC 检查(如 pin_not_drivenpassive 最保守。power 网用 power_in 才理想,但需要 driver 元件,超出本轮范围。
  • 保留占位符号 placement 同时再 emit global_label5-Voltage 占位符在 KiCad 里画出来虽然冗余但不影响 ERC删了反而丢视觉信息。
  • 不实现自动 junctiongeometry 计算成本明显高于本轮收益(<10 PNC 的预期降幅),做成下一轮独立改动更清晰。

测试

47 → 52 单测全过root_sch_writer 3 个 + power-port label 2 个 + sheet_path/page_num propagate 1 个 - test_named_wire 改 global_label 重写 1 个。

下一步建议

  • wire-pin junction emission(中等工作量):算每个 COMPONENT 的 abs pin 位置,对每条 wire 查"非端点 pin 命中",命中就 split wire 或 emit junction。预期把 PNC 从 190 再砍一半左右。
  • 或者直接进 Phase 3 真正的 .kicad_pcb 导出——schematic 这边已经够用PCB 才是 Forge 投影最后一块。

2026-04-28 23:55 KiCad 导出修真实连接错wire_dangling -88%, pin_not_connected -52%

Claude 会话

fb577cc 后续。Handoff 选项 #1用 ERC violation 数从 850 降到接近 0 作为目标。Bisect 出两个根因,都是结构性的 KiCad 语义错(不是浮点 / 精度问题,所以 grid round 不是答案):

Bug A — sym_writer 漏 Y-flip

KiCad lib symbol 是 Y-upschematic 是 Y-downplacement 时 KiCad 会再翻一次 Y。我们把 EPRO2 (Y-down) 的 PIN/RECT/POLY/CIRCLE/TEXT 坐标直接当 lib coord 写KiCad 的翻转就把整个 symbol body 上下镜像了。U8 4-pin 磁吸座实测pin 1↔4 对调、2↔3 对调,结果 wire 端点跟错位的 pin tip 撞不上 → ERC 报 pin_not_connected + wire_dangling。修法lib_y = -epro2_ypin/text rotation 也镜像 lib_rot = (360 - rot) % 360

Bug B — 没发 net labelKiCad 看不出 EPRO2 的命名网络

EPRO2 的 WIRE op 带 NET attrTXD / GND / VBUS ……),多段 LINE 通过同名 NET 连成一个网,不需要几何相邻。KiCad 不知道这套,只看几何。修法:在 sch_writer 里查每条 LINE 的 lineGroup → WIRE → NET attr,命中就在 LINE 起点 emit 一个 (label "<NET>")。同名 label 在多条物理 wire 上 → KiCad ERC 才认这是同一个网。per-LINE 不是 per-WIRE:单个 WIRE id 下面的 LINE 段不一定共端点,每段都得有 label 才不被判 dangling。

ESP-VoCat 9 sheets ERC 对比

Type baseline after Δ
wire_dangling 444 52 88%
pin_not_connected 406 196 52%
real connectivity 合计 850 248 71%
label_dangling 0 111 new (warn)
pin_not_driven 0 23 new (connector pin 类型问题)
endpoint_off_grid 1372 1372 unchanged (cosmetic, EPRO2 用 30/20/10 mil pitch不在 KiCad 50 mil 网格上;不能 round——会把 < 50 mil 的 pin 间距压到一起)
lib_symbol_issues 571 571 unchanged (没注册 facere libcosmetic)

剩余 248 real-connectivity 错主要是 single-sheet ERC 的天然限制:很多网只在一个 sheet 上有这一个 pin对端在别的 sheet。kicad-cli 一次只看一个 .kicad_sch看不见跨 sheet。彻底修要 Phase 3hierarchical 顶层 + sheet links

关键决策(记 Why

  • 不 round to grid50 mil grid 会塌缩 < 50 mil pin pitch实测 4-pin 磁吸座是 10 mil pitch破坏几何。EPRO2 源已经在整数 mil浮点不是 root cause。
  • per-LINE label 不是 per-WIRE:同 WIRE id 下两条 LINE 段不共端点是常态(不同地方各连一段),都得 label 才不孤立。
  • (label) 不是 (global_label)实验过两种语法single-sheet ERC 都判 dangling因为这个 sheet 上只出现一次);语义上 EPRO2 net 全局,但 single-sheet 校验视角看不见跨 sheet换 global_label 帮不上。Phase 3 hierarchical 重构时再切。
  • 不做 ERC config tweakKiCad 8 的 connection grid 是硬编码 50 milschematic 文件里没法配;想消 endpoint_off_grid 必须破坏 EPRO2 几何或者升级到 KiCad 9 + custom severity。

测试

41 → 46 单测全过:新增 test_pin_y_negated_for_kicad_lib_y_up_convention / test_pin_rotation_mirrored_to_compensate_y_flip / test_rect_y_negated / test_named_wire_emits_label_at_line_start / test_unnamed_wire_emits_no_label

下一步建议

  • Phase 3 hierarchical写 root .kicad_sch 引用所有子 sheet + 把跨 sheet 的 NET 升级成 (global_label)single-sheet ERC 残留的 248 + 111 大概率随之降到 < 100。
  • (并行) 消 lib_symbol_issues 571emit sym-lib-table + 独立 .kicad_sym。

2026-04-28 23:30 oshwhub 全量 listing 索引落本地33,695 项 / 28.4 MB

Claude 会话

为了在"扩量到 top-30 / top-50 / 全量"前先量化候选池规模 + 质量分布,把 oshwhub listing API 全量扫一遍落地。

关键收获(之前以为是黑箱)

  • listing API 直接返回 total 字段Pro 21,202 / Std 12,493合计 33,695
  • pageSize 无上限,实测 1000 工作良好;全量索引 = 35 次请求 / 71 秒 / 35 MB 流量。
  • sort 参数被服务端静默忽略——传啥都返回相同顺序grade desc → 隐式质量分 desc。"按时间排序"必须先拉全集再本地排。
  • origin 默认 std——不带参数永远看不到 Pro 池。
  • license 不在 listing 响应必须挨个抓详情页QPS=0.5 → ~19 小时全量)。

数据画像(写到 docs/sources/oshwhub_listing_full.md

  • Pro 长尾极重grade=1 占 82%,真正 A 档grade≥3 & like≥10只有 1,356 (6.4%)
  • Std 高质量比例反而高A 档 1,450 (11.6%),因为平台老 7 年2016 起 vs Pro 2021 起),项目有时间累积点赞
  • Std 已停滞2021-2022 见顶3.4k/年之后断崖1.5k → 0.9k → 0.4k → 0.05k 2026Q1
  • Pro 还在快速膨胀2023 起线性增长2025 全年 7.4k2026Q1 已 1.1k
  • 作者长尾健康Pro 10,536 个 / Std 5,531 个唯一作者top-1 占比 0.4% / 1.5%
  • 立创官方账号占据头部course-examples / li-chuang-kai-fa-ban / li-chuang-zhi-neng-ying-jian-bu

实操含义

放量决策有了量化锚点S 档 583 项 / A 档 2,806 项 / B 档 6,243 项 / 全量 33,695。Pro 工程源体积外推(基于 5 项实测均值),全 Pro 约 1 TB——超出 Gitea LFS 舒适区,必须配 size cap + license 白名单。

下一步

  1. 在本地 jsonl 上按 A 档过滤,做 license 详情页扫描(一次性 ~7 小时)
  2. license 白名单 ∩ A 档 → 真候选清单
  3. 然后再决定批量下载源

文件

  • scripts/dump_listing_index.py —— 一次性全量扫描脚本,可重抓
  • data/state/oshwhub_listing_full.jsonl —— 28.4 MBgitignore可重建不入库
  • docs/sources/oshwhub_listing_full.md —— 给人看的简报

2026-04-28 22:00 Pro 2.x 旧版工程源抓取链路打通5/5 Pro 项目全部

Claude 会话

承接刚做的 3/5 modern Pro 批量。用户录了第三份 HARtmp/prodownload3.har 103 MB / 178 请求,目标是登录态打开梁山派 legacy Pro 2.x 项目),让我把 RK3566 / 梁山派两个旧版项目也补上。

HAR 反推Pro 2.x 用一套完全不同的端点

旧版(editorVersion: 2.1.x)没有 git-style branch + history 模型。HAR 里看到的关键端点:

端点 作用
GET /api/projects/<P>/ticket?uuid=<P>&g_ticket=-1 完整项目 manifestschematics / schs / pcbs / coppers / textpath / blobs / symbols / footprints / devices / block_symbol
POST /api/schematic/lists {uuids:[<sch>]} 父 schematic 容器,含 sort: [{uuid: <sheet>, ticket}] 即子图 UUIDs
POST /api/v2/documents/lists {uuids:[...], docType:1} 抓 schematic 子图(plaintext dataStr跟 Std 一样的 ["DOCTYPE","SCH","1.1"]\n[HEAD,...] 格式)
POST /api/v2/documents/lists {uuids:[...], docType:3} 抓 PCB同样 plaintext dataStr
POST /api/coppers/search {paths:[...]} 铺铜层数据PCB 上独立分发的增量层)
POST /api/textpath/search {paths, project_uuid, path} 字体 / 文字路径(同上)
POST /api/v2/resources/search {hash, project_uuid} BLOB嵌入图片
POST /api/v2/components/searchByIds / /api/devices/searchByIds 元件库 metadatadataStrId 仍走 modules.lceda.cn AES 加密 blob

关键差异:旧版的工程主体 plaintext(无加密 / 无 history 重放),只有元件库走 Pro 3.x 的 AES 方案。这反而比 3.x 简单很多。

判别规则/api/v4/projects/<P> 返回的 branch_uuid 是不是 null。null 即旧版。

实现

crawlers/oshwhub/crawler.py refactor

  • fetch_pro_source() 拆成 dispatcher先 GET project meta → 检查 branch_uuid
  • _fetch_pro_modern() —— 原 EPRO2 chain 流程,去掉重复的 project meta 调用
  • _fetch_pro_legacy() —— 新增,按上面 9 步流程拉所有 doc + 辅助层
  • _pro_post_json() —— POST helper与 GET helper 对称)

落盘约定(旧版):

  • source/ticket.json —— 完整 manifest保留备 lib 重建)
  • source/<sheet_uuid>.json —— 每张原理图
  • source/pcb_<pcb_uuid>.json —— 每块 PCB
  • source/coppers.json / textpath.json / blobs.json —— 辅助 PCB 层资源
  • source/manifest.json —— 索引 + structure_summary

schemasource_format enum 加 easyeda-pro-legacy

实测2/2 legacy 项目打通

项目 editor sheets pcbs sym fp dev coppers textpath blobs size
立创·梁山派 2.1.30 2 1 78 191 128 29 3 1 1.0 MB
立创·泰山派 RK3566 2.1.40 29 1 299 524 295 0 0 32 0.8 MB

旧版项目比新版小两个数量级(梁山派 1 MB vs RK3576 66 MB—— 没有增量 history、组件库走单独端点、本身就是当前快照。

5/5 Pro 项目终极汇总

# 项目 source_format editor docs size
1 X86 主板 easyeda-pro 3.2.15 7374 481 MB
2 立创·泰山派 RK3566 easyeda-pro-legacy 2.1.40 30 0.8 MB
3 立创·梁山派 easyeda-pro-legacy 2.1.30 3 1.0 MB
4 220V 桌面电源 easyeda-pro 3.2.69 771 26 MB
5 ESP-VoCat 喵伴 easyeda-pro 3.2.91 278 7.5 MB

合计 8456 docs / ~516 MB plain 源数据5/5 端到端打通。

下一步

  1. EPRO2 → KiCad 转换器(仍是 Forge 投影硬门槛ESP-VoCat 7.5 MB 是不错的小样本起手)
  2. 旧版 dataStr → KiCad 复用 Std 转换链(同格式,已有 easyeda2kicad.py 支持)
  3. 阶梯放量到 50 / 500 项目时做风控压测

2026-04-28 21:35 Pro 工程源EPRO2批量抓取打通3/5 modern Pro 项目 2/5 legacy 2.x

Claude 会话

承接刚做完 Std 链路;用户给了浏览器 HAR (tmp/prodownload.har174 请求,目标 = 立创·泰山派 RK3576 现代 Pro 项目) 验证 endpoint 形态,要求"找 5 个专业版项目,看能不能批量抓 EPRO2"。

5 个候选

oshwhub /api/project?origin=pro&sort=hot 取前 5详情 API 拿 license

# UUID License 项目 结果
1 b77840665e2e... GPL 3.0 【全网首发】X86电脑主板
2 7360e73de5dd... GPL 3.0 立创·泰山派 RK3566 开发板 legacy 2.x
3 0c4675983733... GPL 3.0 立创·梁山派开发板 legacy 2.x
4 dc91a91e6693... CC BY-NC-SA 4.0 高颜值220V 300W 桌面电源
5 ba64bd6f1c9c... GPL 3.0 ESP-VoCat 喵伴 AI 萌宠

License 全是限制性CC-NC-SA / GPL— Pro 用户群是立创团队/教育机构,默认上 NC-SA。和用户对齐本仓库为研究用、不再分发license 字段忠实落库;下游 Forge 投影时再用白名单过滤。

关键发现Pro 2.x ≠ Pro 3.x重要

立创开发板的旗舰板RK3566 / 梁山派)抓 /api/v4/projects/<uuid> 返回 branch_uuid: null + editorVersion: "2.1.40"

这是 Pro 编辑器旧版2.x:没有 git-style branch/history 模型,文档直接通过 boards: [{sch, name, pcb}] 字段定位。我们之前调研的全是 3.x泰山派 RK3576 / 无界 PLUS3.x 才有 /branches/<B>/structures + /histories/<H> 全套。

旧版的访问端点暂未挖通:/api/v4/documents/<doc> 404/api/documents/<doc> 401/api/v4/projects/<P>/snapshots 200 但响应体是 project meta 不是 doc。需要录一份"在 pro.lceda.cn 编辑器里打开 RK3566"的 HAR 才能继续。已记入 docs/sources/easyeda_pro_source.md §1.1

EPRO2 解析坑:行末单 |(找了 2 轮才看到)

第一轮跑 5 个项目结果X86 board 7374 docs 抽出来只剩 2 个220V 电源和 ESP-VoCat 都是 0 docs

定位过程:

  1. 直接 dump 一条 history 的 lines看到 DOCHEAD payload 行末有单个 |,例如 {"docType":"BOARD",...,"version":"..."}|
  2. 我的解析 json.loads(ln.split(b"||")[1]) 拿到带尾随 | 的字符串 → Extra data: line 1 column 127
  3. silently swallow exception → cur_doc 没设 → 全 chain 的 message 被丢弃。
  4. 修复:解析前先 ln.rstrip(b"|").split(b"||")。已在 docs/sources/easyeda_pro_source.md §3.1 记录"行末单 | 是行终止符不是字段分隔符"。

修复后批量结果

3 个 modern Pro 项目完整解出来:

项目 chain docs plain blob editor
ESP-VoCat 12 278 7.5 MB 1.1 MB 3.2.91
220V 电源 28 771 26.3 MB 7.4 MB 3.2.69
X86 主板 85 7374 481 MB 61 MB 3.2.15

X86 主板5123 FOOTPRINT + 1243 DEVICE + 837 SYMBOL = 7203 个组件库 doc数据量惊人是个超复杂工程。

docType 取值表(实测扩展)

之前 doc 只列了 BOARD/PCB/SCHEMATIC/SHEET。实测 EPRO2 流里 docType 实际取值更细:

  • 用户级文档:BOARD(板物理边框)+ PCB(板内容)+ SCH(原理图)+ SCH_PAGE(子图)。一个完整 PCB 板 = 一对 BOARD + PCB,不是命名变化。
  • 组件库 / 资源:SYMBOL / FOOTPRINT / DEVICE / BLOB / FONT / CONFIG。每个独特组件 / 字体 / 项目配置都是独立 doc。
  • 抓 EPRO2 = 抓项目 + 完整的局部组件库快照。下游做 EPRO2 → KiCad 转换时必须先把 lib doc 加载进 symbol cache。

已更新到 docs/sources/easyeda_pro_source.md §3.4

代码改动

  • crawlers/oshwhub/crawler.py
    • 新增 make_pro_source_client() —— 加载 ~/.secrets/pro-lceda-cookie-header.txt,自动配 Editor-Version / Referer / per-request path header
    • 新增 fetch_pro_source(client, project_uuid, proj_dir, sleep) —— 4 步流程project meta → branch HEAD → structures → history chain然后逐 history 解密AES-128-GCM, 16 字节 IV+ gunzip + 按 DOCHEAD 切 per-doc
    • 新增 _order_history_chain() —— 沿 parent 链从 root 到 HEAD 排序
    • 新增 _pro_get_json() —— 包装 /api/v4 GET 调用,自动加 path header + 校验 success
    • 扩展 crawl_one()pro_source_client 参数,按 list_item.origin 自动 dispatch
    • 新增 CLI flag --with-pro-source / --backfill-pro-source / --pro-cookie / --origin
    • 新增 _run_backfill_pro_source()filter on raw_fields.origin == "pro"
  • schemas/project.schema.jsondocType 类型从 integer 放宽到 ["integer","string","null"](兼容 Std 的 1/3 + Pro 的 BOARD/PCB/SCH 等字符串),新增 message_count 字段
  • docs/sources/easyeda_pro_source.md rev 3加 §1.1 Pro 2.x vs 3.x、§3.1 行末 | 警告、§3.4 docType 实测表、§2.2 单 history endpoint 即返完整 chain

数据落地

data/raw/oshwhub/
  b77840665e2e48148c1b04ce84b5f7e7/   # X86 主板modern Pro 3.2.15
    source/
      manifest.json          # 7374 docs index
      structure.json         # 项目树boards/schematics/sheets/pcbs
      <doc_uuid>.epro2       # 7374 个 EPRO2 文件
  dc91a91e669349898d709a5ba02f5b5f/   # 220V 电源modern Pro 3.2.69
  ba64bd6f1c9c467ba3b674a54943557d/   # ESP-VoCatmodern Pro 3.2.91
  7360e73de5dd428e9f29e10573f2d8ac/   # legacy Pro 2.x无 source/
  0c46759837334318aa4882d6d37f96fa/   # legacy Pro 2.x无 source/

下一步

  1. 重要legacy Pro 2.x 抓取链:录 HAR 看 RK3566 / 梁山派 在 pro.lceda.cn 编辑器打开时走什么 endpoint
  2. 想跑量到 50 / 500 项目时,先做风控测试:阶梯放量,监控 403 / 429 / 1111111
  3. EPRO2 → KiCad 转换器是 Forge 投影前置硬门槛
  4. 可考虑 cookie 轮换 / 多账号 poolPro 风控相对 Std 严)

2026-04-28 19:50 Std 工程源链路打通 + 10 板子 schematic/PCB 全部回填

Claude 会话

承接计划:把已抓 10 个板子的"需登录才能下载的原理图 + PCB"补齐。

关键发现:根本不需要登录

10 个板子全是 origin: "std"EasyEDA 标准版)。原 plan.md §1.6 假设源数据要登录 — 实际公开项目的 dataStr 匿名可访

调研路径4 轮探测,留痕在 data/state/std_probe[1-5]/

  1. /api/user/api/projects 401cookie 已过期,但和源抓取无关)
  2. oshwhub.com/api/project/<uuid> 浏览器 UA 匿名 200,返回 version_documents[](含 doc uuid + master + history chain
  3. modules.lceda.cn/histories/<hash>.json 仍 403与 Pro 同结构,但 Std 不走这条路)
  4. 翻 Std 编辑器 /editor/6.5.51/js/main.min.js5 MBgrep /api/,找到 76 个端点。关键的 ajaxDetail = '/api/documents/{uuid}'
  5. 命中:https://lceda.cn/api/documents/<doc>?uuid=<doc>&path=<doc> 匿名 200body 是完整 EasyEDA JSONdataStr.{head,canvas,shape,BBox,colors[layers,objects,DRCRULE,...]}

两种响应 shape(按 docType 区分):

  • docType=1 (Schematic):返"项目视图"result.schematics[0].dataStr
  • docType=3 (PCB):返"文档视图"result.dataStr 直接在顶层

与 Pro 的差异

维度 Std (本轮) Pro (docs/sources/easyeda_pro_source.md)
鉴权 无需 lceda_pro_session 必须
加密 AES-128-GCM + gzip
源格式 扁平 EasyEDA JSONshape[] EPRO2 消息流(事件溯源)
多 doc version_documents[] 逐个 GET /structures + history chain 重放

实施

  • docs/sources/easyeda_std_source.md:完整调研(含 dataStr 字段、抓取伪代码、附录重跑脚本)
  • crawlers/oshwhub/crawler.py
    • 新增 make_source_client() —— 浏览器 UA + Referer规避 oshwhub /api/project/<uuid> 端点对 FacereDataset/0.1 UA 的 reject在 commit message 注明 UA 例外原因)
    • 新增 fetch_std_source():项目 → version_documents → 逐文档 dataStr → 落 source/<doc>.json + source/manifest.json
    • 新增 --with-source 标志(爬新项目时一并抓源)和 --backfill-source(仅扫已有项目补源)
    • QPS ≤ 0.2SLEEP_SOURCE = 5.0s
  • schemas/project.schema.json:加 source_format/source_path/source_documents/editor_version 字段(前 3 个进 enum 锁定,后续新源好对齐)

跑批结果dev1QPS 0.2

10/10 全成功schema 验证 10/10 pass

项目 docs docTypes 大小 editor
ST-LINK V2-1 2 [1,3] 682 KB 6.5.39
USB 电压电流表 4 [3] 1.2 MB 6.5.15
红外热成像 2 [1,3] 1.6 MB 6.5.22
t12-858d 焊台 11 [1,3] 6.1 MB 6.5.15
加热台量产计划 6 [1,3] 12.0 MB 6.5.43
ESP32-S3 智能手表 4 [1,3] 1.4 MB 6.5.41
RT300-MKV 可调电源 3 [1,3] 3.3 MB 6.5.23
YuzuMaix V831 4 [1,3] 5.4 MB 6.5.37
盖革计数器 6 [1,3] 1.2 MB 6.5.47
ZVS 感应加热 3 [1,3] 990 KB 6.5.40

合计45 个文档 / 33.2 MB中位 ~1.5 MB / 项目,附件主体约为附件主流量的 6%

观察:

  • USB 电压电流表只有 PCB 文档4 个:主板 + 盖板 + 底板 + 面板,作者未上传原理图源)
  • t12 焊台 11 个文档(拆得碎,估计含多个独立模块)
  • editor 版本散布在 6.5.15 - 6.5.47(取决于作者上一次保存项目时的客户端版本)

落盘结构per project

data/raw/oshwhub/<uuid>/
├── metadata.json         # ★ 新增字段source_format/source_path/source_documents/editor_version
├── description.md
├── cover.*
├── _urls.json
├── files/                # 用户附件已存在LFS
└── source/               # ★ 新EasyEDA Std 工程源
    ├── manifest.json     # 文档清单 + 抓取时间 + upstream version_documents 留档
    └── <doc_uuid>.json   # 完整 dataStr 响应(普通 git文件 1-5 MB 量级)

source/*.json 走普通 git 而不是 LFS10 项目共 33 MB 完全够用;放量时再考虑加 LFS 规则)。

安全 / 合规

  • 无登录态、无凭据使用 → 无账号封禁风险,无 cookie 泄漏顾虑
  • UA 例外:源抓取使用浏览器 UA 而非 FacereDataset/0.1,原因写入 docs/sources/easyeda_std_source.md §3 与本 commit message
  • License与原项目附件相同的 license 字段已在 metadata下游 whitelist 过滤逻辑不变

未决 / 下一步

  1. dev1 上的 ~/.secrets/lceda.json cookie 已过期XSRF 4-22 失效,今天 4-28本任务已不依赖它。是否保留待定 —— Pro 流程可能仍要
  2. easyeda2kicad.py 转换:现有 45 个 dataStr 是关键测试样本,可立刻跑(plan.md §1.7
  3. 放量决策:从 10 → 50 → 全量 12,493 时,按 33 MB / 10 ≈ 3.3 MB/proj 估算,全量源 ~40 GB不含附件本体
  4. 多账号轮询、Pro 链路打通仍是 plan.md §1.6 的开放项(仅当遇到 Pro 项目时才用得上)

改动清单

  • 新增:docs/sources/easyeda_std_source.mdscripts/probe_std_api[1-5].pydata/raw/oshwhub/<uuid>/source/10 项目)
  • 修改:crawlers/oshwhub/crawler.pyschemas/project.schema.json、10 项目的 metadata.json
  • 待人工:projects.md 重生成(脚本未跑);plan.md §1.6 状态从 §1.7 unblocked

2026-04-24 00:25 打通 pro.lceda.cn 工程源完整链 + EPRO2 格式解析

Claude 会话

核心成果:立创 EDA Pro 工程源的 API + 加密 + 格式三层全打通

完整链路

1. GET /api/v4/projects/<PROJ>                                    → branch_uuid
2. GET /api/v4/projects/<PROJ>/branches/<BRANCH>                  → history_uuid
3. GET /api/v4/projects/<PROJ>/branches/<BRANCH>/histories/<HIST> → {key, iv, dataStrUrl}
4. GET <dataStrUrl> (modules.lceda.cn)                            → 417 KB 加密 blob
5. AES-128-GCM decrypt (tag=blob[-16:])                           → 417 KB gzip
6. gunzip                                                          → 2.7 MB EPRO2 源流

关键 headersEditor-Version: 3.2.127 / path: <PROJ_UUID> / Referer: https://pro.lceda.cn/editor / Cookie: lceda_pro_session=...(与 u.lceda.cn 的 session 不共享)

加密细节

  • 算法: AES-128-GCMmodules.lceda.cn/pro-mgr/.../project-worker.jsthis.tool.decrypt({name:"AES-GCM",iv:this.iv,tagLength:128},...) 反查确认)
  • key / iv 都是 32 hex = 16 byte
  • WebCrypto 约定ciphertext || 16-byte-authTag末尾附
  • 解密后 gzip magic 1f 8b 08gunzip 得最终源流

EPRO2 格式

立创 EDA Pro 2 的事件溯源格式:消息流按 \n 分行,每行 {"type":...,"ticket":N,"id":...}||{payload}||[extra]。 示例样本(无界PLUS BOARD 文档)8 357 条消息40 种 type

  • PART / COMPONENT / ATTR / PIN零件与属性
  • PAD / VIA / WIRE / NET / PAD_NETPCB 电气)
  • LINE / POLY / RECT / ARC / CIRCLE / ELLIPSE / TEXT几何
  • LAYER (1572) / LAYER_PHYS / ACTIVE_LAYER层堆叠
  • FILL / POUR / POURED铺铜
  • RULE / RULE_SELECTOR / RULE_TEMPLATE设计规则

与 Std 版对比

u.lceda.cn (Std) pro.lceda.cn (Pro)
Cookie lceda_session lceda_pro_session
源 API 单一 /api/projects/<uuid> 4 步 /api/v4/...
版本控制 branches + histories
加密 待验证 AES-128-GCM
源格式 EasyEDA JSON扁平 EPRO2 消息流
工具 easyeda2kicad.py 第三方 现成 KiCad 转换器

落地

  • 新建 docs/sources/easyeda_pro_source.md(完整调研,见该文档附录 A 一键重跑)
  • pyproject.tomlpycryptodome>=3.23.0
  • 清理dev1 上 /tmp/source.blob/tmp/source.json(后者含 Charles 私人工程源 2.7 MB

待验证 / 下一步

  1. 他人公开 Pro 工程能否同样 4 步通 —— 需 HAR
  2. SCHEMATIC docType 的 API 入口(本次只解出 BOARD
  3. 多 document 枚举project → documents 列表端点)
  4. Pro 编辑器"导出 KiCad"功能的 API 端点(若存在,能省自写转换器的工作)
  5. 对齐 OSHWHUB_INGEST_SPEC.mdForge 消费侧要求 .kicad_sch + 更严 license whitelist

⚠️ 安全

Charles 在聊天里粘过两次 cookieu.lceda.cn 一次 + pro.lceda.cn 一次),已写入 dev1 ~/.secrets/。 当前会话 transcript 含明文 —— 本轮验证完 Charles 应登出再重登一次,让测试期间暴露过的 session invalidate。


2026-04-23 20:10 策略大调:登录内容入场 + 云服务器 + EDA→KiCad 转换

Claude 会话

四项变更落实到文档(暂不写代码,等云服务器到位):

1. 登录态内容纳入范围

原则(CLAUDE.md

  • 合法账号登录后抓,禁止盗号 / 共享号
  • 凭据集中云服务器 ~/.secrets/ (mode 700)不入 git / 日志 / metadata
  • 仍不绕付费墙、不破 DRM、不抓站点明确禁抓的内容
  • 换号 / 重登事件记 docs/secrets.md(只事件、不含值)

2. 云服务器(广州,待交付)

新增 plan.md §0.5 基础设施段:

  • 0.5.1 机器初始化git / git-lfs / uv / python 3.11+,非 root SSH~/.secrets/
  • 0.5.2 调度tmux/nohup 长跑 + systemd timer 增量
  • 0.5.3 登录态获取cookie 导出流程

3. 存储分级演进

plan.md §1.4 改写:

  • 前期 < 50 GB云服务器磁盘 + Gitea LFS
  • 中期 50200 GB评估 Gitea 容量压力;扩容 or 分仓
  • 后期 > 200 GB迁对象存储OSS / COS / MinIOGitea 只存元数据 + 指针
  • 50 GB 是决策评估点,过早迁移

4. 立创 EDA → KiCad 转换

新增 plan.md §1.6(登录态工程源抓取)+ §1.7EDA→KiCad 转换):

  • §1.6 用登录账号抓 u.lceda.cn/api/project/<uuid> 工程源 JSONsource.json
  • §1.7 写 scripts/convert_to_kicad.py,候选工具 easyeda2kicad.pypypi活跃维护
  • 批处理扫 data/raw/oshwhub/ → 输出 data/processed/oshwhub/<uuid>/kicad/
  • kicad-cli sch erc / pcb drc 做语法校验,失败样本记 data/state/convert_failed.jsonl
  • 目的:打通 oshwhub (EasyEDA) 与 bshada/open-schematics (KiCad) 两个生态的训练语料

同步改动

  • docs/sources/oshwhub.md §3.5 从"未开放"改为"需登录,纳入范围"R4 风险更新
  • README.md 数据源表加「登录态」列,加运行环境说明

等待

  • 广州云服务器到位 → 启动 Phase 0.5
  • 账号登录凭据由 Charles 提供

2026-04-23 19:55 oshwhub.md 重写成完整调研文档

Claude 会话

Charles 要求把 12 493 总数验证、90 项目采样结果合进主调研文档。

docs/sources/oshwhub.md 重写为 9 节 + 2 附录的完整调研:

  1. 一页纸 TL;DR 表
  2. 站点架构
  3. robots.txt 与合规
  4. API 与抓取入口(列表 / SSR 详情 / 附件 CDN / 排除项 / 未开放端点)
  5. 项目总数验证(新):三路 sort 一致 + 分页二分搜索250 × 50 = 12 500 吻合)+ grade 覆盖抽样
  6. 抽样语料特征(从 oshwhub_corpus_estimate.md 并入):体积 / 文件类型 / license 分布
  7. Schema 映射
  8. 速率与礼貌
  9. 目录输出约定
  10. 风险与未解决7 条)
  11. 附录:重跑命令、变更历史

删除重复文件 oshwhub_corpus_estimate.md(内容已并入 §5


2026-04-23 19:50 加入 HF bshada/open-schematics 计划

Claude 会话

Charles 点名把 https://huggingface.co/datasets/bshada/open-schematics 纳入第一批。

调研结论:

  • 这是已预处理的 HF 数据集,非待爬网站
  • 78 parquet shards 6.4 GBCC-BY-4.0(商用友好)
  • 10K+ 条记录,每条含 .kicad_sch 源 / PNG / 组件列表 / JSON / YAML / name / description
  • 与 oshwhub (EasyEDA) 互补,补 KiCad 生态

决定:

  • 整包镜像data/external/huggingface/bshada--open-schematics/拆 per-project 目录
  • huggingface-cli download ... --repo-type datasetparquet 走 LFS
  • 维护单独的 datasets.md,不与 per-project 的 projects.md

改动:

  • 新增 docs/sources/hf_bshada_open_schematics.md 完整调研
  • plan.md 加 Phase 1.5
  • README.md 数据源表加一行

未下载,等拍板 6.4 GB LFS 预算。


2026-04-23 19:30 Phase 1 MVP10 个高质量 oshwhub 项目入库

Claude 会话:承接仓库初始化

API 调研结论

  • 列表 APIGET https://oshwhub.com/api/project?page=N&pageSize=M&sort=hot,无鉴权,返回 12493 个项目元数据(含 grade / likes / stars / views / forks
  • 详情:GET https://oshwhub.com/<path> 是 SSR HTML嵌入 escaped JSONlicense + attachments[](每个带 name / src / size / md5 / ext / mime / download_count
  • 附件 CDNhttps://image.lceda.cn{src} — 已验证无鉴权直接下载
  • EasyEDA 工程源 JSONu.lceda.cn需登录v0.1 不抓
  • 详细调研见 docs/sources/oshwhub.md

选 10 个高质量项目

判据:grade == 4(平台精品徽章) + likes ≥ 100 + 应用领域多样(避免同类堆叠)+ 排除 _copy 派生仓。

10 个项目覆盖调试器、加热台、盖革计数器、可调电源、焊台、智能手表、USB 测电流、ZVS 感应加热、AI 开发板、红外热成像。

MVP 爬虫

位置:crawlers/oshwhub/crawler.py

  • list_projects() — 列表 API 分页
  • pick_top() — 按 like×3 + star + fork×2 + views/100 + comments×2 + grade×50 排序
  • parse_detail_html() — 从 SSR HTML 提取 title / license / description / attachments
  • crawl_one() — 每项目产出:metadata.json / description.md / cover.* / files/* / _urls.json
  • QPS ≤ 0.5SLEEP_BETWEEN = 2.0UA 显式声明 FacereDataset/0.1

抓取与入库

  • 10/10 成功52 个附件,524 MB
  • Gitea LFSv25.4.3 原生支持)+ 本地 git-lfs 3.5.1(用户态二进制装在 ~/.local/bin/
  • .gitattributes 规则:data/raw/**/files/** 一律走 LFS元数据metadata.json / description.md / _urls.json / cover.*)走普通 git
  • 每项目目录结构:
    data/raw/oshwhub/<uuid>/
    ├── metadata.json      # 按 schemas/project.schema.json
    ├── description.md
    ├── cover.{jpg,png,jpeg}
    ├── _urls.json         # 所有原始 URL 清单
    └── files/*            # 原始附件LFS
    

改动汇总

  • 新增:crawlers/oshwhub/{__init__,__main__,crawler}.pyschemas/project.schema.jsondocs/sources/oshwhub.mdpyproject.toml
  • 修改:.gitattributes(缩窄到 data/raw/**/files/**)、.gitignore(移除 data/raw/* 排除)

下一步建议

  1. 验收 10 个项目元数据质量(随机抽 2-3 条对照原站)
  2. 决定 Phase 1.4 放量目标50500全量 12493
  3. Phase 2 准备GitHub KiCad repo 调研

2026-04-23 19:40 fs-web-stream 排查 + schema 自动校验

Claude 会话(自主推进)

fs-web-stream.jlc.com 定性

重新抓 /CYIIOT/ST_LINK-V2_1 并看 13 个 fs-web-stream.jlc.com 链接的上下文:全部是嘉立创服务侧栏/推广图标3D 打印、发热片、Ican、EDA 扩展广场、开源硬件平台 badge 等),与项目本身无关image.lceda.cn/attachments/ 就是项目附件的唯一入口,已确认闭环。docs/sources/oshwhub.md 对应章节已更新。

scripts/validate.py

jsonschema 做两层校验:

  • 默认:所有 data/raw/**/metadata.jsonschemas/project.schema.json 的结构校验
  • --check-files:另外验证每条 file 的本地 path 存在且 sha256 匹配

结果10/10 项目两项全通过。

新增

  • scripts/validate.py
  • pyproject.tomljsonschema>=4.26

待决策

  • 放量规模 —— 已提供实测数据:median ≈ 110 GBp90 上界 ≈ 660 GB建议预算 150180 GB(见 docs/sources/oshwhub_corpus_estimate.md
  • 是否需要抓 u.lceda.cn 的 EasyEDA 源 JSON需登录v0.1 跳过)

2026-04-23 19:45 全量规模实测 + License 分布

Claude 会话(自主推进)

scripts/estimate_size.py,只抓详情 HTML 解析 attachments[].size,不下载;采样 90 个 hot 项目3 页 × 30

关键发现

  • 单项目 median 9 MB / mean 22 MB / p90 54 MB / max 204 MB12493 全量 median 估算 110 GBp90 上界 660 GB
  • 视频 (.mp4 + .qt) 占 54% 存储!如果训练只要 PCB/原理图/BOM--skip-ext mp4,qt 存储直接砍半
  • License 分布健康GPL 3.0 占 49%Public Domain 21%CC 系列 ~20%CERN/TAPR OHL 6%;样本内无闭源
  • NC (Non-Commercial) 占 ~11%,商用场景必须过滤

结果固化到 docs/sources/oshwhub_corpus_estimate.md,可随时重跑验证。

建议

  1. 存储预算定 180 GBmedian + 15% buffer
  2. Phase 1.4 前给 crawler 加 --skip-ext 开关滤视频
  3. 下游建立 license whitelist 过滤 NC / 未知

2026-04-23 18:50 仓库初始化 & 数据源调研

Claude 会话:初始化

完成:

  • git.deepknow.site/Facere/FacereDataset 克隆空仓到 ~/repo/FacereDataset
  • 调研立创开源平台oshwhub.com初步数据
    • robots.txt 仅 Disallow /posts,其他路径允许
    • 存在 sitemap.xml(首页 + explore + activities + market 等入口已列出)
    • 项目详情页路径为 /detail/<uuid>(示例 f0652fd2ae3e40b8a0ecc8dc773e3512
    • 图片 CDNimage.lceda.cn/oshwhub/pullImage/...
    • 文件下载:fs-web-stream.jlc.com/fs-web-stream/file-operation/download/<snowflake-id>
    • 页面是 Next.js SPA首屏 HTML 800KB但数据加载具体 API 入口需要浏览器 trace留给 Phase 1.1
  • 创建项目骨架:
    • README.md — 项目简介与数据源表
    • CLAUDE.md — 项目级 Claude 指令爬虫规约、合规红线、schema 要求)
    • plan.md — 6 阶段建设计划Phase 0 骨架 → Phase 5 数据清洗 → Phase 6 持续运营)
    • log.md — 本文件
    • .gitignore — 排除 data/raw data/processed data/state Python 缓存等
    • 目录骨架 crawlers/ schemas/ scripts/ data/{raw,processed,state} docs/{sources,}
    • 每个空目录放 .gitkeep
  • 首次提交 & 推送到 origin main

下一步建议

  1. 拍板存储方案(本地盘 / Gitea LFS / 外部 OSS—— 影响 Phase 1.4 放量时机
  2. 目标规模1 万 / 10 万 / 全量)
  3. 决定是否保留二进制附件或只存 URL
  4. 完成上述 3 项后启动 Phase 1.1(用 chrome-devtools MCP 录 oshwhub 的 network trace 定位真实 API