Easy-to-manage touch screens
무료 오픈소스 터치스크린으로 USB 설정 가이드, 드래그‑드롭 편집기, 로컬 제어를 제공하며 Home Assistant용으로 제작되었습니다
아직 감지된 업데이트가 없습니다. GitHub 릴리스가 나오면 여기에 보입니다.
개인 계정의 공개 저장소 소유자는 GitHub로 로그인해 운영자를 확인하고 소개를 직접 관리할 수 있습니다. 관리자 승인은 필요하지 않습니다.
로그인한 뒤 확인 파일을 만들어 이 저장소의 기본 브랜치에 30분 안에 커밋해 주세요. 다른 브랜치나 포크의 파일은 인정하지 않습니다. 파일을 올린 뒤 아래에서 확인하면 관리자 승인 없이 관리할 수 있습니다. 자격을 유지하려면 파일을 남겨 두세요.
로그인이 필요하면 로그인 화면으로 이동합니다. 로그인 후 이곳으로 돌아와 원하는 확인 버튼을 다시 눌러 주세요.
프로젝트 폴더에서 /nomorevibe verify를 실행하면 소유권을 확인하고 소개를 직접 관리할 수 있습니다.
1분 만에 확인하기Live national junkyard inventory PWA — part-value intelligence for Pick-n-Pull + Utah yards
저장소 README에서 가져옴
README 전체를 불러오는 중입니다. 아래는 저장된 발췌입니다.
Spot the valuable "unobtanium" parts that common junkyard cars originally came with, and see typical resale ranges before you drive to the yard. This is an information tool — estimates, not income promises: parts may already be pulled, and condition decides everything. Scrapes live inventory from ~150 self-service yards across four national/regional chains:
It cross-references every car against a curated database of rare/valuable parts and shows you exactly what to pull, what it costs (when the yard publishes a real price list), what it sells for, and where to sell it.README 전문 보기 ↗Live app: https://yardscout.io/
Find the donor car before you drive. YardScout shows what every car in a self-service yard came from the factory with, what the yard charges to pull a part, and what the same used part goes for online — so you know whether the trip is worth it before you leave the house. An information tool for enthusiasts and DIY wrenchers: estimates, never promises — parts may already be pulled, and condition decides everything. Scrapes live inventory from ~150 self-service yards across four national/regional chains:
| Chain | Yards | Coverage |
|---|---|---|
| Pick-n-Pull | ~48 | West Coast heavy (CA/WA/OR/NV), plus TX, MO, IL, IN, OH, VA, RI, Canada |
| LKQ Pick Your Part (pyp.com) | ~60 | CA, TX, FL, plus MD, NC, SC, GA, TN, OH, IN, IL, WI, MI, KS, OK, CO, AL |
| Pull-A-Part (incl. former U-Pull-&-Pay yards) | ~36 | Southeast/Gulf (GA/AL/TN/LA/MS/NC/SC/KY/FL), OH, IN, PA, TX, AZ, NM, CO |
| Tear-A-Part + Utah Pic-A-Part | 4 | Utah (SLC, Ogden, Orem) |
It cross-references every car against a curated database of hard-to-find factory parts and shows you what to look for in the row, what the yard charges (when it publishes a real price list), and what you'd save pulling it yourself. The Parts tab covers engines, transmissions, and body panels too, with honest warnings about what a pull really takes. Every part that leaves beat the shredder.
Inventory auto-refreshes every 4 hours via GitHub Actions (see .github/workflows/scan.yml).
yardscout/
├── docs/ # Web app — served by GitHub Pages
│ ├── index.html # App shell (markup only; no build step)
│ ├── styles.css # All styling
│ ├── data.js # Static datasets: parts database, yard directory
│ ├── app.js # App logic: loading, filtering, rendering, saved list, alerts
│ ├── sw.js # Service worker (offline + instant launch)
│ ├── manifest.webmanifest # PWA manifest + icons (icon-*.png, apple-touch-icon.png)
│ └── data/ # Generated JSONs the app fetches
│ ├── inventory_live.json # Latest scrape (schema v2: compact rows + lookup tables)
│ ├── picknpull_pricing.json # Real Pick-n-Pull part prices (472 parts)
│ ├── pyp_pricing.json # Real LKQ Pick Your Part prices, per yard (--refresh-chain-pricing)
│ ├── pap_pricing.json # Real Pull-A-Part prices, per yard (--refresh-chain-pricing)
│ ├── tearapart_pricing.json # Real Tear-A-Part part prices (503 parts)
│ └── utpap_pricing.json # Utah Pic-A-Part list prices (--refresh-utpap-pricing)
├── scraper/
│ ├── junkyard_scraper.py # Python scraper + unobtanium parts database
│ ├── db.py # SQLite history tracking (inventory_history.db, gitignored)
│ └── watchlist.json # Your alert rules (export from UI or hand-edit; commit for CI)
└── .github/workflows/scan.yml # Auto-scan every 4 hours, commits fresh inventory
Copy the entire yardscout/ folder to your personal machine. All you need is:
index.htmljunkyard_scraper.pypicknpull_pricing.jsontearapart_pricing.jsonutpap_pricing.json (optional; run python scraper/junkyard_scraper.py --refresh-utpap-pricing to update from Parts Pricelist)The JSON inventory files are regenerated every time you run the scraper.
Requires Python 3.10+ (tested on 3.13).
cd yardscout
# Create a virtual environment
python3 -m venv .venv
source .venv/bin/activate # macOS/Linux
# .venv\Scripts\activate # Windows
# Install dependencies
pip install requests beautifulsoup4 rich
# Scan the SLC-radius yards, output to terminal (rich table); add --national for all chains
python scraper/junkyard_scraper.py
# Scan and save JSON for the web app (include ALL vehicles)
python scraper/junkyard_scraper.py --json --all > inventory_live.json
# Watch mode — rescan every 6 hours
python scraper/junkyard_scraper.py --watch --save --all
# Just list all tracked unobtanium parts
python scraper/junkyard_scraper.py --list-parts
# Filter by make
python scraper/junkyard_scraper.py --list-parts --make toyota
# Optional: NHTSA VPIC VIN decode for trim-aware matching (slower; one HTTP per unique VIN)
python scraper/junkyard_scraper.py --json --all --decode-vins > inventory_live.json
# Same via env: JUNKYARD_DECODE_VINS=1
# Optional: VPIC_WORKERS=8 (default) parallel NHTSA calls; VPIC_DELAY_SEC=0 (default) or add delay if needed
# Decode only the highest carry-profit rows (after matching), not every VIN in the fleet:
# python scraper/junkyard_scraper.py --save --all --decode-vins-profit-top 10
# NATIONAL scan — every supported yard: Pick-n-Pull US+Canada, LKQ Pick Your Part
# (~60 yards), and Pull-A-Part (~36 yards). Utah chains are always included.
python scraper/junkyard_scraper.py --save --all --national --decode-vins-profit-top 10
# Refresh per-yard price lists for Pick Your Part + Pull-A-Part (writes
# pyp_pricing.json / pap_pricing.json; the 6-hour CI scan does this automatically)
python scraper/junkyard_scraper.py --refresh-chain-pricing
yardscout-bv-8k2j, and subscribe to it in the app.NTFY_TOPIC=yardscout-bv-8k2j in the environment
(locally or as a GitHub Actions secret NTFY_TOPIC).Watchlist hits then push straight to your phone. Up to 12 hits are sent as individual notifications; more than that collapses into one summary. Email alerts (if SMTP is configured) arrive as a single digest per run.
Watchlist entries in watchlist.json accept an optional "states" list to
scope alerts on national scans, e.g. {"make": "Toyota", "model": "4Runner", "states": ["UT", "NV"]} — omit it to get hits from every yard nationwide.
VPIC uses the public decodevinvaluesextended API (no key). When the API returns a single trim (or ≤2 comma-separated trims), that text is merged into trim matching for parts that list a trim requirement; each vehicle row then includes vpicDecodeWell: true in JSON and a “VIN trim OK” badge in the UI. Some manufacturers return a long comma-separated list of every trim for the model; those are treated as ambiguous (vpicDecodeWell: false) and matching falls back to the yard’s year/make/model only (so we do not false-positive trim-specific parts).
The app loads inventory_live.json via fetch(), so you need a local HTTP server (browsers block fetch from file:// URLs).
# Must run from the yardscout folder (where index.html lives), or you get 404
cd yardscout
python3 -m http.server 8765
# Open in your browser
open http://localhost:8765/index.html
That's it. No npm, no webpack, no build step.
Add vehicles to your watchlist in the Alerts tab of the web app, then click "Export for Scraper" to download a watchlist.json file. Place it in the yardscout/ folder next to the scraper.
When you run the scraper (especially in --watch mode), it reads watchlist.json and sends macOS desktop notifications when a watched vehicle appears.
Email (phone via Gmail app): set these environment variables before running the scraper. Uses Gmail SMTP; no extra Python packages.
export SMTP_USER="[email protected]"
export SMTP_PASS="xxxx xxxx xxxx xxxx" # Gmail App Password, not your login password
export ALERT_EMAIL="[email protected]" # where to receive alerts (can match SMTP_USER)
python scraper/junkyard_scraper.py --save --all
Optional: SMTP_HOST (default smtp.gmail.com), SMTP_PORT (default 587).
# Watch mode with alerts — checks every 6 hours, notifies on new finds
python scraper/junkyard_scraper.py --watch --save --all
You can also hand-edit watchlist.json:
[
{"make": "Toyota", "model": "Tacoma", "yrMin": null, "yrMax": null, "matchOnly": true},
{"make": "Toyota", "model": "4Runner", "yrMin": 1996, "yrMax": 2002, "matchOnly": true},
{"make": "Jeep", "model": "Wrangler", "yrMin": null, "yrMax": null, "matchOnly": false}
]
Use this when you want the scraper to run without your laptop being on, and get emails on your phone (Gmail app).
You do not need to be logged into GitHub or Gmail on the machine where you develop. Push from any machine that has your personal GitHub auth, then add secrets in the browser.
Create a repo on your personal GitHub account (private is fine) and push this folder as the repo root (so junkyard_scraper.py is at the top level).
Gmail App Password (on your phone or any browser, logged into your Google account):
Repository secrets (GitHub → your repo → Settings → Secrets and variables → Actions → New repository secret):
SMTP_USER — your Gmail addressSMTP_PASS — the App Password (spaces optional)ALERT_EMAIL — where to send alerts (usually the same Gmail; you get push notifications from the Gmail app)Commit watchlist.json to the repo (export from the Alerts tab or create by hand). Without it, the workflow still updates inventory but sends no watchlist emails.
The workflow .github/workflows/scan.yml runs every 6 hours and on manual dispatch. It runs python scraper/junkyard_scraper.py --save --all, commits inventory_live.json (and watch_alerted.json when present) if anything changed, and sends email when new watchlist matches appear. Alert dedupe in Actions is stored in watch_alerted.json at the repo root so you do not get repeat emails for the same vehicle.
Manual run: GitHub → Actions → YardScout Scan → Run workflow.
index.html and inventory_live.json are in the repo; Pages serves the site at https://YOUR_USERNAME.github.io/REPO_NAME/..../index.html).| Tab | What it shows |
|---|---|
| Live | Every vehicle across all scanned yards, demand-first cards with resale ranges. Filter by zip + radius, GPS, make, yard. |
| Parts | The full unobtanium database — every tracked vehicle and its valuable parts. |
| Alerts | Vehicle watchlist; browser notifications; phone push via ntfy; optional email (Gmail) + GitHub Actions every 6 hours. |
Every part includes:
Pull costs are shown only when they come from that yard's own published price list, and each chain's list is applied only to that chain's yards:
If no real price list covers a part, the app shows "check yard price list" and the value estimate becomes a resale-only range — it never invents a pull cost.
Newer arrivals are more likely to still have valuable parts. The app applies a time-decay multiplier:
| Days since added | Freshness |
|---|---|
| 0–3 days | 100% |
| 4–7 days | 90% |
| 8–14 days | 75% |
| 15–30 days | 55% |
| 30+ days | 35% |
Parts are matched to the actual vehicle, not just the model:
usage: junkyard_scraper.py [-h] [--json] [--all] [--watch] [--interval N]
[--list-parts] [--make MAKE] [--save]
options:
--json Output JSON to stdout (pipe to file)
--all Include vehicles with no unobtanium match
--watch Continuous mode — rescan on interval
--interval N Seconds between scans in watch mode (default: 21600 = 6hr)
--list-parts Print the full unobtanium parts database
--make MAKE Filter --list-parts by make (e.g., toyota)
--save Write JSON directly to inventory_live.json
Option A — zip and transfer:
# From this machine
cd /path/to/admin
zip -r yardscout.zip yardscout/ \
-x "yardscout/.venv/*" \
-x "yardscout/.cache/*" \
-x "yardscout/scraper_err.log"
# Transfer the zip however you like (AirDrop, USB, email, etc.)
# On the new machine, unzip and follow Quick Start above
Option B — git (recommended if you want to keep it updated):
# Initialize a standalone repo
cd yardscout
git init
# .gitignore is already in this project; or: echo -e ".venv/\n.cache/\n*.log\n..." > .gitignore
git add -A
git commit -m "Initial commit — YardScout"
# Push to your personal GitHub
gh repo create yardscout --private --source=. --push
# OR
git remote add origin [email protected]:YOUR_USERNAME/yardscout.git
git push -u origin main
# On the new machine
git clone [email protected]:YOUR_USERNAME/yardscout.git
cd yardscout
python3 -m venv .venv && source .venv/bin/activate
pip install requests beautifulsoup4 rich
python scraper/junkyard_scraper.py --json --all > inventory_live.json
python3 -m http.server 8765
Option C — just copy 4 files:
If you're in a hurry, the absolute minimum you need is:
index.htmljunkyard_scraper.pypicknpull_pricing.jsontearapart_pricing.jsonEverything else is generated by the scraper. AirDrop those 4 files, run the scraper, start the server.
Resale ranges are never edited automatically. The loop that keeps them honest:
scraper/validate_prices.py ranks the top ~50 parts by live-inventory
exposure (frequency × displayed high) and classifies each displayed range
ACCURATE / INFLATED / UNDERSTATED / UNVERIFIABLE against market evidence.
Evidence comes from scraper/price_baseline.json — the machine-readable
ranges from the most recent manual audit (price_validation_report.md)
— plus a polite fresh eBay-sold attempt each run (eBay bot-blocks CI IPs, so
the source backs off after two failures instead of crashing; that's expected)..github/workflows/price-review.yml runs it on the 1st of Jan/Apr/Jul/Oct
(and on dispatch). If anything classifies INFLATED, or the report changed
materially (DB drift vs. reviewed evidence, verdict changes, or new
high-exposure parts with no reviewed evidence), it opens a
chore/price-review-<date> PR with the regenerated report — never merging
and never touching prices. Quiet quarters produce no PR.PRICES_LAST_REVIEWED in scraper/junkyard_scraper.py is exported into the
v2 JSON and shown in the app footer ("Value ranges last reviewed …"). Bump it
(and refresh price_baseline.json) whenever corrections from a review PR are
accepted.The history DB records when each vehicle appears and departs. From departed vehicles the scraper computes an average lot lifespan per yard (lifespan = departure date minus the yard-reported arrival date, so it's meaningful even with a short scan history). Guards:
avgLifespanDays / lifespanBasis in the v2 yards table)
and the UI says which one it's quoting.Every car has a deep link (#car=<vin-or-id>). The share button on each card
uses navigator.share on mobile (clipboard fallback elsewhere) and shares
"2010 Mercury Mountaineer — Row 30 at Pick-n-Pull - Salt Lake City, arrived
Jun 4" plus the link. Opening a shared link shows a focused single-car view
(ignoring the viewer's filters and zip state, with a "See all cars" action);
dead links get an honest "no longer in the current inventory" message.
Every scheduled scan runs --decode-vins-incremental:
vin_decodes table of
scraper/inventory_history.db (persisted between CI runs by the Actions
cache). VINs are immutable, so entries never expire — a previously-seen VIN
costs zero API calls forever.VPIC_MAX_PER_RUN (CI uses 10000 — 200 batch POSTs of 50 VINs at 0.3s
spacing, well inside vPIC's stated 1000–2000 tx/min comfort zone; their
FAQ sets no daily limit). Backlogs drain across successive runs;
steady-state runs only decode the day's new arrivals.docs/data/vin_decodes.json.gz and committed, but only after ~20k new
decodes accumulate — frequent multi-MB commits would bloat git history. If
the Actions cache is ever evicted, the scraper re-imports the gz on start
and at most re-decodes the recent delta.The web app gates value data (resale ranges, pull costs, demand badges), saves beyond 5 cars, and instant push alerts behind a Pro upgrade sheet. There is no real billing — the sheet is a waitlist email capture to measure demand.
jh-pro-waitlist-7g4kx2m
in the ntfy app (or curl -s "https://ntfy.sh/jh-pro-waitlist-7g4kx2m/json?poll=1"
for anything still in ntfy's ~12-hour cache). Each message contains the email,
which locked feature triggered the sheet, and a timestamp. Keep the phone
subscribed so nothing is lost; swap in a Formspree endpoint
(WAITLIST_NTFY_TOPIC in docs/app.js) for durable storage later.localStorage.jh_waitlist_log.?pro=1 (or tap the
footer version string 7 times). ?pro=0 re-locks. Client-side gating is
bypassable by design in this phase.| Source | Method | Notes |
|---|---|---|
| Pick-n-Pull | JSON API (picknpull.com/api/vehicle/search) | Public, no auth needed; distance=0 = national |
| LKQ Pick Your Part | Server-rendered inventory pages (pyp.com) | 20 rows/page per store; store list + coords embedded on /inventory/ |
| Pull-A-Part | JSON microservices (inventoryservice.pullapart.com) | Anonymous website token; one search per make covers all yards |
| U-Pull-&-Pay | — merged into Pull-A-Part | Former UPAP yards (Albuquerque, Denver, Aurora, Colorado Springs, Cincinnati, ...) are in the Pull-A-Part feed |
| Tear-A-Part SLC + Ogden | WordPress AJAX (tearapart.com/wp-admin/admin-ajax.php) | Requires a nonce token (auto-fetched) |
| PnP part pricing | JSON API (picknpull.com/api/parts/list/{storeId}) | |
| PYP part pricing | JSON API (pyp.com/DesktopModules/pyp_api/api/PriceList/) | Per-location price lists |
| PAP part pricing | JSON API (enterpriseservice.pullapart.com/partprice/...) | Per-location price lists |
| TAP part pricing | Static price list (bundled in tearapart_pricing.json) | Manually sourced |
| Resale estimates | eBay sold listings research + community pricing | Validated March 2026 |
| Sell channels | eBay, FB Marketplace, T4R.org, IH8MUD, etc. | eBay national; FB Marketplace varies by local market |
requests — HTTP clientbeautifulsoup4 — HTML parsing (used for nonce extraction)rich — Pretty terminal output (optional, falls back to plain text)