Commit Graph

27 Commits

Author SHA1 Message Date
d89a7cdf9c oshwhub: dump full listing index (33,695 projects) for batch sizing
Probed listing API and learned: total field is exposed (Pro=21,202 / Std=12,493),
pageSize accepts >=1000 (full corpus = 35 requests / 71s), sort param is silently
ignored. Dump all listings via scripts/dump_listing_index.py to local jsonl so
downstream batch-selection no longer hits the API.

Why: needed quantitative anchors before scaling Pro batch beyond top-5. License
is detail-page only (~19h serial scan), so we want to filter on grade/like
*locally* first to shortlist before paying that cost. Quality-tier counts now
known: A-tier (grade>=3 & like>=10) = 2,806 across both origins.

- scripts/dump_listing_index.py: one-shot scraper, polite QPS, streams to jsonl
- docs/sources/oshwhub_listing_full.md: human-readable report with growth
  trends, quality tiers, owner concentration, and storage-budget anchors

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-28 23:30:56 +08:00
67a2d0448b .gitignore: add *.rpt for kicad-cli sch erc default output
bisect KiCad 8 语法时跑 kicad-cli sch erc 没传 --output 参数会把
报告写到当前目录的 <input>.rpt,跑十几次主目录就堆了 20 个 .rpt
垃圾。加进 ignore 防回流。

同时清掉本次留下的:
  20 个 *.rpt 报告(已 rm)
  data/state/std_probe[1-5]/ 5 个旧 probe 状态目录(~8.5 MB stale,
  这些目录里的 probe scripts 在前一会话已删;状态本身也没用了)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-28 23:11:20 +08:00
fb577cc89f tools/epro2/kicad: fix two KiCad 8 parse blockers (newline + pin_numbers)
装 kicad 8.0.9 (apt PPA) 后跑 kicad-cli sch erc 校验我们 emit 的
.kicad_sch 文件,发现 9/9 sheets 一开始全部报 "Failed to load schematic
file" — 父节点解析就挂掉。Bisect 找到两个语法 bug:

1. **(pin_numbers (hide no)) 不被 KiCad 8 接受**
   KiCad 8 lib_symbols 里 `pin_numbers` 是 token-form,不接受 (hide
   yes/no) 子块。要么省略整个 block 默认 visible,要么 `(pin_numbers
   hide)` 表示隐藏。原来的 `(hide no)` 风格是 KiCad 7 旧语法。

   Fix: tools/epro2/kicad/sym_writer.py 删掉 (pin_numbers (hide no))
        行;KiCad 默认 visible 行为正是我们想要的。

2. **String 里的字面 \n / \r / \t 让 KiCad 解析器中止**
   ESP-VoCat 的 Overview sheet 有 TEXT "Battary\n3.7V 700mAH"(多行
   电池标签),EPRO2 里以**字面 0x0a 字符**存储。我们把它原样 emit
   成 "..." 包住的字符串 → KiCad reader 在 quoted string 内遇到 \n
   就报 parse error 不给 message。

   Fix: tools/epro2/kicad/sexpr.py 在 str escape 路径加 \n / \r / \t
        转义;reader 加 \r 解码(roundtrip 用)。

修完后:

  9/9 sheets parse OK in KiCad 8.0.9
  ERC 跑通,9 个 sheet 共 2793 violations,分布:
     1372 endpoint_off_grid        (49%, cosmetic — 30-mil EPRO2 grid 不
                                    snap KiCad 默认 50-mil grid)
      571 lib_symbol_issues        (20%, cosmetic — facere 库未注册到
                                    user library table;库已 embed 在
                                    .kicad_sch 内联可用)
      444 wire_dangling            (16%, real — wire 端点没精确对齐 pin)
      406 pin_not_connected        (15%, 同上的另一面)

  Cosmetic 占 70%,real connectivity 30%,下个 phase 处理:
    - grid 校准(把 coord 精确 round 到统一 grid 上)
    - pin tip 端点匹配(KiCad 需要 wire 端点 == pin (at) 字段对应的
      绝对坐标,浮点必须精确相等)
    - 生成 sym-lib-table 注册 facere 库(消 lib_symbol_issues)

测试:
  + test_string_escapes_newlines_and_tabs
  + test_lib_symbol_omits_pin_numbers_block
  reader 加 \r 解码

41/41 通过(39 旧 + 2 新)。

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-28 23:04:58 +08:00
8a91ce43f4 tools/epro2/kicad: Phase-2 lib_symbols — render symbol bodies from SYMBOL docs
Phase 1 emit的 .kicad_sch 里组件位置 + 属性都对,但 lib_symbols 是空
stub —— KiCad 渲染时每个组件显示成红色 "?"。Phase 2 把 SYMBOL 文档
里的 PART + RECT/POLY/CIRCLE/TEXT/PIN primitives 翻成 KiCad lib symbol
块,填到 lib_symbols 里,让 KiCad 显示真正的原理图符号。

新增 tools/epro2/kicad/sym_writer.py:
  write_lib_symbol(symbol_doc) → S-expr list 形如:
    (symbol "facere:<partId>"
      (pin_numbers (hide no))
      (pin_names (offset 1.016))
      (in_bom yes) (on_board yes)
      (property "Reference" "U" ...)
      (property "Value" "<title>" ...)
      (property "Footprint" "" hide)
      (property "Datasheet" "" hide)
      (symbol "<partId>_1_1"
        (rectangle ...)        ← from RECT.dotX1/Y1/dotX2/Y2
        (polyline (pts ...))   ← from POLY.points + closed → fill
        (circle ...)            ← from CIRCLE.center/radius
        (text "..." ...)        ← from TEXT.value/x/y/rotation
        (pin <type> line (at ...) (length ...) (name ...) (number ...))
                                ← from PIN + sibling ATTR ops
      ))

