Created
August 11, 2026 08:27
-
-
Save mckabue/e8c05a13bdadfdf654bb279fb9b37d55 to your computer and use it in GitHub Desktop.
Profile & verify a used laptop's real specs (RAM, SSD, CPU/threads, battery, TPM)
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #!/usr/bin/env python3 | |
| # -*- coding: utf-8 -*- | |
| """ | |
| hardware_profile.py — Verify a (second-hand) laptop's real hardware. | |
| Cross-platform: Windows (PowerShell/WMIC), Linux and macOS. | |
| Zero dependencies (stdlib only). | |
| Why: used laptops are often sold as "16GB / 256GB SSD / i7" but actually | |
| arrive with 8GB, an HDD, or a different CPU. This script builds a detailed | |
| profile (CPU + cores/threads, RAM modules/type/speed, disk type & health, | |
| GPU, battery health, TPM / Windows 11 readiness) and can cross-check it | |
| against the specs you were promised. | |
| Usage: | |
| python3 hardware_profile.py # print profile | |
| python3 hardware_profile.py --json # also write hardware_profile.json | |
| python3 hardware_profile.py \ | |
| --expect-ram 16 --expect-ssd 256 \ | |
| --expect-cpu "4600U" --expect-threads 12 # PASS/FAIL against the ad | |
| python3 hardware_profile.py --expect-all 16,256,"4600U",12 | |
| Exit code: 0 = ok, 2 = a --expect check failed, 3 = something could not be read. | |
| """ | |
| import argparse | |
| import json | |
| import os | |
| import platform | |
| import re | |
| import subprocess | |
| import sys | |
| from datetime import datetime | |
| # --------------------------------------------------------------------------- # | |
| # Helpers | |
| # --------------------------------------------------------------------------- # | |
| def run(cmd, timeout=40, binary=False): | |
| """Run a shell command, return stdout (or b'' when binary=True).""" | |
| try: | |
| p = subprocess.run( | |
| cmd, shell=True, capture_output=True, text=not binary, timeout=timeout | |
| ) | |
| return p.stdout if not binary else p.stdout.encode() if p.stdout else b"" | |
| except Exception: | |
| return "" if not binary else b"" | |
| def gb_bytes(value): | |
| """Best-effort bytes -> GiB, returns None if unknown.""" | |
| try: | |
| return int(value) / (1024 ** 3) | |
| except (TypeError, ValueError): | |
| return None | |
| def fmt_gb(bytes_n): | |
| g = gb_bytes(bytes_n) | |
| return f"{g:.0f} GB" if g is not None else "unknown" | |
| def pretty_json(obj): | |
| return json.dumps(obj, indent=2, default=str) | |
| # --------------------------------------------------------------------------- # | |
| # Per-OS collectors | |
| # --------------------------------------------------------------------------- # | |
| def cpu_info(os_name): | |
| info = {"model": None, "cores": None, "threads": None, "max_mhz": None, "virtualized": None} | |
| if os_name == "Windows": | |
| out = run('wmic cpu get Name,NumberOfCores,NumberOfLogicalProcessors,MaxClockSpeed /value') | |
| m = re.search(r"Name=(.+)", out) | |
| if m: | |
| info["model"] = m.group(1).strip() | |
| m = re.search(r"NumberOfCores=(\d+)", out) | |
| if m: | |
| info["cores"] = int(m.group(1)) | |
| m = re.search(r"NumberOfLogicalProcessors=(\d+)", out) | |
| if m: | |
| info["threads"] = int(m.group(1)) | |
| m = re.search(r"MaxClockSpeed=(\d+)", out) | |
| if m: | |
| info["max_mhz"] = int(m.group(1)) | |
| elif os_name == "Linux": | |
| out = run("lscpu") | |
| m = re.search(r"Model name:\s*(.+)", out) | |
| if m: | |
| info["model"] = m.group(1).strip() | |
| m = re.search(r"^CPU\(s\):\s*(\d+)", out, re.M) | |
| if m: | |
| info["threads"] = int(m.group(1)) | |
| m = re.search(r"^Core\(s\) per socket:\s*(\d+)", out, re.M) | |
| if m: | |
| info["cores"] = int(m.group(1)) | |
| m = re.search(r"^CPU max MHz:\s*([\d.]+)", out, re.M) | |
| if m: | |
| info["max_mhz"] = float(m.group(1)) | |
| info["virtualized"] = "Hypervisor vendor" in out | |
| if info["model"] is None: # fallback | |
| m = re.search(r'"model name"\s*:\s*(.+)', run("grep -m1 'model name' /proc/cpuinfo")) | |
| if m: | |
| info["model"] = m.group(1).strip() | |
| elif os_name == "Darwin": | |
| out = run("sysctl -n machdep.cpu.brand_string machdep.cpu.core_count machdep.cpu.thread_count") | |
| lines = out.splitlines() | |
| info["model"] = lines[0].strip() if lines else None | |
| info["cores"] = int(lines[1]) if len(lines) > 1 and lines[1].strip().isdigit() else None | |
| info["threads"] = int(lines[2]) if len(lines) > 2 and lines[2].strip().isdigit() else None | |
| return info | |
| def ram_info(os_name): | |
| """Returns (total_gb, modules[]).""" | |
| modules = [] | |
| total = None | |
| if os_name == "Windows": | |
| out = run('wmic computersystem get TotalPhysicalMemory /value') | |
| m = re.search(r"TotalPhysicalMemory=(\d+)", out) | |
| if m: | |
| total = gb_bytes(m.group(1)) | |
| out = run('wmic memorychip get Capacity,Speed,MemoryType,Manufacturer,PartNumber /value') | |
| blocks = re.split(r"\n\s*\n", out.strip()) | |
| for b in blocks: | |
| cap = re.search(r"Capacity=(\d+)", b) | |
| spd = re.search(r"Speed=(\d+)", b) | |
| typ = re.search(r"MemoryType=(\d+)", b) | |
| man = re.search(r"Manufacturer=(.*)", b) | |
| pn = re.search(r"PartNumber=(.*)", b) | |
| if cap: | |
| mem_type = {20: "DDR", 21: "DDR2", 24: "DDR3", 26: "DDR4", 0: "Unknown"}.get( | |
| int(typ.group(1)) if typ else 0, "Unknown") | |
| modules.append({ | |
| "capacity_gb": gb_bytes(cap.group(1)), | |
| "speed_mhz": int(spd.group(1)) if spd else None, | |
| "type": mem_type, | |
| "manufacturer": (man.group(1).strip() if man else None), | |
| "part": (pn.group(1).strip() if pn else None), | |
| }) | |
| elif os_name == "Linux": | |
| out = run("free -b | awk 'NR==2{print $2}'") | |
| if out.strip().isdigit(): | |
| total = gb_bytes(out.strip()) | |
| # module detail needs dmidecode (root); fallback silently if unavailable | |
| out = run("sudo -n dmidecode -t memory 2>/dev/null || dmidecode -t memory 2>/dev/null || true") | |
| if out: | |
| for b in re.split(r"Memory Device$", out, flags=re.M): | |
| if "Size:" not in b or "No Module Installed" in b: | |
| continue | |
| sz = re.search(r"Size:\s*(\d+)\s*MB", b) | |
| spd = re.search(r"Speed:\s*(\d+)\s*MT/s", b) | |
| typ = re.search(r"Type:\s*(\S+)", b) | |
| man = re.search(r"Manufacturer:\s*(\S+)", b) | |
| pn = re.search(r"Part Number:\s*(\S+)", b) | |
| if sz: | |
| modules.append({ | |
| "capacity_gb": int(sz.group(1)) / 1024, | |
| "speed_mhz": int(spd.group(1)) if spd else None, | |
| "type": typ.group(1) if typ else None, | |
| "manufacturer": man.group(1) if man else None, | |
| "part": pn.group(1) if pn else None, | |
| }) | |
| if not modules and total: # fallback: single aggregate entry | |
| modules.append({"capacity_gb": total, "speed_mhz": None, | |
| "type": "unknown (run with sudo for detail)", "manufacturer": None, "part": None}) | |
| elif os_name == "Darwin": | |
| out = run("sysctl -n hw.memsize") | |
| if out.strip().isdigit(): | |
| total = gb_bytes(out.strip()) | |
| out = run("system_profiler SPMemoryDataType 2>/dev/null") | |
| for b in re.split(r"BANK\s*\d+/", out): | |
| cap = re.search(r"Size:\s*(\d+)\s*GB", b) | |
| spd = re.search(r"Speed:\s*(\d+)\s*MHz", b) | |
| typ = re.search(r"Type:\s*(\S+)", b) | |
| if cap: | |
| modules.append({"capacity_gb": int(cap.group(1)), | |
| "speed_mhz": int(spd.group(1)) if spd else None, | |
| "type": typ.group(1) if typ else None, | |
| "manufacturer": None, "part": None}) | |
| if not modules and total: | |
| modules.append({"capacity_gb": total, "speed_mhz": None, | |
| "type": "unknown", "manufacturer": None, "part": None}) | |
| return total, modules | |
| def disk_info(os_name): | |
| """List of disks: model, size_gb, type(SSD/HDD/NVMe), health lines, wear%.""" | |
| disks = [] | |
| if os_name == "Windows": | |
| # PowerShell gives reliable MediaType (SSD vs HDD) | |
| out = run( | |
| "powershell -NoProfile -Command " | |
| "\"Get-PhysicalDisk | Select-Object FriendlyName,MediaType,BusType,Size | ConvertTo-Csv -NoTypeInformation\"" | |
| ) | |
| for row in out.strip().splitlines()[1:]: | |
| if not row.strip(): | |
| continue | |
| parts = [p.strip().strip('"') for p in row.split(",")] | |
| if len(parts) >= 4: | |
| media = parts[1] | |
| bus = parts[2] | |
| kind = "NVMe" if "NVMe" in bus else ("SSD" if media == "SSD" else ("HDD" if media == "HDD" else media or "unknown")) | |
| disks.append({"model": parts[0], "size_gb": gb_bytes(parts[3]) if parts[3].isdigit() else None, | |
| "type": kind}) | |
| elif os_name == "Linux": | |
| out = run("lsblk -d -o NAME,MODEL,SIZE,ROTA,TRAN,TYPE 2>/dev/null") | |
| for line in out.splitlines()[1:]: | |
| parts = line.split() | |
| if len(parts) >= 5 and parts[-1] in ("disk",): | |
| name = parts[0] | |
| model = parts[1] if parts[1] not in ("disk",) and not parts[1].startswith("/dev") else name | |
| size_raw = parts[2] | |
| rota = parts[3] | |
| tran = parts[4] if len(parts) > 4 else "" | |
| size_gb = None | |
| m = re.match(r"([\d.]+)([GT])", size_raw) | |
| if m: | |
| v = float(m.group(1)) | |
| size_gb = v * 1024 if m.group(2) == "T" else v | |
| kind = "NVMe" if "nvme" in tran else ("HDD" if rota == "1" else "SSD") | |
| health = run(f"sudo -n smartctl -H -A /dev/{name} 2>/dev/null | tail -n +1 | grep -E 'SMART overall|Percentage Used|Temperature_Celsius|Reallocated_Sector|Power_On_Hours' || true") | |
| disks.append({"model": model, "size_gb": size_gb, "type": kind, "health": health.strip()}) | |
| elif os_name == "Darwin": | |
| out = run("system_profiler SPStorageDataType 2>/dev/null") | |
| for block in re.split(r"\n\s*\n", out): | |
| if "Medium Type:" not in block: | |
| continue # skips virtual Disk Images | |
| dev = re.search(r"Device Name:\s*(.+)", block) | |
| med = re.search(r"Medium Type:\s*(.+)", block) | |
| cap = re.search(r"Capacity:\s*([\d.]+)\s*(\w+)", block) | |
| size_gb = None | |
| if cap: | |
| size_gb = float(cap.group(1)) * 1024 if cap.group(2) == "TB" else float(cap.group(1)) | |
| kind = med.group(1).strip() if med else "unknown" | |
| disks.append({"model": dev.group(1).strip() if dev else "", | |
| "size_gb": size_gb, "type": kind, "health": ""}) | |
| # De-duplicate: macOS lists the same physical disk once per volume. | |
| seen, deduped = set(), [] | |
| for d in disks: | |
| key = d.get("model") or id(d) | |
| if key not in seen: | |
| seen.add(key) | |
| deduped.append(d) | |
| return deduped | |
| def gpu_info(os_name): | |
| if os_name == "Windows": | |
| out = run('wmic path win32_VideoController get Name,AdapterRAM /value') | |
| gpus = re.findall(r"Name=(.+?)(?:\n|$)", out) | |
| return [g.strip() for g in gpus if g.strip()] or ["(none detected)"] | |
| if os_name == "Linux": | |
| out = run("lspci | grep -Ei 'vga|3d|display' || true") | |
| return [l.split(":", 2)[-1].strip() for l in out.splitlines() if l.strip()] or ["(none detected)"] | |
| if os_name == "Darwin": | |
| out = run("system_profiler SPDisplaysDataType 2>/dev/null | grep -E 'Chipset Model|Vendor'") | |
| return [l.strip() for l in out.splitlines() if l.strip()] or ["(none detected)"] | |
| return [] | |
| def battery_info(os_name): | |
| info = {"percent": None, "health_pct": None} | |
| if os_name == "Windows": | |
| out = run("powershell -NoProfile -Command \"(Get-CimInstance Win32_Battery | Select-Object EstimatedChargeRemaining,DesignCapacity,FullChargeCapacity | ConvertTo-Csv -NoTypeInformation)\"") | |
| rows = out.strip().splitlines() | |
| if len(rows) > 1: | |
| parts = [p.strip().strip('"') for p in rows[1].split(",")] | |
| try: | |
| info["percent"] = int(parts[0]) | |
| except (ValueError, IndexError): | |
| pass | |
| try: | |
| if parts[2] and float(parts[2]) > 0: | |
| info["health_pct"] = round(float(parts[2]) / float(parts[1]) * 100, 1) | |
| except (ValueError, ZeroDivisionError, IndexError): | |
| pass | |
| elif os_name == "Linux": | |
| out = run("cat /sys/class/power_supply/BAT0/capacity 2>/dev/null || cat /sys/class/power_supply/BAT1/capacity 2>/dev/null || true") | |
| if out.strip().isdigit(): | |
| info["percent"] = int(out.strip()) | |
| de = run("grep -E 'ENERGY_FULL_DESIGN|ENERGY_FULL' /sys/class/power_supply/BAT0/uevent 2>/dev/null || true") | |
| m_full = re.search(r"ENERGY_FULL=(\d+)", de) | |
| m_design = re.search(r"ENERGY_FULL_DESIGN=(\d+)", de) | |
| if m_full and m_design and float(m_design.group(1)) > 0: | |
| info["health_pct"] = round(float(m_full.group(1)) / float(m_design.group(1)) * 100, 1) | |
| elif os_name == "Darwin": | |
| out = run("ioreg -rn AppleSmartBattery 2>/dev/null | grep -E '\"(CurrentCapacity|MaxCapacity|DesignCapacity)\" ='") | |
| cap = dict(re.findall(r'"(\w+)"\s*=\s*(\d+)', out)) | |
| if cap.get("MaxCapacity") and cap.get("CurrentCapacity"): | |
| info["percent"] = round(float(cap["CurrentCapacity"]) / float(cap["MaxCapacity"]) * 100) | |
| if cap.get("MaxCapacity") and cap.get("DesignCapacity"): | |
| info["health_pct"] = round(float(cap["MaxCapacity"]) / float(cap["DesignCapacity"]) * 100, 1) | |
| return info | |
| def system_info(os_name): | |
| sys_info = {} | |
| if os_name == "Windows": | |
| out = run('wmic computersystem get Manufacturer,Model /value') | |
| m = re.search(r"Manufacturer=(.+)", out) | |
| if m: | |
| sys_info["manufacturer"] = m.group(1).strip() | |
| m = re.search(r"Model=(.+)", out) | |
| if m: | |
| sys_info["model"] = m.group(1).strip() | |
| out = run('wmic bios get SerialNumber,SMBIOSBIOSVersion /value') | |
| m = re.search(r"SerialNumber=(.+)", out) | |
| if m: | |
| sys_info["serial"] = m.group(1).strip() | |
| m = re.search(r"SMBIOSBIOSVersion=(.+)", out) | |
| if m: | |
| sys_info["bios"] = m.group(1).strip() | |
| elif os_name == "Linux": | |
| for key, path in [("manufacturer", "/sys/class/dmi/id/sys_vendor"), | |
| ("model", "/sys/class/dmi/id/product_name"), | |
| ("serial", "/sys/class/dmi/id/product_serial")]: | |
| try: | |
| with open(path) as f: | |
| sys_info[key] = f.read().strip() | |
| except OSError: | |
| pass | |
| sys_info["os"] = run("grep PRETTY_NAME /etc/os-release | cut -d= -f2 | tr -d '\"'").strip() or platform.platform() | |
| elif os_name == "Darwin": | |
| out = run("system_profiler SPHardwareDataType 2>/dev/null | grep -E 'Model Name|Model Identifier|Serial Number|Chip|Memory'") | |
| for line in out.splitlines(): | |
| if ":" in line: | |
| k, v = [x.strip() for x in line.split(":", 1)] | |
| sys_info[k.lower().replace(" ", "_")] = v | |
| return sys_info | |
| def tpm_windows11(os_name): | |
| """Windows 11 readiness signals: TPM present + generation.""" | |
| if os_name == "Windows": | |
| out = run("powershell -NoProfile -Command \"Get-Tpm | Select-Object TpmPresent,TpmReady | ConvertTo-Csv -NoTypeInformation\" 2>/dev/null || wmic /namespace:\\\\root\\cimv2\\security\\microsofttpm path Win32_Tpm get IsEnabled_InitialValue /value 2>/dev/null || echo unavailable") | |
| return out.strip() or "unavailable" | |
| if os_name == "Linux": | |
| if os.path.isdir("/sys/class/tpm/tpm0"): | |
| out = run("cat /sys/class/tpm/tpm0/device/description 2>/dev/null || echo TPM present") | |
| return out.strip() | |
| return "no TPM device found in /sys/class/tpm" | |
| return "not-applicable (macOS)" | |
| # --------------------------------------------------------------------------- # | |
| # Report + verification | |
| # --------------------------------------------------------------------------- # | |
| def build_profile(): | |
| os_name = platform.system() | |
| os_release = platform.release() | |
| cpu = cpu_info(os_name) | |
| ram_total, ram_modules = ram_info(os_name) | |
| disks = disk_info(os_name) | |
| gpus = gpu_info(os_name) | |
| battery = battery_info(os_name) | |
| sysinfo = system_info(os_name) | |
| profile = { | |
| "generated_at": datetime.now().isoformat(timespec="seconds"), | |
| "os": f"{os_name} {os_release}", | |
| "hostname": platform.node(), | |
| "system": sysinfo, | |
| "cpu": cpu, | |
| "ram": { | |
| "total_gb": round(ram_total, 1) if ram_total else None, | |
| "module_count": len(ram_modules), | |
| "modules": ram_modules, | |
| }, | |
| "disks": disks, | |
| "gpu": gpus, | |
| "battery": battery, | |
| "tpm": tpm_windows11(os_name), | |
| } | |
| return profile | |
| def render(profile): | |
| s = profile["system"] | |
| c = profile["cpu"] | |
| r = profile["ram"] | |
| disks = profile["disks"] | |
| gpus = profile["gpu"] | |
| battery = profile["battery"] | |
| lines = [] | |
| w = 78 | |
| lines.append("=" * w) | |
| lines.append(" LAPTOP HARDWARE PROFILE") | |
| lines.append(" " + profile["generated_at"]) | |
| lines.append("=" * w) | |
| lines.append(f" Host OS : {profile['os']} (host: {profile['hostname']})") | |
| if s: | |
| for k, v in s.items(): | |
| if v: | |
| lines.append(f" {k.replace('_', ' ').title():<15}: {v}") | |
| lines.append("-" * w) | |
| lines.append(" CPU") | |
| if c.get("model"): | |
| lines.append(f" Model : {c['model']}") | |
| lines.append(f" Cores / Threads: {c.get('cores', '?')} / {c.get('threads', '?')}") | |
| if c.get("max_mhz"): | |
| lines.append(f" Max clock : {c['max_mhz']} MHz") | |
| if c.get("virtualized"): | |
| lines.append(" WARNING: This machine looks VIRTUALIZED (a VM, not real hardware)!") | |
| lines.append("-" * w) | |
| lines.append(" RAM") | |
| lines.append(f" Total : {r['total_gb']} GB ({r['module_count']} module(s))") | |
| for i, mod in enumerate(r["modules"], 1): | |
| lines.append( | |
| f" Module {i} : {mod['capacity_gb']} GB {mod['type']} @ {mod['speed_mhz']} MHz" | |
| f"{' (' + mod['manufacturer'] + ')' if mod.get('manufacturer') else ''}" | |
| ) | |
| lines.append("-" * w) | |
| lines.append(" STORAGE") | |
| for d in profile["disks"]: | |
| lines.append(f" {d['type']:<5} {d.get('model', '')} {round(d['size_gb']) if d.get('size_gb') else '?':>6} GB") | |
| if d.get("health"): | |
| lines.append(" SMART: " + d["health"].replace("\n", " | ")) | |
| lines.append("-" * w) | |
| lines.append(" GRAPHICS") | |
| for g in profile["gpu"]: | |
| lines.append(f" {g}") | |
| lines.append("-" * w) | |
| lines.append(" BATTERY") | |
| lines.append(f" Current charge : {battery.get('percent', '?')} %") | |
| lines.append(f" Health (wear) : {battery.get('health_pct', '?')} % of design capacity") | |
| if battery.get("health_pct") is not None and battery["health_pct"] < 60: | |
| lines.append(" WARNING: Battery health < 60% — expect poor battery life.") | |
| lines.append("-" * w) | |
| lines.append(" WINDOWS 11 / TPM") | |
| lines.append(f" {profile['tpm']}") | |
| lines.append("=" * w) | |
| return "\n".join(lines) | |
| def verify(profile, expect): | |
| """expect: dict with ram_gb, ssd_gb, cpu_sub, threads.""" | |
| failures = [] | |
| warns = [] | |
| passes = [] | |
| ram = profile["ram"]["total_gb"] | |
| if expect.get("ram_gb"): | |
| if ram is None: | |
| warns.append(f"RAM: could not read total RAM, cannot verify against {expect['ram_gb']} GB") | |
| elif ram >= expect["ram_gb"] - 0.5: | |
| passes.append(f"RAM: {ram} GB >= expected {expect['ram_gb']} GB") | |
| else: | |
| failures.append(f"RAM: found {ram} GB, expected ~{expect['ram_gb']} GB") | |
| if expect.get("ssd_gb"): | |
| total_ssd = sum(d["size_gb"] or 0 for d in profile["disks"] if d.get("type") in ("SSD", "NVMe")) | |
| has_ssd = any(d.get("type") in ("SSD", "NVMe") for d in profile["disks"]) | |
| if not has_ssd: | |
| failures.append("STORAGE: no SSD/NVMe found (likely an HDD — bad for CapCut)") | |
| elif total_ssd >= expect["ssd_gb"] - 30: | |
| passes.append(f"SSD: {total_ssd:.0f} GB SSD/NVMe >= expected {expect['ssd_gb']} GB") | |
| else: | |
| failures.append(f"SSD: only {total_ssd:.0f} GB SSD found, expected ~{expect['ssd_gb']} GB") | |
| if expect.get("cpu_sub"): | |
| model = (profile["cpu"].get("model") or "").lower() | |
| if expect["cpu_sub"].lower() in model: | |
| passes.append(f"CPU: '{expect['cpu_sub']}' present in '{profile['cpu'].get('model')}'") | |
| else: | |
| failures.append(f"CPU: expected substring '{expect['cpu_sub']}' NOT in '{profile['cpu'].get('model')}'") | |
| if expect.get("threads"): | |
| t = profile["cpu"].get("threads") | |
| if t is None: | |
| warns.append("THREADS: could not read thread count") | |
| elif t == expect["threads"]: | |
| passes.append(f"THREADS: {t} == expected {expect['threads']}") | |
| else: | |
| failures.append(f"THREADS: found {t}, expected {expect['threads']}") | |
| return passes, warns, failures | |
| def main(): | |
| ap = argparse.ArgumentParser(description="Profile & verify laptop hardware.") | |
| ap.add_argument("--json", action="store_true", help="write hardware_profile.json") | |
| ap.add_argument("--expect-ram", type=float, help="expected RAM in GB (e.g. 16)") | |
| ap.add_argument("--expect-ssd", type=float, help="expected SSD size in GB (e.g. 256)") | |
| ap.add_argument("--expect-cpu", help="expected CPU substring (e.g. 4600U or i7)") | |
| ap.add_argument("--expect-threads", type=int, help="expected logical processors (e.g. 12)") | |
| ap.add_argument("--expect-all", help="comma list: RAM,SSD,cpu,threads (e.g. 16,256,4600U,12)") | |
| args = ap.parse_args() | |
| profile = build_profile() | |
| if args.expect_all: | |
| parts = [p.strip() for p in args.expect_all.split(",")] | |
| try: | |
| args.expect_ram = float(parts[0]) | |
| except (ValueError, IndexError): | |
| pass | |
| try: | |
| args.expect_ssd = float(parts[1]) | |
| except (ValueError, IndexError): | |
| pass | |
| if len(parts) > 2: | |
| args.expect_cpu = parts[2] | |
| try: | |
| args.expect_threads = int(parts[3]) | |
| except (ValueError, IndexError): | |
| pass | |
| expect = { | |
| "ram_gb": args.expect_ram, | |
| "ssd_gb": args.expect_ssd, | |
| "cpu_sub": args.expect_cpu, | |
| "threads": args.expect_threads, | |
| } | |
| print(render(profile)) | |
| if args.json: | |
| with open("hardware_profile.json", "w") as f: | |
| f.write(pretty_json(profile)) | |
| print("\nProfile written to hardware_profile.json") | |
| exit_code = 0 | |
| if any(expect.values()): | |
| passes, warns, failures = verify(profile, expect) | |
| print("=" * 78) | |
| print(" VERIFICATION vs. the advertised specs") | |
| print("=" * 78) | |
| for p in passes: | |
| print(f" [PASS] {p}") | |
| for w in warns: | |
| print(f" [WARN] {w}") | |
| for f in failures: | |
| print(f" [FAIL] {f}") | |
| if failures: | |
| print("\n >>> MACHINE DOES NOT MATCH THE AD. Negotiate or walk away.") | |
| exit_code = 2 | |
| elif warns: | |
| print("\n >>> Matches claimed specs, but some details unverified.") | |
| else: | |
| print("\n >>> All claimed specs verified. Happy CapCut editing!") | |
| return exit_code | |
| if __name__ == "__main__": | |
| sys.exit(main()) |
Author
Author
# Just see the profile (no expectations)
curl -sL https://gist.githubusercontent.com/mckabue/e8c05a13bdadfdf654bb279fb9b37d55/raw/laptop-hardware-verify.py | python3 -
# Save the JSON report too (writes hardware_profile.json in cwd)
curl -sL https://gist.githubusercontent.com/mckabue/e8c05a13bdadfdf654bb279fb9b37d55/raw/laptop-hardware-verify.py | python3 - --json
# One check at a time
curl -sL <url> | python3 - --expect-ram 16 --expect-ssd 256 --expect-cpu 4600U --expect-threads 12
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Cross-platform (Windows / Linux / macOS) hardware profiler that verifies a
(second-hand) laptop against the specs you were promised.
Used laptops are often advertised as "16GB RAM / 256GB SSD / i7" but arrive
with 8GB, an HDD, or a different CPU. This zero-dependency Python script
(auto-detects the OS) builds a detailed hardware profile and can PASS/FAIL
each claimed spec so you can negotiate or walk away.
Collected:
• CPU: exact model, cores / threads, max clock (flags VMs)
• RAM: total GB + module-by-module (capacity, DDR3/4 type, MHz, brand)
• Storage: SSD vs HDD vs NVMe detection, size, SMART health/wear (if smartctl)
• Graphics: integrated + discrete GPU (for CapCut hardware encoding)
• Battery: charge % + health % vs design capacity
• TPM / Windows 11 readiness
• System: manufacturer, model, serial number, BIOS
Usage:
python3 laptop-hardware-verify.py # print profile
python3 laptop-hardware-verify.py --json # + hardware_profile.json
python3 laptop-hardware-verify.py --expect-all 16,256,4600U,12 # verify vs ad
python3 laptop-hardware-verify.py --expect-ram 16 --expect-ssd 256
--expect-cpu 4600U --expect-threads 12
Exit codes: 0 = matches ad, 2 = mismatch, 3 = unreadable. No external
dependencies — copy it to a USB stick and run it on the machine at pickup.