@ifc-lite/rules 0.2.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 +373 -0
- package/README.md +45 -0
- package/dist/engine/rule-engine-chunk.d.ts +25 -0
- package/dist/engine/rule-engine-chunk.d.ts.map +1 -0
- package/dist/engine/rule-engine-chunk.js +43 -0
- package/dist/engine/rule-engine-chunk.js.map +1 -0
- package/dist/engine/rule-engine-compare.d.ts +17 -0
- package/dist/engine/rule-engine-compare.d.ts.map +1 -0
- package/dist/engine/rule-engine-compare.js +69 -0
- package/dist/engine/rule-engine-compare.js.map +1 -0
- package/dist/engine/rule-engine-requirements.d.ts +37 -0
- package/dist/engine/rule-engine-requirements.d.ts.map +1 -0
- package/dist/engine/rule-engine-requirements.js +293 -0
- package/dist/engine/rule-engine-requirements.js.map +1 -0
- package/dist/engine/rule-engine-sets.d.ts +46 -0
- package/dist/engine/rule-engine-sets.d.ts.map +1 -0
- package/dist/engine/rule-engine-sets.js +298 -0
- package/dist/engine/rule-engine-sets.js.map +1 -0
- package/dist/engine/rule-engine-unit.d.ts +23 -0
- package/dist/engine/rule-engine-unit.d.ts.map +1 -0
- package/dist/engine/rule-engine-unit.js +64 -0
- package/dist/engine/rule-engine-unit.js.map +1 -0
- package/dist/engine/rule-engine.d.ts +29 -0
- package/dist/engine/rule-engine.d.ts.map +1 -0
- package/dist/engine/rule-engine.js +244 -0
- package/dist/engine/rule-engine.js.map +1 -0
- package/dist/filter/effective-filter-fields.d.ts +13 -0
- package/dist/filter/effective-filter-fields.d.ts.map +1 -0
- package/dist/filter/effective-filter-fields.js +44 -0
- package/dist/filter/effective-filter-fields.js.map +1 -0
- package/dist/filter/entity-predefined-type.d.ts +24 -0
- package/dist/filter/entity-predefined-type.d.ts.map +1 -0
- package/dist/filter/entity-predefined-type.js +34 -0
- package/dist/filter/entity-predefined-type.js.map +1 -0
- package/dist/filter/filter-evaluate-groups.d.ts +36 -0
- package/dist/filter/filter-evaluate-groups.d.ts.map +1 -0
- package/dist/filter/filter-evaluate-groups.js +111 -0
- package/dist/filter/filter-evaluate-groups.js.map +1 -0
- package/dist/filter/filter-evaluate-model-tag.d.ts +22 -0
- package/dist/filter/filter-evaluate-model-tag.d.ts.map +1 -0
- package/dist/filter/filter-evaluate-model-tag.js +35 -0
- package/dist/filter/filter-evaluate-model-tag.js.map +1 -0
- package/dist/filter/filter-evaluate-mutations.d.ts +51 -0
- package/dist/filter/filter-evaluate-mutations.d.ts.map +1 -0
- package/dist/filter/filter-evaluate-mutations.js +111 -0
- package/dist/filter/filter-evaluate-mutations.js.map +1 -0
- package/dist/filter/filter-evaluate-yield.d.ts +9 -0
- package/dist/filter/filter-evaluate-yield.d.ts.map +1 -0
- package/dist/filter/filter-evaluate-yield.js +32 -0
- package/dist/filter/filter-evaluate-yield.js.map +1 -0
- package/dist/filter/filter-evaluate.d.ts +120 -0
- package/dist/filter/filter-evaluate.d.ts.map +1 -0
- package/dist/filter/filter-evaluate.js +408 -0
- package/dist/filter/filter-evaluate.js.map +1 -0
- package/dist/filter/filter-group-rule.d.ts +23 -0
- package/dist/filter/filter-group-rule.d.ts.map +1 -0
- package/dist/filter/filter-group-rule.js +69 -0
- package/dist/filter/filter-group-rule.js.map +1 -0
- package/dist/filter/filter-groups.d.ts +65 -0
- package/dist/filter/filter-groups.d.ts.map +1 -0
- package/dist/filter/filter-groups.js +136 -0
- package/dist/filter/filter-groups.js.map +1 -0
- package/dist/filter/filter-iteration-source.d.ts +26 -0
- package/dist/filter/filter-iteration-source.d.ts.map +1 -0
- package/dist/filter/filter-iteration-source.js +154 -0
- package/dist/filter/filter-iteration-source.js.map +1 -0
- package/dist/filter/filter-iteration.d.ts +5 -0
- package/dist/filter/filter-iteration.d.ts.map +1 -0
- package/dist/filter/filter-iteration.js +25 -0
- package/dist/filter/filter-iteration.js.map +1 -0
- package/dist/filter/filter-match.d.ts +114 -0
- package/dist/filter/filter-match.d.ts.map +1 -0
- package/dist/filter/filter-match.js +302 -0
- package/dist/filter/filter-match.js.map +1 -0
- package/dist/filter/filter-ops.d.ts +90 -0
- package/dist/filter/filter-ops.d.ts.map +1 -0
- package/dist/filter/filter-ops.js +202 -0
- package/dist/filter/filter-ops.js.map +1 -0
- package/dist/filter/filter-rule-guards.d.ts +4 -0
- package/dist/filter/filter-rule-guards.d.ts.map +1 -0
- package/dist/filter/filter-rule-guards.js +74 -0
- package/dist/filter/filter-rule-guards.js.map +1 -0
- package/dist/filter/filter-rules.d.ts +275 -0
- package/dist/filter/filter-rules.d.ts.map +1 -0
- package/dist/filter/filter-rules.js +68 -0
- package/dist/filter/filter-rules.js.map +1 -0
- package/dist/filter/lens-material-names.d.ts +13 -0
- package/dist/filter/lens-material-names.d.ts.map +1 -0
- package/dist/filter/lens-material-names.js +36 -0
- package/dist/filter/lens-material-names.js.map +1 -0
- package/dist/filter/measure-units.d.ts +29 -0
- package/dist/filter/measure-units.d.ts.map +1 -0
- package/dist/filter/measure-units.js +72 -0
- package/dist/filter/measure-units.js.map +1 -0
- package/dist/filter/model-tag.d.ts +65 -0
- package/dist/filter/model-tag.d.ts.map +1 -0
- package/dist/filter/model-tag.js +57 -0
- package/dist/filter/model-tag.js.map +1 -0
- package/dist/filter/read-measure-subject.d.ts +28 -0
- package/dist/filter/read-measure-subject.d.ts.map +1 -0
- package/dist/filter/read-measure-subject.js +116 -0
- package/dist/filter/read-measure-subject.js.map +1 -0
- package/dist/filter/read-subject.d.ts +72 -0
- package/dist/filter/read-subject.d.ts.map +1 -0
- package/dist/filter/read-subject.js +128 -0
- package/dist/filter/read-subject.js.map +1 -0
- package/dist/filter/subject-match.d.ts +27 -0
- package/dist/filter/subject-match.d.ts.map +1 -0
- package/dist/filter/subject-match.js +52 -0
- package/dist/filter/subject-match.js.map +1 -0
- package/dist/filter/subject-read-options.d.ts +33 -0
- package/dist/filter/subject-read-options.d.ts.map +1 -0
- package/dist/filter/subject-read-options.js +8 -0
- package/dist/filter/subject-read-options.js.map +1 -0
- package/dist/ids/ids-facet-to-rules.d.ts +20 -0
- package/dist/ids/ids-facet-to-rules.d.ts.map +1 -0
- package/dist/ids/ids-facet-to-rules.js +230 -0
- package/dist/ids/ids-facet-to-rules.js.map +1 -0
- package/dist/ids/ids-regex.d.ts +44 -0
- package/dist/ids/ids-regex.d.ts.map +1 -0
- package/dist/ids/ids-regex.js +334 -0
- package/dist/ids/ids-regex.js.map +1 -0
- package/dist/ids/ids-to-rule-set.d.ts +39 -0
- package/dist/ids/ids-to-rule-set.d.ts.map +1 -0
- package/dist/ids/ids-to-rule-set.js +103 -0
- package/dist/ids/ids-to-rule-set.js.map +1 -0
- package/dist/ids/ids-xml-writer.d.ts +11 -0
- package/dist/ids/ids-xml-writer.d.ts.map +1 -0
- package/dist/ids/ids-xml-writer.js +172 -0
- package/dist/ids/ids-xml-writer.js.map +1 -0
- package/dist/ids/rule-set-to-ids.d.ts +45 -0
- package/dist/ids/rule-set-to-ids.d.ts.map +1 -0
- package/dist/ids/rule-set-to-ids.js +140 -0
- package/dist/ids/rule-set-to-ids.js.map +1 -0
- package/dist/ids/rule-to-ids-facets.d.ts +52 -0
- package/dist/ids/rule-to-ids-facets.d.ts.map +1 -0
- package/dist/ids/rule-to-ids-facets.js +291 -0
- package/dist/ids/rule-to-ids-facets.js.map +1 -0
- package/dist/ids/stored-unit-scale.d.ts +15 -0
- package/dist/ids/stored-unit-scale.d.ts.map +1 -0
- package/dist/ids/stored-unit-scale.js +64 -0
- package/dist/ids/stored-unit-scale.js.map +1 -0
- package/dist/index.d.ts +39 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +46 -0
- package/dist/index.js.map +1 -0
- package/dist/rule-set/between-chip.d.ts +54 -0
- package/dist/rule-set/between-chip.d.ts.map +1 -0
- package/dist/rule-set/between-chip.js +95 -0
- package/dist/rule-set/between-chip.js.map +1 -0
- package/dist/rule-set/requirement-invariants.d.ts +25 -0
- package/dist/rule-set/requirement-invariants.d.ts.map +1 -0
- package/dist/rule-set/requirement-invariants.js +29 -0
- package/dist/rule-set/requirement-invariants.js.map +1 -0
- package/dist/rule-set/requirement-text.d.ts +20 -0
- package/dist/rule-set/requirement-text.d.ts.map +1 -0
- package/dist/rule-set/requirement-text.js +179 -0
- package/dist/rule-set/requirement-text.js.map +1 -0
- package/dist/rule-set/rule-set-io-requirement.d.ts +22 -0
- package/dist/rule-set/rule-set-io-requirement.d.ts.map +1 -0
- package/dist/rule-set/rule-set-io-requirement.js +140 -0
- package/dist/rule-set/rule-set-io-requirement.js.map +1 -0
- package/dist/rule-set/rule-set-io-shared.d.ts +24 -0
- package/dist/rule-set/rule-set-io-shared.d.ts.map +1 -0
- package/dist/rule-set/rule-set-io-shared.js +48 -0
- package/dist/rule-set/rule-set-io-shared.js.map +1 -0
- package/dist/rule-set/rule-set-io-subject.d.ts +10 -0
- package/dist/rule-set/rule-set-io-subject.d.ts.map +1 -0
- package/dist/rule-set/rule-set-io-subject.js +67 -0
- package/dist/rule-set/rule-set-io-subject.js.map +1 -0
- package/dist/rule-set/rule-set-io.d.ts +37 -0
- package/dist/rule-set/rule-set-io.d.ts.map +1 -0
- package/dist/rule-set/rule-set-io.js +142 -0
- package/dist/rule-set/rule-set-io.js.map +1 -0
- package/dist/rule-set/rule-set.d.ts +109 -0
- package/dist/rule-set/rule-set.d.ts.map +1 -0
- package/dist/rule-set/rule-set.js +5 -0
- package/dist/rule-set/rule-set.js.map +1 -0
- package/package.json +59 -0
|
@@ -0,0 +1,244 @@
|
|
|
1
|
+
/* This Source Code Form is subject to the terms of the Mozilla Public
|
|
2
|
+
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
3
|
+
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
|
|
4
|
+
import { exactTypeName } from '@ifc-lite/data';
|
|
5
|
+
import { calculateSummary, } from '@ifc-lite/ids';
|
|
6
|
+
import { setOpMatches } from '../filter/filter-ops.js';
|
|
7
|
+
import { evaluateFilterGroupsFederated } from '../filter/filter-evaluate-groups.js';
|
|
8
|
+
import { throwAbort } from '../filter/filter-evaluate-yield.js';
|
|
9
|
+
import { checkElementForEntity } from './rule-engine-requirements.js';
|
|
10
|
+
import { checkAggregate, checkUnique } from './rule-engine-sets.js';
|
|
11
|
+
import { checkCompare } from './rule-engine-compare.js';
|
|
12
|
+
import { checkUnit } from './rule-engine-unit.js';
|
|
13
|
+
import { maybeYieldChunk, finalProgress } from './rule-engine-chunk.js';
|
|
14
|
+
/** `targets` narrows the loaded-model list to the rule set's declared
|
|
15
|
+
* default targets (plan §3: never a local model id; both lists OR'd;
|
|
16
|
+
* empty/absent = every loaded model). */
|
|
17
|
+
export function resolveTargetModels(models, targets) {
|
|
18
|
+
const fingerprints = targets?.modelFingerprints ?? [];
|
|
19
|
+
const tagIds = targets?.modelTagIds ?? [];
|
|
20
|
+
if (fingerprints.length === 0 && tagIds.length === 0)
|
|
21
|
+
return [...models];
|
|
22
|
+
const fpSet = new Set(fingerprints);
|
|
23
|
+
const tagSet = new Set(tagIds);
|
|
24
|
+
return models.filter((m) => {
|
|
25
|
+
if (fpSet.size > 0 && m.filterIdentity !== undefined && fpSet.has(m.filterIdentity))
|
|
26
|
+
return true;
|
|
27
|
+
if (tagSet.size > 0 && m.tagIds)
|
|
28
|
+
for (const t of m.tagIds)
|
|
29
|
+
if (tagSet.has(t))
|
|
30
|
+
return true;
|
|
31
|
+
return false;
|
|
32
|
+
});
|
|
33
|
+
}
|
|
34
|
+
function buildModelInfo(models) {
|
|
35
|
+
const out = [];
|
|
36
|
+
for (const m of models) {
|
|
37
|
+
if (!m.store)
|
|
38
|
+
continue;
|
|
39
|
+
out.push({
|
|
40
|
+
modelId: m.id,
|
|
41
|
+
schemaVersion: m.store.schemaVersion || 'IFC4',
|
|
42
|
+
entityCount: m.store.entityCount || m.store.entities.count,
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
return out;
|
|
46
|
+
}
|
|
47
|
+
/** Every `exactClass`-flagged `ifcType` rule in an applicability block (plan
|
|
48
|
+
* §2 row "Class inheritance", §4 item 1 last sentence). Scoped to the WHOLE
|
|
49
|
+
* block rather than per-`FilterGroup` — a rule authoring `exactClass` on one
|
|
50
|
+
* OR-branch and not another is not a shape the editor (PR 5) offers, so a
|
|
51
|
+
* whole-rule post-filter is the closest correct reading, not a narrowing of
|
|
52
|
+
* an actually-supported shape (documented here per the brief's "closest
|
|
53
|
+
* correct thing" instruction rather than left unhandled). */
|
|
54
|
+
function exactClassRulesOf(block) {
|
|
55
|
+
const out = [];
|
|
56
|
+
for (const group of block.groups) {
|
|
57
|
+
for (const rule of group.rules) {
|
|
58
|
+
if (rule.kind === 'ifcType' && rule.exactClass)
|
|
59
|
+
out.push(rule);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
return out;
|
|
63
|
+
}
|
|
64
|
+
function applyExactClassFilter(elements, rules, storesById, opts) {
|
|
65
|
+
if (rules.length === 0)
|
|
66
|
+
return [...elements];
|
|
67
|
+
return elements.filter((el) => {
|
|
68
|
+
const store = storesById.get(el.modelId);
|
|
69
|
+
if (!store)
|
|
70
|
+
return false;
|
|
71
|
+
const exact = exactTypeName(store.entities, el.expressId);
|
|
72
|
+
return rules.some((r) => setOpMatches(r.op, exact, r.values, opts));
|
|
73
|
+
});
|
|
74
|
+
}
|
|
75
|
+
function checkCardinality(cardinality, count) {
|
|
76
|
+
if (!cardinality || (cardinality.minApplicable === undefined && cardinality.maxApplicable === undefined)) {
|
|
77
|
+
return undefined;
|
|
78
|
+
}
|
|
79
|
+
const messages = [];
|
|
80
|
+
let passed = true;
|
|
81
|
+
if (cardinality.minApplicable !== undefined && count < cardinality.minApplicable) {
|
|
82
|
+
passed = false;
|
|
83
|
+
messages.push(`expected at least ${cardinality.minApplicable}, found ${count}`);
|
|
84
|
+
}
|
|
85
|
+
if (cardinality.maxApplicable !== undefined && count > cardinality.maxApplicable) {
|
|
86
|
+
passed = false;
|
|
87
|
+
messages.push(`expected at most ${cardinality.maxApplicable}, found ${count}`);
|
|
88
|
+
}
|
|
89
|
+
return {
|
|
90
|
+
passed,
|
|
91
|
+
actualCount: count,
|
|
92
|
+
minExpected: cardinality.minApplicable,
|
|
93
|
+
maxExpected: cardinality.maxApplicable,
|
|
94
|
+
message: messages.length > 0 ? messages.join('; ') : 'Cardinality satisfied',
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
async function runElementRequirement(ruleId, block, applicable, storesById, opts, ruleIndex, signal, onProgress) {
|
|
98
|
+
const out = [];
|
|
99
|
+
for (let i = 0; i < applicable.length; i++) {
|
|
100
|
+
const el = applicable[i];
|
|
101
|
+
const store = storesById.get(el.modelId);
|
|
102
|
+
if (store)
|
|
103
|
+
out.push(checkElementForEntity(ruleId, block, el, store, opts));
|
|
104
|
+
await maybeYieldChunk(i + 1, applicable.length, ruleIndex, signal, onProgress);
|
|
105
|
+
}
|
|
106
|
+
finalProgress(applicable.length, ruleIndex, signal, onProgress);
|
|
107
|
+
return out;
|
|
108
|
+
}
|
|
109
|
+
function finalizeSpecification(rule, applicableCount, cardinalityResult, outcome, error) {
|
|
110
|
+
const specification = { id: rule.id, name: rule.name, description: rule.description };
|
|
111
|
+
if (error !== undefined) {
|
|
112
|
+
// Existing IDS convention (`packages/ids/src/report-types.ts`'s
|
|
113
|
+
// `SpecificationResult.error` doc): an unevaluable rule is `status:
|
|
114
|
+
// 'fail'` with `error` set, never silently folded into `pass`.
|
|
115
|
+
return {
|
|
116
|
+
specification, status: 'fail', applicableCount, passedCount: 0, failedCount: 0, passRate: 0,
|
|
117
|
+
entityResults: [], error,
|
|
118
|
+
};
|
|
119
|
+
}
|
|
120
|
+
const failedEntities = outcome.entityResults.filter((e) => !e.passed).length;
|
|
121
|
+
const passedEntities = outcome.entityResults.filter((e) => e.passed).length;
|
|
122
|
+
const setsFail = (outcome.setResults ?? []).some((s) => !s.passed);
|
|
123
|
+
// `element`/`compare`/`unit` report every applicable entity (pass and fail), so
|
|
124
|
+
// their counts read straight off `entityResults`. `unique`/`aggregate`
|
|
125
|
+
// only ever construct FAILING rows (plan §4.5/§4.6 — a whole federation's
|
|
126
|
+
// worth of passing rows would dwarf the report for no reporting value),
|
|
127
|
+
// so their `passedCount` is the complement against `applicableCount`.
|
|
128
|
+
//
|
|
129
|
+
// `aggregate` is the exception to "rows are complete for failures": it
|
|
130
|
+
// emits no row for a member of a group that fails on its aggregate value
|
|
131
|
+
// (none at all for `fn: 'count'`), so it reports the distinct failing
|
|
132
|
+
// element count alongside (#5177). Reading `failedEntities` there left a
|
|
133
|
+
// failing aggregate rule at `failedCount: 0` / `passRate: 100`.
|
|
134
|
+
const isPerElementKind = rule.requirement.kind === 'element' || rule.requirement.kind === 'compare'
|
|
135
|
+
|| rule.requirement.kind === 'unit';
|
|
136
|
+
const failedCount = outcome.failedElementCount ?? failedEntities;
|
|
137
|
+
const passedCount = isPerElementKind ? passedEntities : Math.max(0, applicableCount - failedCount);
|
|
138
|
+
const anyFail = failedCount > 0 || setsFail || cardinalityResult?.passed === false;
|
|
139
|
+
// With zero applicable elements a `universe`-seeded aggregate group can
|
|
140
|
+
// still fail (every assembly has 0 plates), so `setsFail` counts here too.
|
|
141
|
+
const status = applicableCount === 0
|
|
142
|
+
? (cardinalityResult?.passed === false || setsFail ? 'fail' : cardinalityResult?.passed === true ? 'pass' : 'not_applicable')
|
|
143
|
+
: (anyFail ? 'fail' : 'pass');
|
|
144
|
+
// A failure no applicable element carries — an unmet/exceeded cardinality,
|
|
145
|
+
// or an aggregate group seeded by `universe` with zero members — leaves the
|
|
146
|
+
// element arithmetic at 100 (or has nothing to rate). `status` and
|
|
147
|
+
// `passRate` must never disagree, so a failing spec never reads 100: the
|
|
148
|
+
// same rule `@ifc-lite/ids`'s `validateSpecification` applies (#5212).
|
|
149
|
+
const elementRate = applicableCount > 0 ? Math.floor((passedCount / applicableCount) * 100) : 100;
|
|
150
|
+
const passRate = status === 'fail' && elementRate === 100 ? 0 : elementRate;
|
|
151
|
+
return {
|
|
152
|
+
specification, status, applicableCount, passedCount, failedCount, passRate,
|
|
153
|
+
entityResults: outcome.entityResults,
|
|
154
|
+
cardinalityResult,
|
|
155
|
+
setResults: outcome.setResults,
|
|
156
|
+
setResultsTruncated: outcome.setResultsTruncated,
|
|
157
|
+
};
|
|
158
|
+
}
|
|
159
|
+
/** Run every rule in `ruleSet` against the targeted models and return one
|
|
160
|
+
* `ValidationReport`. Rejects with `AbortError` on cancellation; the report
|
|
161
|
+
* object is constructed only after the loop completes, so a caller can
|
|
162
|
+
* never observe a partial one. */
|
|
163
|
+
export async function runRuleSet(options) {
|
|
164
|
+
const { ruleSet, models, definedModelTagIds, signal, onProgress } = options;
|
|
165
|
+
const targetModels = resolveTargetModels(models, ruleSet.targets);
|
|
166
|
+
const storesById = new Map();
|
|
167
|
+
for (const m of targetModels)
|
|
168
|
+
if (m.store)
|
|
169
|
+
storesById.set(m.id, m.store);
|
|
170
|
+
const modelInfo = buildModelInfo(targetModels);
|
|
171
|
+
// A rule set that DECLARES targets (`modelFingerprints`/`modelTagIds`) but
|
|
172
|
+
// resolves to zero loaded models is a MISMATCH, not an empty applicable
|
|
173
|
+
// set (#5138 PR 7b review): every model tag id or fingerprint is opaque
|
|
174
|
+
// and unresolved-by-construction (`Subject` §3), so this is exactly as
|
|
175
|
+
// likely to mean "the caller's `filterIdentity`/tag set doesn't match
|
|
176
|
+
// what the rule set was authored against" as "no targeted model happens
|
|
177
|
+
// to be loaded right now" — and a silent `not_applicable` pass reads as
|
|
178
|
+
// a clean report either way. Every rule is unevaluable, not vacuously
|
|
179
|
+
// satisfied, so every rule reports `error`, the same convention
|
|
180
|
+
// `finalizeSpecification` already uses for an exception mid-evaluation.
|
|
181
|
+
const targetsDeclared = (ruleSet.targets?.modelFingerprints?.length ?? 0) > 0 || (ruleSet.targets?.modelTagIds?.length ?? 0) > 0;
|
|
182
|
+
const targetsUnresolved = targetsDeclared && targetModels.length === 0;
|
|
183
|
+
const specificationResults = [];
|
|
184
|
+
for (let ruleIndex = 0; ruleIndex < ruleSet.rules.length; ruleIndex++) {
|
|
185
|
+
if (signal?.aborted)
|
|
186
|
+
throwAbort(signal);
|
|
187
|
+
const rule = ruleSet.rules[ruleIndex];
|
|
188
|
+
const opts = { caseSensitive: rule.caseSensitive ?? true, tolerance: rule.tolerance ?? 1e-6 };
|
|
189
|
+
if (targetsUnresolved) {
|
|
190
|
+
specificationResults.push(finalizeSpecification(rule, 0, undefined, { entityResults: [] }, 'no loaded model matches the rule set targets'));
|
|
191
|
+
continue;
|
|
192
|
+
}
|
|
193
|
+
// Tracked outside the `try` so the `catch` can report the REAL applicable
|
|
194
|
+
// count when applicability itself succeeded and only the requirement
|
|
195
|
+
// check threw — `0` is reserved for when applicability never resolved.
|
|
196
|
+
let applicableCount = 0;
|
|
197
|
+
try {
|
|
198
|
+
const applicableRaw = await evaluateFilterGroupsFederated(targetModels, rule.applicability.groups, {
|
|
199
|
+
limit: Number.MAX_SAFE_INTEGER,
|
|
200
|
+
signal,
|
|
201
|
+
definedModelTagIds,
|
|
202
|
+
onProgress: (done, total) => onProgress?.({ ruleIndex, phase: 'applicability', done, total }),
|
|
203
|
+
});
|
|
204
|
+
const applicable = applyExactClassFilter(applicableRaw, exactClassRulesOf(rule.applicability), storesById, opts);
|
|
205
|
+
applicableCount = applicable.length;
|
|
206
|
+
const cardinalityResult = checkCardinality(rule.cardinality, applicableCount);
|
|
207
|
+
let outcome;
|
|
208
|
+
switch (rule.requirement.kind) {
|
|
209
|
+
case 'element': {
|
|
210
|
+
const entityResults = await runElementRequirement(rule.id, rule.requirement.block, applicable, storesById, opts, ruleIndex, signal, onProgress);
|
|
211
|
+
outcome = { entityResults };
|
|
212
|
+
break;
|
|
213
|
+
}
|
|
214
|
+
case 'unique':
|
|
215
|
+
outcome = await checkUnique(rule.id, rule.requirement, applicable, storesById, opts, ruleIndex, signal, onProgress);
|
|
216
|
+
break;
|
|
217
|
+
case 'aggregate':
|
|
218
|
+
outcome = await checkAggregate(rule.id, rule.requirement, applicable, storesById, targetModels, opts, ruleIndex, signal, onProgress);
|
|
219
|
+
break;
|
|
220
|
+
case 'compare':
|
|
221
|
+
outcome = await checkCompare(rule.id, rule.requirement, applicable, storesById, opts, ruleIndex, signal, onProgress);
|
|
222
|
+
break;
|
|
223
|
+
case 'unit':
|
|
224
|
+
outcome = await checkUnit(rule.id, rule.requirement, applicable, storesById, ruleIndex, signal, onProgress);
|
|
225
|
+
break;
|
|
226
|
+
}
|
|
227
|
+
specificationResults.push(finalizeSpecification(rule, applicableCount, cardinalityResult, outcome));
|
|
228
|
+
}
|
|
229
|
+
catch (err) {
|
|
230
|
+
if (err instanceof DOMException && err.name === 'AbortError')
|
|
231
|
+
throw err;
|
|
232
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
233
|
+
specificationResults.push(finalizeSpecification(rule, applicableCount, undefined, { entityResults: [] }, message));
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
return {
|
|
237
|
+
source: { kind: 'rules', ruleSet: { name: ruleSet.name, description: ruleSet.description } },
|
|
238
|
+
modelInfo,
|
|
239
|
+
timestamp: new Date(),
|
|
240
|
+
summary: calculateSummary(specificationResults),
|
|
241
|
+
specificationResults,
|
|
242
|
+
};
|
|
243
|
+
}
|
|
244
|
+
//# sourceMappingURL=rule-engine.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"rule-engine.js","sourceRoot":"","sources":["../../src/engine/rule-engine.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { type EffectiveEntityRecord, type IfcDataStore } from '@ifc-lite/parser';
|
|
2
|
+
import type { MutablePropertyView } from '@ifc-lite/mutations';
|
|
3
|
+
import type { FilteredElement } from './filter-evaluate.js';
|
|
4
|
+
export interface EffectiveFilterFields {
|
|
5
|
+
ifcType: string;
|
|
6
|
+
name: string | undefined;
|
|
7
|
+
globalId: string;
|
|
8
|
+
}
|
|
9
|
+
export declare function createdFilterRecord(store: IfcDataStore, view: MutablePropertyView | undefined, expressId: number): EffectiveEntityRecord | null;
|
|
10
|
+
/** The same class and root values export will write for a live filter result. */
|
|
11
|
+
export declare function effectiveFilterFields(store: IfcDataStore, view: MutablePropertyView | undefined, expressId: number): EffectiveFilterFields;
|
|
12
|
+
export declare function buildFilterResult(modelId: string, expressId: number, fields: EffectiveFilterFields): FilteredElement;
|
|
13
|
+
//# sourceMappingURL=effective-filter-fields.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"effective-filter-fields.d.ts","sourceRoot":"","sources":["../../src/filter/effective-filter-fields.ts"],"names":[],"mappings":"AAIA,OAAO,EAAsD,KAAK,qBAAqB,EAAE,KAAK,YAAY,EAAE,MAAM,kBAAkB,CAAC;AACrI,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,qBAAqB,CAAC;AAC/D,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,sBAAsB,CAAC;AAE5D,MAAM,WAAW,qBAAqB;IACpC,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,EAAE,MAAM,GAAG,SAAS,CAAC;IACzB,QAAQ,EAAE,MAAM,CAAC;CAClB;AAED,wBAAgB,mBAAmB,CACjC,KAAK,EAAE,YAAY,EACnB,IAAI,EAAE,mBAAmB,GAAG,SAAS,EACrC,SAAS,EAAE,MAAM,GAChB,qBAAqB,GAAG,IAAI,CAQ9B;AAED,iFAAiF;AACjF,wBAAgB,qBAAqB,CACnC,KAAK,EAAE,YAAY,EACnB,IAAI,EAAE,mBAAmB,GAAG,SAAS,EACrC,SAAS,EAAE,MAAM,GAChB,qBAAqB,CAmBvB;AAED,wBAAgB,iBAAiB,CAAC,OAAO,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,MAAM,EAAE,qBAAqB,GAAG,eAAe,CAQpH"}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
/* This Source Code Form is subject to the terms of the Mozilla Public
|
|
2
|
+
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
3
|
+
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
|
|
4
|
+
import { normalizeIfcTypeName, resolveEffectiveEntityRecord } from '@ifc-lite/parser';
|
|
5
|
+
export function createdFilterRecord(store, view, expressId) {
|
|
6
|
+
const created = view?.getNewEntity(expressId);
|
|
7
|
+
if (!created || !view)
|
|
8
|
+
return null;
|
|
9
|
+
return resolveEffectiveEntityRecord(created, {
|
|
10
|
+
retype: view.getEntityTypeMutation(expressId)?.newType,
|
|
11
|
+
named: view.getAttributeMutationsForEntity(expressId).map(({ name, value }) => [name, value]),
|
|
12
|
+
positional: view.getPositionalMutationsForEntity(expressId) ?? [],
|
|
13
|
+
}, store.schemaVersion);
|
|
14
|
+
}
|
|
15
|
+
/** The same class and root values export will write for a live filter result. */
|
|
16
|
+
export function effectiveFilterFields(store, view, expressId) {
|
|
17
|
+
const record = createdFilterRecord(store, view, expressId);
|
|
18
|
+
if (record) {
|
|
19
|
+
const value = (name) => {
|
|
20
|
+
const index = record.names.indexOf(name);
|
|
21
|
+
const raw = index < 0 ? undefined : record.attributes[index];
|
|
22
|
+
return typeof raw === 'string' && raw !== '$' ? raw : undefined;
|
|
23
|
+
};
|
|
24
|
+
return { ifcType: record.type, name: value('Name'), globalId: value('GlobalId') ?? '' };
|
|
25
|
+
}
|
|
26
|
+
const retype = view?.getEntityTypeMutation(expressId)?.newType;
|
|
27
|
+
const edits = view?.getAttributeMutationsForEntity(expressId);
|
|
28
|
+
const edited = (name) => edits?.find((entry) => entry.name === name)?.value;
|
|
29
|
+
return {
|
|
30
|
+
ifcType: retype ? normalizeIfcTypeName(retype) : store.entities.getTypeName(expressId),
|
|
31
|
+
name: edited('Name') ?? store.entities.getNameOrUndefined(expressId),
|
|
32
|
+
globalId: edited('GlobalId') ?? store.entities.getGlobalId(expressId),
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
export function buildFilterResult(modelId, expressId, fields) {
|
|
36
|
+
return {
|
|
37
|
+
modelId,
|
|
38
|
+
expressId,
|
|
39
|
+
ifcType: fields.ifcType,
|
|
40
|
+
name: fields.name ?? '',
|
|
41
|
+
globalId: fields.globalId,
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
//# sourceMappingURL=effective-filter-fields.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"effective-filter-fields.js","sourceRoot":"","sources":["../../src/filter/effective-filter-fields.ts"],"names":[],"mappings":"AAAA;;+DAE+D;AAE/D,OAAO,EAAE,oBAAoB,EAAE,4BAA4B,EAAiD,MAAM,kBAAkB,CAAC;AAUrI,MAAM,UAAU,mBAAmB,CACjC,KAAmB,EACnB,IAAqC,EACrC,SAAiB;IAEjB,MAAM,OAAO,GAAG,IAAI,EAAE,YAAY,CAAC,SAAS,CAAC,CAAC;IAC9C,IAAI,CAAC,OAAO,IAAI,CAAC,IAAI;QAAE,OAAO,IAAI,CAAC;IACnC,OAAO,4BAA4B,CAAC,OAAO,EAAE;QAC3C,MAAM,EAAE,IAAI,CAAC,qBAAqB,CAAC,SAAS,CAAC,EAAE,OAAO;QACtD,KAAK,EAAE,IAAI,CAAC,8BAA8B,CAAC,SAAS,CAAC,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,EAAE,KAAK,EAAE,EAAE,EAAE,CAAC,CAAC,IAAI,EAAE,KAAK,CAAU,CAAC;QACtG,UAAU,EAAE,IAAI,CAAC,+BAA+B,CAAC,SAAS,CAAC,IAAI,EAAE;KAClE,EAAE,KAAK,CAAC,aAAa,CAAC,CAAC;AAC1B,CAAC;AAED,iFAAiF;AACjF,MAAM,UAAU,qBAAqB,CACnC,KAAmB,EACnB,IAAqC,EACrC,SAAiB;IAEjB,MAAM,MAAM,GAAG,mBAAmB,CAAC,KAAK,EAAE,IAAI,EAAE,SAAS,CAAC,CAAC;IAC3D,IAAI,MAAM,EAAE,CAAC;QACX,MAAM,KAAK,GAAG,CAAC,IAAY,EAAsB,EAAE;YACjD,MAAM,KAAK,GAAG,MAAM,CAAC,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC;YACzC,MAAM,GAAG,GAAG,KAAK,GAAG,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,MAAM,CAAC,UAAU,CAAC,KAAK,CAAC,CAAC;YAC7D,OAAO,OAAO,GAAG,KAAK,QAAQ,IAAI,GAAG,KAAK,GAAG,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,SAAS,CAAC;QAClE,CAAC,CAAC;QACF,OAAO,EAAE,OAAO,EAAE,MAAM,CAAC,IAAI,EAAE,IAAI,EAAE,KAAK,CAAC,MAAM,CAAC,EAAE,QAAQ,EAAE,KAAK,CAAC,UAAU,CAAC,IAAI,EAAE,EAAE,CAAC;IAC1F,CAAC;IACD,MAAM,MAAM,GAAG,IAAI,EAAE,qBAAqB,CAAC,SAAS,CAAC,EAAE,OAAO,CAAC;IAC/D,MAAM,KAAK,GAAG,IAAI,EAAE,8BAA8B,CAAC,SAAS,CAAC,CAAC;IAC9D,MAAM,MAAM,GAAG,CAAC,IAAY,EAAsB,EAAE,CAClD,KAAK,EAAE,IAAI,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,IAAI,KAAK,IAAI,CAAC,EAAE,KAAK,CAAC;IACrD,OAAO;QACL,OAAO,EAAE,MAAM,CAAC,CAAC,CAAC,oBAAoB,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,QAAQ,CAAC,WAAW,CAAC,SAAS,CAAC;QACtF,IAAI,EAAE,MAAM,CAAC,MAAM,CAAC,IAAI,KAAK,CAAC,QAAQ,CAAC,kBAAkB,CAAC,SAAS,CAAC;QACpE,QAAQ,EAAE,MAAM,CAAC,UAAU,CAAC,IAAI,KAAK,CAAC,QAAQ,CAAC,WAAW,CAAC,SAAS,CAAC;KACtE,CAAC;AACJ,CAAC;AAED,MAAM,UAAU,iBAAiB,CAAC,OAAe,EAAE,SAAiB,EAAE,MAA6B;IACjG,OAAO;QACL,OAAO;QACP,SAAS;QACT,OAAO,EAAE,MAAM,CAAC,OAAO;QACvB,IAAI,EAAE,MAAM,CAAC,IAAI,IAAI,EAAE;QACvB,QAAQ,EAAE,MAAM,CAAC,QAAQ;KAC1B,CAAC;AACJ,CAAC"}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { type IfcDataStore } from '@ifc-lite/parser';
|
|
2
|
+
/**
|
|
3
|
+
* Pick the `PredefinedType` value out of the schema-named attribute pairs
|
|
4
|
+
* returned by {@link extractAllEntityAttributes}. The extractor already strips
|
|
5
|
+
* the STEP enum markers (`.FLOOR.` → `FLOOR`), so this returns the display
|
|
6
|
+
* token (e.g. `"FLOOR"`, `"FLOORING"`, `"USERDEFINED"`, `"NOTDEFINED"`).
|
|
7
|
+
* Returns `undefined` when the slot is absent or empty so callers can ghost /
|
|
8
|
+
* skip elements that carry no PredefinedType.
|
|
9
|
+
*/
|
|
10
|
+
export declare function pickPredefinedType(attrs: ReadonlyArray<{
|
|
11
|
+
name: string;
|
|
12
|
+
value: string | number | boolean;
|
|
13
|
+
}>): string | undefined;
|
|
14
|
+
/**
|
|
15
|
+
* Resolve an entity's IFC `PredefinedType` (e.g. `IfcSlab.FLOOR`,
|
|
16
|
+
* `IfcCovering.FLOORING`) from the source buffer. Schema-driven, so it works
|
|
17
|
+
* for any product type whose attribute layout the registry knows. (#1364)
|
|
18
|
+
*
|
|
19
|
+
* Like `Tag` and the property / quantity auto-color sources, this re-parses
|
|
20
|
+
* the entity on demand — there is no columnar PredefinedType accessor — which
|
|
21
|
+
* is the same cost class as those existing per-entity attribute reads.
|
|
22
|
+
*/
|
|
23
|
+
export declare function resolveEntityPredefinedType(store: IfcDataStore, expressId: number): string | undefined;
|
|
24
|
+
//# sourceMappingURL=entity-predefined-type.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"entity-predefined-type.d.ts","sourceRoot":"","sources":["../../src/filter/entity-predefined-type.ts"],"names":[],"mappings":"AAIA,OAAO,EAA8B,KAAK,YAAY,EAAE,MAAM,kBAAkB,CAAC;AAEjF;;;;;;;GAOG;AACH,wBAAgB,kBAAkB,CAChC,KAAK,EAAE,aAAa,CAAC;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,GAAG,MAAM,GAAG,OAAO,CAAA;CAAE,CAAC,GACvE,MAAM,GAAG,SAAS,CAKpB;AAED;;;;;;;;GAQG;AACH,wBAAgB,2BAA2B,CACzC,KAAK,EAAE,YAAY,EACnB,SAAS,EAAE,MAAM,GAChB,MAAM,GAAG,SAAS,CAGpB"}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/* This Source Code Form is subject to the terms of the Mozilla Public
|
|
2
|
+
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
3
|
+
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
|
|
4
|
+
import { extractAllEntityAttributes } from '@ifc-lite/parser';
|
|
5
|
+
/**
|
|
6
|
+
* Pick the `PredefinedType` value out of the schema-named attribute pairs
|
|
7
|
+
* returned by {@link extractAllEntityAttributes}. The extractor already strips
|
|
8
|
+
* the STEP enum markers (`.FLOOR.` → `FLOOR`), so this returns the display
|
|
9
|
+
* token (e.g. `"FLOOR"`, `"FLOORING"`, `"USERDEFINED"`, `"NOTDEFINED"`).
|
|
10
|
+
* Returns `undefined` when the slot is absent or empty so callers can ghost /
|
|
11
|
+
* skip elements that carry no PredefinedType.
|
|
12
|
+
*/
|
|
13
|
+
export function pickPredefinedType(attrs) {
|
|
14
|
+
const entry = attrs.find((a) => a.name === 'PredefinedType');
|
|
15
|
+
if (!entry)
|
|
16
|
+
return undefined;
|
|
17
|
+
const value = String(entry.value).trim();
|
|
18
|
+
return value.length > 0 ? value : undefined;
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Resolve an entity's IFC `PredefinedType` (e.g. `IfcSlab.FLOOR`,
|
|
22
|
+
* `IfcCovering.FLOORING`) from the source buffer. Schema-driven, so it works
|
|
23
|
+
* for any product type whose attribute layout the registry knows. (#1364)
|
|
24
|
+
*
|
|
25
|
+
* Like `Tag` and the property / quantity auto-color sources, this re-parses
|
|
26
|
+
* the entity on demand — there is no columnar PredefinedType accessor — which
|
|
27
|
+
* is the same cost class as those existing per-entity attribute reads.
|
|
28
|
+
*/
|
|
29
|
+
export function resolveEntityPredefinedType(store, expressId) {
|
|
30
|
+
if (!(store.source?.length > 0) || !store.entityIndex)
|
|
31
|
+
return undefined;
|
|
32
|
+
return pickPredefinedType(extractAllEntityAttributes(store, expressId));
|
|
33
|
+
}
|
|
34
|
+
//# sourceMappingURL=entity-predefined-type.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"entity-predefined-type.js","sourceRoot":"","sources":["../../src/filter/entity-predefined-type.ts"],"names":[],"mappings":"AAAA;;+DAE+D;AAE/D,OAAO,EAAE,0BAA0B,EAAqB,MAAM,kBAAkB,CAAC;AAEjF;;;;;;;GAOG;AACH,MAAM,UAAU,kBAAkB,CAChC,KAAwE;IAExE,MAAM,KAAK,GAAG,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,KAAK,gBAAgB,CAAC,CAAC;IAC7D,IAAI,CAAC,KAAK;QAAE,OAAO,SAAS,CAAC;IAC7B,MAAM,KAAK,GAAG,MAAM,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,IAAI,EAAE,CAAC;IACzC,OAAO,KAAK,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,SAAS,CAAC;AAC9C,CAAC;AAED;;;;;;;;GAQG;AACH,MAAM,UAAU,2BAA2B,CACzC,KAAmB,EACnB,SAAiB;IAEjB,IAAI,CAAC,CAAC,KAAK,CAAC,MAAM,EAAE,MAAM,GAAG,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,WAAW;QAAE,OAAO,SAAS,CAAC;IACxE,OAAO,kBAAkB,CAAC,0BAA0B,CAAC,KAAK,EAAE,SAAS,CAAC,CAAC,CAAC;AAC1E,CAAC"}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Group evaluation (OR-of-AND-groups, #4904).
|
|
3
|
+
*
|
|
4
|
+
* Split out of `filter-evaluate.ts` (allowlisted at 583 lines,
|
|
5
|
+
* `scripts/module-size-allowlist.txt`) rather than grown in place — the
|
|
6
|
+
* ratchet forbids a listed file growing, and this needs its own home
|
|
7
|
+
* regardless: it's the one seam where "groups" become a second concern
|
|
8
|
+
* layered on top of the single-list evaluator, not a change to it.
|
|
9
|
+
*
|
|
10
|
+
* A `FilterGroup[]` ORs its groups together; within each group, the group's
|
|
11
|
+
* own `combinator` still governs (mirrors the selector's `+`: AND within a
|
|
12
|
+
* group, OR across groups). Rather than desugar this into a full-scan pass
|
|
13
|
+
* over some merged rule list — a second matching path that could not answer
|
|
14
|
+
* "why did this match" per-group, and could not be saved — each group is run
|
|
15
|
+
* through the EXACT SAME per-entity evaluator as a single-group query
|
|
16
|
+
* (`evaluateFilterRules(Federated)`), one call per group, unioned by
|
|
17
|
+
* (modelId, expressId). A single group (the overwhelmingly common case,
|
|
18
|
+
* and every query before #4904) takes this function's fast path straight
|
|
19
|
+
* through to the existing single-list evaluator with zero extra overhead.
|
|
20
|
+
*
|
|
21
|
+
* This also means the AND+`op:in` index prefilter is NOT lost for
|
|
22
|
+
* multi-group queries: each group still gets its own prefilter via
|
|
23
|
+
* `selectIterationSource`, same as it would running alone. Measured on
|
|
24
|
+
* `tests/models/buildingsmart/Building-Architecture.ifc` (the largest
|
|
25
|
+
* fixture available on this host; see the PR body for the exact numbers) —
|
|
26
|
+
* a two-group union costs about the sum of the two single-group runs, not a
|
|
27
|
+
* full-scan multiple of it.
|
|
28
|
+
*/
|
|
29
|
+
import type { IfcDataStore } from '@ifc-lite/parser';
|
|
30
|
+
import type { FilterGroup } from './filter-groups.js';
|
|
31
|
+
import { type EvaluateOptions, type FederatedEvaluateOptions, type FilteredElement, type EvaluatorModel } from './filter-evaluate.js';
|
|
32
|
+
/** Sync entry — small candidate sets, tests. Mirrors `evaluateFilterRules`. */
|
|
33
|
+
export declare function evaluateFilterGroups(modelId: string, store: IfcDataStore, groups: readonly FilterGroup[], options?: EvaluateOptions): FilteredElement[];
|
|
34
|
+
/** Async federated entry — production UI path. Mirrors `evaluateFilterRulesFederated`. */
|
|
35
|
+
export declare function evaluateFilterGroupsFederated(models: ReadonlyArray<EvaluatorModel>, groups: readonly FilterGroup[], options?: FederatedEvaluateOptions): Promise<FilteredElement[]>;
|
|
36
|
+
//# sourceMappingURL=filter-evaluate-groups.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"filter-evaluate-groups.d.ts","sourceRoot":"","sources":["../../src/filter/filter-evaluate-groups.ts"],"names":[],"mappings":"AAIA;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AAEH,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,kBAAkB,CAAC;AACrD,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAC;AACtD,OAAO,EAGL,KAAK,eAAe,EACpB,KAAK,wBAAwB,EAC7B,KAAK,eAAe,EACpB,KAAK,cAAc,EACpB,MAAM,sBAAsB,CAAC;AAQ9B,+EAA+E;AAC/E,wBAAgB,oBAAoB,CAClC,OAAO,EAAE,MAAM,EACf,KAAK,EAAE,YAAY,EACnB,MAAM,EAAE,SAAS,WAAW,EAAE,EAC9B,OAAO,GAAE,eAAoB,GAC5B,eAAe,EAAE,CAkCnB;AAED,0FAA0F;AAC1F,wBAAsB,6BAA6B,CACjD,MAAM,EAAE,aAAa,CAAC,cAAc,CAAC,EACrC,MAAM,EAAE,SAAS,WAAW,EAAE,EAC9B,OAAO,GAAE,wBAA6B,GACrC,OAAO,CAAC,eAAe,EAAE,CAAC,CAsD5B"}
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
/* This Source Code Form is subject to the terms of the Mozilla Public
|
|
2
|
+
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
3
|
+
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
|
|
4
|
+
import { evaluateFilterRules, evaluateFilterRulesFederated, } from './filter-evaluate.js';
|
|
5
|
+
const DEFAULT_LIMIT = 5_000;
|
|
6
|
+
function groupKey(el) {
|
|
7
|
+
return `${el.modelId}:${el.expressId}`;
|
|
8
|
+
}
|
|
9
|
+
/** Sync entry — small candidate sets, tests. Mirrors `evaluateFilterRules`. */
|
|
10
|
+
export function evaluateFilterGroups(modelId, store, groups, options = {}) {
|
|
11
|
+
if (groups.length === 0)
|
|
12
|
+
return [];
|
|
13
|
+
if (groups.length === 1) {
|
|
14
|
+
return evaluateFilterRules(modelId, store, groups[0].rules, groups[0].combinator, options);
|
|
15
|
+
}
|
|
16
|
+
const limit = options.limit ?? DEFAULT_LIMIT;
|
|
17
|
+
const seen = new Set();
|
|
18
|
+
const out = [];
|
|
19
|
+
for (const group of groups) {
|
|
20
|
+
if (out.length >= limit)
|
|
21
|
+
break;
|
|
22
|
+
if (group.rules.length === 0)
|
|
23
|
+
continue;
|
|
24
|
+
// Each group's OWN cap is the full `limit`, not `limit - out.length` —
|
|
25
|
+
// review (PR #4987) caught that a shrinking per-group cap can underfill
|
|
26
|
+
// the union: if THIS group's first N matches all duplicate ones an
|
|
27
|
+
// earlier group already returned, capping it at the (small) remaining
|
|
28
|
+
// budget stops the scan before its later, actually-unique matches are
|
|
29
|
+
// ever reached. The outer `out.length >= limit` check above still skips
|
|
30
|
+
// whole groups once the union is already full, and the inner loop below
|
|
31
|
+
// still stops appending once it is; the only change is that a later
|
|
32
|
+
// group may now be asked to find more candidates than strictly remain,
|
|
33
|
+
// which costs some extra scanning but never drops a real match.
|
|
34
|
+
const groupOut = evaluateFilterRules(modelId, store, group.rules, group.combinator, {
|
|
35
|
+
...options,
|
|
36
|
+
limit,
|
|
37
|
+
});
|
|
38
|
+
for (const el of groupOut) {
|
|
39
|
+
const key = groupKey(el);
|
|
40
|
+
if (seen.has(key))
|
|
41
|
+
continue;
|
|
42
|
+
seen.add(key);
|
|
43
|
+
out.push(el);
|
|
44
|
+
if (out.length >= limit)
|
|
45
|
+
break;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
return out;
|
|
49
|
+
}
|
|
50
|
+
/** Async federated entry — production UI path. Mirrors `evaluateFilterRulesFederated`. */
|
|
51
|
+
export async function evaluateFilterGroupsFederated(models, groups, options = {}) {
|
|
52
|
+
if (groups.length === 0)
|
|
53
|
+
return [];
|
|
54
|
+
if (groups.length === 1) {
|
|
55
|
+
return evaluateFilterRulesFederated(models, groups[0].rules, groups[0].combinator, options);
|
|
56
|
+
}
|
|
57
|
+
const limit = options.limit ?? DEFAULT_LIMIT;
|
|
58
|
+
const seen = new Set();
|
|
59
|
+
const out = [];
|
|
60
|
+
// A single-use candidate iterable (a generator, say) would be exhausted
|
|
61
|
+
// by the first group's call and come back empty for every later one —
|
|
62
|
+
// review (PR #4987). Materialize each model's candidates ONCE, up front,
|
|
63
|
+
// so every group's `evaluateFilterRulesFederated` call gets its own fresh
|
|
64
|
+
// pass over the same (now-array) candidate set.
|
|
65
|
+
const candidatesByModel = options.candidateExpressIdsByModel
|
|
66
|
+
? new Map([...options.candidateExpressIdsByModel].map(([id, c]) => [id, [...c]]))
|
|
67
|
+
: undefined;
|
|
68
|
+
// Accumulate `scanned` ACROSS groups rather than handing each group's raw
|
|
69
|
+
// per-group progress straight through — review (PR #4987) caught that
|
|
70
|
+
// without this, a multi-group run's progress bar reached 100% at the end
|
|
71
|
+
// of group 1 and then reset to 0% when group 2 started. `total` is
|
|
72
|
+
// reported as -1 (unknown) for the whole multi-group run: the UI's own
|
|
73
|
+
// progress renderer already has an unknown-total mode (a plain "scanned
|
|
74
|
+
// N" count, no bar) for exactly this case, rather than a bar this
|
|
75
|
+
// function would otherwise have to guess a cross-group total for.
|
|
76
|
+
let scannedSoFar = 0;
|
|
77
|
+
for (const group of groups) {
|
|
78
|
+
if (out.length >= limit)
|
|
79
|
+
break;
|
|
80
|
+
if (group.rules.length === 0)
|
|
81
|
+
continue;
|
|
82
|
+
let lastGroupScanned = 0;
|
|
83
|
+
// See the sync entry above (`evaluateFilterGroups`) for why this is
|
|
84
|
+
// `limit`, not `limit - out.length`: a shrinking per-group cap can
|
|
85
|
+
// underfill the union when a later group's early matches duplicate an
|
|
86
|
+
// earlier group's.
|
|
87
|
+
const groupOut = await evaluateFilterRulesFederated(models, group.rules, group.combinator, {
|
|
88
|
+
...options,
|
|
89
|
+
candidateExpressIdsByModel: candidatesByModel,
|
|
90
|
+
limit,
|
|
91
|
+
onProgress: options.onProgress
|
|
92
|
+
? (scanned) => {
|
|
93
|
+
lastGroupScanned = scanned;
|
|
94
|
+
options.onProgress(scannedSoFar + scanned, -1);
|
|
95
|
+
}
|
|
96
|
+
: undefined,
|
|
97
|
+
});
|
|
98
|
+
scannedSoFar += lastGroupScanned;
|
|
99
|
+
for (const el of groupOut) {
|
|
100
|
+
const key = groupKey(el);
|
|
101
|
+
if (seen.has(key))
|
|
102
|
+
continue;
|
|
103
|
+
seen.add(key);
|
|
104
|
+
out.push(el);
|
|
105
|
+
if (out.length >= limit)
|
|
106
|
+
break;
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
return out;
|
|
110
|
+
}
|
|
111
|
+
//# sourceMappingURL=filter-evaluate-groups.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"filter-evaluate-groups.js","sourceRoot":"","sources":["../../src/filter/filter-evaluate-groups.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import type { FilterRule, ModelRule, ModelTagRule } from './filter-rules.js';
|
|
2
|
+
/** What a model-scoped rule needs to know about one model. */
|
|
3
|
+
export interface ModelScope {
|
|
4
|
+
/** Stable source identity compared by `ModelRule` (`sourceFingerprint`, else the id). */
|
|
5
|
+
filterIdentity: string;
|
|
6
|
+
/** The model's tag ids; absent = untagged. */
|
|
7
|
+
tagIds?: ReadonlySet<string>;
|
|
8
|
+
/** Every tag id that currently exists — a rule naming any other id is unresolved. */
|
|
9
|
+
definedModelTagIds: ReadonlySet<string>;
|
|
10
|
+
}
|
|
11
|
+
export type ModelScopedRule = ModelRule | ModelTagRule;
|
|
12
|
+
export declare function isModelScopedRule(rule: FilterRule): rule is ModelScopedRule;
|
|
13
|
+
export declare function modelScopedRuleMatches(rule: ModelScopedRule, scope: ModelScope): boolean;
|
|
14
|
+
/**
|
|
15
|
+
* Under AND, may this model be skipped outright? True when some model-scoped
|
|
16
|
+
* rule fails for it. Under OR the answer is always `false`: the entity-level
|
|
17
|
+
* rules on the other side of the OR can still admit its entities.
|
|
18
|
+
*/
|
|
19
|
+
export declare function modelPrunedUnderAnd(rules: readonly FilterRule[], scope: ModelScope): boolean;
|
|
20
|
+
/** Empty set for callers that know of no tags (single-model tests, adapters without a tag store). */
|
|
21
|
+
export declare const NO_MODEL_TAGS: ReadonlySet<string>;
|
|
22
|
+
//# sourceMappingURL=filter-evaluate-model-tag.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"filter-evaluate-model-tag.d.ts","sourceRoot":"","sources":["../../src/filter/filter-evaluate-model-tag.ts"],"names":[],"mappings":"AAkBA,OAAO,KAAK,EAAE,UAAU,EAAE,SAAS,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC;AAE7E,8DAA8D;AAC9D,MAAM,WAAW,UAAU;IACzB,yFAAyF;IACzF,cAAc,EAAE,MAAM,CAAC;IACvB,8CAA8C;IAC9C,MAAM,CAAC,EAAE,WAAW,CAAC,MAAM,CAAC,CAAC;IAC7B,qFAAqF;IACrF,kBAAkB,EAAE,WAAW,CAAC,MAAM,CAAC,CAAC;CACzC;AAED,MAAM,MAAM,eAAe,GAAG,SAAS,GAAG,YAAY,CAAC;AAEvD,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,UAAU,GAAG,IAAI,IAAI,eAAe,CAE3E;AAED,wBAAgB,sBAAsB,CAAC,IAAI,EAAE,eAAe,EAAE,KAAK,EAAE,UAAU,GAAG,OAAO,CAGxF;AAED;;;;GAIG;AACH,wBAAgB,mBAAmB,CAAC,KAAK,EAAE,SAAS,UAAU,EAAE,EAAE,KAAK,EAAE,UAAU,GAAG,OAAO,CAE5F;AAED,qGAAqG;AACrG,eAAO,MAAM,aAAa,EAAE,WAAW,CAAC,MAAM,CAAa,CAAC"}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/* This Source Code Form is subject to the terms of the Mozilla Public
|
|
2
|
+
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
3
|
+
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
|
|
4
|
+
/**
|
|
5
|
+
* The MODEL-SCOPED rule arm of the evaluator (`filter-evaluate.ts`): `model`
|
|
6
|
+
* and `modelTag` (#4215) are decided by the owning model alone, never by the
|
|
7
|
+
* entity, so one answer per model serves every entity in it.
|
|
8
|
+
*
|
|
9
|
+
* Used twice, by design: as the per-entity arm (so OR keeps its boolean
|
|
10
|
+
* meaning — a model failing the tag condition still contributes entities that
|
|
11
|
+
* pass the other side of the OR) and as the whole-model prune under AND (a
|
|
12
|
+
* model that fails any model-scoped rule can contribute no entity, so its scan
|
|
13
|
+
* is skipped). Same function both times, so the two cannot disagree.
|
|
14
|
+
*/
|
|
15
|
+
import { modelTagRuleMatches } from './model-tag.js';
|
|
16
|
+
import { setOpMatches } from './filter-ops.js';
|
|
17
|
+
export function isModelScopedRule(rule) {
|
|
18
|
+
return rule.kind === 'model' || rule.kind === 'modelTag';
|
|
19
|
+
}
|
|
20
|
+
export function modelScopedRuleMatches(rule, scope) {
|
|
21
|
+
if (rule.kind === 'model')
|
|
22
|
+
return setOpMatches(rule.op, scope.filterIdentity, rule.values);
|
|
23
|
+
return modelTagRuleMatches(rule.op, rule.tagIds, scope.tagIds, scope.definedModelTagIds);
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Under AND, may this model be skipped outright? True when some model-scoped
|
|
27
|
+
* rule fails for it. Under OR the answer is always `false`: the entity-level
|
|
28
|
+
* rules on the other side of the OR can still admit its entities.
|
|
29
|
+
*/
|
|
30
|
+
export function modelPrunedUnderAnd(rules, scope) {
|
|
31
|
+
return rules.some((rule) => isModelScopedRule(rule) && !modelScopedRuleMatches(rule, scope));
|
|
32
|
+
}
|
|
33
|
+
/** Empty set for callers that know of no tags (single-model tests, adapters without a tag store). */
|
|
34
|
+
export const NO_MODEL_TAGS = new Set();
|
|
35
|
+
//# sourceMappingURL=filter-evaluate-model-tag.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"filter-evaluate-model-tag.js","sourceRoot":"","sources":["../../src/filter/filter-evaluate-model-tag.ts"],"names":[],"mappings":"AAAA;;+DAE+D;AAE/D;;;;;;;;;;GAUG;AAEH,OAAO,EAAE,mBAAmB,EAAE,MAAM,gBAAgB,CAAC;AACrD,OAAO,EAAE,YAAY,EAAE,MAAM,iBAAiB,CAAC;AAe/C,MAAM,UAAU,iBAAiB,CAAC,IAAgB;IAChD,OAAO,IAAI,CAAC,IAAI,KAAK,OAAO,IAAI,IAAI,CAAC,IAAI,KAAK,UAAU,CAAC;AAC3D,CAAC;AAED,MAAM,UAAU,sBAAsB,CAAC,IAAqB,EAAE,KAAiB;IAC7E,IAAI,IAAI,CAAC,IAAI,KAAK,OAAO;QAAE,OAAO,YAAY,CAAC,IAAI,CAAC,EAAE,EAAE,KAAK,CAAC,cAAc,EAAE,IAAI,CAAC,MAAM,CAAC,CAAC;IAC3F,OAAO,mBAAmB,CAAC,IAAI,CAAC,EAAE,EAAE,IAAI,CAAC,MAAM,EAAE,KAAK,CAAC,MAAM,EAAE,KAAK,CAAC,kBAAkB,CAAC,CAAC;AAC3F,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,mBAAmB,CAAC,KAA4B,EAAE,KAAiB;IACjF,OAAO,KAAK,CAAC,IAAI,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,iBAAiB,CAAC,IAAI,CAAC,IAAI,CAAC,sBAAsB,CAAC,IAAI,EAAE,KAAK,CAAC,CAAC,CAAC;AAC/F,CAAC;AAED,qGAAqG;AACrG,MAAM,CAAC,MAAM,aAAa,GAAwB,IAAI,GAAG,EAAE,CAAC"}
|