PIN 名字/编号/电气类型解析(这是关键数据探测点):
  EPRO2 PIN 不直接带 number/name/type 字段;这些信息存为独立 ATTR 操作
  (parentId=<pin_id>, key="Pin Name"/"Pin Number"/"Pin Type")
  Pin Type 取值映射:IN→input, OUT→output, BIDIR→bidirectional,
  POWER_IN→power_in, POWER_OUT→power_out, NC→no_connect, ...
  默认 passive(保守)

sch_writer 集成(lib_symbols 自动填):
  write_sch_page(doc, project_relations=pr) — 增 pr 可选参数
  内部 _build_lib_symbols(): 收集本 sheet 用到的 partIds → 通过
  ProjectRelations.parts_by_id 解析到 SYMBOL 文档 → write_lib_symbol →
  组装 (lib_symbols ...) 块;同 partId 多 SYMBOL 候选取第一个,去重
  WriteStats 增 lib_symbols_embedded / lib_symbols_missing

CLI 加 --no-lib-symbols 用于回到 Phase-1 行为(占位符调试用)。

ESP-VoCat 重导出验证:9/9 SCH_PAGE 全部 0 lib_miss
  P1_45092758.kicad_sch    wires=187 symbols=138 lib_emb=29
  codec_0b0163fa.kicad_sch wires=190 symbols=112 lib_emb=20
  Interface_b336a7c7.kicad_sch        symbols=95  lib_emb=13
  ...
  P1_408c9f4f.kicad_sch    wires=  6 symbols= 10 lib_emb= 3

测试:6 个新单测覆盖 outer wrapper / pin ATTR pull / 多形状 primitives /
sch_writer 集成路径 / 缺失 lib 计数 / no-pr 回退到 Phase 1。
合计 **39/39 通过**(parser 6 + relations 9 + project_relations 6 +
sexpr 6 + sch_writer 6 + sym_writer 6)。

下一步 Phase 3:footprint library + .kicad_pcb 导出。

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-28 22:45:19 +08:00
9213429a57 tools/epro2/kicad: Phase-1 EPRO2 → KiCad schematic exporter
写第一版 EPRO2 → .kicad_sch 转换:把 SCH_PAGE Document 的 wires +
COMPONENT placements + TEXT 输出到一个可被 KiCad 7+ 打开的 sch 文件。
不含 symbol 主体(lib_symbols 留空 stub),所以 KiCad 里组件会渲染
成红色 "?" 占位,但布线 + 位置 + Designator/Value 属性都正确。完整
symbol 库导出留 Phase 2。

模块结构:
  tools/epro2/kicad/sexpr.py        手写 S-expr emitter,Sym 标记裸符号,
                                    str 自动加引号 + 转义;float 去尾零;
                                    bool→yes/no;NaN/Inf 主动报错
  tools/epro2/kicad/_sexpr_reader.py  极简 S-expr parser,仅给 round-trip
                                    测试用(非完整 KiCad reader)
  tools/epro2/kicad/sch_writer.py   write_sch_page(doc) → str;处理:
                                      LINE  → (wire (pts ...) ...)
                                      COMPONENT → (symbol (lib_id facere:<partId>)
                                                  (at x y rot) (property Reference ...) ...)
                                      TEXT  → (text "..." (at ...))
                                    单位 mil → mm × 0.0254;零长 wire 跳过
  tools/epro2/kicad/__main__.py     CLI: --doc <uuid> | --all-sch

ESP-VoCat 验证(python -m tools.epro2.kicad <project> --all-sch):
  9 SCH_PAGE 全部转换成功
  P1_408c9f4f.kicad_sch    wires=  6  symbols= 10  text=  0  skipped= 2  (370 lines)
  P1_ee409917.kicad_sch    wires= 20  symbols= 14  text=  0  skipped= 3
  P1_54743d77.kicad_sch    wires= 42  symbols= 30  text=  3
  Overview_dc13d6d2.kicad_sch wires=  0  symbols=  1  text= 34   (说明页)
  MCU_510cff33.kicad_sch   wires= 91  symbols= 86  text=  9
  Interface_b336a7c7.kicad_sch wires= 99  symbols= 95  text=  6
  P1_5c38f45b.kicad_sch    wires=179  symbols= 86  text=  9
  P1_45092758.kicad_sch    wires=187  symbols=138  text= 10  (主图)
  codec_0b0163fa.kicad_sch wires=190  symbols=112  text= 10

输出落在 data/processed/kicad_sch/<filename>.kicad_sch(gitignore 内,
可重新生成;不入库)。

测试:6 个 sexpr 测 + 6 个 sch_writer 测,含 round-trip parse 验证。
parser/relations/project_relations 的旧 21 个不动,合计 **33/33 通过**。

下一步:
1. Phase 2 — symbol library 导出 (.kicad_sym),把 SYMBOL doc 的 PIN/RECT/
   TEXT primitives 转 KiCad symbol 主体;填 lib_symbols 块让组件渲染
   出真正的 schematic 符号
