Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
"version": "0.1.0",
"private": true,
"scripts": {
"fetch": "node scripts/fetch-release.mjs && node scripts/fetch-engine-docs.mjs && node scripts/fetch-discussions.mjs && node scripts/fetch-milestones.mjs",
"fetch": "node scripts/fetch-release.mjs && node scripts/fetch-engine-docs.mjs && node scripts/fetch-discussions.mjs && node scripts/fetch-milestones.mjs && node scripts/build-fee-data.mjs",
"dev": "npm run fetch && astro dev",
"build": "npm run fetch && astro build",
"preview": "astro preview",
Expand Down
78 changes: 78 additions & 0 deletions scripts/build-fee-data.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
#!/usr/bin/env node
/**
* #136 — Fee Reality experiment data builder.
*
* Parses recorded experiment numbers from src/content/engine-docs/
* (specifically experiment-rule-family-significance.md) and updates
* data/fee-reality-experiments.json at build time.
*
* Every number in the Fee Reality widget is traceable directly to
* keel's recorded experiment docs.
*/
import { readFileSync, writeFileSync, existsSync } from "node:fs";
import { join, dirname } from "node:path";
import { fileURLToPath } from "node:url";

const root = dirname(dirname(fileURLToPath(import.meta.url)));
const docFile = join(root, "src/content/engine-docs/experiment-rule-family-significance.md");
const outFile = join(root, "data/fee-reality-experiments.json");

let currentData = null;
if (existsSync(outFile)) {
try {
currentData = JSON.parse(readFileSync(outFile, "utf8"));
} catch (err) {
// fallback
}
}

if (!existsSync(docFile)) {
console.log(" fee-data: experiment doc absent, using existing fallback data");
process.exit(0);
}

try {
const content = readFileSync(docFile, "utf8");
const lines = content.split("\n");
const families = currentData?.families || {};

for (const line of lines) {
if (!line.startsWith("|")) continue;
const parts = line.split("|").map((p) => p.trim());
if (parts.length < 12) continue;

const familyKey = parts[1];
const regime = parts[2];
const n = parseInt(parts[3].replace(/,/g, ""), 10);

Check warning on line 46 in scripts/build-fee-data.mjs

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Prefer `String#replaceAll()` over `String#replace()`.

See more on https://sonarcloud.io/project/issues?id=CodeGateSoftware_keeltrading.com&issues=AaDXGK4qAduN7hzM7dj3&open=AaDXGK4qAduN7hzM7dj3&pullRequest=145

Check warning on line 46 in scripts/build-fee-data.mjs

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Prefer `Number.parseInt` over `parseInt`.

See more on https://sonarcloud.io/project/issues?id=CodeGateSoftware_keeltrading.com&issues=AaDXGK4qAduN7hzM7dj2&open=AaDXGK4qAduN7hzM7dj2&pullRequest=145
const nEff = parseFloat(parts[4].replace(/,/g, ""));

Check warning on line 47 in scripts/build-fee-data.mjs

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Prefer `String#replaceAll()` over `String#replace()`.

See more on https://sonarcloud.io/project/issues?id=CodeGateSoftware_keeltrading.com&issues=AaDXGK4qAduN7hzM7dj5&open=AaDXGK4qAduN7hzM7dj5&pullRequest=145

Check warning on line 47 in scripts/build-fee-data.mjs

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Prefer `Number.parseFloat` over `parseFloat`.

See more on https://sonarcloud.io/project/issues?id=CodeGateSoftware_keeltrading.com&issues=AaDXGK4qAduN7hzM7dj4&open=AaDXGK4qAduN7hzM7dj4&pullRequest=145
const payoff = parseFloat(parts[5]);

Check warning on line 48 in scripts/build-fee-data.mjs

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Prefer `Number.parseFloat` over `parseFloat`.

See more on https://sonarcloud.io/project/issues?id=CodeGateSoftware_keeltrading.com&issues=AaDXGK4qAduN7hzM7dj6&open=AaDXGK4qAduN7hzM7dj6&pullRequest=145
const breakEven = parseFloat(parts[6]);

Check warning on line 49 in scripts/build-fee-data.mjs

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Prefer `Number.parseFloat` over `parseFloat`.

See more on https://sonarcloud.io/project/issues?id=CodeGateSoftware_keeltrading.com&issues=AaDXGK4qAduN7hzM7dj7&open=AaDXGK4qAduN7hzM7dj7&pullRequest=145
const winRate = parseFloat(parts[7]);

Check warning on line 50 in scripts/build-fee-data.mjs

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Prefer `Number.parseFloat` over `parseFloat`.

See more on https://sonarcloud.io/project/issues?id=CodeGateSoftware_keeltrading.com&issues=AaDXGK4qAduN7hzM7dj8&open=AaDXGK4qAduN7hzM7dj8&pullRequest=145
const edge = parseFloat(parts[8].replace(/\*\*/g, "").replace(/−/g, "-"));

Check warning on line 51 in scripts/build-fee-data.mjs

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Prefer `Number.parseFloat` over `parseFloat`.

See more on https://sonarcloud.io/project/issues?id=CodeGateSoftware_keeltrading.com&issues=AaDXGK4qAduN7hzM7dj9&open=AaDXGK4qAduN7hzM7dj9&pullRequest=145

Check warning on line 51 in scripts/build-fee-data.mjs

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Prefer `String#replaceAll()` over `String#replace()`.

See more on https://sonarcloud.io/project/issues?id=CodeGateSoftware_keeltrading.com&issues=AaDXGK4qAduN7hzM7dj_&open=AaDXGK4qAduN7hzM7dj_&pullRequest=145

Check warning on line 51 in scripts/build-fee-data.mjs

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Prefer `String#replaceAll()` over `String#replace()`.

See more on https://sonarcloud.io/project/issues?id=CodeGateSoftware_keeltrading.com&issues=AaDXGK4qAduN7hzM7dj-&open=AaDXGK4qAduN7hzM7dj-&pullRequest=145

if (!families[familyKey]) continue;

const targetKey = regime.includes("outside") ? "outside_allowance_120bp" : "inside_allowance_0bp";
families[familyKey][targetKey] = {
...families[familyKey][targetKey],
payoff_b: payoff,
break_even_win_rate: breakEven,
observed_win_rate: winRate,
edge: edge,
};
if (!isNaN(n)) families[familyKey].n_pooled = n;

Check warning on line 63 in scripts/build-fee-data.mjs

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Prefer `Number.isNaN` over `isNaN`.

See more on https://sonarcloud.io/project/issues?id=CodeGateSoftware_keeltrading.com&issues=AaDXGK4qAduN7hzM7dkA&open=AaDXGK4qAduN7hzM7dkA&pullRequest=145
if (!isNaN(nEff)) families[familyKey].n_eff = nEff;

Check warning on line 64 in scripts/build-fee-data.mjs

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Prefer `Number.isNaN` over `isNaN`.

See more on https://sonarcloud.io/project/issues?id=CodeGateSoftware_keeltrading.com&issues=AaDXGK4qAduN7hzM7dkB&open=AaDXGK4qAduN7hzM7dkB&pullRequest=145
}

const output = {
updatedAt: new Date().toISOString(),
sourceDoc: "docs/experiments/2026-08-21-rule-family-significance.md",
sourceUrl: "https://github.com/CodeGateSoftware/keel/blob/main/docs/experiments/2026-08-21-rule-family-significance.md",
families,
};

writeFileSync(outFile, JSON.stringify(output, null, 2) + "\n");
console.log(" wrote data/fee-reality-experiments.json (synced from experiment docs)");
} catch (err) {
console.warn(" fee-data warning:", err.message, "— retaining fallback data");
}
Loading
Loading