constructelligence's picture
Data Centre Nameplates — static demo
98410e9 verified
Raw History Blame Contribute Delete
15.9 kB
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Data Centre Nameplates — read &amp; verify equipment nameplates</title>
<meta name="description" content="Photograph an equipment nameplate and read it in your browser: model, serial, voltage and rating checked against your equipment schedule, with an asset register CSV. Nothing is uploaded." />
<meta name="robots" content="index, follow" />
<link rel="icon" href="data:image/svg+xml,<svg xmlns=%22http://www.w3.org/2000/svg%22 viewBox=%220 0 24 24%22><rect width=%2224%22 height=%2224%22 rx=%225%22 fill=%22%230f2547%22/><rect x=%225%22 y=%227%22 width=%2214%22 height=%2210%22 rx=%221.5%22 fill=%22none%22 stroke=%22white%22 stroke-width=%221.8%22/><path d=%22M8 11h8M8 14h5%22 stroke=%22white%22 stroke-width=%221.6%22/></svg>" />
<meta name="theme-color" content="#0f2547" />
<meta name="author" content="Constructelligence" />
<meta name="keywords" content="nameplate OCR, equipment nameplate reader, data centre nameplate, data center commissioning, UPS nameplate, PDU nameplate, switchgear, generator nameplate, transformer nameplate, equipment schedule verification, asset register, CRAH, chiller, MEP commissioning, browser OCR, BuildVision" />
<meta property="og:type" content="website" />
<meta property="og:site_name" content="BuildVision" />
<meta property="og:title" content="Nameplates — read &amp; verify equipment nameplates in the browser" />
<meta property="og:description" content="Photograph a data centre equipment nameplate and read it in your browser: model, serial, voltage and rating checked against your equipment schedule, with an asset register CSV. Nothing is uploaded." />
<meta property="og:image" content="og-nameplates.png" />
<meta property="og:image:width" content="1200" />
<meta property="og:image:height" content="630" />
<meta property="og:image:alt" content="BuildVision Nameplates — read and verify data centre equipment nameplates against the equipment schedule" />
<meta name="twitter:card" content="summary_large_image" />
<meta name="twitter:title" content="Nameplates — read &amp; verify equipment nameplates in the browser" />
<meta name="twitter:description" content="Photograph a UPS, PDU, switchgear or chiller nameplate and check model, serial, voltage and rating against the equipment schedule. Runs in your browser; nothing is uploaded." />
<meta name="twitter:image" content="og-nameplates.png" />
<meta name="twitter:image:alt" content="BuildVision Nameplates — equipment-nameplate OCR checked against the equipment schedule" />
<script type="application/ld+json">
{
"@context": "https://schema.org",
"@type": "WebApplication",
"name": "BuildVision Nameplates",
"description": "Browser tool that reads an equipment nameplate (UPS, PDU, switchgear, generator, transformer, battery, CRAH, chiller) with in-browser OCR and checks model, serial, voltage, phase, Hz, kVA, kW, amps, MCA, MOCP and refrigerant against the equipment schedule, then exports an asset register CSV. Photos never leave the device.",
"applicationCategory": "BusinessApplication",
"operatingSystem": "Any (runs in a modern web browser)",
"offers": { "@type": "Offer", "price": "0", "priceCurrency": "USD" },
"featureList": [
"On-device nameplate OCR (nothing uploaded)",
"13 equipment types: UPS, generator, ATS, switchgear, transformer, PDU, panelboard, busway, battery, CDU, chiller, CRAH/CRAC, pump",
"Field-by-field check against the equipment schedule",
"Flags wrong voltage, rating or model instead of a silent pass",
"Asset register with duplicate-serial and missing-tag detection, exported as CSV"
]
}
</script>
<link rel="preconnect" href="https://cdn.jsdelivr.net" crossorigin>
<link rel="stylesheet" href="style.css" />
<style>
body { min-height: 100%; }
.np-wrap { display: grid; grid-template-columns: 320px minmax(0, 1fr); flex: 1; min-height: 0; }
.np-side { border-right: 1px solid var(--border); padding: 18px 18px 28px; display: flex; flex-direction: column; gap: 18px; background: var(--bg-panel-2); overflow-y: auto; }
.np-side h2, .np-main h2 { font-size: 0.78rem; letter-spacing: 0.06em; text-transform: uppercase; color: var(--text-dim); margin: 0 0 8px; }
.np-side p, .np-note { font-size: 0.82rem; color: var(--text-dim); line-height: 1.45; margin: 0; }
.np-row { display: flex; gap: 8px; flex-wrap: wrap; align-items: center; }
.np-btn { border: 1px solid var(--border); background: var(--bg); color: var(--text); padding: 8px 12px; border-radius: var(--radius-sm); font-weight: 600; font-size: 0.84rem; cursor: pointer; }
.np-btn:hover { border-color: var(--accent); }
.np-btn.primary { background: var(--accent); color: #fff; border-color: var(--accent); }
.np-btn.primary:hover { background: var(--accent-hover); }
.np-btn:disabled { opacity: 0.5; cursor: default; }
.np-btn.link { border: none; background: none; padding: 4px 6px; color: var(--text-dim); font-weight: 500; }
.np-drop { display: block; border: 1.5px dashed var(--border); border-radius: var(--radius); padding: 16px; text-align: center; font-size: 0.84rem; color: var(--text-dim); background: var(--bg); cursor: pointer; }
.np-drop.over { border-color: var(--accent); background: var(--accent-soft); }
.np-status { font-size: 0.82rem; min-height: 1.2em; color: var(--text-dim); }
.np-status.err { color: var(--danger); }
.np-bar { height: 4px; background: var(--border); border-radius: 2px; overflow: hidden; }
.np-bar > i { display: block; height: 100%; width: 0; background: var(--accent); transition: width 0.15s; }
.np-main { display: flex; flex-direction: column; min-width: 0; min-height: 0; overflow-y: auto; }
.np-top { display: grid; grid-template-columns: minmax(0, 1.2fr) minmax(300px, 1fr); border-bottom: 1px solid var(--border); }
.np-stage { position: relative; min-height: 340px; background: var(--bg-subtle); display: grid; place-items: center; padding: 12px; }
.np-stage canvas { max-width: 100%; max-height: 62vh; height: auto; display: block; border-radius: var(--radius-sm); }
.np-empty { text-align: center; color: var(--text-dim); font-size: 0.9rem; padding: 24px; }
.np-reading { padding: 16px 18px 20px; border-left: 1px solid var(--border); display: flex; flex-direction: column; gap: 12px; }
.np-fields { display: grid; grid-template-columns: auto minmax(0, 1fr); gap: 6px 10px; align-items: center; font-size: 0.84rem; }
.np-fields label { color: var(--text-dim); font-weight: 600; white-space: nowrap; display: flex; align-items: center; gap: 6px; }
.np-fields input, .np-fields select, .np-tag select { width: 100%; padding: 6px 8px; border: 1px solid var(--border); border-radius: var(--radius-sm); font-size: 16px; background: var(--bg); color: var(--text); min-width: 0; }
.np-fields input.low { border-color: var(--warning, #c98a00); background: color-mix(in srgb, var(--warning, #c98a00) 8%, var(--bg)); }
.np-key { display: inline-block; width: 9px; height: 9px; border-radius: 2px; flex: none; }
.np-tag { display: flex; flex-direction: column; gap: 6px; font-size: 0.84rem; font-weight: 600; }
.np-checks { list-style: none; margin: 0; padding: 0; font-size: 0.84rem; display: flex; flex-direction: column; gap: 4px; }
.np-checks li { display: flex; gap: 8px; align-items: baseline; }
.np-checks .ok { color: var(--success, #1f8a4c); }
.np-checks .mismatch { color: var(--danger); font-weight: 600; }
.np-checks .unread { color: var(--text-dim); }
.np-verdict { padding: 8px 10px; border-radius: var(--radius-sm); font-size: 0.86rem; font-weight: 600; }
.np-verdict.verified { background: color-mix(in srgb, var(--success, #1f8a4c) 12%, transparent); color: var(--success, #1f8a4c); }
.np-verdict.mismatch { background: color-mix(in srgb, var(--danger) 12%, transparent); color: var(--danger); }
.np-verdict.partial, .np-verdict.unmatched { background: var(--bg-subtle); color: var(--text); }
.np-register { padding: 14px 18px 24px; }
.np-summary { display: flex; gap: 18px; flex-wrap: wrap; margin-bottom: 10px; font-size: 0.86rem; }
.np-summary b { font-size: 1.05rem; }
table.np-table { border-collapse: collapse; width: 100%; font-size: 0.82rem; }
.np-table th, .np-table td { text-align: left; padding: 6px 8px; border-bottom: 1px solid var(--border); vertical-align: top; }
.np-table th { color: var(--text-dim); font-weight: 600; font-size: 0.74rem; text-transform: uppercase; letter-spacing: 0.04em; }
.np-table tr[data-id] { cursor: pointer; }
.np-table tr[data-id]:hover td { background: var(--bg-subtle); }
.np-table tr.sel td { background: var(--accent-soft); }
.np-table img { width: 56px; height: 40px; object-fit: cover; border-radius: 3px; display: block; }
.np-chip { display: inline-block; padding: 2px 8px; border-radius: 999px; font-size: 0.74rem; font-weight: 600; white-space: nowrap; background: var(--bg-subtle); }
.np-chip.verified { background: color-mix(in srgb, var(--success, #1f8a4c) 14%, transparent); color: var(--success, #1f8a4c); }
.np-chip.mismatch { background: color-mix(in srgb, var(--danger) 14%, transparent); color: var(--danger); }
.np-missing { font-size: 0.82rem; color: var(--text-dim); margin-top: 10px; }
@media (max-width: 980px) {
.np-top { grid-template-columns: 1fr; }
.np-reading { border-left: none; border-top: 1px solid var(--border); }
}
@media (max-width: 820px) {
/* One column that scrolls as a page, not panels that each scroll (they overlapped the photo). */
html, body { height: auto; }
.np-wrap { display: block; }
.np-side, .np-main { overflow: visible; }
.np-side { border-right: none; border-bottom: 1px solid var(--border); padding: 16px; }
.np-stage { min-height: 220px; }
.np-reading, .np-register { padding: 14px 16px; }
.np-table .np-hide-sm { display: none; }
}
</style>
</head>
<body>
<header class="app-header">
<div class="brand">
<a class="brand-home" href="https://huggingface.co/constructelligence/data-centre-nameplates" title="Model card on Hugging Face" style="text-decoration:none;color:inherit;display:flex;align-items:center;gap:8px">
<h1 class="brand-name">BuildVision</h1>
</a>
</div>
<nav class="mode-switch" aria-label="Page">
<a class="mode-link" href="https://huggingface.co/constructelligence/data-centre-nameplates" target="_blank" rel="noopener">Model card</a>
<a class="mode-link" href="https://constructelligence.co" target="_blank" rel="noopener">constructelligence.co</a>
<span class="mode-link active" aria-current="page">Nameplates</span>
</nav>
<div class="header-status"><span id="npModel" class="status-pill model-status">OCR idle</span></div>
</header>
<main class="np-wrap">
<aside class="np-side">
<section>
<h2>1. Equipment schedule</h2>
<label id="npSchedDrop" class="np-drop" tabindex="0">
<input id="npSchedFile" type="file" accept=".csv,.tsv,.txt,text/csv" hidden />
<span>Drop the equipment schedule as CSV, or click to choose</span>
</label>
<div class="np-row" style="margin-top:8px">
<button id="npSchedSample" class="np-btn" type="button">Use sample schedule</button>
<button id="npSchedClear" class="np-btn link" type="button" hidden>Remove</button>
</div>
<p id="npSchedStatus" class="np-status" style="margin-top:8px" aria-live="polite"></p>
<p style="margin-top:6px">Optional. Needs a Tag or Model column; Manufacturer, Voltage, Phase, kVA, kW, Amps, Refrigerant, Serial and Location are checked when present.</p>
</section>
<section>
<h2>2. Nameplate photo</h2>
<label id="npDrop" class="np-drop" tabindex="0">
<input id="npFile" type="file" accept="image/*" multiple hidden />
<span>Take or drop a nameplate photo, or a batch from a walk</span>
</label>
<div class="np-row" style="margin-top:8px">
<button id="npSample" class="np-btn" type="button">Try a sample nameplate</button>
<button id="npRotate" class="np-btn" type="button" disabled title="Rotate the photo 90°">Rotate</button>
</div>
<p style="margin-top:8px">Fill the frame with the plate, without glare across the text. Tilted shots are straightened automatically. Pick several photos at once to read a whole walk into the register, then click a row to review it.</p>
</section>
<section>
<button id="npRun" class="np-btn primary" type="button" disabled>Read nameplate</button>
<div class="np-bar" style="margin-top:10px"><i id="npProg"></i></div>
<div id="npStatus" class="np-status" role="status" aria-live="polite"></div>
</section>
<section>
<p class="np-note">Runs in your browser: photos never leave this device. The first read downloads the OCR engine and English text model (about 11 MB) from a public CDN. OCR misreads happen, so fields with low confidence are highlighted for you to check against the plate.</p>
</section>
</aside>
<section class="np-main">
<div class="np-top">
<div class="np-stage">
<canvas id="npCanvas" width="0" height="0" hidden></canvas>
<div id="npEmpty" class="np-empty">Load a nameplate photo to begin.</div>
</div>
<div id="npReading" class="np-reading" hidden>
<h2>Reading</h2>
<div id="npVerdict" class="np-verdict" aria-live="polite"></div>
<div class="np-fields" id="npFields"></div>
<div class="np-tag">
<span>Schedule tag</span>
<select id="npTag"></select>
<span id="npTagNote" class="np-note" style="font-weight:400"></span>
</div>
<ul id="npChecks" class="np-checks" aria-label="Checks against the schedule"></ul>
<div class="np-row">
<button id="npAdd" class="np-btn primary" type="button">Add to register</button>
<details style="font-size:0.8rem;color:var(--text-dim)"><summary>OCR text</summary><pre id="npText" style="white-space:pre-wrap;font-size:0.78rem;margin:6px 0 0"></pre></details>
</div>
</div>
</div>
<div class="np-register">
<h2>Asset register</h2>
<div id="npSummary" class="np-summary"></div>
<div class="np-row" style="margin-bottom:10px">
<button id="npCsv" class="np-btn" type="button" disabled>Download register CSV</button>
<button id="npClearReg" class="np-btn link" type="button" disabled>Clear register</button>
</div>
<table class="np-table">
<thead><tr><th class="np-hide-sm">Photo</th><th>Tag</th><th>Status</th><th>Model</th><th class="np-hide-sm">Serial</th><th class="np-hide-sm">Voltage</th><th>Issues</th><th></th></tr></thead>
<tbody id="npRows"></tbody>
</table>
<p id="npEmptyReg" class="np-note" style="margin-top:10px">Nothing scanned yet. Each plate you add appears here with its check result.</p>
<p id="npMissing" class="np-missing"></p>
</div>
</section>
</main>
<script type="module" src="nameplates-app.js"></script>
</body>
</html>