2. footprint library + .kicad_pcb 导出
3. 用 KiCad CLI (kicad-cli sch erc) 跑 ERC 校验

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-28 22:29:15 +08:00
3052e42991 tools/epro2: add ProjectRelations for cross-document resolution
per-doc Relations 在大量 cross-doc 引用前是不够的:PCB 的 PAD_NET 复合
id [PAD_NET, comp, pin, pad] 里的 pad 实际是 FOOTPRINT 文档里的 pad
实例;SCH_PAGE 的 COMPONENT.partId 指向某个 SYMBOL 文档的 PART.id。

ProjectRelations 在 per-doc Relations 之上做项目级聚合,把这些跨文档
引用拼起来。

Probe 阶段(ESP-VoCat)发现的映射规则(已写入 docstring):

1. SCH_PAGE COMPONENT.partId  ===  PART.id in some SYMBOL doc
   - 命名两种风格:'pid<hex>' (anonymous/系统 part) + '<name>.<n>' (具
     名 SKU),但都直接相等 PART.id,**不**是不同 namespace
   - 同一 PART.id 可能出现在多个 SYMBOL 文档里(库快照),
     parts_by_id 保留全部,consumer 通常取第一个

2. PCB COMPONENT.id  →  FOOTPRINT 文档 UUID  via 单独 ATTR op:
       ATTR(parentId=<comp>, key="Footprint", value=<fp_doc_uuid>)
   COMPONENT.attrs 子 dict 只有内务字段(Unique ID / Channel ID / ...),
   **不**含 footprint 引用。这跟 schematic 的 partId 在 COMPONENT 上的
   做法不一样,是 EPRO2 流的一处不对称

3. PCB PAD_NET[comp,pin,pad] 里的 pad 是 FOOTPRINT 文档内部的 pad id;
   解析链: comp → ATTR Footprint → FOOTPRINT relations.pads[pad]

API:
  ProjectRelations.build(project) — 单遍构建
  resolve_symbol_docs(sch_uuid, comp_id) → [SYMBOL doc uuids]
  resolve_footprint_doc(pcb_uuid, comp_id) → FOOTPRINT doc uuid | None
  pad_in_footprint(fp_uuid, pad_id) → PAD payload | None
  resolve_pcb_pad_net(pcb_uuid, comp, pin, pad) → {footprint, pad} | None
  attrs_for_pcb_component(pcb_uuid, comp_id) → {key: value} 折叠

CLI 加 --project-relations,跑 ESP-VoCat:
  documents                                 278
  distinct_parts                             87
  duplicated_parts                            9
  pcb_components_with_footprint             206
  pcb_components_unresolved_footprint         0
  sch_components_with_partid                572
  sch_components_unresolved_part              0

PCB 样本验证:comp=e0 → fp=1069352d81c6 Designator='U8',
PAD_NET pin=1 pad=e7 net=GND 跨文档解到坐标 (-37.4,-45.24)。

测试:6 个新单测覆盖 partId→symbol、comp→footprint、PAD_NET 跨文档、
attrs 折叠、unresolved 计数。parser + relations + project_relations
共 21/21 通过。

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-28 22:22:39 +08:00
7f9e2fad73 tools/epro2: add Relations layer for cross-object navigation
在 replay 的扁平 objects[id] -> payload 之上盖一层 Relations,建索引和
反向引用,把孤立对象拼成可遍历的图,是后续 EPRO2 → KiCad 转换器的
中间表示前置。

Relations.build(doc) 单遍扫所有对象,得到:

主集合(按类型分桶):
  parts / components / pins / pads / wires / nets / layers / rules

复合 ID 解析(关键):
  '["LAYER",1]'                          → layers[1]
  '["NET","GND"]'                        → nets["GND"]
  '["PAD_NET","e0","1","e7"]'            → pad_nets_by_pad/by_net
  '["RULE","SAFE","copperThickness1oz"]' → rules[("RULE","SAFE",...)]

反向引用:
  obj_ids_by_part         partId            → 引用对象 ids(lib 内 RECT/TEXT/PIN 都带 partId)
  components_by_part      partId            → component ids
  attrs_by_parent         parentId          → ATTR ids
  lines_by_wire           WIRE.id           → LINE ids(wire 由若干 LINE 段组成)
  pad_nets_by_pad         PAD.id            → PAD_NET 记录
  pad_nets_by_net         net name          → PAD_NET 记录
  objects_on_layer / objects_in_net  字段反查

便捷 accessor:
  attrs_dict(parent_id)   折叠所有 ATTR ops 到 {key: value} dict(last
                          write wins),KiCad 转换时按 component 拿
                          Designator/Value/Footprint 的常用入口

ATTR.parentId 解析(实测发现的两种坑):
1. 不仅指向 COMPONENT/PART —— 也大量指向 WIRE(schematic 上的网络
   标签 / 网络属性)。原查重函数漏算,636 个 false positive
   unresolved;改为"任意 doc.objects[parentId] 命中即算 resolved"
2. 复合形式 `<comp_id>-<pin_id>` 用于把 ATTR 挂在某 component 的某个
   pin 上(如 PinName)。`_resolve_parent()` 用 split("-",1) 兜底

CLI 加 --relations,按 docType 聚合 stats:
  uv run python -m tools.epro2 data/raw/oshwhub/<uuid> --relations

ESP-VoCat 验证:
  SCH_PAGE 9 docs : 572 components, 563 wires, 934 lines_grouped,
                    4111 attrs_attached, 0 unresolved_parents
  PCB      6 docs : 206 components, 807 pad_nets, 173 nets, 544 layers
  SYMBOL 105 docs : 106 parts, 560 pins, 1680 attrs_attached
  FOOTPRINT 55 docs: 496 pads, 9 nets, 1771 layers, 140 rules

注:PCB 内 pads=6 vs pad_nets=807 不矛盾 —— PAD 实例存在 FOOTPRINT
文档里,PCB stream 用 ["PAD_NET",comp,pin,pad] 复合 id 跨文档引用;
解析"comp 的某 pin 通过哪个 footprint 的哪个 pad"需要 project-级
Relations 聚合(下个 task)。

测试:tools/epro2/tests/test_relations.py 9 个单测覆盖复合 id 解析、
lineGroup 链接、parentId 直/复合解析、partId 反查、attrs 折叠。
parser + relations 共 15/15 通过。

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-28 22:17:28 +08:00
3c57e75d51 Add tools/epro2 — EPRO2 parser + replay prototype
为 Pro 3.x .epro2 工程源数据写解析骨架,下游做 EPRO2→KiCad 转换器
前的基础设施。在 ESP-VoCat (278 docs / 7.5 MB) + 220V 桌面电源
(771 docs / 26 MB) 端到端跑通,0 parse errors。

模块结构:
  tools/epro2/parser.py    单行 → Op:rstrip("|") + split("||") + json.loads
  tools/epro2/replay.py    state-machine:DOCHEAD 设头;其它 op 按 id 做
                           upsert(payload=None 当 delete);EDIT_HEAD/
                           META/CANVAS/PREFERENCE/PANELIZE 当 doc 级单
                           例存
  tools/epro2/__main__.py  CLI:传项目目录走 manifest.json 重放每个 doc,
                           按 docType 聚合输出 + 可选 --dump-doc 看单文
                           档详情
  tools/epro2/tests/       6 个单测 pin 死 trailing-pipe / 三段消息 /
                           id-only-no-payload / 嵌入管道符等坑

ESP-VoCat 输出示例:
  Documents: 278  (parse_errors=0)
   count  docType         objects        ops  deletes  untyped_ops
     105  SYMBOL             4124       4439        0            0
      88  DEVICE               88        264        0            0
      55  FOOTPRINT          4641       4855        0            0
       9  SCH_PAGE           7982       8167       42            0
       6  PCB                8428       8547       38            0
       6  BOARD                 9         18        0            0
       6  SCH                   9         26        0            0
       1  BLOB                  4          8        0            0
       1  FONT                 16         28        0            0
       1  CONFIG                2          3        0            0
  Top ops: ATTR 7035 / ELE_PLACEHOLDER 4225 / LINE 3005 / LAYER 2318 ...

PCB 文档单 dump 验证语义正确:META 含 title (PCB-EchoEar-CoreBoard-V1_0)
+ board 引用;CANVAS 含 origin/grid/unit (mm);LAYER 1/2/3 = TOP/BOTTOM/
TOP_SILK 配色齐全。

跑法:
  uv run python -m tools.epro2 data/raw/oshwhub/<project_uuid>
  uv run python -m tools.epro2 data/raw/oshwhub/<uuid> --dump-doc <doc_uuid>

下一步(不在本 commit):
1. 把对象间关系建起来(COMPONENT.partId → PART;LINE.lineGroup → WIRE;
   PAD_NET id → PAD + NET 三方关联)—— 当前 replay 只做扁平 dict
2. EPRO2 → KiCad 序列化层(Forge 投影硬门槛)
3. 在 Pro 3.x 三个项目做整体回归(X86 主板 7374 docs 可作压力测试)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-28 22:10:27 +08:00
c721e08c93 projects.md: replace Comments column with 版本 (Std / Pro 3.x / Pro 2.x)
Comments 那列对工程"品质"信号弱(评论量主要看话题热度);换成"版本"
列直接告诉读者每个项目源是哪种 EDA 格式 + 编辑器版本号。当前 15
个项目里 10 Std / 3 Pro 3.x / 2 Pro 2.x。

source_format 字段映射:
  easyeda-std        → Std
  easyeda-pro        → Pro 3.x
  easyeda-pro-legacy → Pro 2.x
  其它               → 透传

editor_version(如 6.5.43 / 3.2.91 / 2.1.40)作为子标签放第二行。

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-28 22:01:41 +08:00
c6279bff08 Add EasyEDA Pro 2.x legacy source ingestion (5/5 batch closure)
补齐前一批失败的 2 个 legacy Pro 项目(立创·泰山派 RK3566、立创·梁山派),
打通 Pro 2.x 旧版工程的源抓取链路。结合上一 commit 的 modern Pro 3.x
路径,本仓库 5/5 Pro 项目 EPRO2/dataStr 全部端到端打通。

Pro 2.x 与 Pro 3.x 是两个完全不同的存储模型:
- Pro 3.x:git-style branch + linear history chain,AES-128-GCM 加密的
  EPRO2 增量消息流,按 history 重放(已在前一 commit 打通)
- Pro 2.x:无 branch / 无 history。文档以 EasyEDA Std plaintext dataStr
  存储(同 ["DOCTYPE","SCH","1.1"] 格式),按 doc UUID 通过
  /api/v2/documents/lists 批量 GET,主体无加密,只组件库走 AES

