Reject WROOM pintables taken from the bare ESP32 die map.

Module pad 1 is GND; extracting ANT/XTAL as pin 1 made every ground look like a shorted antenna.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-08-28 02:55:12 +02:00
co-authored by Cursor
parent e84418f975
commit ed75aabaf0
5 changed files with 46 additions and 2 deletions
+5 -1
View File
@@ -148,8 +148,12 @@ async def run_skill_locally(
errors: list[str] = []
if validator is not None:
check = dict(payload)
mpn_hint = re.search(r"MPN:\s*(\S+)", user_text or "", re.I)
if mpn_hint and "mpn" not in check:
check["mpn"] = mpn_hint.group(1).rstrip(".,;")
try:
errors = list(validator(payload) or [])
errors = list(validator(check) or [])
except Exception as exc:
log.warning(
"Skill %s validate.py raised: %s", skill_name, exc,
+1 -1
View File
@@ -1,5 +1,5 @@
{
"default_model_version": "1.5.0",
"default_model_version": "1.6.0",
"extract-pintable": {
"skill_id": "skill_01VMWPZuvuZAe4LmLbmsNWNY",
"latest_version": "1784798970179642",
+3
View File
@@ -30,6 +30,9 @@ Rules for pin extraction:
- For multiplexed pins, put the primary name in `name` and alternates in `functions`
- If the datasheet has separate tables for different packages, extract for the package matching the MPN
- Pay careful attention to pin numbering — off-by-one errors here break everything downstream
- **Modules vs bare die (critical).** MPNs containing `WROOM`, `WROVER`, `MODULE`, `MOD-`, or `SIP` are *modules*. Extract the **module landing-pad table** (connector pins the schematic uses). Do **not** extract the SoC/QFN ball map from a nested chip chapter or a sibling chip-only PDF.
- Espressif WROOM: pin 1 is GND (often a group of GND pads). Pin 1 named `ANT`, `CHIP_PU`, or `XTAL_*` means you grabbed the bare ESP32 die table — that will mark every module GND as “antenna shorted” and is invalid.
- Crystal, RF antenna, and flash on a WROOM module are **inside the can**; they must not appear as schematic pin numbers.
### 3. Extract package info
+19
View File
@@ -2,6 +2,7 @@
"""Validate extraction output against the pintable schema."""
import json
import re
import sys
from pathlib import Path
@@ -47,6 +48,24 @@ def validate(data: dict) -> list[str]:
if dupes:
errors.append(f"Duplicate pin numbers: {dupes}")
names = {
str(p.get("number")): str(p.get("name") or "").upper()
for p in pins if "number" in p
}
pin1 = names.get("1", "")
looks_like_rf_die = bool(
re.search(r"\bANT\b|^CHIP_PU$|^XTAL", pin1)
and any("XTAL" in n for n in names.values())
)
mpn = str(data.get("mpn") or "")
is_module_mpn = bool(re.search(r"WROOM|WROVER|\bMODULE\b|\bSIP\b", mpn, re.I))
if looks_like_rf_die and is_module_mpn:
errors.append(
"Pin 1 looks like a bare RF SoC ball (ANT/CHIP_PU) with XTAL "
"pins in the table. Module footprints (WROOM) use pad 1 = GND; "
"extract the module landing-pad table, not the die map."
)
if "absolute_maximum_ratings" in data:
ratings = data["absolute_maximum_ratings"]
if ratings is not None and not isinstance(ratings, list):
+18
View File
@@ -212,6 +212,7 @@ def test_local_skills_load():
validate = load_skill_validator("extract-pintable")
assert validate is not None
errors = validate({
"mpn": "MSPM0G3507SPTR",
"component_subtype": "ic.mcu",
"component_subtype_description": "MCU",
"package_info": {"base_family": "MSPM0", "package": "LQFP-48", "pin_count": 2},
@@ -223,6 +224,23 @@ def test_local_skills_load():
assert errors == []
def test_wroom_rejects_bare_soc_pin1_ant():
from backend.services.llm.local_skill import load_skill_validator
validate = load_skill_validator("extract-pintable")
errors = validate({
"mpn": "ESP32-S31-WROOM-3",
"component_subtype": "ic.mcu",
"package_info": {"base_family": "ESP32-S31", "package": "module", "pin_count": 2},
"pintable": [
{"number": 1, "name": "ANT"},
{"number": 2, "name": "CHIP_PU"},
{"number": 78, "name": "XTAL_N"},
{"number": 79, "name": "XTAL_P"},
],
})
assert any("module" in e.lower() or "pad" in e.lower() or "SoC" in e or "WROOM" in e for e in errors)
def test_factory_routes_deepseek(monkeypatch):
monkeypatch.setattr(settings, "deepseek_api_key", "sk-test")
from backend.services.llm.factory import get_provider_by_name