Finalize Altium 365 Part Request end task: build_part_request_manifest.py (typeid->component-type), runner review-mode (fill+leave for Save), full end-to-end workflow in SKILL.md
parent
c74c9d8e22
commit
7ff9751504
57
SKILL.md
57
SKILL.md
|
|
@ -376,26 +376,42 @@ Request Id. Full field mapping, prerequisites, and the exact browser steps are i
|
||||||
Because the files upload from local disk, commit each component's `.SchLib`/`.PcbLib`/datasheet
|
Because the files upload from local disk, commit each component's `.SchLib`/`.PcbLib`/datasheet
|
||||||
to the operator's machine (device bridge) first, and put those local paths in the manifest.
|
to the operator's machine (device bridge) first, and put those local paths in the manifest.
|
||||||
|
|
||||||
### Fully automatic end-of-run (no operator action)
|
### The end-to-end workflow (this is the standard run)
|
||||||
|
|
||||||
To make the Part Request the automatic tail of every run, the end task doesn't drive the browser
|
When the central library is a managed Altium 365 Workspace, a full run goes:
|
||||||
from the cloud (it can't reach the operator's desktop Chrome). Instead:
|
|
||||||
|
|
||||||
1. After the Gitea push, the skill **writes `part_requests.json`** for the component(s) processed
|
1. **Datasheet + make** → extract parameters, classify to a typeid, fill the per-part Excel
|
||||||
this run, and **commits it plus each component's `.SchLib`/`.PcbLib`/datasheet to an *inbox
|
workbook, human-verify (the existing flow).
|
||||||
folder on the operator's machine*** via the device bridge (e.g.
|
2. **Operator provides the `.SchLib` + `.PcbLib`** → write the full parameter set into the
|
||||||
`.../altium-library-master/_part_request_inbox/`). Use the local file paths (under that inbox)
|
`.SchLib` with `schlib_write.py --typeid <ID>` (template columns + SOP params, plus the
|
||||||
in the manifest's `files`.
|
ComponentDescription), exactly as before.
|
||||||
2. A one-time **local runner** on the operator's Windows machine (`scripts/local_runner.py`, set
|
3. **Push to Gitea** (part folder + symbol/footprint, per the normal push steps).
|
||||||
up as a Task Scheduler job at logon or a startup shortcut, configured via
|
4. **Prepare the Altium 365 Part Request and hand it to the local runner** — the automatic tail:
|
||||||
`runner_config.json`) watches that inbox, ensures Chrome is up with the debug port on the
|
a. Build the manifest:
|
||||||
signed-in profile, runs `altium365_part_request.py` on the manifest, and archives it.
|
```bash
|
||||||
|
python scripts/build_part_request_manifest.py --params <part params.json> --typeid <ID> \
|
||||||
|
--file <inbox>/<MPN>.SchLib --file <inbox>/<MPN>.PcbLib --file <inbox>/<MPN>_datasheet.pdf \
|
||||||
|
--out <inbox>/part_requests.json
|
||||||
|
```
|
||||||
|
(`build_part_request_manifest.py` maps the typeid to the Workspace Component Type — e.g.
|
||||||
|
`CER → Capacitors` — pulls manufacturer/MPN/Description/parameters from the params, and lists
|
||||||
|
the local attachment paths.)
|
||||||
|
b. **Commit the `.SchLib`, `.PcbLib`, datasheet, and `part_requests.json` into the operator's
|
||||||
|
Altium Runner inbox** on their PC via the device bridge (the `inbox` folder in
|
||||||
|
`runner_config.json`, e.g. `C:/Altium Runner/inbox`). The manifest's `files` paths must be
|
||||||
|
those on-disk inbox paths.
|
||||||
|
5. **The local runner finishes it, hands-off.** `scripts/local_runner.py` (installed once on the
|
||||||
|
operator's machine as a Task Scheduler job at logon) watches the inbox, ensures Chrome is up
|
||||||
|
with the debug port on the signed-in profile, and runs `altium365_part_request.py`, which
|
||||||
|
**fills the Part Request completely — fields, parameters, and all three attachments — and
|
||||||
|
leaves it on screen without saving** (review mode). The operator just **reviews and clicks
|
||||||
|
Save**. Nothing is submitted without their click.
|
||||||
|
|
||||||
So each run ends with the skill dropping the manifest+files into the inbox; the local runner
|
So the operator's only actions per component are giving the datasheet+make, then the
|
||||||
submits the Part Request on its own. The only standing requirements are that the operator's
|
`.SchLib`+`.PcbLib`, then reviewing the filled Part Request and clicking Save. Everything between
|
||||||
machine is on and its Chrome profile has been signed into the Workspace at least once (the runner
|
is automatic, and the submission step costs zero Claude tokens. Standing requirements: the
|
||||||
relaunches Chrome with the debug port and the persisted session is reused). This keeps the
|
operator's machine is on, the runner is installed, and its Chrome profile has been signed into the
|
||||||
per-component Claude-token cost at zero for the submission step.
|
Workspace once (the runner relaunches Chrome with the debug port and reuses the session).
|
||||||
|
|
||||||
## Per-typeid versioning
|
## Per-typeid versioning
|
||||||
|
|
||||||
|
|
@ -563,10 +579,13 @@ plain flat push, but it does not merge the changelog or blank the token, so pref
|
||||||
- `scripts/altium365_part_request.py` — standalone **Selenium** submitter: attaches to the
|
- `scripts/altium365_part_request.py` — standalone **Selenium** submitter: attaches to the
|
||||||
operator's signed-in Chrome and loops the `part_requests.json` manifest, filling and saving each
|
operator's signed-in Chrome and loops the `part_requests.json` manifest, filling and saving each
|
||||||
Part Request. Token-free per component; the token-economical end task for a whole library.
|
Part Request. Token-free per component; the token-economical end task for a whole library.
|
||||||
|
- `scripts/build_part_request_manifest.py` — build/append `part_requests.json` from a component's
|
||||||
|
schlib `params.json` + local file paths; maps typeid → Altium Component Type. Run at the end of
|
||||||
|
a run, before committing the manifest+files to the operator's inbox.
|
||||||
- `scripts/local_runner.py` (+ `runner_config.example.json`) — the operator installs this **once**
|
- `scripts/local_runner.py` (+ `runner_config.example.json`) — the operator installs this **once**
|
||||||
on their machine; it watches the inbox for manifests the skill drops there, ensures Chrome's
|
on their machine; it watches the inbox for manifests the skill drops there, ensures Chrome's
|
||||||
debug session, runs the Selenium submitter, and archives each manifest — making submission the
|
debug session, and runs the Selenium submitter in **review mode** (fills everything, leaves the
|
||||||
automatic, hands-off tail of every run.
|
form for the operator to review + Save) — the automatic, hands-off tail of every run.
|
||||||
- `assets/template/versions.json` — per-typeid `template_version` + `skill_version`.
|
- `assets/template/versions.json` — per-typeid `template_version` + `skill_version`.
|
||||||
- `assets/CHANGELOG.xlsx` — global version/parameter changelog (created on first add;
|
- `assets/CHANGELOG.xlsx` — global version/parameter changelog (created on first add;
|
||||||
merged into the skill repo's copy in Gitea by `push-skill`).
|
merged into the skill repo's copy in Gitea by `push-skill`).
|
||||||
|
|
|
||||||
Binary file not shown.
|
|
@ -104,10 +104,17 @@ Run it:
|
||||||
python scripts/altium365_part_request.py --manifest part_requests.json \
|
python scripts/altium365_part_request.py --manifest part_requests.json \
|
||||||
--base https://<workspace>.365.altium.com [--review-first | --no-submit]
|
--base https://<workspace>.365.altium.com [--review-first | --no-submit]
|
||||||
```
|
```
|
||||||
`--no-submit` fills every form without saving (dry run); `--review-first` pauses after the first
|
Default operation is **review mode**: the runner (`local_runner.py`) invokes the submitter with
|
||||||
form so the operator can eyeball the mapping before the batch submits. The skill writes the
|
`--no-submit`, so it fills the form completely — fields, parameters, and all attachments — and
|
||||||
`part_requests.json` manifest and commits each component's files to the operator's disk (device
|
**leaves it on screen for the operator to review and click Save**. This is the review-first gate
|
||||||
bridge) so the local `files` paths resolve.
|
and works even unattended (a console "press Enter" can't). Set `"auto_submit": true` in
|
||||||
|
`runner_config.json` only to submit without review. (`--review-first` is a console-only variant
|
||||||
|
that pauses for Enter.)
|
||||||
|
|
||||||
|
Build the manifest with `scripts/build_part_request_manifest.py` (it maps the typeid to the
|
||||||
|
Workspace Component Type and pulls manufacturer/MPN/Description/parameters from the schlib
|
||||||
|
`params.json`). The skill writes `part_requests.json` and commits it + each component's files to
|
||||||
|
the operator's inbox (device bridge) so the local `files` paths resolve.
|
||||||
|
|
||||||
## Safety and auditing
|
## Safety and auditing
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,94 @@
|
||||||
|
#!/usr/bin/env python3
|
||||||
|
"""Build (or append to) a `part_requests.json` manifest for the Altium 365 Part Request end task,
|
||||||
|
from a component's schlib `params.json` plus its local file paths. The skill runs this at the end
|
||||||
|
of a run, then commits the manifest + the component's `.SchLib`/`.PcbLib`/datasheet into the
|
||||||
|
operator's Altium Runner inbox; the local runner picks it up and fills the Part Request.
|
||||||
|
|
||||||
|
Usage:
|
||||||
|
python build_part_request_manifest.py --params <schlib params.json> --typeid CER \
|
||||||
|
--file "C:/Altium Runner/inbox/JMK105BJ105KV-F.SchLib" \
|
||||||
|
--file "C:/Altium Runner/inbox/JMK105BJ105KV-F.PcbLib" \
|
||||||
|
--file "C:/Altium Runner/inbox/JMK105BJ105KV-F.pdf" \
|
||||||
|
--out "C:/Altium Runner/inbox/part_requests.json"
|
||||||
|
|
||||||
|
`--component-type` overrides the typeid→type mapping; `--assignee` sets the librarian.
|
||||||
|
"""
|
||||||
|
import argparse, json, os
|
||||||
|
|
||||||
|
# Vecmocon typeid -> Altium 365 Part Request "Component Type" (options captured live from the
|
||||||
|
# form's dropdown). Edit as your Workspace's component-type list evolves; --component-type wins.
|
||||||
|
TYPEID_TO_COMPONENT_TYPE = {
|
||||||
|
"CER": "Capacitors", "ELE": "Capacitors", "TAN": "Capacitors", "PLY": "Capacitors",
|
||||||
|
"FLM": "Capacitors", "SFY": "Capacitors", "SUP": "Capacitors",
|
||||||
|
"FIX": "Resistors", "TFR": "Resistors", "MFR": "Resistors", "CFR": "Resistors",
|
||||||
|
"MOR": "Resistors", "WWR": "Resistors", "SHT": "Resistors", "ARR": "Resistors",
|
||||||
|
"POT": "Resistors", "FSR": "Resistors", "NTC": "Resistors", "PTC": "Resistors",
|
||||||
|
"PWR": "Inductors", "FBD": "Inductors", "CMC": "Inductors", "RFI": "Inductors",
|
||||||
|
"XFM": "Inductors", "CTX": "Inductors", "CPL": "Inductors",
|
||||||
|
"REC": "Diodes", "FRD": "Diodes", "SCH": "Diodes", "SIC": "Diodes", "ZEN": "Diodes",
|
||||||
|
"TVS": "Diodes", "ESD": "Diodes", "SWI": "Diodes", "BRG": "Diodes", "LED": "LED",
|
||||||
|
"BJT": "Transistors", "MOS": "Transistors", "SCM": "Transistors", "GAN": "Transistors",
|
||||||
|
"IGBT": "Transistors", "JFET": "Transistors", "DIG": "Transistors",
|
||||||
|
"MCU": "Processors", "LDO": "Power Supply", "DCD": "Power Supply", "PMU": "Power Supply",
|
||||||
|
"BMS": "Integrated Circuits", "DRV": "Drivers", "AMP": "Amplifiers", "CMP": "Amplifiers",
|
||||||
|
"VRF": "Data Converters", "ADC": "Data Converters", "DAC": "Data Converters",
|
||||||
|
"ISO": "Optoelectronics", "XCV": "Interface", "AFE": "Integrated Circuits", "MEM": "Memory",
|
||||||
|
"LOG": "Logic", "SEN": "Sensors", "IFC": "Interface", "CLK": "Crystals & Oscillators",
|
||||||
|
"SVR": "Power Supply", "MTR": "Integrated Circuits",
|
||||||
|
"FUS": "Fuses", "RSF": "Fuses", "VAR": "Miscellaneous", "GDT": "Miscellaneous", "CBK": "Switches",
|
||||||
|
"DCM": "Power Supply", "IDC": "Power Supply", "INV": "Power Supply", "OBC": "Power Supply",
|
||||||
|
"CHG": "Power Supply", "PSU": "Power Supply", "RCM": "Power Supply",
|
||||||
|
"RLS": "Relays", "RLP": "Relays", "SSR": "Relays", "RLR": "Relays", "CTC": "Relays",
|
||||||
|
"SWT": "Switches", "PBT": "Switches", "DSW": "Switches", "RSW": "Switches", "RSY": "Switches",
|
||||||
|
"CWB": "Connectors", "CBB": "Connectors", "HDR": "Connectors", "FFC": "Connectors",
|
||||||
|
"USB": "Connectors", "PWC": "Connectors", "TBK": "Connectors",
|
||||||
|
"ANC": "Radio&RF", "ANP": "Radio&RF", "ANE": "Radio&RF", "SAW": "Radio&RF", "RFM": "Radio&RF",
|
||||||
|
"XTL": "Crystals & Oscillators", "OSC": "Crystals & Oscillators", "MMO": "Crystals & Oscillators",
|
||||||
|
"RSN": "Crystals & Oscillators",
|
||||||
|
"CLI": "Batteries", "CLF": "Batteries", "CCO": "Batteries", "CNI": "Batteries",
|
||||||
|
"BPK": "Batteries", "CHL": "Batteries",
|
||||||
|
"BUZ": "Audio", "PBZ": "Audio", "SPK": "Audio", "IND": "Optoelectronics",
|
||||||
|
"DSG": "Optoelectronics", "OLE": "Optoelectronics", "LCD": "Optoelectronics", "TFT": "Optoelectronics",
|
||||||
|
"STE": "Sensors", "SCU": "Sensors", "SVO": "Sensors", "SHA": "Sensors", "SIM": "Sensors",
|
||||||
|
"SPR": "Sensors",
|
||||||
|
"FAN": "Mechanicals", "HSK": "Mechanicals", "TPD": "Mechanicals",
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def build_entry(params, typeid, component_type, files, assignee):
|
||||||
|
p = params.get("parameters", {})
|
||||||
|
ctype = component_type or TYPEID_TO_COMPONENT_TYPE.get((typeid or "").upper(), "Miscellaneous")
|
||||||
|
mpn = p.get("Manufacturer Part") or params.get("component") or ""
|
||||||
|
return {
|
||||||
|
"manufacturer": p.get("Manufacturer", ""),
|
||||||
|
"mpn": mpn,
|
||||||
|
"description": p.get("Description", ""),
|
||||||
|
"component_type": ctype,
|
||||||
|
"assignee": assignee or params.get("assignee", ""),
|
||||||
|
"parameters": {k: v for k, v in p.items() if v not in ("", None)},
|
||||||
|
"files": files,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
ap = argparse.ArgumentParser()
|
||||||
|
ap.add_argument("--params", required=True, help="the schlib params.json for the component")
|
||||||
|
ap.add_argument("--typeid", help="component typeid (auto-maps to an Altium Component Type)")
|
||||||
|
ap.add_argument("--component-type", help="Altium Component Type (overrides the typeid map)")
|
||||||
|
ap.add_argument("--assignee", default="", help="librarian to assign the request to (optional)")
|
||||||
|
ap.add_argument("--file", action="append", default=[],
|
||||||
|
help="local path to attach (repeatable): .SchLib, .PcbLib, datasheet")
|
||||||
|
ap.add_argument("--out", required=True, help="part_requests.json (created or appended to)")
|
||||||
|
a = ap.parse_args()
|
||||||
|
|
||||||
|
params = json.load(open(a.params, encoding="utf-8"))
|
||||||
|
entry = build_entry(params, a.typeid, a.component_type, a.file, a.assignee)
|
||||||
|
data = json.load(open(a.out, encoding="utf-8")) if os.path.exists(a.out) else {"requests": []}
|
||||||
|
data.setdefault("requests", []).append(entry)
|
||||||
|
json.dump(data, open(a.out, "w", encoding="utf-8"), indent=2, ensure_ascii=False)
|
||||||
|
print(f"wrote {a.out} (+1 request: {entry['mpn']} -> {entry['component_type']}, "
|
||||||
|
f"{len(a.file)} file(s))")
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
|
|
@ -74,8 +74,12 @@ def process(cfg, manifest):
|
||||||
"--out", manifest + ".result.json"]
|
"--out", manifest + ".result.json"]
|
||||||
if cfg.get("chromedriver"):
|
if cfg.get("chromedriver"):
|
||||||
cmd += ["--chromedriver", cfg["chromedriver"]]
|
cmd += ["--chromedriver", cfg["chromedriver"]]
|
||||||
if cfg.get("review_first"):
|
# Default is REVIEW mode: fill the form completely (fields + parameters + attachments) and
|
||||||
cmd += ["--review-first"]
|
# leave it on screen WITHOUT saving, so the operator reviews and clicks Save themselves.
|
||||||
|
# This is the review-first gate and it works even when the runner runs unattended (unlike a
|
||||||
|
# console "press Enter"). Set "auto_submit": true in the config only to submit without review.
|
||||||
|
if not cfg.get("auto_submit", False):
|
||||||
|
cmd += ["--no-submit"]
|
||||||
subprocess.run(cmd, check=False)
|
subprocess.run(cmd, check=False)
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,11 @@
|
||||||
{
|
{
|
||||||
"base": "https://vecmocon-technologies-pvt-ltd.365.altium.com",
|
"base": "https://vecmocon-technologies-pvt-ltd.365.altium.com",
|
||||||
"inbox": "D:/User Data/Desktop/altium-library-master/_part_request_inbox",
|
"inbox": "C:/Altium Runner/inbox",
|
||||||
"processed": "D:/User Data/Desktop/altium-library-master/_part_request_inbox/processed",
|
"processed": "C:/Altium Runner/inbox/processed",
|
||||||
"chrome": "C:/Program Files/Google/Chrome/Application/chrome.exe",
|
"chrome": "C:/Program Files/Google/Chrome/Application/chrome.exe",
|
||||||
"user_data_dir": "%LOCALAPPDATA%/Google/Chrome/User Data",
|
"user_data_dir": "C:/altium-runner/chrome-profile",
|
||||||
"debug_port": 9222,
|
"debug_port": 9222,
|
||||||
"chromedriver": null,
|
"chromedriver": null,
|
||||||
"review_first": false,
|
"auto_submit": false,
|
||||||
"poll_seconds": 30
|
"poll_seconds": 30
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue