"""Import Cantaloupe machine list XLSX into canteen asset tracker DB. Mapping (corrected per user feedback): Asset ID → machine_id (MID — primary search key, e.g. "60006") Device → description (CC reader telemetry ID, secondary search) Location → name (human-readable asset name) Place → room (breakroom name) Type → stored in description alongside Device Address+City+State+ZIP → address Serial Number → serial_number Make → make Model → model Status → all set to "active" (Excel RPC status stored in description only) Customer → customers table (auto-created) """ import sqlite3 import openpyxl import sys from pathlib import Path DB_PATH = str(Path(__file__).parent / "assets.db") XLSX_PATH = "/home/oplabs/.hermes/profiles/kanban/cache/documents/doc_5c60b6e791ea_Machine List(4).xlsx" def connect(): conn = sqlite3.connect(DB_PATH) conn.execute("PRAGMA foreign_keys=ON") conn.row_factory = sqlite3.Row return conn def ensure_makes(conn, make_names): """Add missing makes to lookup table.""" existing = {r["name"] for r in conn.execute("SELECT name FROM makes").fetchall()} for name in make_names: if name and name not in existing: conn.execute("INSERT INTO makes (name) VALUES (?)", (name,)) print(f" Added make: {name}") def ensure_customers(conn, customer_names): """Create customer records, return {name: id} map.""" existing = {r["name"]: r["id"] for r in conn.execute("SELECT id, name FROM customers").fetchall()} for name in customer_names: if name and name not in existing: conn.execute("INSERT INTO customers (name) VALUES (?)", (name,)) existing[name] = conn.execute( "SELECT id FROM customers WHERE name = ?", (name,) ).fetchone()["id"] return existing def import_assets(): conn = connect() # Step 1: Read Excel print("Reading Excel...") wb = openpyxl.load_workbook(XLSX_PATH, read_only=True, data_only=True) ws = wb['Machine List'] # Column index (0-based from header row) COL = { "Device": 0, "Location": 1, "Asset ID": 2, "Place": 3, "Type": 4, "City": 5, "Address": 6, "Make": 39, "Model": 40, "Serial Number": 37, "Status": 56, "Customer": 21, "State": 33, "Postal Code": 34, } rows = [] for i, row in enumerate(ws.iter_rows(min_row=2, values_only=True)): if row[0] is None: continue rows.append(row) print(f"Loaded {len(rows)} rows from Excel") # Step 2: Collect unique makes and customers all_makes = set() all_customers = set() for r in rows: make = str(r[COL["Make"]]).strip() if r[COL["Make"]] else "" if make: all_makes.add(make) cust = str(r[COL["Customer"]]).strip() if r[COL["Customer"]] else "" if cust: all_customers.add(cust) print(f"\nUnique makes: {len(all_makes)} — {sorted(all_makes)}") print(f"Unique customers: {len(all_customers)}") # Step 3: Seed lookup tables ensure_makes(conn, all_makes) customer_map = ensure_customers(conn, all_customers) conn.commit() # Step 4: Insert assets inserted = 0 skipped = 0 errors = 0 for r in rows: device = str(r[COL["Device"]]).strip() if r[COL["Device"]] else "" location = str(r[COL["Location"]]).strip() if r[COL["Location"]] else "" place = str(r[COL["Place"]]).strip() if r[COL["Place"]] else "" machine_type = str(r[COL["Type"]]).strip() if r[COL["Type"]] else "" address = str(r[COL["Address"]]).strip() if r[COL["Address"]] else "" city = str(r[COL["City"]]).strip() if r[COL["City"]] else "" state = str(r[COL["State"]]).strip() if r[COL["State"]] else "" zip_code = str(r[COL["Postal Code"]]).strip() if r[COL["Postal Code"]] else "" serial = str(r[COL["Serial Number"]]).strip() if r[COL["Serial Number"]] else "" make = str(r[COL["Make"]]).strip() if r[COL["Make"]] else "" model = str(r[COL["Model"]]).strip() if r[COL["Model"]] else "" raw_status = str(r[COL["Status"]]).strip() if r[COL["Status"]] else "" customer_name = str(r[COL["Customer"]]).strip() if r[COL["Customer"]] else "" asset_id_str = str(r[COL["Asset ID"]]).strip() if r[COL["Asset ID"]] else "" if not asset_id_str or not location: errors += 1 continue machine_id = asset_id_str name = location # Build full address parts = [p for p in [address, city, state, zip_code] if p] full_address = ", ".join(parts) # Status — all set to active (RPC "On/Action Required/Incompatible" is # pricing telemetry status, not machine condition. Stored in description.) status = "active" # Build description — Device (telemetry ID) + RPC status + Type desc_parts = [] if device: desc_parts.append(f"Device: {device}") if raw_status: desc_parts.append(f"RPC: {raw_status}") if machine_type: desc_parts.append(machine_type) description = " — ".join(desc_parts) # Customer lookup customer_id = customer_map.get(customer_name) is_disney = 1 if customer_name.startswith('D-') else 0 try: cursor = conn.execute(""" INSERT INTO assets (machine_id, name, description, category, status, make, model, serial_number, address, room, customer_id, is_disney) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) """, ( machine_id, name, description, "Equipment", status, make, model, serial, full_address, place, customer_id, is_disney, )) inserted += 1 except sqlite3.IntegrityError as e: errors += 1 if errors <= 3: print(f" SKIP (dupe?): {machine_id} — {e}") conn.commit() conn.close() wb.close() print(f"\n=== Import Summary ===") print(f" Inserted: {inserted}") print(f" Skipped: {skipped}") print(f" Errors: {errors}") return inserted if __name__ == "__main__": count = import_assets() print(f"\nDone. {count} assets imported.")