{"_id":"@console-one/disambiguator","_rev":"3-5c860c12b6ac36b33445afb49111160e","name":"@console-one/disambiguator","dist-tags":{"latest":"0.1.1"},"versions":{"0.1.0":{"name":"@console-one/disambiguator","version":"0.1.0","keywords":["disambiguator","decision-tree","probability","priority-queue","authorization","classifier","weighted-evaluation","probabilistic"],"author":{"name":"Andrew Chalmers"},"license":"MIT","_id":"@console-one/disambiguator@0.1.0","maintainers":[{"name":"andrewchalms","email":"andrew@netos.one"}],"contributors":[{"name":"Jayden Nikifork"}],"homepage":"https://github.com/console-one/disambiguator#readme","bugs":{"url":"https://github.com/console-one/disambiguator/issues"},"dist":{"shasum":"797b3e7325f73ffa5b9e4e6f4942ba22f6866875","tarball":"https://registry.npmjs.org/@console-one/disambiguator/-/disambiguator-0.1.0.tgz","fileCount":52,"integrity":"sha512-pbdQdWNmgk87a+/W/boCFuHW1U5vSfQmqXNKGTLfPOIf3BFsBqBWWcDulFgGjDTZIRKdLNuexywVYmoInOy6Vg==","signatures":[{"sig":"MEQCIDo0p0G718m61cKfgr7RKIhEA8eQtBSAbN/jWkd6iJwyAiA37wCbCnw1ST9bsQFWaPLUbh3wSY/cB/eorTIjTJJd6w==","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":127290},"main":"./dist/index.js","type":"module","types":"./dist/index.d.ts","exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.js"}},"gitHead":"2ee83dfa0987955c20f0f142ee4093db6c9a55b2","scripts":{"test":"npm run smoke","build":"tsc","clean":"rm -rf dist","smoke":"node dist/smoke.js","prepublishOnly":"npm run clean && npm run build && npm run smoke"},"_npmUser":{"name":"andrewchalms","email":"andrew@netos.one"},"repository":{"url":"git+https://github.com/console-one/disambiguator.git","type":"git"},"_npmVersion":"10.9.2","description":"Heap-based probabilistic decision-tree executor. Traverses weighted nodes in parallel; propagates probabilities from leaves to root via AND/OR/NOT switches; prunes dead branches. Pluggable leaf validators and storage.","directories":{},"_nodeVersion":"22.17.0","dependencies":{"heap-js":"^2.7.1"},"_hasShrinkwrap":false,"devDependencies":{"typescript":"^5.9.3","@types/node":"^20.0.0"},"_npmOperationalInternal":{"tmp":"tmp/disambiguator_0.1.0_1776922111756_0.03370971509353593","host":"s3://npm-registry-packages-npm-production"}},"0.1.1":{"name":"@console-one/disambiguator","version":"0.1.1","keywords":["disambiguator","decision-tree","probability","priority-queue","authorization","classifier","weighted-evaluation","probabilistic"],"author":{"name":"Andrew Chalmers"},"license":"MIT","_id":"@console-one/disambiguator@0.1.1","maintainers":[{"name":"andrewchalms","email":"andrew@netos.one"}],"contributors":[{"name":"Jayden Nikifork"}],"homepage":"https://github.com/console-one/disambiguator#readme","bugs":{"url":"https://github.com/console-one/disambiguator/issues"},"dist":{"shasum":"945ecbbf7944fc4e1c11a3ee1d09cc4a1e759015","tarball":"https://registry.npmjs.org/@console-one/disambiguator/-/disambiguator-0.1.1.tgz","fileCount":64,"integrity":"sha512-o+F8ALstXHqG17LXe+iPA4v7SLJpJqi+D/mvhnd3RPKoIyKCy8Oq5eGzHhXe/PLXkqSNZ6cgodYNor44ttdpWw==","signatures":[{"sig":"MEUCIQDjvCzJhyaBZOpGJEUmCpiW40+V10r9pKzUnoK7HCANKAIgLv5FOqRfk8cC1h8KXcHyCAGLKJXWmUrWRFBi7Uf1Axg=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":138228},"main":"./dist/index.js","type":"module","types":"./dist/index.d.ts","exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.js"}},"gitHead":"ae15d45030073a8a8a420bf7e561bce14886ca48","scripts":{"test":"node dist/test-runner.js","build":"tsc -p tsconfig.build.json","clean":"rm -rf dist","smoke":"node dist/smoke.js","prepublishOnly":"npm run clean && npm run build && npm run test"},"_npmUser":{"name":"andrewchalms","email":"andrew@netos.one"},"repository":{"url":"git+https://github.com/console-one/disambiguator.git","type":"git"},"_npmVersion":"10.9.2","description":"Heap-based probabilistic decision-tree executor. Traverses weighted nodes in parallel; propagates probabilities from leaves to root via AND/OR/NOT switches; prunes dead branches. Pluggable leaf validators and storage.","directories":{},"_nodeVersion":"22.17.0","dependencies":{"heap-js":"^2.7.1","@console-one/assessable":"file:../assessable"},"_hasShrinkwrap":false,"devDependencies":{"typescript":"^5.9.3","@types/node":"^20.0.0"},"_npmOperationalInternal":{"tmp":"tmp/disambiguator_0.1.1_1777362596617_0.38748945822138814","host":"s3://npm-registry-packages-npm-production"}}},"time":{"created":"2026-04-23T05:28:31.637Z","modified":"2026-05-05T21:20:10.656Z","0.1.0":"2026-04-23T05:28:31.892Z","0.1.1":"2026-04-28T07:49:56.751Z"},"bugs":{"url":"https://github.com/console-one/disambiguator/issues"},"author":{"name":"Andrew Chalmers"},"license":"MIT","homepage":"https://github.com/console-one/disambiguator#readme","keywords":["disambiguator","decision-tree","probability","priority-queue","authorization","classifier","weighted-evaluation","probabilistic"],"repository":{"url":"git+https://github.com/console-one/disambiguator.git","type":"git"},"description":"Heap-based probabilistic decision-tree executor. Traverses weighted nodes in parallel; propagates probabilities from leaves to root via AND/OR/NOT switches; prunes dead branches. Pluggable leaf validators and storage.","contributors":[{"name":"Jayden Nikifork"}],"maintainers":[{"email":"andrew@netos.one","name":"andrewchalms"},{"email":"19ds6@queensu.ca","name":"damiensmith1"}],"readme":"# @console-one/disambiguator\n\nA heap-based, probabilistic decision-tree executor. Build a tree of `AND`/`OR`/`NOT` switches over weighted leaf nodes; supply a validator function for the leaves; `.execute()` walks the tree in parallel-greedy-weighted order, resolves leaves via your validator, propagates probabilities up the tree, and short-circuits dead branches.\n\nNot quite a rule engine, not quite a Bayesian network — it's an evaluator for decision trees where **each node has a probability**, execution order is prioritized by probability × node weight, and resolved outcomes flow back up through logical combinators.\n\nUse cases (from the original design doc):\n\n- Authorization trees where permissions cascade through nested AND/OR clauses\n- Medical-diagnosis-style decision trees where leaf probabilities change as evidence accumulates\n- ML classification pipelines where early nodes prune expensive later branches\n- Fraud / risk scoring where combining signals follows a declared logic tree\n\n## Install\n\n```bash\nnpm install @console-one/disambiguator\n```\n\n## Quick start\n\n```ts\nimport {\n  Disambiguator,\n  InMemoryDisambiguatorDAO,\n  NodeType,\n  Condition,\n  type RunValidation\n} from '@console-one/disambiguator'\n\n// 1. Build a DAO, seed the tree:\n//    rootAnd\n//      ├── userNode (matches user.id === 'alice')\n//      └── orAccess\n//            ├── adminNode (matches user.role === 'admin')\n//            └── guestNode (matches user.role === 'guest')\nconst dao = new InMemoryDisambiguatorDAO()\n  .seedNode({\n    id: 'user', type: NodeType.assessableJSON, probability: 0.5,\n    labels: ['userId'],\n    executionInput: { field: 'user.id', expected: 'alice' } as any\n  })\n  .seedNode({\n    id: 'admin', type: NodeType.assessableJSON, probability: 0.5,\n    labels: ['role'],\n    executionInput: { field: 'user.role', expected: 'admin' } as any\n  })\n  .seedNode({\n    id: 'guest', type: NodeType.assessableJSON, probability: 0.5,\n    labels: ['role'],\n    executionInput: { field: 'user.role', expected: 'guest' } as any\n  })\n  .seedSwitch({\n    id: 'orAccess', type: Condition.OR, probability: 0.5,\n    labels: ['access'], childIds: ['admin', 'guest'], size: 2\n  })\n  .seedSwitch({\n    id: 'rootAnd', type: Condition.AND, probability: 0.5,\n    labels: ['root'], childIds: ['user', 'orAccess'], size: 2\n  })\n\n// 2. Supply a leaf validator. The engine calls this for each Node as it\n//    resolves; return true/false (or a Promise of the same).\nconst runValidation: RunValidation = (_type, input, toValidate) => {\n  const { field, expected } = input as { field: string; expected: any }\n  return field.split('.').reduce((v, k) => v?.[k], toValidate) === expected\n}\n\n// 3. Execute.\nconst d = new Disambiguator(\n  'rootAnd',\n  () => ({ user: { id: 'alice', role: 'admin' } }),\n  [],\n  { dataAccessor: dao, runValidation, max_parallel: 3 }\n)\nconst result = await d.execute()\n// true — user matched + admin matched → AND → true\n```\n\n## How it works\n\n1. `execute()` loads the root Switch via the DAO.\n2. Root and its children are added to a min-heap ordered by `(-weight)` — the \"most promising unresolved node\" is always at the top.\n3. The engine pops up to `max_parallel` nodes at a time and executes them in parallel.\n4. When a Node resolves, its probability becomes 0 or 1 and its parent Switch recalculates its own probability via `calculateProbability(children)`.\n5. Updated probabilities propagate through subscriber chains to all ancestors.\n6. Pruned / resolved branches are removed from the heap; the loop continues until the heap is empty or the root has \"shorted\" (hit a terminal state for its combinator).\n\n## Public surface\n\n- `Disambiguator` — the executor. Constructor: `(id, getObjectToValidate, handlers, options)`.\n  - `options.dataAccessor` — required. Anything implementing `DisambiguatorDAO`.\n  - `options.runValidation` — required for leaves. `(type, executionInput, toValidate) => boolean | Promise<boolean>`.\n  - `options.max_parallel` — default 5.\n  - `options.switchFilter` — optionally gate which Switches get executed.\n- `InMemoryDisambiguatorDAO` — reference storage impl. Stores nodes and switches in Maps; includes a `.seedNode()` / `.seedSwitch()` fluent builder for tests.\n- `Node`, `Switch`, `And`, `Or`, `Not`, `Probability` — tree building blocks. Subclass for custom combinators; register via `registerSwitchImpl(condition, ctor)`.\n- `Condition` enum (`AND | OR | NOT`), `NodeType` enum (`assessableJSON | aggregation`).\n- `DisambiguatorDAO` interface — implement against Redis, Postgres, Neo4j, etc.\n- `RunValidation` type — caller-supplied leaf validator.\n- Types: `Validation`, `NodeDBProps`, `SwitchDBProps`, `ConditionRequirement`, `Requirement`, `Handler`, and the CRUD request shapes.\n\n## Storage adapters\n\nThe engine operates against `DisambiguatorDAO` — never against a specific vendor. The `InMemoryDisambiguatorDAO` (shipped) is the reference adapter; for production, implement the same interface against Redis / Postgres / Neo4j / Dynamo. The interface has 13 methods (5 CRUD × 2 entity types + 3 helpers), roughly 150 lines to implement.\n\nThe original source in the monorepo included a Neo4j-backed implementation (`impl/neo4j.ts`, ~300 lines) that used Cypher queries to express the tree. That implementation isn't shipped here — it pulled in `neo4j-driver` + `dotenv` as hard dependencies and was tied to environment-variable configuration. Reimplementing against Neo4j with the interface above is straightforward if you need it.\n\n## Layout\n\n```\nsrc/\n├── index.ts                # Public surface\n├── smoke.ts                # End-to-end smoke test\n├── disambiguator.ts        # The executor\n├── node.ts                 # Leaf Node class\n├── switch.ts               # Abstract Switch base + registry\n├── switches.ts             # And / Or / Not concrete combinators\n├── probability.ts          # Probability propagation machinery\n├── heap.ts                 # Heap helpers (init/resort/remove)\n├── interfaces.ts           # DisambiguatorDAO, RunValidation, …\n├── types.ts                # Enums + DB prop types\n├── utils.ts                # isConditionRequirement predicate\n└── impl/\n    └── memory.ts           # InMemoryDisambiguatorDAO reference impl\n```\n\n## Smoke test\n\n```bash\nnpm install\nnpm run build\nnpm run smoke\n```\n\nAsserts the engine runs end-to-end against the in-memory DAO, with AND + OR combinators resolving to `true` under matching inputs.\n\n## Known limitations\n\n- **Probability-propagation math in `And` / `Or` assumes the seeded initial probabilities on Switch nodes match the formula applied to children's initial probabilities.** If your seeded Switch probability differs from `reduce(childs, product)`, the incremental update formula (`newProb = this.probability / oldProb * newProb`) can drift and short-circuit prematurely. The safest seeding pattern for a fresh tree is: set all leaf probabilities to the same value `p`, set AND switches to `p^n`, and set OR switches to `1 - (1 - p)^n` — i.e., pre-compute the derived probabilities rather than blanket-seeding everything to 0.5. A proper fix would initialize Switch probabilities lazily from children at tree-load time; that's a behavior change beyond extraction scope.\n- **The `aggregation` NodeType** in the original code dispatched to an `runAggregator` helper that depended on the unshipped event-based transpiler and had a `// TODO: implement this function` marker on its core. Dropped. Supply your own runValidation for `NodeType.aggregation` if you want to keep that branch.\n- **Node / Switch caches are module-globals.** Two Disambiguator instances operating on the same node id will share the same cached instance. Fine for most uses; be aware if you reset state between tests.\n\n## Credit\n\nThe design, algorithm, and original implementation are by **Jayden Nikifork** in `src/core/authorization/disambiguator/` on the `transpilationNation` branch of the Console One monorepo (commit `edc46fa34`, 2024-08-23). This package ports that work to a standalone module with the Neo4j-specific DAO dropped in favor of a pluggable storage adapter.\n\nThe original README in that directory (281 lines) covers prior-art comparison, use cases across medical diagnosis / fraud detection / ML / financial risk, and patent-opportunity notes. It's preserved in `ORIGINAL_README.md` alongside this one.\n\n## License\n\nMIT\n","readmeFilename":"README.md"}