File size: 15,918 Bytes
176941f
98410e9
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
176941f
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
<!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>