@causalmap/filter-engine 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE.md ADDED
@@ -0,0 +1,110 @@
1
+ # Functional Source License, Version 1.1, MIT Future License
2
+
3
+ ## Abbreviation
4
+
5
+ FSL-1.1-MIT
6
+
7
+ ## Notice
8
+
9
+ Copyright 2026 Causal Map Ltd
10
+
11
+ ## Terms and Conditions
12
+
13
+ ### Licensor ("We")
14
+
15
+ The party offering the Software under these Terms and Conditions.
16
+
17
+ ### The Software
18
+
19
+ The "Software" is each version of the software that we make available under
20
+ these Terms and Conditions, as indicated by our inclusion of these Terms and
21
+ Conditions with the Software.
22
+
23
+ ### License Grant
24
+
25
+ Subject to your compliance with this License Grant and the Patents,
26
+ Redistribution and Trademark clauses below, we hereby grant you the right to
27
+ use, copy, modify, create derivative works, publicly perform, publicly display
28
+ and redistribute the Software for any Permitted Purpose identified below.
29
+
30
+ ### Permitted Purpose
31
+
32
+ A Permitted Purpose is any purpose other than a Competing Use. A Competing Use
33
+ means making the Software available to others in a commercial product or
34
+ service that:
35
+
36
+ 1. substitutes for the Software;
37
+
38
+ 2. substitutes for any other product or service we offer using the Software
39
+ that exists as of the date we make the Software available; or
40
+
41
+ 3. offers the same or substantially similar functionality as the Software.
42
+
43
+ Permitted Purposes specifically include using the Software:
44
+
45
+ 1. for your internal use and access;
46
+
47
+ 2. for non-commercial education;
48
+
49
+ 3. for non-commercial research; and
50
+
51
+ 4. in connection with professional services that you provide to a licensee
52
+ using the Software in accordance with these Terms and Conditions.
53
+
54
+ ### Patents
55
+
56
+ To the extent your use for a Permitted Purpose would necessarily infringe our
57
+ patents, the license grant above includes a license under our patents. If you
58
+ make a claim against any party that the Software infringes or contributes to
59
+ the infringement of any patent, then your patent license to the Software ends
60
+ immediately.
61
+
62
+ ### Redistribution
63
+
64
+ The Terms and Conditions apply to all copies, modifications and derivatives of
65
+ the Software.
66
+
67
+ If you redistribute any copies, modifications or derivatives of the Software,
68
+ you must include a copy of or a link to these Terms and Conditions and not
69
+ remove any copyright notices provided in or with the Software.
70
+
71
+ ### Disclaimer
72
+
73
+ THE SOFTWARE IS PROVIDED "AS IS" AND WITHOUT WARRANTIES OF ANY KIND, EXPRESS OR
74
+ IMPLIED, INCLUDING WITHOUT LIMITATION WARRANTIES OF FITNESS FOR A PARTICULAR
75
+ PURPOSE, MERCHANTABILITY, TITLE OR NON-INFRINGEMENT.
76
+
77
+ IN NO EVENT WILL WE HAVE ANY LIABILITY TO YOU ARISING OUT OF OR RELATED TO THE
78
+ SOFTWARE, INCLUDING INDIRECT, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES,
79
+ EVEN IF WE HAVE BEEN INFORMED OF THEIR POSSIBILITY IN ADVANCE.
80
+
81
+ ### Trademarks
82
+
83
+ Except for displaying the License Details and identifying us as the origin of
84
+ the Software, you have no right under these Terms and Conditions to use our
85
+ trademarks, trade names, service marks or product names.
86
+
87
+ ## Grant of Future License
88
+
89
+ We hereby irrevocably grant you an additional license to use the Software under
90
+ the MIT license that is effective on the second anniversary of the date we make
91
+ the Software available. On or after that date, you may use the Software under
92
+ the MIT license, in which case the following will apply:
93
+
94
+ Permission is hereby granted, free of charge, to any person obtaining a copy of
95
+ this software and associated documentation files (the "Software"), to deal in
96
+ the Software without restriction, including without limitation the rights to
97
+ use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
98
+ of the Software, and to permit persons to whom the Software is furnished to do
99
+ so, subject to the following conditions:
100
+
101
+ The above copyright notice and this permission notice shall be included in all
102
+ copies or substantial portions of the Software.
103
+
104
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
105
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
106
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
107
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
108
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
109
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
110
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,121 @@
1
+ # @causalmap/filter-engine
2
+
3
+ Pure, dependency-free link-filter engine for causal maps. Give it a list of links and an ordered
4
+ list of filter configs; get the filtered links back. No DOM, no database, no framework. Plain ES
5
+ modules, Node 18+ or any modern browser.
6
+
7
+ This is the filtering core extracted from [Causal Map](https://causalmap.app). The code under
8
+ `src/` is a snapshot of the app's canonical engine, so its behaviour tracks the live product.
9
+
10
+ ## Licence
11
+
12
+ Source-available under the [Functional Source License 1.1 (FSL-1.1-MIT)](./LICENSE.md): free to use,
13
+ copy, modify and redistribute for any purpose except a competing use, that is, putting it into a
14
+ product or service that substitutes for the Software or for Causal Map. Internal use, education,
15
+ research and professional services are all permitted. Each release converts to the MIT licence two
16
+ years after it is published. This is source-available rather than an OSI open-source licence until
17
+ that conversion.
18
+
19
+ ## Install
20
+
21
+ ```sh
22
+ npm install @causalmap/filter-engine
23
+ ```
24
+
25
+ ## Quick start
26
+
27
+ ```js
28
+ import { applyOrderedLinkFilters } from '@causalmap/filter-engine'
29
+
30
+ const links = [
31
+ { cause: 'training', effect: 'confidence', source_id: 's1', tags: 'outcome' },
32
+ { cause: 'confidence', effect: 'income', source_id: 's1', tags: 'impact' },
33
+ { cause: 'training', effect: 'income', source_id: 's2', tags: 'impact' },
34
+ ]
35
+
36
+ const filters = [
37
+ { type: 'tags', selectedTags: ['impact'] },
38
+ { type: 'exclude-self-loops' },
39
+ ]
40
+
41
+ const { rows, unsupported } = applyOrderedLinkFilters(links, filters /*, sources */)
42
+ // rows = the surviving links, in order; unsupported = any filter types this build does not handle
43
+ ```
44
+
45
+ `applyOrderedLinkFilters(links, filters, sources = [])` runs the filters in array order and returns
46
+ `{ rows, unsupported }`. The optional third argument is the sources table (objects with at least
47
+ `id`); it is only needed by filters that read source metadata.
48
+
49
+ ## The link object
50
+
51
+ A link is a plain object. The fields the engine reads:
52
+
53
+ - `cause`, `effect`: factor labels (strings). The core of every link.
54
+ - `source_id`: which source the link came from; used by frequency and path-tracing counts.
55
+ - `tags`: string (comma-separated) or array, used by the tag filters.
56
+ - any other column you attach (for example `sentiment`, or flattened source-group columns): read by
57
+ the `everything` and `source-groups` filters via a resolved field name.
58
+
59
+ Extra fields you put on a link are preserved through filtering.
60
+
61
+ ## Filters
62
+
63
+ Each filter is `{ type, ...options }`. Set `enabled: false` to skip one. Supported types:
64
+
65
+ | type | what it does | key options |
66
+ |------|--------------|-------------|
67
+ | `label` | keep links within N steps of factors matching a label | `selectedLabels`, `stepsUp`, `stepsDown`, `traceThreads` |
68
+ | `exclude-label` | drop links whose cause/effect match labels | `selectedLabels`, `matchMode`, `excludeAny` |
69
+ | `tags` | keep links matching any selected tag | `selectedTags`, `matchMode` |
70
+ | `exclude-tags` | drop links matching selected tags | `selectedTags`, `matchMode`, `excludeAny` |
71
+ | `path-tracing` | keep links on paths between two factor sets | see path tracing below |
72
+ | `link-frequency` | keep the most or least frequent cause to effect pairs | `which`, `mode`, `threshold` |
73
+ | `factor-frequency` | keep links whose factors are frequent enough | `which`, `mode`, `threshold` |
74
+ | `combine-opposites` | merge negative variants into their positive label | sets `flipped_*` flags |
75
+ | `zoom` | trim factor labels to a hierarchy level | `level` |
76
+ | `collapse` | merge a set of labels into one | `selectedLabels` |
77
+ | `replace-brackets` | rewrite bracketed label parts | |
78
+ | `source-groups` | membership over a resolved source-group column | `selectedField`, `selectedValues` |
79
+ | `everything` | membership over any resolved link column | `selectedField`, `selectedValues` |
80
+ | `exclude-self-loops` | drop links where cause equals effect | |
81
+
82
+ `matchMode` is `'start'` (default), `'anywhere'`, or `'exact'`. Frequency `mode` is `'top'`
83
+ (default, keep the top `threshold` by count) or `'minimum'` (keep count at or above `threshold`);
84
+ `which` is `'sources'` to count unique sources, otherwise it counts appearances.
85
+
86
+ Per-filter functions are also exported if you want to drive them yourself
87
+ (`applyTagsFilter`, `applyLinkFrequencyFilter`, `applyPathTracingFilter`, and so on).
88
+
89
+ ### Path tracing
90
+
91
+ ```js
92
+ import { applyPathTracingFilter } from '@causalmap/filter-engine'
93
+ ```
94
+
95
+ Keeps links lying on paths from one factor set to another within a step budget. Highlight helpers
96
+ `matchPathFactors` and `tracePaths` are exported alongside it.
97
+
98
+ ## Reserved output fields
99
+
100
+ Some filters write fields onto the returned links. Do not strip these if you render downstream:
101
+
102
+ - `_recoded`: `{ cause: { original, recoded }, effect: {...} }`, set by zoom/collapse/replace-brackets; the earliest original is preserved. Used to display a renamed factor.
103
+ - `_recoded_cause` / `_recoded_effect`: booleans, set by collapse and replace-brackets only.
104
+ - `flipped_cause` / `flipped_effect`: booleans, set by combine-opposites and read by link-frequency bundling.
105
+
106
+ ## Host-resolved inputs
107
+
108
+ A few filters need a value the host application resolves and supplies:
109
+
110
+ - `everything` / `source-groups` read `link[filter.selectedField]`, so set `selectedField` to a real
111
+ column before running (the app resolves friendly names to columns like `sentiment` or `s_region`).
112
+ - `link-frequency` bundling can separate flipped opposite pairs. Pass
113
+ `applyLinkFrequencyFilter(links, filter, { separateFlippedBundles })` when driving it directly. It
114
+ defaults to `false`, so a consumer without combine-opposites needs nothing.
115
+
116
+ ## Provenance
117
+
118
+ Canonical source: `webapp/js/filter-engine` in the Causal Map app. `src/` here is generated by
119
+ `sync-from-webapp.mjs` and regenerated on every publish. The test suite under `tests/` (run with
120
+ `npm test`) includes the app's differential harnesses, which check this engine against a faithful
121
+ copy of the app's filter logic across thousands of configurations.
package/index.js ADDED
@@ -0,0 +1,38 @@
1
+ // Public surface of @causalmap/filter-engine.
2
+ //
3
+ // The code under ./src is a generated snapshot of the canonical engine in the
4
+ // causal-map-extension webapp (webapp/js/filter-engine + two pure deps). Do not
5
+ // edit ./src by hand; run `npm run sync` to refresh it. This file is the authored
6
+ // public API and is the one place to widen or narrow what consumers can import.
7
+
8
+ // Primary entry: ordered structural link filtering, data in and data out.
9
+ export { applyOrderedLinkFilters } from './src/apply-filters.js'
10
+
11
+ // Per-filter functions, for consumers that drive filters individually.
12
+ export {
13
+ applyLabelFilter,
14
+ applyTagsFilter,
15
+ applyExcludeTagsFilter,
16
+ applyExcludeLabelFilter,
17
+ applyEverythingFilter,
18
+ applySourceGroupsFilter,
19
+ applyExcludeSelfLoops,
20
+ applyLinkFrequencyFilter,
21
+ applyFactorFrequencyFilter,
22
+ applyZoomFilter,
23
+ applyCollapseFilter,
24
+ applyReplaceBracketsFilter,
25
+ applyRecodingTransform,
26
+ applyZoomToLabel,
27
+ applyCollapseToLabel,
28
+ applyCombineOppositesFilter,
29
+ // Highlight helper: which factors a label filter matched.
30
+ matchLabelFactors,
31
+ } from './src/apply-filters.js'
32
+
33
+ // Path tracing and its highlight/trace helpers.
34
+ export {
35
+ applyPathTracingFilter,
36
+ matchPathFactors,
37
+ tracePaths,
38
+ } from './src/path-tracing.js'
package/package.json ADDED
@@ -0,0 +1,34 @@
1
+ {
2
+ "name": "@causalmap/filter-engine",
3
+ "version": "0.1.0",
4
+ "description": "Pure, dependency-free link-filter engine for causal maps: ordered structural filters (tags, label, path-tracing, frequency, combine-opposites, zoom/collapse and more), data in and data out.",
5
+ "type": "module",
6
+ "license": "FSL-1.1-MIT",
7
+ "author": "Causal Map Ltd",
8
+ "homepage": "https://causalmap.app",
9
+ "main": "./index.js",
10
+ "exports": {
11
+ ".": "./index.js"
12
+ },
13
+ "files": [
14
+ "index.js",
15
+ "src/",
16
+ "LICENSE.md",
17
+ "README.md"
18
+ ],
19
+ "scripts": {
20
+ "sync": "node ./sync-from-webapp.mjs",
21
+ "test": "node --test tests/filters/traversal.test.mjs tests/filters/ordered-filters.test.mjs && node tests/filters/_filtering-diff.mjs && node tests/filters/_transform-diff.mjs && node tests/filters/_combine-diff.mjs && node tests/filters/_frequency-diff.mjs && node tests/filters/_path-diff.mjs",
22
+ "prepack": "node ./sync-from-webapp.mjs"
23
+ },
24
+ "keywords": [
25
+ "causal-map",
26
+ "causal-mapping",
27
+ "filter",
28
+ "graph",
29
+ "qualitative-data"
30
+ ],
31
+ "engines": {
32
+ "node": ">=18"
33
+ }
34
+ }