Pro 2.x 抓取链由 HAR (tmp/prodownload3.har, 178 请求) 反推:

  GET  /api/v4/projects/<P>                     → boards: [{sch, pcb, name}]
  GET  /api/projects/<P>/ticket?uuid=&g_ticket=-1
                                                → 完整项目 manifest
  POST /api/schematic/lists {uuids:[<sch>]}     → sort: [{uuid:<sheet>}]
  POST /api/v2/documents/lists {uuids,docType:1} → schematic plaintext
  POST /api/v2/documents/lists {uuids,docType:3} → PCB plaintext
  POST /api/coppers/search {paths}              → 铺铜层
  POST /api/textpath/search {paths,project_uuid}→ 字体/文字
  POST /api/v2/resources/search {hash,project_uuid} → BLOB 图片

实现:
- crawlers/oshwhub/crawler.py:
  - fetch_pro_source() refactor 成 dispatcher,先 GET project meta
    检查 branch_uuid,null 即旧版走 _fetch_pro_legacy(),非空走
    _fetch_pro_modern()
  - _fetch_pro_legacy() 新增(按上面 9 步流程拉所有 doc + 辅助层)
  - _pro_post_json() POST helper(与 _pro_get_json 对称)
- schemas/project.schema.json: source_format enum 加 easyeda-pro-legacy
- docs/sources/easyeda_pro_source.md rev 4: §1.1 旧版 vs 新版判别表更新、
  §2.7 新增旧版抓取流程 + 实测数据

落盘约定(旧版):
  source/ticket.json                     完整 manifest
  source/<sheet_uuid>.json               每张原理图(含 dataStr)
  source/pcb_<pcb_uuid>.json             每块 PCB
  source/coppers.json/textpath.json/blobs.json  辅助 PCB 层资源
  source/manifest.json                   索引

实测:
  立创·梁山派      editor=2.1.30, 2 sheets+1 pcb,    1.0 MB,  78 sym/191 fp/128 dev
  立创·泰山派 RK3566 editor=2.1.40, 29 sheets+1 pcb, 0.8 MB, 299 sym/524 fp/295 dev

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

5/5 Pro 项目终极汇总:
  X86 主板          easyeda-pro        3.2.15  7374 docs / 481 MB
  泰山派 RK3566     easyeda-pro-legacy 2.1.40    30 docs / 0.8 MB
  梁山派            easyeda-pro-legacy 2.1.30     3 docs / 1.0 MB
  220V 桌面电源     easyeda-pro        3.2.69   771 docs /  26 MB
  ESP-VoCat         easyeda-pro        3.2.91   278 docs / 7.5 MB

共 8456 docs / ~516 MB plain。

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-28 21:59:25 +08:00
3282a028c4 Add EasyEDA Pro EPRO2 source ingestion (3/5 batch test)
打通 oshwhub origin=pro 现代 Pro 3.x 工程的 EPRO2 源抓取链路。3/5
modern Pro 项目完整解出(共 8423 docs / 542 MB plain):

- X86 主板        7374 docs / 481 MB plain (chain=85, editor=3.2.15)
- 220V 桌面电源     771 docs /  26 MB plain (chain=28, editor=3.2.69)
- ESP-VoCat       278 docs / 7.5 MB plain (chain=12, editor=3.2.91)

剩余 2/5 是 legacy Pro 2.x(立创泰山派 RK3566、梁山派),项目 meta
返回 branch_uuid=null + editorVersion="2.1.40",没有 git-style chain
模型,文档直接挂在 boards[].sch/pcb 字段上,访问端点暂未挖通;元
数据落库 metadata.json,source/ 留空。

实现要点:
- fetch_pro_source(): 4 步流程(project → branch HEAD → structures
  → /branches/<B>/histories/<HEAD> 即返完整 chain,无需 ?limit 批量
  端点)+ 逐 history 走 AES-128-GCM 解密(16 字节 IV,pycryptodome
  原生支持)+ gunzip + 按 DOCHEAD 切 per-doc EPRO2 流
- EPRO2 解析坑:行末单 `|` 是行终止符不是字段分隔符,必须先
  rstrip("|") 再 split("||"),否则 payload JSON 解析失败 silently
  swallow 导致 cur_doc 不设 → 第一轮 X86 板 7374 docs 抽出来只剩 2 个
- docType 实测远不止 BOARD/PCB/SCH/SCH_PAGE,还含 SYMBOL /
  FOOTPRINT / DEVICE / BLOB / FONT / CONFIG —— Pro 把组件库快照也
  随项目存到 history,下游做 EPRO2→KiCad 转换时必须先把这些 lib
  doc 加载进 symbol cache
- Pro 2.x vs 3.x 是不同存储模型 —— 3.x 走 branch 模型(已打通),
  2.x 走 boards[] 直链(未打通);判别条件:project meta 的
  branch_uuid 是否为 null

CLI 新增 --with-pro-source / --backfill-pro-source / --pro-cookie /
--origin(按 origin 字段服务端过滤 listing API),crawl_one() 按
origin=pro 自动 dispatch 到 Pro fetcher。

schema:docType 类型从 integer 放宽到 [integer, string, null]
(兼容 Std 的 1/3 + Pro 的 BOARD/SCH 等),新增 message_count 字段。

License 注意:本批 5 个项目全是 NC-SA / GPL,未达 Pro source doc
§4.2 Forge 白名单(MIT/BSD/Apache/CC0/CC-BY/CERN-OHL-P/Unlicense)。
按 CLAUDE.md "研究用、不再分发" 原则 raw 入库无碍;Forge 投影时
另过白名单。

详细技术细节见 docs/sources/easyeda_pro_source.md rev 3 + log.md。

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-28 21:45:52 +08:00
d874278bc5 Add EasyEDA Std project source ingestion (10 boards backfilled)
打通 oshwhub origin=std 项目的工程源(schematic + PCB dataStr)抓取链路。原
plan.md §1.6 假设需要登录,实测 lceda.cn/api/documents/<doc>?uuid=<doc>&path=<doc>
对公开项目匿名可访问 —— 无需 cookie,无账号封禁风险。

调研:4 轮探测留痕在 data/state/std_probe[1-5]/(gitignored);翻 Std 编辑器
v6.5.51 的 main.min.js bundle 找到 ajaxDetail 端点;按 docType 区分两种
响应 shape(schematic 项目视图 vs PCB 文档视图)。

Crawler:
  - make_source_client() 用浏览器 UA + lceda.cn/editor Referer,因为
    oshwhub /api/project/<uuid> 端点拒绝 FacereDataset/0.1 UA(CLAUDE.md
    UA 例外条款:目标站主动封自定义 UA + 公开静态资源)
  - fetch_std_source(): 项目元 → version_documents → 逐文档 dataStr → 落
    source/<doc>.json + source/manifest.json
  - --with-source(爬新项目时一并抓源)/ --backfill-source(仅扫已有)
  - QPS ≤ 0.2 (SLEEP_SOURCE = 5s) 自律

Schema: 加 source_format / source_path / source_documents / editor_version
(前 3 进 enum 锁定,便于后续 Pro / KiCad 源对齐)。

回填结果:10/10 成功,45 个文档,33.2 MB;schema validate 全通。
docTypes 主要是 1 (schematic) 与 3 (pcb);USB 电压电流表只有 PCB 文档(4 个:
主板+盖板+底板+面板,作者未上传原理图源)。

完整调研:docs/sources/easyeda_std_source.md。
2026-04-28 20:07:40 +08:00
b0d3afd2a9 update readme 2026-04-26 11:54:01 +08:00
Zhang Jiahao
a3942c03df Update EasyEDA Pro source research 2026-04-24 00:40:18 +08:00
Zhang Jiahao
a16cb11c7d Add easyeda_pro_source.md: Pro 工程源完整链 + EPRO2 格式解析
Why:
- pro.lceda.cn (立创 EDA 专业版) 的工程源抓取链已经打通:4 步 API +
  AES-128-GCM 解密 + gzip 解压 + EPRO2 消息流解析,所有信息需要落成
  文档独立保留,避免丢失;也为后续实现 EPRO2 → KiCad 转换器/选型铺路。
- 与 oshwhub.md(Std 版)并列成为独立调研文档 —— Pro 和 Std 是两套
  独立编辑器,cookie/API/格式都不同,混在一起反而乱。

What:
- docs/sources/easyeda_pro_source.md:
  * TL;DR 表 + §1 Std vs Pro 对照
  * §2 4 步 API 链 + 必需 headers (Editor-Version/path/Referer/Cookie)
    + Python 解密代码 + 实测数据(2.7 MB 源流 / 8357 条消息)
  * §3 EPRO2 格式完整分类:40 种 message type 按功能分组
    (零件/几何/PCB/层/规则/...) + 每类样例
  * §4 安全合规(风控 / license / 密钥泄漏语义)
  * §5 接入 Forge (OSHWHUB_INGEST_SPEC.md) 的 gap 表
  * §6 已知未验证 7 条
  * 附录 A 一键重跑命令

- pyproject.toml: + pycryptodome>=3.23.0(AES-GCM 解密依赖)
- log.md: 本次会话记录

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-24 00:11:32 +08:00
f797205dc8 add epec md 2026-04-23 23:42:21 +08:00
Zhang Jiahao
1a67df44ba Add docs/infra.md: dev1 (广州) 部署记录
Why:
- Phase 0.5 要求的基础设施文档落地。机器已部署好,记录机器规格、
  SSH 约定、仓库位置、依赖装法、凭据管理规则、环境变量、长跑与日志
  策略、磁盘应急阈值,方便后续自己或新协作者快速接手。
- 严格不包含任何凭据值(token / cookie / 密码),只写事件与结构。

Deployment notes:
- GIT_LFS_SKIP_SMUDGE=1 克隆:省带宽和磁盘(本地仓库 32MB 而非 535MB+),
  历史 LFS 对象按需 pull
- uv 走清华镜像 + only-system python 3.10:sync 秒完成,避免下载独立 Python
- ~/.secrets/ mode 700 就绪

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-23 23:36:21 +08:00
Zhang Jiahao
fb7c0488bc Relax Python requirement 3.11 → 3.10
Why:
- Ubuntu 22.04(广州云服务器 dev1 的发行版)自带 python3.10。之前要求 3.11
  会让 uv 去下载独立 Python 解释器,从国内拉 Astral 的 python-build
  release 很慢。放到 3.10 直接用系统 Python,sync 秒完成。
