skill
Alternate Part
Public Made by Adomby adom
Which part can replace this one on my board, in stock now: any part, with the answer saying how sure it is.
← Commit history
Publish 0.2.2
5 files changed
+24−9
package.json+1−1@@ -2,7 +2,7 @@ "name": "alternate-part", "slug": "alternate-part", "title": "Alternate Part",- "version": "0.2.1",+ "version": "0.2.2", "type": "skill", "org": "adom", "description": "Find a part that can replace one on THIS board, in stock now, for any part: it reads the design, works out which pins it uses, and ranks same-package candidates by stock. Every answer says its tier and how sure it is: STM32 from ST's own pin data, any part with an Adom page from its datasheet-verified pin table, passives and simple parts by parameters, and for everything else a step-by-step check from the datasheets.",
page.json+1−1@@ -2,7 +2,7 @@ "name": "alternate-part", "slug": "alternate-part", "title": "Alternate Part",- "version": "0.2.1",+ "version": "0.2.2", "type": "skill", "org": "adom", "description": "Find a part that can replace one on THIS board, in stock now, for any part: it reads the design, works out which pins it uses, and ranks same-package candidates by stock. Every answer says its tier and how sure it is: STM32 from ST's own pin data, any part with an Adom page from its datasheet-verified pin table, passives and simple parts by parameters, and for everything else a step-by-step check from the datasheets.",
skills/alternate-part/SKILL.md+6−2@@ -47,8 +47,12 @@ Tier 2 covers every part with an Adom page (`adom_pages.indexed` in the response `drop-in`, `drop-in except listed pins`, `equivalent` (tier 3, by parameters), `not compatible` (in `rejected`: never offer these); - `stock`: per distributor, with its source. `live` is a distributor answer at most an hour old; `cached` is an- older one (the refresh failed: say how old); `snapshot` is the JLCPCB catalog, with its date. Rank by live- stock; never present a cached or snapshot number as "in stock now" without saying its age;+ older one (the refresh failed: say how old); `snapshot` is a JLCPCB catalog row, and its age is the row's own+ listing date (`fetched_at`), which can be years older than the catalog copy. The script prints `IN STOCK` only+ on a live answer and `stale stock` otherwise, and a NOTE when no live answer came back at all (the distributor+ services were down). Rank by live stock; never present a cached or catalog number as "in stock now" without+ its age, and tell the user to confirm on the distributor before ordering. A distributor missing from the line+ gave no number: that is unknown, not zero; - `warnings`: read every one aloud to the user (a different test condition, an unknown rating, an unchecked pin order). 4. **Answer plainly.** Lead with the tier and how sure it is, then the best in-stock candidates with where they are in
skills/alternate-part/scripts/alternates.py+11−3@@ -95,7 +95,8 @@ def _age(ts): except (ValueError, TypeError, OSError): return '' h = (_dt.datetime.now(_dt.timezone.utc) - t).total_seconds() / 3600- return f'{h * 60:.0f} min old' if h < 1 else f'{h:.0f} h old' if h < 48 else f'{h / 24:.0f} days old'+ return f'{h * 60:.0f} min old' if h < 1 else f'{h:.0f} h old' if h < 48 else f'{h / 24:.0f} days old' if h < 24 * 365 \+ else f'{h / 24 / 365:.1f} years old' def stock_line(st):@@ -105,7 +106,10 @@ def stock_line(st): v = st.get(key) if isinstance(st, dict) else None if not v or v.get('stock') is None: continue src = v.get('source') or '?'- when = f"snapshot {v.get('snapshot_date')}" if src == 'snapshot' else f"{src}, {_age(v.get('fetched_at'))}"+ # a catalog row carries its own listing date (as_of / fetched_at); the snapshot date only says when the catalog+ # was copied, so a 2024 listing in an April 2026 snapshot reads as 2024 (fresh-thread test, 2026-10-30)+ when = f"catalog, {_age(v.get('fetched_at') or v.get('as_of')) or 'date unknown'}" if src == 'snapshot' \+ else f"{src}, {_age(v.get('fetched_at'))}" out.append(f"{name} {v['stock']} ({when.rstrip(', ')})") return ', '.join(out) or 'no stock data' @@ -122,12 +126,16 @@ def render(d): for c in (d.get('candidates_unverified') or d.get('unverified') or [])[:8]: lines.append(f" unverified same-package candidate: {c.get('mpn') if isinstance(c, dict) else c}") cands = d.get('candidates') or []+ if cands and not any(any((v or {}).get('source') == 'live' for v in (c.get('stock') or {}).values()) for c in cands[:10]):+ lines.append(' NOTE: no live stock came back (the distributor services did not answer); every number below is an '+ 'older answer or the JLCPCB catalog, with its age. Confirm on the distributor before ordering.') if cands: lines.append(f" {len(cands)} candidates" + (f", {d.get('rejected_total')} rejected" if d.get('rejected_total') else '') + (f", {d['downgrades_hidden']} downgrades hidden (--allow-downgrade shows them)" if d.get('downgrades_hidden') else '')) for c in cands[:10]: live = c.get('in_stock_live')- flag = 'IN STOCK ' if live else ('in stock?' if c.get('in_stock') else 'out ')+ # 'in stock' only on a live answer; an older or catalog number says so, it is not a promise (2026-10-30)+ flag = 'IN STOCK ' if live else ('stale stock' if c.get('in_stock') else 'out ') dl = c.get('deltas') or {} delta = f" flash {dl['flash_kb']:+g} KB, RAM {dl['ram_kb']:+g} KB" if dl.get('flash_kb') is not None and dl.get('ram_kb') is not None else '' who = f"{c.get('mpn', '')} ({c['manufacturer']})" if c.get('manufacturer') else c.get('mpn', '')
skills/alternate-part/tests/run.py+5−2@@ -34,12 +34,15 @@ def main(): assert t1.splitlines()[0].endswith('tier 1, confidence high'), t1.splitlines()[0] assert 'stock: Mouser' in t1 and '(cached' in t1, 'tier 1: stock with its source' t2 = A.render(load('LM358DR'))- assert 'tier 2, confidence medium-high' in t2.splitlines()[0] and 'snapshot 2026-04-01' in t2, 'tier 2: a JLCPCB number says it is a snapshot'+ assert 'tier 2, confidence medium-high' in t2.splitlines()[0] and '(catalog, ' in t2 and 'snapshot 2026-04-01' not in t2, \+ 'tier 2: a JLCPCB number gives its own listing date, not the snapshot date'+ assert 'NOTE: no live stock came back' in t2 and 'stale stock' in t2 and 'IN STOCK' not in t2, \+ 'no live answer: said up front, and nothing claims IN STOCK on old numbers' t3 = A.render(load('2N7002')) assert 'tier 3' in t3.splitlines()[0] and 'warning:' in t3 and '(JSMSEMI)' in t3, 'tier 3: warnings and the maker shown' t4 = A.render(load('TPS54202DDCR')) assert 'Do the check yourself' in t4 and '1. List same-package candidates' in t4, 'no verified candidate: the method, step by step'- print('ok: 6 tests')+ print('ok: 8 tests') if __name__ == '__main__': main()