From 06fb4a243e4d2980d50a32a110178aa3f9f221b2 Mon Sep 17 00:00:00 2001 From: olsch01 Date: Thu, 9 Apr 2026 17:45:36 -0400 Subject: [PATCH] feat: Cast Iron Scout agent prototype MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Created autonomous cast iron deal scanner - Scans eBay RSS feeds hourly for cast iron cookware - Calculates FMV based on brand, type, size - Sends Telegram alerts for deals ≥50% below FMV - Identifies Griswold, Wagner, Wapak, Birmingham, Lodge, Victor - Tracks seen items to avoid duplicate alerts - Valuation engine with size multipliers - Configurable preferences in config.json Known issue: eBay RSS unreliable - next iteration will use proper scraping --- agents/cast-iron-scout/README.md | 101 +++++++++++++++ .../__pycache__/valuation.cpython-314.pyc | Bin 0 -> 4027 bytes agents/cast-iron-scout/config.json | 44 +++++++ .../cast-iron-scout/logs/scanner-20260409.log | 3 + agents/cast-iron-scout/requirements.txt | 6 + agents/cast-iron-scout/scanner.py | 118 ++++++++++++++++++ .../__pycache__/ebay_scanner.cpython-314.pyc | Bin 0 -> 2909 bytes .../cast-iron-scout/sources/ebay_scanner.py | 71 +++++++++++ agents/cast-iron-scout/state/seen_items.json | 4 + agents/cast-iron-scout/valuation.py | 115 +++++++++++++++++ 10 files changed, 462 insertions(+) create mode 100644 agents/cast-iron-scout/README.md create mode 100644 agents/cast-iron-scout/__pycache__/valuation.cpython-314.pyc create mode 100644 agents/cast-iron-scout/config.json create mode 100644 agents/cast-iron-scout/logs/scanner-20260409.log create mode 100644 agents/cast-iron-scout/requirements.txt create mode 100644 agents/cast-iron-scout/scanner.py create mode 100644 agents/cast-iron-scout/sources/__pycache__/ebay_scanner.cpython-314.pyc create mode 100644 agents/cast-iron-scout/sources/ebay_scanner.py create mode 100644 agents/cast-iron-scout/state/seen_items.json create mode 100644 agents/cast-iron-scout/valuation.py diff --git a/agents/cast-iron-scout/README.md b/agents/cast-iron-scout/README.md new file mode 100644 index 0000000..777d965 --- /dev/null +++ b/agents/cast-iron-scout/README.md @@ -0,0 +1,101 @@ +# Cast Iron Scout 🔥🍳 + +Autonomous agent that scans for undervalued cast iron cookware deals and alerts you when pieces are priced ≥50% below fair market value. + +## Status: 🚧 Prototype + +**Built:** April 9, 2026 +**Current Sources:** eBay (RSS feeds) +**Next:** Facebook Marketplace, Craigslist integration + +## What It Does + +1. **Scans** marketplaces every hour for cast iron cookware +2. **Identifies** brands (Griswold, Wagner, Wapak, Birmingham, Lodge, etc.) +3. **Calculates** fair market value based on brand, type, and size +4. **Alerts** you via Telegram when deals ≥50% below FMV are found +5. **Tracks** seen items to avoid duplicate alerts + +## Example Alert + +``` +🔥 CAST IRON DEAL ALERT! + +Item: Griswold #8 Skillet - Slant Logo +Price: $45.00 +FMV: $180.00 +Discount: 75% below FMV! 💰 + +Source: eBay +Found: 2026-04-09T17:44:23 + +🔗 [link to item] + +Action: Buy now / Bid / Ignore +``` + +## Architecture + +``` +cast-iron-scout/ +├── scanner.py # Main scanning engine +├── valuation.py # FMV calculator +├── config.json # Your preferences +├── state/ # Track seen items +├── logs/ # Scan logs +└── sources/ + ├── ebay_scanner.py # eBay scanner (RSS) + ├── facebook.py # [TODO] + └── craigslist.py # [TODO] +``` + +## Configuration + +Edit `config.json` to customize: +- **min_discount_percent**: Minimum discount to alert (default: 50%) +- **max_distance_miles**: Max distance for local pickup (default: 500) +- **preferred_brands**: Brands to prioritize +- **item_types**: Types of items to scan for +- **telegram_target**: Where to send alerts + +## Current Features + +✅ Basic eBay RSS scanning +✅ Brand identification (Griswold, Wagner, Wapak, etc.) +✅ FMV calculation engine +✅ Telegram alerts +✅ Duplicate prevention +✅ Hourly scanning + +## Known Issues / TODO + +- [ ] eBay RSS feed unreliable - need to implement proper scraping +- [ ] Add Facebook Marketplace scanner +- [ ] Add Craigslist scanner +- [ ] Image recognition for logos/marks +- [ ] Price history tracking +- [ ] Condition assessment from photos +- [ ] Mobile app integration? + +## Running Manually + +```bash +cd /Users/claw/.openclaw/workspace/agents/cast-iron-scout +python3 scanner.py +``` + +## Logs + +Check `logs/scanner-YYYYMMDD.log` for scan history. + +## Next Steps + +1. Fix eBay scanner (use proper HTML scraping vs RSS) +2. Add Facebook Marketplace (requires Selenium/Playwright) +3. Add image recognition for logos +4. Build sold items database for better FMV + +--- + +**Maintained by:** Forge (Chris's SaaS Operations Bot) +**Version:** 0.1.0 (Prototype) diff --git a/agents/cast-iron-scout/__pycache__/valuation.cpython-314.pyc b/agents/cast-iron-scout/__pycache__/valuation.cpython-314.pyc new file mode 100644 index 0000000000000000000000000000000000000000..8cb7d434ae700fe4f32a731109c60616d3e8bfcd GIT binary patch literal 4027 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 literal 0 HcmV?d00001 diff --git a/agents/cast-iron-scout/config.json b/agents/cast-iron-scout/config.json new file mode 100644 index 0000000..fe1445d --- /dev/null +++ b/agents/cast-iron-scout/config.json @@ -0,0 +1,44 @@ +{ + "scan_interval_minutes": 30, + "min_discount_percent": 50, + "max_distance_miles": 500, + "preferred_brands": [ + "griswold", + "wagner", + "wapak", + "birmingham", + "lodge", + "victor", + "hollowware" + ], + "item_types": [ + "skillet", + "griddle", + "dutch oven", + "pot", + "pan", + "grill pan", + "waffle iron", + "mold" + ], + "condition_keywords": [ + "rust", + "restoration", + "as-is", + "vintage", + "antique", + "estate", + "garage sale", + "dirty", + "needs work" + ], + "exclude_keywords": [ + "new", + "reproduction", + "replica", + "modern", + "calphalon", + "le creuset" + ], + "telegram_target": "telegram:8269921691" +} diff --git a/agents/cast-iron-scout/logs/scanner-20260409.log b/agents/cast-iron-scout/logs/scanner-20260409.log new file mode 100644 index 0000000..486c87a --- /dev/null +++ b/agents/cast-iron-scout/logs/scanner-20260409.log @@ -0,0 +1,3 @@ +[2026-04-09 17:44:23] 🔍 Starting cast iron scan... +[2026-04-09 17:44:28] Found 0 items on eBay +[2026-04-09 17:44:28] Scan complete. Deals found: 0, Total items processed: 0 diff --git a/agents/cast-iron-scout/requirements.txt b/agents/cast-iron-scout/requirements.txt new file mode 100644 index 0000000..6d71ee5 --- /dev/null +++ b/agents/cast-iron-scout/requirements.txt @@ -0,0 +1,6 @@ +requests +beautifulsoup4 +lxml +pillow +python-dateutil +feedparser diff --git a/agents/cast-iron-scout/scanner.py b/agents/cast-iron-scout/scanner.py new file mode 100644 index 0000000..734fe3a --- /dev/null +++ b/agents/cast-iron-scout/scanner.py @@ -0,0 +1,118 @@ +#!/usr/bin/env python3 +""" +Cast Iron Scout - Main Scanner Engine +Continuously scans for cast iron deals and alerts when good deals found +""" +import json +import subprocess +import sys +from datetime import datetime +from pathlib import Path +from sources.ebay_scanner import search_ebay_cast_iron +from valuation import is_good_deal, calculate_fmv + +SCRIPT_DIR = Path(__file__).parent +STATE_FILE = SCRIPT_DIR / "state" / "seen_items.json" +CONFIG_FILE = SCRIPT_DIR / "config.json" +LOG_FILE = SCRIPT_DIR / "logs" / f"scanner-{datetime.now().strftime('%Y%m%d')}.log" + +def load_config(): + """Load configuration""" + if CONFIG_FILE.exists(): + return json.loads(CONFIG_FILE.read_text()) + return {} + +def load_state(): + """Load previously seen items to avoid duplicates""" + if STATE_FILE.exists(): + return json.loads(STATE_FILE.read_text()) + return {"seen_links": [], "last_scan": None} + +def save_state(state): + """Save state to file""" + state['last_scan'] = datetime.now().isoformat() + STATE_FILE.write_text(json.dumps(state, indent=2)) + +def log(message): + """Log message""" + ts = datetime.now().strftime('%Y-%m-%d %H:%M:%S') + log_line = f"[{ts}] {message}" + print(log_line) + with open(LOG_FILE, 'a') as f: + f.write(log_line + '\n') + +def send_telegram_alert(item, fmv, discount): + """Send Telegram alert for a good deal""" + config = load_config() + target = config.get('telegram_target', 'telegram:8269921691') + + message = f"""🔥 *CAST IRON DEAL ALERT!* + +*Item:* {item['title']} +*Price:* ${item['price']:.2f} +*FMV:* ${fmv:.2f} +*Discount:* {discount:.0f}% below FMV! 💰 + +*Source:* {item['source']} +*Found:* {item['found_at']} + +🔗 {item['link']} + +_Action: Buy now / Bid / Ignore_""" + + try: + subprocess.run([ + 'openclaw', 'message', 'send', + '--channel', 'telegram', + '--target', target, + '--message', message + ], capture_output=True, timeout=30) + log(f"✅ Alert sent for: {item['title'][:50]}") + except Exception as e: + log(f"❌ Failed to send alert: {e}") + +def scan_all_sources(): + """Scan all sources for cast iron items""" + log("🔍 Starting cast iron scan...") + + # Load config and state + config = load_config() + state = load_state() + seen_links = set(state.get('seen_links', [])) + + # Scan eBay + items = search_ebay_cast_iron() + log(f"Found {len(items)} items on eBay") + + deals_found = 0 + min_discount = config.get('min_discount_percent', 50) + + for item in items: + # Skip if already seen + if item['link'] in seen_links: + continue + + # Check if it's a good deal + is_deal, discount, fmv = is_good_deal(item['price'], item['title'], min_discount) + + if is_deal: + log(f"🎯 DEAL FOUND: {item['title'][:50]} - ${item['price']} ({discount:.0f}% off)") + send_telegram_alert(item, fmv, discount) + deals_found += 1 + + # Mark as seen + seen_links.add(item['link']) + + # Keep only last 1000 seen items to prevent state file from growing forever + if len(seen_links) > 1000: + seen_links = set(list(seen_links)[-1000:]) + + state['seen_links'] = list(seen_links) + save_state(state) + + log(f"Scan complete. Deals found: {deals_found}, Total items processed: {len(items)}") + return deals_found + +if __name__ == "__main__": + deals = scan_all_sources() + sys.exit(0) diff --git a/agents/cast-iron-scout/sources/__pycache__/ebay_scanner.cpython-314.pyc b/agents/cast-iron-scout/sources/__pycache__/ebay_scanner.cpython-314.pyc new file mode 100644 index 0000000000000000000000000000000000000000..888a374e1ce3cf19ab61d68bcdbe1071af047a75 GIT binary patch literal 2909 zcmaJ@U2NOd6}}WHiIOP))vn{en67O3FP75SPCUn6GXE`sJy&BKtyrWGv_#v4C6Xnn zDG@TjPlI0yc-@9N4};yIK-6x-{J6Je1CsSohTK`<+AYBPvNtCVhGE5kolA+9)21WD zd(J)goOACzKOZ$%_i;Zj0{)Y%{j;3Mkx6MgxXP@?n0bvgeZIML~&Oa8mVkyc&t@- z+}nlX9$SY&9DRYbXJFysJm*>~CP3=YTo56{XW4q=KF)V~m19oNz?UC1YF?IIpsL>I z`w;S=vv9+J_4e21u6o5O;VwHZ=mvXTQ)Ko=LXc%q^J1#A*5Znyu?egoVTFXEtY-yH zWRs$h(!GoQgh?j_Lo}qc2)utv6f%ail1bfAGHRlF6?_?lNQ9{47>pYzgt+Fn2ow}5pWrOc63N$b9%E1^api#3OTm@Iit>q|K(LY1 z^F}>f9aqma#MgoBXz5$mzNa7{}NsP)&E{0utX z$+l0}|Ce9)+J7cD>AfKeT4L>09V{7QTK96IkmB1t~1E%LP9Vm z&j%N-@k^4C5@$p`0aISn^~pB@A>0@#l#G$1Q+Lv-A$Uy+Z$7^GPuy#ZrqjS?CY6%p zb6x%gl#bS0J?=r@UTCNiC>CM87Er7IZ?D1{Nrq^)c4f??8N z&SQ95%(cvF8X%dD0Zx)vtC8u=k-g(=zbjX_s!4iQNhMi*T}q`yBUhIdRsjvG@Rw`( zkt7?!s#q1BC^uC>QWJGU(F8+MQKSrD6XyA#Kb0Uo+jBsHOzC0IiAd zWWZWC$yL!XeYznS8J$lkNl^pXM5a5T$c8Ax)m9`q$qOk6ALdNT5bqeKL&L$aa&J0U zQVPVtxvBvan=V0BMLDTq=rT!J$(kNXR{-48pyQdnlMq#WUMr%q(O+XSWjDMbYH8hc zS_Oi4(b8~SUFS1e3LjTj6`RZi7`~~CnjTK1glssZz_~3ht7z-G3SkOE24n-);j=V^KTAV;0!k`et^z%b zOP04XEove4uGv(%iY3QJJT}t8u(Je@G{r;XDO zXa2nK$A!Z8mkQBa#hF-PI$nt1D>VFg(|ts>c_uzDzQBeydQY;mx}E0Av?Cuj%_g|6Z{YomFpMH3=7@Xhs{XKH)L+9@LVery^@Y3&zVsQL0IJF;~ zdN}-v0~8L}g@UlW?R*J0 zb#{Py)K0tSrjDc*B`4S~<|vjOVy%J((L_Zw#CBVC+|QV9 zo=++Xp4ae~Qo|ppN~ZCLi@L= min_discount: + return True, discount_percent, fmv + + return False, discount_percent, fmv + +if __name__ == "__main__": + # Test the valuation engine + test_items = [ + "Griswold #8 Skillet - Rusty", + "Wagner Sidney O -AI - Vintage Cast Iron", + "Wapak Funny Face Skillet #10", + "Birmingham Stove & Range Co. #12 Skillet" + ] + + print("Valuation Engine Test:\n") + for title in test_items: + fmv = calculate_fmv(title) + print(f"{title[:50]}") + print(f" → FMV: ${fmv}\n")