- 代码没有用任何 3.11+ 特性(用了 `from __future__ import annotations`
  支持 PEP 604/585 在 3.10 上下文),降版本零代价。

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-23 23:34:54 +08:00
Zhang Jiahao
b0ddcf3f14 Allow login content; plan cloud infra, storage tiers, EDA→KiCad conversion
Why:
- 策略调整:登录后才能访问的内容从"禁止"改为"纳入本项目范围",
  同时明确凭据管理红线(合法账号、不入 git、云服务器隔离)。
  解锁 u.lceda.cn 工程源 JSON,这是训练数据质量的关键升级。
- 计划中"存储"和"运行环境"一直模糊,现在按 Charles 提供的广州云服务器
  + 存储分级演进(Gitea LFS → 对象存储)给出清晰路径。
- 打通 oshwhub (EasyEDA) 与 bshada/open-schematics (KiCad) 两个生态,
  需要一个 EDA→KiCad 批转换脚本。先把它纳入 plan,等拿到工程源再实现。

What:
- CLAUDE.md: 登录态条款从"不抓"改为"合法账号可抓",凭据管理写死在
  ~/.secrets/,事件记 docs/secrets.md;合规红线同步更新
- plan.md §0.5: 新增 基础设施段(机器初始化 / 调度 / 登录态获取)
- plan.md §1.4: 存储分级演进(< 50 GB 云盘,50-200 GB 评估,> 200 GB 迁对象存储)
- plan.md §1.6: 登录态抓 u.lceda.cn 工程源
- plan.md §1.7: scripts/convert_to_kicad.py 批处理,候选 easyeda2kicad.py
- plan.md 风险表: 加账号封禁 / 转换失败 / 云服务器单点故障三条
- docs/sources/oshwhub.md: u.lceda.cn 从"未开放"移到"需登录,已纳入范围"
- README.md 数据源表: 加"登录态"列 + 运行环境说明
- log.md: 本次策略变更记录

未改:未新增 docs/infra.md(等机器到位 + 真实细节后再写),scripts/convert_to_kicad.py
尚未实现(等拿到工程源样本再实现)。

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-23 20:57:30 +08:00
Zhang Jiahao
ba501c328c Remove personal name from suggestion/decision phrasing
Why:
- "给 Charles 的建议"、"待 Charles 拍板"、"需要 Charles 决策" 这些写法
  把具体人绑到了文档里,换维护者就失准。改成中性的 "建议 / 待决策 /
  待拍板",文档对未来协作者和 agent 都更通用。

What:
- log.md: 四处去掉 "给 Charles / 还是需要 Charles 决策 / 等 Charles 拍板"
- plan.md: 三处去掉 "待 Charles / Charles 定目标 / 需要 Charles 定"
- docs/sources/hf_bshada_open_schematics.md: "待 Charles 决策" → "待决策"
- scripts/estimate_size.py: docstring 去掉 "给 Charles 一个估计"
- CLAUDE.md: 数据删除确认规则从 "先跟 Charles 确认" 改成 "先跟用户确认"

保留的 Charles 提及都是事实性的:
- README/plan 里的 "维护者:Charles"(身份字段)
- log.md 历史条目里 "Charles 要求..." / "Charles 点名..."(历史事件记录)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-23 20:01:52 +08:00
Zhang Jiahao
ed4837dedf Rewrite oshwhub.md as canonical data source investigation
Why:
- Charles 要求把 12493 总数验证 + 90 项目采样结果合进主调研文档,消除
  oshwhub_corpus_estimate.md 与 oshwhub.md 的重复与分散。
- 一份高质量的数据源调查应该独立完备:任何人(人或 agent)读完就能
  复现爬取 / 估算 / 合规判断,不用跨文件拼凑。

What:
- docs/sources/oshwhub.md 重写为 9 节 + 附录:
  - TL;DR 表(一页纸核心事实)
  - 站点架构 / robots / API 入口 / 项目详情 SSR / 附件 CDN
  - 排除项:fs-web-stream.jlc.com 推广图标 / u.lceda.cn 登录源
  - §4 项目总数验证(新):三路 sort 一致 12493 + 分页二分边界 ≈250 页 + grade 覆盖抽样
  - §5 抽样语料特征(从 corpus_estimate 并入):体积 median 9MB/p90 54MB、
    视频占 54%、license 分布 GPL 3.0 49%/Public Domain 21%
  - 风险表 7 条、附录重跑命令
- 删除 docs/sources/oshwhub_corpus_estimate.md(内容已并入 §5)
- log.md: 本次记录

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-23 19:59:05 +08:00
Zhang Jiahao
53b7648984 Add HF bshada/open-schematics to Phase 1 plan
Why:
- Charles 点名把该 HF 数据集纳入第一批。它是已预处理包(非待爬网站),
  和 oshwhub 的抓取逻辑不一样,先把决策面在 plan 里讲清楚,再动手拉。
- 与 oshwhub (EasyEDA 生态) 互补,补 KiCad 原生路径。

What:
- docs/sources/hf_bshada_open_schematics.md: 调研文档
  - 78 parquet shards, 6.4 GB 总量
  - CC-BY-4.0 商用友好
  - 字段:.kicad_sch 源 / PNG / 组件列表 / JSON / YAML / name / desc
  - 镜像方案(整包存 data/external/..., 不拆 per-project)
  - .gitattributes 建议(data/external/**/*.{parquet,png} → LFS)
- plan.md §1.5: 阶段说明 + 待 Charles 批 6.4 GB 预算
- README.md 数据源表: 加一行
- log.md: 本次记录

