fixed issues w/ accessing website on other computers
This commit is contained in:
parent
c1e453d962
commit
8bb76d8e6c
37
README.md
37
README.md
@ -31,7 +31,7 @@ Start with the [final report](docs/final_report.md). Supporting deliverables:
|
||||
- [Private-to-public architecture](docs/architecture.mmd)
|
||||
- [10-minute presentation outline](docs/presentation_outline.md)
|
||||
- [Dashboard demo script](docs/demo_script.md)
|
||||
- [Dashboard-only Bolt deployment](docs/bolt_deployment.md)
|
||||
- [Optional public-hosting notes](docs/bolt_deployment.md)
|
||||
|
||||
The [data inventory](docs/data_inventory.md) and archived
|
||||
[project options](docs/project_options.md) provide source-discovery history;
|
||||
@ -142,17 +142,34 @@ checksums. It shows no estimates if the approved aggregate bundle fails its
|
||||
contract. Every checked-in asset is marked as a development preview and not a
|
||||
population estimate.
|
||||
|
||||
## Publish through Bolt
|
||||
### Live demo from another computer
|
||||
|
||||
Do **not** import this full source repository into Bolt. Create a separate
|
||||
deployment project or repository containing only the contents of `dashboard/`,
|
||||
so `index.html` is at that project's root. Do not copy `.env`, private data,
|
||||
artifacts, models, SQL, pipeline scripts, credentials, or unrelated repository
|
||||
history.
|
||||
When both computers are on the same network, start the LAN demo from the
|
||||
repository root. Stop any older dashboard process with `Ctrl-C` first:
|
||||
|
||||
Follow the complete preflight, import, Bolt Hosting, and post-publication checks
|
||||
in [docs/bolt_deployment.md](docs/bolt_deployment.md). No publishing action is
|
||||
performed by this repository.
|
||||
```bash
|
||||
node dashboard/server.mjs --lan
|
||||
```
|
||||
|
||||
The server prints a **This computer** URL and one or more interface-labeled
|
||||
**Other devices** URLs. Open the URL for the shared Wi-Fi or Ethernet interface
|
||||
on the second computer; do not use `0.0.0.0` as the browser address. Confirm the
|
||||
header reads **Validated sample aggregates**. Stop the server with `Ctrl-C`
|
||||
after the demo.
|
||||
|
||||
The LAN page still checks every approved JSON asset against its SHA-256 digest.
|
||||
Browsers do not expose the SubtleCrypto digest API to a non-loopback plain-HTTP
|
||||
page, so the dashboard includes a dependency-free checksum implementation for
|
||||
that case. These checks validate bundle consistency; plain HTTP does not
|
||||
authenticate the network transport, so use a trusted demo network.
|
||||
|
||||
## Public hosting is not required
|
||||
|
||||
The current presentation plan is the same-network live demo above. Nothing
|
||||
needs to be uploaded or published, and the server exposes only the dashboard's
|
||||
explicit static-file allowlist. The older
|
||||
[public-hosting checklist](docs/bolt_deployment.md) is retained only in case the
|
||||
delivery requirements change later.
|
||||
|
||||
## Author
|
||||
|
||||
|
||||
@ -25,6 +25,28 @@ URL; browsers will block the JSON module requests. To use another port:
|
||||
PORT=8080 node dashboard/server.mjs
|
||||
```
|
||||
|
||||
## Run a live demo on the same network
|
||||
|
||||
From the source-repository root, run:
|
||||
|
||||
```bash
|
||||
node dashboard/server.mjs --lan
|
||||
```
|
||||
|
||||
Stop any older dashboard process with `Ctrl-C` before starting. Or, from this
|
||||
directory, run `npm run start:lan`. The server prints a **This computer** URL
|
||||
and one or more interface-labeled **Other devices** URLs. Open the URL for the
|
||||
shared Wi-Fi or Ethernet interface on the second computer; `0.0.0.0` is a bind
|
||||
address, not the address to put in a browser. Confirm the in-app status reads
|
||||
**Validated sample aggregates**, then stop the server with `Ctrl-C` after the
|
||||
demo.
|
||||
|
||||
All data-contract and checksum validation remains enabled over LAN HTTP. When a
|
||||
remote browser does not expose the SubtleCrypto digest API to the plain-HTTP
|
||||
page, the dashboard uses its dependency-free SHA-256 implementation and still
|
||||
rejects changed assets. The checks validate bundle consistency, not the
|
||||
authenticity of a plain-HTTP connection, so use a trusted demo network.
|
||||
|
||||
Run the dependency-free contract checks with:
|
||||
|
||||
```bash
|
||||
@ -68,28 +90,9 @@ operational connection, or row-level prediction output. VINs, plates, ZIPs,
|
||||
stations, technician identifiers, raw source JSON, credentials, and operational
|
||||
records must never enter this directory.
|
||||
|
||||
## Publish through Bolt
|
||||
## Public hosting is not required
|
||||
|
||||
Use a separate static Bolt project backed by a dashboard-only repository. Do
|
||||
not import this source repository into Bolt.
|
||||
|
||||
1. Run `npm test` from this directory and confirm every contract check passes.
|
||||
2. Create a clean dashboard-only repository or Bolt project.
|
||||
3. Copy only the *contents* of `dashboard/` into that project, so `index.html`
|
||||
is at the project root.
|
||||
4. Confirm that private `data/`, `artifacts/`, `models/`, `.env` files, SQL,
|
||||
notebooks, and database tooling are absent.
|
||||
5. Preview the project. There is no install or build step; if Bolt requests a
|
||||
preview command, use `node server.mjs` with `HOST=0.0.0.0` and let Bolt
|
||||
provide `PORT`.
|
||||
6. Verify all four views, the development-sample messaging on every view, and
|
||||
that the in-app status reads **Validated sample aggregates**.
|
||||
7. Use **Publish** only after the aggregate bundle completes privacy review.
|
||||
Bolt hosting is the default; Netlify can instead be selected before the
|
||||
first publish if desired.
|
||||
|
||||
See Bolt's official documentation for [Git
|
||||
integration](https://support.bolt.new/integrations/git) and [publishing with
|
||||
Netlify](https://support.bolt.new/integrations/netlify).
|
||||
|
||||
No publishing action is performed by this repository.
|
||||
The current delivery path is the same-network live demo. No Bolt project,
|
||||
public URL, dependency install, or build step is needed. If public hosting is
|
||||
ever reconsidered, publish only a separately reviewed copy of this dashboard
|
||||
directory—never the private source repository.
|
||||
|
||||
@ -10,6 +10,34 @@ const ENVELOPE_KEYS = Object.freeze([
|
||||
"population_estimate_allowed",
|
||||
"schema_version",
|
||||
]);
|
||||
const SHA256_INITIAL_STATE = Object.freeze([
|
||||
0x6a09e667,
|
||||
0xbb67ae85,
|
||||
0x3c6ef372,
|
||||
0xa54ff53a,
|
||||
0x510e527f,
|
||||
0x9b05688c,
|
||||
0x1f83d9ab,
|
||||
0x5be0cd19,
|
||||
]);
|
||||
const SHA256_ROUND_CONSTANTS = Object.freeze([
|
||||
0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5,
|
||||
0x3956c25b, 0x59f111f1, 0x923f82a4, 0xab1c5ed5,
|
||||
0xd807aa98, 0x12835b01, 0x243185be, 0x550c7dc3,
|
||||
0x72be5d74, 0x80deb1fe, 0x9bdc06a7, 0xc19bf174,
|
||||
0xe49b69c1, 0xefbe4786, 0x0fc19dc6, 0x240ca1cc,
|
||||
0x2de92c6f, 0x4a7484aa, 0x5cb0a9dc, 0x76f988da,
|
||||
0x983e5152, 0xa831c66d, 0xb00327c8, 0xbf597fc7,
|
||||
0xc6e00bf3, 0xd5a79147, 0x06ca6351, 0x14292967,
|
||||
0x27b70a85, 0x2e1b2138, 0x4d2c6dfc, 0x53380d13,
|
||||
0x650a7354, 0x766a0abb, 0x81c2c92e, 0x92722c85,
|
||||
0xa2bfe8a1, 0xa81a664b, 0xc24b8b70, 0xc76c51a3,
|
||||
0xd192e819, 0xd6990624, 0xf40e3585, 0x106aa070,
|
||||
0x19a4c116, 0x1e376c08, 0x2748774c, 0x34b0bcb5,
|
||||
0x391c0cb3, 0x4ed8aa4a, 0x5b9cca4f, 0x682e6ff3,
|
||||
0x748f82ee, 0x78a5636f, 0x84c87814, 0x8cc70208,
|
||||
0x90befffa, 0xa4506ceb, 0xbef9a3f7, 0xc67178f2,
|
||||
]);
|
||||
|
||||
export const REQUIRED_ASSETS = Object.freeze({
|
||||
manifest: "data_manifest.json",
|
||||
@ -541,9 +569,80 @@ function parseJsonBytes(bytes, label) {
|
||||
}
|
||||
}
|
||||
|
||||
function rotateRight(value, shift) {
|
||||
return ((value >>> shift) | (value << (32 - shift))) >>> 0;
|
||||
}
|
||||
|
||||
function sha256HexFallback(bytes) {
|
||||
const input = new Uint8Array(bytes);
|
||||
const bitLength = input.byteLength * 8;
|
||||
if (!Number.isSafeInteger(bitLength)) {
|
||||
throw new DataContractError("Dashboard asset integrity verification failed.");
|
||||
}
|
||||
|
||||
const paddedLength = Math.ceil((input.byteLength + 9) / 64) * 64;
|
||||
const padded = new Uint8Array(paddedLength);
|
||||
padded.set(input);
|
||||
padded[input.byteLength] = 0x80;
|
||||
|
||||
const paddedView = new DataView(padded.buffer);
|
||||
paddedView.setUint32(paddedLength - 8, Math.floor(bitLength / 0x100000000), false);
|
||||
paddedView.setUint32(paddedLength - 4, bitLength >>> 0, false);
|
||||
|
||||
const state = [...SHA256_INITIAL_STATE];
|
||||
const words = new Uint32Array(64);
|
||||
for (let offset = 0; offset < paddedLength; offset += 64) {
|
||||
for (let index = 0; index < 16; index += 1) {
|
||||
words[index] = paddedView.getUint32(offset + index * 4, false);
|
||||
}
|
||||
for (let index = 16; index < 64; index += 1) {
|
||||
const sigma0 =
|
||||
rotateRight(words[index - 15], 7) ^
|
||||
rotateRight(words[index - 15], 18) ^
|
||||
(words[index - 15] >>> 3);
|
||||
const sigma1 =
|
||||
rotateRight(words[index - 2], 17) ^
|
||||
rotateRight(words[index - 2], 19) ^
|
||||
(words[index - 2] >>> 10);
|
||||
words[index] =
|
||||
(words[index - 16] + sigma0 + words[index - 7] + sigma1) >>> 0;
|
||||
}
|
||||
|
||||
let [a, b, c, d, e, f, g, h] = state;
|
||||
for (let index = 0; index < 64; index += 1) {
|
||||
const sum1 = rotateRight(e, 6) ^ rotateRight(e, 11) ^ rotateRight(e, 25);
|
||||
const choose = (e & f) ^ (~e & g);
|
||||
const temporary1 =
|
||||
(h + sum1 + choose + SHA256_ROUND_CONSTANTS[index] + words[index]) >>> 0;
|
||||
const sum0 = rotateRight(a, 2) ^ rotateRight(a, 13) ^ rotateRight(a, 22);
|
||||
const majority = (a & b) ^ (a & c) ^ (b & c);
|
||||
const temporary2 = (sum0 + majority) >>> 0;
|
||||
h = g;
|
||||
g = f;
|
||||
f = e;
|
||||
e = (d + temporary1) >>> 0;
|
||||
d = c;
|
||||
c = b;
|
||||
b = a;
|
||||
a = (temporary1 + temporary2) >>> 0;
|
||||
}
|
||||
|
||||
state[0] = (state[0] + a) >>> 0;
|
||||
state[1] = (state[1] + b) >>> 0;
|
||||
state[2] = (state[2] + c) >>> 0;
|
||||
state[3] = (state[3] + d) >>> 0;
|
||||
state[4] = (state[4] + e) >>> 0;
|
||||
state[5] = (state[5] + f) >>> 0;
|
||||
state[6] = (state[6] + g) >>> 0;
|
||||
state[7] = (state[7] + h) >>> 0;
|
||||
}
|
||||
|
||||
return state.map((word) => word.toString(16).padStart(8, "0")).join("");
|
||||
}
|
||||
|
||||
async function sha256Hex(bytes, cryptoImplementation) {
|
||||
if (!cryptoImplementation?.subtle || typeof cryptoImplementation.subtle.digest !== "function") {
|
||||
throw new DataContractError("This browser cannot verify dashboard asset integrity.");
|
||||
return sha256HexFallback(bytes);
|
||||
}
|
||||
let digest;
|
||||
try {
|
||||
|
||||
@ -6,6 +6,7 @@
|
||||
"description": "Dependency-free static dashboard for approved Utah Vehicle Health aggregates.",
|
||||
"scripts": {
|
||||
"start": "node server.mjs",
|
||||
"start:lan": "node server.mjs --lan",
|
||||
"test": "node --test tests/contract.test.mjs"
|
||||
},
|
||||
"engines": {
|
||||
|
||||
@ -1,10 +1,12 @@
|
||||
import { createReadStream, realpathSync, statSync } from "node:fs";
|
||||
import { createServer } from "node:http";
|
||||
import { networkInterfaces } from "node:os";
|
||||
import path from "node:path";
|
||||
import { fileURLToPath } from "node:url";
|
||||
|
||||
const ROOT = realpathSync(path.dirname(fileURLToPath(import.meta.url)));
|
||||
const HOST = process.env.HOST || "127.0.0.1";
|
||||
const LAN_MODE = process.argv.includes("--lan");
|
||||
const HOST = LAN_MODE ? "0.0.0.0" : process.env.HOST || "127.0.0.1";
|
||||
const PORT = Number.parseInt(process.env.PORT || "4173", 10);
|
||||
const TYPES = new Map([
|
||||
[".css", "text/css; charset=utf-8"],
|
||||
@ -50,6 +52,28 @@ function safeFile(requestUrl) {
|
||||
}
|
||||
}
|
||||
|
||||
function displayUrls(host, port) {
|
||||
if (host !== "0.0.0.0") {
|
||||
const displayHost = host === "::" ? "::1" : host;
|
||||
const urlHost = displayHost.includes(":") ? `[${displayHost}]` : displayHost;
|
||||
return [{ label: "Open", url: `http://${urlHost}:${port}` }];
|
||||
}
|
||||
|
||||
const entries = [{ label: "This computer", url: `http://127.0.0.1:${port}` }];
|
||||
const seen = new Set();
|
||||
for (const [interfaceName, addresses] of Object.entries(networkInterfaces())) {
|
||||
for (const address of addresses || []) {
|
||||
if (address.family !== "IPv4" || address.internal || seen.has(address.address)) continue;
|
||||
seen.add(address.address);
|
||||
entries.push({
|
||||
label: `Other devices (${interfaceName})`,
|
||||
url: `http://${address.address}:${port}`,
|
||||
});
|
||||
}
|
||||
}
|
||||
return entries;
|
||||
}
|
||||
|
||||
const server = createServer((request, response) => {
|
||||
if (!request.url || !["GET", "HEAD"].includes(request.method || "")) {
|
||||
response.writeHead(405, { Allow: "GET, HEAD" });
|
||||
@ -66,7 +90,7 @@ const server = createServer((request, response) => {
|
||||
const extension = path.extname(file).toLowerCase();
|
||||
response.writeHead(200, {
|
||||
"Content-Type": TYPES.get(extension) || "application/octet-stream",
|
||||
"Cache-Control": extension === ".json" ? "no-store" : "public, max-age=300",
|
||||
"Cache-Control": "no-store",
|
||||
"X-Content-Type-Options": "nosniff",
|
||||
"Referrer-Policy": "no-referrer",
|
||||
});
|
||||
@ -77,6 +101,22 @@ const server = createServer((request, response) => {
|
||||
createReadStream(file).on("error", () => response.destroy()).pipe(response);
|
||||
});
|
||||
|
||||
server.listen(PORT, HOST, () => {
|
||||
process.stdout.write(`Utah Vehicle Health dashboard: http://${HOST}:${PORT}\n`);
|
||||
server.on("error", (error) => {
|
||||
if (error.code === "EADDRINUSE") {
|
||||
process.stderr.write(
|
||||
`Dashboard port ${PORT} is already in use. Stop the existing server or set a different PORT.\n`,
|
||||
);
|
||||
} else {
|
||||
process.stderr.write(`Dashboard server could not start (${error.code || "unknown error"}).\n`);
|
||||
}
|
||||
process.exitCode = 1;
|
||||
});
|
||||
|
||||
server.listen(PORT, HOST, () => {
|
||||
const address = server.address();
|
||||
const actualPort = typeof address === "object" && address ? address.port : PORT;
|
||||
const urls = displayUrls(HOST, actualPort)
|
||||
.map(({ label, url }) => ` ${label}: ${url}`)
|
||||
.join("\n");
|
||||
process.stdout.write(`Utah Vehicle Health dashboard:\n${urls}\n`);
|
||||
});
|
||||
|
||||
@ -214,6 +214,32 @@ test("browser loader verifies every raw asset digest before rendering", async ()
|
||||
);
|
||||
});
|
||||
|
||||
test("LAN HTTP loader verifies asset digests without Web Crypto", async () => {
|
||||
const validated = await loadDashboardData({
|
||||
basePath: "http://dashboard.test/public/data/",
|
||||
fetchImplementation: publicDataFetch(),
|
||||
cryptoImplementation: null,
|
||||
});
|
||||
assert.equal(validated.manifest.schema_version, SCHEMA_VERSION);
|
||||
|
||||
const changedOverview = Buffer.concat([
|
||||
readFileSync(path.join(PUBLIC_DATA, REQUIRED_ASSETS.overview)),
|
||||
Buffer.from("\n"),
|
||||
]);
|
||||
await assert.rejects(
|
||||
loadDashboardData({
|
||||
basePath: "http://dashboard.test/public/data/",
|
||||
fetchImplementation: publicDataFetch(
|
||||
new Map([[REQUIRED_ASSETS.overview, changedOverview]]),
|
||||
),
|
||||
cryptoImplementation: null,
|
||||
}),
|
||||
(error) =>
|
||||
error instanceof DataContractError &&
|
||||
/Integrity verification failed for overview_period_county\.json/.test(error.message),
|
||||
);
|
||||
});
|
||||
|
||||
test("sha256 manifest covers and matches every approved data asset", () => {
|
||||
const manifest = json(REQUIRED_ASSETS.shaManifest);
|
||||
const expectedNames = Object.entries(REQUIRED_ASSETS)
|
||||
|
||||
@ -63,7 +63,7 @@ flowchart TD
|
||||
end
|
||||
EXPORT -.-> GUARDRAILS
|
||||
|
||||
SITE --> BOLT[Separate dashboard-only<br/>Bolt project]
|
||||
SITE --> DEMO[Allowlisted local static server<br/>same-network live demo]
|
||||
|
||||
subgraph TESTS[Verification]
|
||||
direction LR
|
||||
@ -80,5 +80,5 @@ flowchart TD
|
||||
|
||||
class SOURCE,SAMPLE,STAGE,EPISODES,MART,PRIVATE,REPORT private
|
||||
class LOGISTIC,TREE model
|
||||
class PUBLIC,CONTRACT,SITE,VIEWS,BOLT public
|
||||
class PUBLIC,CONTRACT,SITE,VIEWS,DEMO public
|
||||
class GUARDRAILS warning
|
||||
|
||||
@ -2,6 +2,11 @@
|
||||
|
||||
Last reviewed: 2026-07-21
|
||||
|
||||
> **Not part of the current delivery plan.** The presentation now uses the
|
||||
> same-network live-demo procedure in [demo_script.md](demo_script.md). This
|
||||
> checklist is retained only as a boundary reference if public hosting is
|
||||
> reconsidered later.
|
||||
|
||||
## Non-negotiable publication boundary
|
||||
|
||||
Publish a **separate Bolt project or GitHub repository containing only the
|
||||
|
||||
@ -121,13 +121,11 @@ developer console.
|
||||
- Preserve the warning banner and fail-closed unavailable state on desktop and
|
||||
mobile layouts.
|
||||
|
||||
## Deployment boundary
|
||||
## Demo boundary
|
||||
|
||||
Bolt receives a separate dashboard-only project whose root is the contents of
|
||||
`dashboard/`. Do not import or upload the full private-pipeline repository and
|
||||
do not rely on a configurable subdirectory working root. The deployable project
|
||||
contains no `.env`, private `data/`, model artifacts, SQL, pipeline scripts, or
|
||||
repository history outside the dashboard directory.
|
||||
|
||||
See [bolt_deployment.md](bolt_deployment.md) for the reviewed handoff and
|
||||
post-publication checks.
|
||||
The current delivery is a same-network live demo started with
|
||||
`node dashboard/server.mjs --lan`. The local server resolves files only beneath
|
||||
`dashboard/` and serves only its explicit shell, JavaScript, stylesheet, and
|
||||
approved aggregate allowlist. It does not expose `.env`, private `data/`, model
|
||||
artifacts, SQL, pipeline scripts, or repository history. Stop the server after
|
||||
the demo; no public hosting step is required.
|
||||
|
||||
@ -5,16 +5,22 @@ Target length: **2 minutes 45 seconds**, embedded in the
|
||||
|
||||
## Before the audience arrives
|
||||
|
||||
1. From the repository root, run `node dashboard/server.mjs`.
|
||||
2. Open `http://127.0.0.1:4173/#overview`.
|
||||
3. Confirm the header says the sample aggregates validated.
|
||||
4. Confirm the private-sample development-preview banner is visible.
|
||||
5. Visit all four routes: Overview, Sample cohorts, Model & benchmark, and Data
|
||||
1. Put the presenting computer and demo computer on the same trusted network.
|
||||
2. Stop any older dashboard process, then from the repository root run
|
||||
`node dashboard/server.mjs --lan`.
|
||||
3. On the demo computer, open the printed **Other devices** URL for the shared
|
||||
Wi-Fi or Ethernet interface with `/#overview` appended. On the presenting
|
||||
computer, use the printed **This computer** URL.
|
||||
4. Confirm the header says **Validated sample aggregates**. If an older copy of
|
||||
the JavaScript was previously loaded, hard-refresh the page once.
|
||||
5. Confirm the private-sample development-preview banner is visible.
|
||||
6. Visit all four routes: Overview, Sample cohorts, Model & benchmark, and Data
|
||||
& methods.
|
||||
6. Reset Sample cohorts and leave its sort on largest support.
|
||||
7. Reset Sample cohorts and leave its sort on largest support.
|
||||
|
||||
Do not open developer tools, private files, model artifacts, database clients,
|
||||
or environment variables during the presentation.
|
||||
or environment variables during the presentation. Stop the server with
|
||||
`Ctrl-C` when the demo is over.
|
||||
|
||||
## Live talk track
|
||||
|
||||
|
||||
@ -172,7 +172,8 @@ The public boundary is intentionally narrow:
|
||||
private read-only source
|
||||
-> private local extraction and model pipeline
|
||||
-> suppression-reviewed aggregate JSON
|
||||
-> dashboard-only static Bolt project
|
||||
-> allowlisted local static server
|
||||
-> same-network demo browser
|
||||
```
|
||||
|
||||
The public dashboard and presentation materials contain no VIN, plate, ZIP,
|
||||
@ -180,10 +181,9 @@ station, technician identifier, private vehicle token, raw JSON, credential,
|
||||
operational record, or row-level prediction. The browser never connects to the
|
||||
source database.
|
||||
|
||||
Bolt publication must use a separate project or repository containing only the
|
||||
contents of `dashboard/`, with `index.html` at its root. The full source
|
||||
repository must not be imported into Bolt. The reviewed procedure is in
|
||||
[bolt_deployment.md](bolt_deployment.md).
|
||||
The presentation uses `node dashboard/server.mjs --lan` only for the live demo.
|
||||
The server has an explicit dashboard-file allowlist and is stopped afterward;
|
||||
the prototype is not published to a public host.
|
||||
|
||||
## Limitations
|
||||
|
||||
@ -222,4 +222,4 @@ ranking, production model, diagnosis, or individual decision tool.
|
||||
- [Private-to-public architecture](architecture.mmd)
|
||||
- [10-minute presentation outline](presentation_outline.md)
|
||||
- [Dashboard demo script](demo_script.md)
|
||||
- [Dashboard-only Bolt deployment](bolt_deployment.md)
|
||||
- [Optional public-hosting notes](bolt_deployment.md)
|
||||
|
||||
@ -22,7 +22,7 @@ model** and histogram gradient boosting as the **benchmark**.
|
||||
| 3:10-4:20 | 5. Model comparison | Logistic final versus prevalence/previous-outcome baselines and boosted-tree benchmark |
|
||||
| 4:20-4:50 | 6. Model decision | Calibrated logistic wins the declared sample comparison and is easier to explain |
|
||||
| 4:50-7:35 | Live dashboard demo | Overview, Sample cohorts, Model & benchmark, Data & methods |
|
||||
| 7:35-8:30 | 7. Privacy architecture | Private pipeline to suppressed aggregate JSON to dashboard-only Bolt project |
|
||||
| 7:35-8:30 | 7. Privacy architecture | Private pipeline to suppressed aggregate JSON to an allowlisted same-network demo server |
|
||||
| 8:30-9:20 | 8. Limitations | Sampling, feed coverage, source/time confounding, heterogeneous non-pass, one-time holdout |
|
||||
| 9:20-10:00 | 9. Close | Prototype is complete and honest about what it can—and cannot—claim |
|
||||
|
||||
@ -124,7 +124,7 @@ Show:
|
||||
|
||||
```text
|
||||
private read-only data -> local modeling -> suppressed aggregate JSON
|
||||
-> dashboard-only Bolt project
|
||||
-> allowlisted local static server -> same-network demo browser
|
||||
```
|
||||
|
||||
State that the public bundle has no VINs, plates, ZIPs, stations, technician
|
||||
@ -159,8 +159,11 @@ cannot establish.
|
||||
|
||||
## Presentation checklist
|
||||
|
||||
- Start the local dashboard before presenting: `node dashboard/server.mjs`.
|
||||
- Open `http://127.0.0.1:4173/#overview` and close unrelated browser tabs.
|
||||
- Put both computers on the same trusted network, stop any older dashboard
|
||||
process, and run `node dashboard/server.mjs --lan`.
|
||||
- On the demo computer, open the printed **Other devices** URL for the shared
|
||||
Wi-Fi or Ethernet interface with `/#overview` appended. Close unrelated
|
||||
browser tabs.
|
||||
- Use a fresh page load to confirm aggregate validation succeeds.
|
||||
- Keep a screenshot or short recording as a backup, with the sample warning
|
||||
visible.
|
||||
|
||||
@ -69,8 +69,8 @@ is not a second final model and is not used to drive the product.
|
||||
5. Histogram gradient boosting as a benchmark only.
|
||||
6. Chronological development evaluation with an explicit one-time 2025 gate.
|
||||
7. Suppression-reviewed static dashboard assets with fail-closed validation.
|
||||
8. Model card, final report, presentation materials, and dashboard-only Bolt
|
||||
deployment instructions.
|
||||
8. Model card, final report, presentation materials, and same-network dashboard
|
||||
demo instructions.
|
||||
|
||||
## Acceptance criteria
|
||||
|
||||
|
||||
Loading…
Reference in New Issue
Block a user