Skip to content

Commit 3954bbf

Browse files
sdieguez-wotcclaude
andcommitted
refactor(benchmarks): address PR #15 code-review findings
- neuron-plugins: drop extraneous 6th constructor arg in resolveParam (StateNumberParameter takes 5). - neuron-js adapter: add default case throwing on unhandled scenario. - cold-start: adapters now expose a named `adapter` export; cold-start-child imports `{ adapter }` instead of Object.values(...)[0]. - extract shared readPath into harness/read-path.ts; reuse in neuron-plugins and the hand-coded adapter (removes duplication). - scenarios: pricing decide/canonical derive from shared PRICING_* constants instead of a hardcoded subtotal duplicated in data. Re-ran yarn benchmark + benchmark:charts; lint/test/examples/build/docs:build all green; no chaos-vault strings in dist. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
1 parent 2f5894a commit 3954bbf

16 files changed

Lines changed: 324 additions & 303 deletions
Lines changed: 4 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,6 @@
1+
import { readPath } from "../read-path.ts";
12
import type { Adapter, Decision, Runner, ScenarioDef } from "../types.ts";
23

3-
function readPath(data: Record<string, unknown>, path: string): unknown {
4-
return path.split(".").reduce<unknown>((current, segment) => {
5-
if (current && typeof current === "object" && segment in current) {
6-
return (current as Record<string, unknown>)[segment];
7-
}
8-
return undefined;
9-
}, data);
10-
}
11-
124
/**
135
* Hand-coded TypeScript baseline. No engine: a direct `>=` comparison, the
146
* floor cost any rules engine is measured against.
@@ -23,3 +15,6 @@ export const handCodedAdapter: Adapter = {
2315
};
2416
},
2517
};
18+
19+
/** Canonical export consumed by the cold-start probe. */
20+
export const adapter = handCodedAdapter;

benchmarks/harness/adapters/json-logic-js.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,3 +18,6 @@ export const jsonLogicAdapter: Adapter = {
1818
};
1919
},
2020
};
21+
22+
/** Canonical export consumed by the cold-start probe. */
23+
export const adapter = jsonLogicAdapter;

benchmarks/harness/adapters/json-rules-engine.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,3 +29,6 @@ export const jsonRulesEngineAdapter: Adapter = {
2929
};
3030
},
3131
};
32+
33+
/** Canonical export consumed by the cold-start probe. */
34+
export const adapter = jsonRulesEngineAdapter;

benchmarks/harness/adapters/neuron-js.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,14 @@ export const neuronJsAdapter: Adapter = {
5757
slaHours: workflow.slaHours as number,
5858
};
5959
}
60+
default:
61+
throw new Error(
62+
`Unhandled scenario in neuronJsAdapter: ${scenario.id}`,
63+
);
6064
}
6165
};
6266
},
6367
};
68+
69+
/** Canonical export consumed by the cold-start probe. */
70+
export const adapter = neuronJsAdapter;

benchmarks/harness/adapters/rule-engine-js.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,3 +19,6 @@ export const ruleEngineJsAdapter: Adapter = {
1919
};
2020
},
2121
};
22+
23+
/** Canonical export consumed by the cold-start probe. */
24+
export const adapter = ruleEngineJsAdapter;

benchmarks/harness/cold-start-child.ts

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -12,11 +12,9 @@ const scenario =
1212
scenarios.find((item) => item.id === scenarioId) ?? scenarios[0];
1313

1414
const start = performance.now();
15-
const moduleExports = (await import(`./adapters/${adapterFile}.ts`)) as Record<
16-
string,
17-
Adapter
18-
>;
19-
const adapter = Object.values(moduleExports)[0];
15+
const { adapter } = (await import(`./adapters/${adapterFile}.ts`)) as {
16+
adapter: Adapter;
17+
};
2018
const runner = adapter.prepare(scenario);
2119
await runner();
2220
const ms = performance.now() - start;

benchmarks/harness/neuron-plugins.ts

Lines changed: 2 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -13,17 +13,13 @@ import {
1313
type Neuron,
1414
type ParameterInterface,
1515
} from "../../dist/esm/index.js";
16+
import { readPath } from "./read-path.ts";
1617

1718
export function readStatePath(
1819
context: ExecutionContext,
1920
path: string,
2021
): unknown {
21-
return path.split(".").reduce<unknown>((current, segment) => {
22-
if (current && typeof current === "object" && segment in current) {
23-
return (current as Record<string, unknown>)[segment];
24-
}
25-
return undefined;
26-
}, context.state);
22+
return readPath(context.state, path);
2723
}
2824

2925
export class StateNumberParameter {
@@ -71,7 +67,6 @@ function resolveParam(
7167
param.name,
7268
param.value,
7369
param.options,
74-
param.defaultValue,
7570
).getValue(context)
7671
: null;
7772
}

benchmarks/harness/read-path.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
/** Reads a dot-path (e.g. "cart.subtotal") from a nested object; undefined if absent. */
2+
export function readPath(source: unknown, path: string): unknown {
3+
return path.split(".").reduce<unknown>((current, segment) => {
4+
if (current && typeof current === "object" && segment in current) {
5+
return (current as Record<string, unknown>)[segment];
6+
}
7+
return undefined;
8+
}, source);
9+
}

benchmarks/harness/scenarios.ts

Lines changed: 15 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,12 @@ import workflowScript from "../../examples/workflow-routing/rules.json" with {
1919
};
2020
import type { Decision, ScenarioDef } from "./types.ts";
2121

22+
// Pricing inputs, declared once so the scenario data and the decision derivation
23+
// stay in sync (matches examples/pricing-rules).
24+
const PRICING_SUBTOTAL = 125;
25+
const PRICING_PERCENT = 16;
26+
const PRICING_DISCOUNT = Math.round(PRICING_SUBTOTAL * (PRICING_PERCENT / 100));
27+
2228
/**
2329
* The three NJS-GROWTH-07 scenarios. Each is a single numeric-threshold decision
2430
* whose canonical output matches the runnable example expected-output fixtures, so
@@ -29,22 +35,24 @@ export const scenarios: ScenarioDef[] = [
2935
id: "pricing-discount",
3036
neuronScript: pricingScript,
3137
neuronInput: pricingInput as ExecutionContext,
32-
data: { cart: { subtotal: 125, currency: "USD" } },
38+
data: { cart: { subtotal: PRICING_SUBTOTAL, currency: "USD" } },
3339
factPath: "cart.subtotal",
34-
flatFacts: { subtotal: 125 },
40+
flatFacts: { subtotal: PRICING_SUBTOTAL },
3541
flatFactName: "subtotal",
3642
threshold: 100,
3743
decide(matched: boolean): Decision {
3844
if (!matched) return { matched: false };
39-
const subtotal = 125;
40-
const discountAmount = Math.round(subtotal * (16 / 100));
4145
return {
4246
matched: true,
43-
discountAmount,
44-
finalTotal: subtotal - discountAmount,
47+
discountAmount: PRICING_DISCOUNT,
48+
finalTotal: PRICING_SUBTOTAL - PRICING_DISCOUNT,
4549
};
4650
},
47-
canonical: { matched: true, discountAmount: 20, finalTotal: 105 },
51+
canonical: {
52+
matched: true,
53+
discountAmount: PRICING_DISCOUNT,
54+
finalTotal: PRICING_SUBTOTAL - PRICING_DISCOUNT,
55+
},
4856
},
4957
{
5058
id: "eligibility-approval",

0 commit comments

Comments
 (0)