下载未触发,等 Charles 拍板。

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-23 19:51:24 +08:00
Zhang Jiahao
ce22717288 Add projects.md index (stars-sorted) + build_index.py generator
Why:
- Charles 要一个索引页看入库项目 + 他们的 stars。手工维护会漂移,
  所以 scripts/build_index.py 直接读 metadata.json 重新生成,保证
  projects.md 永远是 data/raw/ 的镜像。

What:
- projects.md: 10 个项目按 Stars 倒序(最高 3293 的加热台量产计划
  → 最低 236 的柚子爱 AI 相机),含 stars/likes/forks/views/comments/
  files/size,+ License 与数据源分布
- scripts/build_index.py: 扫 metadata.json 渲染 markdown,支持未来
  多数据源(source 字段区分),下次新增 oshwhub / github / hackaday
  项目后重跑即可
- README.md: 加 projects.md 链接

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-23 19:48:21 +08:00
Zhang Jiahao
e222b08f27 Add corpus size/license estimator; snapshot 90-project findings
Why:
- 放量决策需要比"52MB/项目 × 12493 = 650GB"更扎实的数据。用
  scripts/estimate_size.py 采样 90 个 hot 项目的 attachments[].size
  得到真实分布(median 9MB / p90 54MB),全量 median 估算 110GB,
  p90 上界 660GB。这给 Charles 一个可信的存储预算。
- 附带 license 和 ext 分布采出两个重要洞察:
  (1) mp4+qt 视频占 54% 存储,加 --skip-ext 开关可节省一半;
  (2) NC (Non-Commercial) 许可 ~11%,下游必须按 whitelist 过滤。

What:
- scripts/estimate_size.py: 无下载的元数据采样器,复用 crawler.parse_detail_html
- docs/sources/oshwhub_corpus_estimate.md: 结果快照 + 决策建议
- log.md: 本次会话记录

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-23 19:45:54 +08:00
Zhang Jiahao
c8d55a22eb Add schema+file validator; pin down fs-web-stream as ad icons
Why:
- schema 必须能自动校验,否则后续放量无法防腐。现在 scripts/validate.py
  对全部 metadata.json 做两层检查(schema + 本地文件 sha256),跑一次
  即可对全量数据签收;10/10 项目已通过。
- docs/sources/oshwhub.md 之前把 fs-web-stream.jlc.com 标为"工程源待查",
  排查后确认那些 URL 全部是嘉立创服务侧栏/推广图标,与项目无关。
  image.lceda.cn/attachments/ 是项目附件的唯一入口,现在调研文档闭合。

What:
- scripts/validate.py: jsonschema 校验 + optional --check-files 核 sha256
- pyproject.toml: 加 jsonschema>=4.26 依赖
- docs/sources/oshwhub.md: fs-web-stream 归类为推广资源(已排除),附 context 证据
- log.md: 本次会话记录

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-23 19:40:55 +08:00
Zhang Jiahao
5ffa10f256 Phase 1 MVP: crawl 10 high-quality oshwhub projects into LFS
Why:
- Charles 指定:先爬 10 个高质量项目存 Gitea LFS,一个项目一个文件夹,
  保留原文件和 URL。先以小批量验证 schema + LFS 流水线,放量前再拍板
  存储规模。

What:
- crawlers/oshwhub: 列表 API (`/api/project?sort=hot`) + SSR HTML 解析,
  一次性产出 metadata / description / cover / files / _urls
- schemas/project.schema.json: 跨源统一 schema
- docs/sources/oshwhub.md: API 入口 / 字段映射 / 陷阱调研
- pyproject.toml: httpx[http2] 单依赖
- .gitattributes: data/raw/**/files/** 一律走 LFS(规则写窄,避免误伤 schemas/*.json 等)
- .gitignore: 移除 data/raw/* 排除(改走 LFS 入库)

10 个项目覆盖:调试器 / 加热台 / 盖革计数器 / 数控电源 / 焊台 /
智能手表 / USB 测电流 / ZVS 感应加热 / AI 开发板 / 红外热成像。
共 52 附件 ≈ 524 MB 入 LFS,筛选判据 grade=4 & likes>=100 & 多样性。

Known gaps(见 plan.md § Phase 1.4):
- EasyEDA 源 JSON 需登录 (u.lceda.cn),v0.1 跳过
- fs-web-stream.jlc.com 的工程源下载未测
- scripts/validate.py 自动 schema 校验未实现

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-23 19:34:09 +08:00
Zhang Jiahao
bf2370f83b Initial skeleton for FacereDataset
Why:
- Facere 需要一个统一的开源硬件设计数据源,用于训练专有模型与
  构建检索型知识库。仓库先立骨架,把合规红线、数据 schema 要求、
  爬虫规约写在 CLAUDE.md 里,避免后续实现时各站点爬虫写法发散。
- plan.md 用阶段化路线图明确"先广度后深度、先合规后规模"的策略,
  让放量前必须经过 Charles 对齐一次,降低存储与法律风险。

Contents:
- README.md: 项目简介、数据源表、仓库结构、合规声明
- CLAUDE.md: 项目级 Claude 指令(工作流 / 爬虫规约 / 合规红线)
- plan.md: Phase 0-6 分阶段计划 + 风险与未决项
- log.md: 首条日志(调研 + 初始化记录)
- .gitignore: 排除 data/{raw,processed,state} 内容,保留目录占位
- 目录骨架: crawlers/ schemas/ scripts/ data/ docs/sources/

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-23 18:58:10 +08:00