@ifc-lite/cli 0.27.0 → 0.28.1
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/README.md +1 -1
- package/dist/commands/bcf.js +1 -1
- package/dist/commands/bcf.js.map +1 -1
- package/dist/commands/delivery-checks.d.ts +56 -0
- package/dist/commands/delivery-checks.d.ts.map +1 -0
- package/dist/commands/delivery-checks.js +222 -0
- package/dist/commands/delivery-checks.js.map +1 -0
- package/dist/commands/delivery-recipe.d.ts +23 -0
- package/dist/commands/delivery-recipe.d.ts.map +1 -0
- package/dist/commands/delivery-recipe.js +104 -0
- package/dist/commands/delivery-recipe.js.map +1 -0
- package/dist/commands/delivery-report.d.ts +51 -0
- package/dist/commands/delivery-report.d.ts.map +1 -0
- package/dist/commands/delivery-report.js +114 -0
- package/dist/commands/delivery-report.js.map +1 -0
- package/dist/commands/delivery.d.ts +2 -0
- package/dist/commands/delivery.d.ts.map +1 -0
- package/dist/commands/delivery.js +127 -0
- package/dist/commands/delivery.js.map +1 -0
- package/dist/commands/export-rust-formats.d.ts +11 -0
- package/dist/commands/export-rust-formats.d.ts.map +1 -0
- package/dist/commands/export-rust-formats.js +161 -0
- package/dist/commands/export-rust-formats.js.map +1 -0
- package/dist/commands/export.d.ts.map +1 -1
- package/dist/commands/export.js +31 -145
- package/dist/commands/export.js.map +1 -1
- package/dist/commands/extract-entities.d.ts +10 -17
- package/dist/commands/extract-entities.d.ts.map +1 -1
- package/dist/commands/extract-entities.js +34 -23
- package/dist/commands/extract-entities.js.map +1 -1
- package/dist/commands/gym.js +2 -2
- package/dist/commands/mutate.d.ts +16 -4
- package/dist/commands/mutate.d.ts.map +1 -1
- package/dist/commands/mutate.js +38 -46
- package/dist/commands/mutate.js.map +1 -1
- package/dist/commands/step-args.d.ts +114 -0
- package/dist/commands/step-args.d.ts.map +1 -0
- package/dist/commands/step-args.js +299 -0
- package/dist/commands/step-args.js.map +1 -0
- package/dist/commands/subset-relations.d.ts +124 -0
- package/dist/commands/subset-relations.d.ts.map +1 -0
- package/dist/commands/subset-relations.js +273 -0
- package/dist/commands/subset-relations.js.map +1 -0
- package/dist/index.js +7 -1
- package/dist/index.js.map +1 -1
- package/package.json +14 -14
package/README.md
CHANGED
|
@@ -48,7 +48,7 @@ Global flags: `--json`, `--out <file>`, `--verbose`, `--quiet`, `--debug`, `--lo
|
|
|
48
48
|
|
|
49
49
|
## gym
|
|
50
50
|
|
|
51
|
-
`ifc-lite gym` is a prototype reset/step/reward environment API over the existing headless checks: the skeleton of an RLVR environment for buildings (see [`
|
|
51
|
+
`ifc-lite gym` is a prototype reset/step/reward environment API over the existing headless checks: the skeleton of an RLVR environment for buildings (see [`tools/world-gym/README.md`](../../tools/world-gym/README.md) M2 and [`tools/world-gym/benchmark/BENCHMARK.md`](../../tools/world-gym/benchmark/BENCHMARK.md) B0.4). It wraps a model - either a fixed file (`--model`) or a procedurally generated World Gym episode (`--seed`) - and lets an agent apply data-mutation ops, scoring each step against the same schema/clash/ids checks the `validate`, `clash`, and `ids` commands already run.
|
|
52
52
|
|
|
53
53
|
```bash
|
|
54
54
|
ifc-lite gym --model model.ifc --checks schema,clash
|
package/dist/commands/bcf.js
CHANGED
|
@@ -21,7 +21,7 @@ export async function bcfCommand(args) {
|
|
|
21
21
|
const author = getFlag(args, '--author') ?? 'cli@ifc-lite.com';
|
|
22
22
|
const outPath = getFlag(args, '--out');
|
|
23
23
|
if (!title)
|
|
24
|
-
fatal('Usage: ifc-lite bcf create --title "
|
|
24
|
+
fatal('Usage: ifc-lite bcf create --title "Missing door" [--description "..."] [--author email] --out file.bcf');
|
|
25
25
|
if (!outPath)
|
|
26
26
|
fatal('--out is required for BCF creation');
|
|
27
27
|
const project = await bcf.createProject({ name: 'CLI Project' });
|
package/dist/commands/bcf.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"bcf.js","sourceRoot":"","sources":["../../src/commands/bcf.ts"],"names":[],"mappings":"AAAA;;+DAE+D;AAE/D;;;;GAIG;AAEH,OAAO,EAAE,QAAQ,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAC;AACvD,OAAO,EAAE,YAAY,EAAE,MAAM,eAAe,CAAC;AAC7C,OAAO,EAAE,SAAS,EAAE,OAAO,EAAE,KAAK,EAAE,MAAM,cAAc,CAAC;AAEzD,MAAM,CAAC,KAAK,UAAU,UAAU,CAAC,IAAc;IAC7C,MAAM,UAAU,GAAG,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,UAAU,CAAC,GAAG,CAAC,CAAC,CAAC;IACtD,IAAI,CAAC,UAAU;QAAE,KAAK,CAAC,yDAAyD,CAAC,CAAC;IAElF,MAAM,GAAG,GAAG,IAAI,YAAY,EAAE,CAAC;IAE/B,QAAQ,UAAU,EAAE,CAAC;QACnB,KAAK,QAAQ,CAAC,CAAC,CAAC;YACd,MAAM,KAAK,GAAG,OAAO,CAAC,IAAI,EAAE,SAAS,CAAC,CAAC;YACvC,MAAM,WAAW,GAAG,OAAO,CAAC,IAAI,EAAE,eAAe,CAAC,CAAC;YACnD,MAAM,MAAM,GAAG,OAAO,CAAC,IAAI,EAAE,UAAU,CAAC,IAAI,kBAAkB,CAAC;YAC/D,MAAM,OAAO,GAAG,OAAO,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC;YACvC,IAAI,CAAC,KAAK;gBAAE,KAAK,CAAC,
|
|
1
|
+
{"version":3,"file":"bcf.js","sourceRoot":"","sources":["../../src/commands/bcf.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { type IfcDataStore } from '@ifc-lite/parser';
|
|
2
|
+
import { type ValidationIssue } from './validate.js';
|
|
3
|
+
export type CheckStatus = 'pass' | 'fail' | 'error';
|
|
4
|
+
export interface LoadedModel {
|
|
5
|
+
path: string;
|
|
6
|
+
/** SHA-256 of the exact bytes read from disk (post ifcZIP unwrap: the STEP text actually checked). */
|
|
7
|
+
sha256: string;
|
|
8
|
+
store: IfcDataStore;
|
|
9
|
+
}
|
|
10
|
+
export interface ModelLoadError {
|
|
11
|
+
path: string;
|
|
12
|
+
error: string;
|
|
13
|
+
}
|
|
14
|
+
/** Load one model file for delivery checking, never throwing and never exiting the process. */
|
|
15
|
+
export declare function loadModelForDelivery(path: string): Promise<LoadedModel | ModelLoadError>;
|
|
16
|
+
export interface StructuralCheckResult {
|
|
17
|
+
type: 'structural';
|
|
18
|
+
model: string;
|
|
19
|
+
status: CheckStatus;
|
|
20
|
+
errorCount: number;
|
|
21
|
+
warningCount: number;
|
|
22
|
+
infoCount: number;
|
|
23
|
+
issues: ValidationIssue[];
|
|
24
|
+
error?: string;
|
|
25
|
+
}
|
|
26
|
+
/** Run the same structural rules `ifc-lite validate` runs. `fail` iff at least one error-severity issue was found. */
|
|
27
|
+
export declare function runStructuralCheck(modelPath: string, store: IfcDataStore): StructuralCheckResult;
|
|
28
|
+
export interface IdsCheckResult {
|
|
29
|
+
type: 'ids';
|
|
30
|
+
model: string;
|
|
31
|
+
source: string;
|
|
32
|
+
status: CheckStatus;
|
|
33
|
+
totalSpecifications?: number;
|
|
34
|
+
passedSpecifications?: number;
|
|
35
|
+
failedSpecifications?: number;
|
|
36
|
+
notApplicableSpecifications?: number;
|
|
37
|
+
totalEntities?: number;
|
|
38
|
+
passedEntities?: number;
|
|
39
|
+
failedEntities?: number;
|
|
40
|
+
error?: string;
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Run one IDS rule file against one already-loaded model.
|
|
44
|
+
*
|
|
45
|
+
* `status` is `error` when the IDS file itself could not be read/parsed, or
|
|
46
|
+
* when it declares zero specifications (nothing was actually evaluated —
|
|
47
|
+
* reporting that as `pass` would hide an empty ruleset behind a green
|
|
48
|
+
* check). Otherwise `fail` iff at least one specification failed; a
|
|
49
|
+
* specification whose applicability matched no entities counts as neither
|
|
50
|
+
* pass nor fail (`notApplicableSpecifications`) — if EVERY specification
|
|
51
|
+
* lands there (nothing was definitively satisfied OR violated), the check
|
|
52
|
+
* reports `error` rather than a `pass` that would rest on zero actual
|
|
53
|
+
* evidence.
|
|
54
|
+
*/
|
|
55
|
+
export declare function runIdsCheck(modelPath: string, store: IfcDataStore, idsPath: string): Promise<IdsCheckResult>;
|
|
56
|
+
//# sourceMappingURL=delivery-checks.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"delivery-checks.d.ts","sourceRoot":"","sources":["../../src/commands/delivery-checks.ts"],"names":[],"mappings":"AA6BA,OAAO,EAA+B,KAAK,YAAY,EAAE,MAAM,kBAAkB,CAAC;AAGlF,OAAO,EAA2B,KAAK,eAAe,EAAE,MAAM,eAAe,CAAC;AAE9E,MAAM,MAAM,WAAW,GAAG,MAAM,GAAG,MAAM,GAAG,OAAO,CAAC;AAEpD,MAAM,WAAW,WAAW;IAC1B,IAAI,EAAE,MAAM,CAAC;IACb,sGAAsG;IACtG,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,EAAE,YAAY,CAAC;CACrB;AAED,MAAM,WAAW,cAAc;IAC7B,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,CAAC;CACf;AAED,+FAA+F;AAC/F,wBAAsB,oBAAoB,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,WAAW,GAAG,cAAc,CAAC,CAyC9F;AAED,MAAM,WAAW,qBAAqB;IACpC,IAAI,EAAE,YAAY,CAAC;IACnB,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,WAAW,CAAC;IACpB,UAAU,EAAE,MAAM,CAAC;IACnB,YAAY,EAAE,MAAM,CAAC;IACrB,SAAS,EAAE,MAAM,CAAC;IAClB,MAAM,EAAE,eAAe,EAAE,CAAC;IAC1B,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED,sHAAsH;AACtH,wBAAgB,kBAAkB,CAAC,SAAS,EAAE,MAAM,EAAE,KAAK,EAAE,YAAY,GAAG,qBAAqB,CAchG;AAED,MAAM,WAAW,cAAc;IAC7B,IAAI,EAAE,KAAK,CAAC;IACZ,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,WAAW,CAAC;IACpB,mBAAmB,CAAC,EAAE,MAAM,CAAC;IAC7B,oBAAoB,CAAC,EAAE,MAAM,CAAC;IAC9B,oBAAoB,CAAC,EAAE,MAAM,CAAC;IAC9B,2BAA2B,CAAC,EAAE,MAAM,CAAC;IACrC,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AA+GD;;;;;;;;;;;;GAYG;AACH,wBAAsB,WAAW,CAAC,SAAS,EAAE,MAAM,EAAE,KAAK,EAAE,YAAY,EAAE,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,cAAc,CAAC,CA6ClH"}
|
|
@@ -0,0 +1,222 @@
|
|
|
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
|
+
* Model loading and per-check evaluation for `ifc-lite delivery`.
|
|
6
|
+
*
|
|
7
|
+
* Three outcomes exist for every check, never just two:
|
|
8
|
+
* - `pass` — the check ran and found nothing to report.
|
|
9
|
+
* - `fail` — the check ran and found a violation.
|
|
10
|
+
* - `error` — the check could NOT be run (unreadable model, unreadable or
|
|
11
|
+
* unparsable IDS file, an IDS document declaring zero
|
|
12
|
+
* specifications). An unevaluable check must never be folded
|
|
13
|
+
* into `pass`: a delivery report that silently skips a rule
|
|
14
|
+
* and still reads as clean is worse than no report.
|
|
15
|
+
*
|
|
16
|
+
* `loadModelForDelivery` deliberately does NOT reuse `loader.ts`'s
|
|
17
|
+
* `loadIfcFile`/`loadIfcBytes`: those call `process.exit(1)` directly on an
|
|
18
|
+
* empty, non-STEP, or unparsable file, which is correct for a single-file
|
|
19
|
+
* command but would kill the whole `delivery` process on the SECOND model in
|
|
20
|
+
* a multi-model recipe just because the first one was bad. This loader
|
|
21
|
+
* performs the same validation (STEP signature, ifcZIP unwrap, parse) but
|
|
22
|
+
* returns a `{ error }` result instead of exiting, so one unreadable model
|
|
23
|
+
* becomes one `error` entry in the report — never a crashed run and never a
|
|
24
|
+
* silently-passing verdict.
|
|
25
|
+
*/
|
|
26
|
+
import { readFile } from 'node:fs/promises';
|
|
27
|
+
import { createHash } from 'node:crypto';
|
|
28
|
+
import { IfcParser, unwrapIfcZipView } from '@ifc-lite/parser';
|
|
29
|
+
import { createDataAccessor } from '@ifc-lite/ids/bridge';
|
|
30
|
+
import { IDSNamespace } from '@ifc-lite/sdk';
|
|
31
|
+
import { computeValidationIssues } from './validate.js';
|
|
32
|
+
/** Load one model file for delivery checking, never throwing and never exiting the process. */
|
|
33
|
+
export async function loadModelForDelivery(path) {
|
|
34
|
+
let bytes;
|
|
35
|
+
try {
|
|
36
|
+
bytes = await readFile(path);
|
|
37
|
+
}
|
|
38
|
+
catch (err) {
|
|
39
|
+
return { path, error: `could not be read: ${err.message}` };
|
|
40
|
+
}
|
|
41
|
+
if (bytes.byteLength === 0) {
|
|
42
|
+
return { path, error: 'is empty (0 bytes)' };
|
|
43
|
+
}
|
|
44
|
+
try {
|
|
45
|
+
bytes = new Uint8Array(await unwrapIfcZipView(bytes));
|
|
46
|
+
}
|
|
47
|
+
catch (err) {
|
|
48
|
+
return { path, error: `could not be unwrapped: ${err.message}` };
|
|
49
|
+
}
|
|
50
|
+
const headerSnippet = new TextDecoder('latin1').decode(bytes.subarray(0, Math.min(bytes.byteLength, 256)));
|
|
51
|
+
if (!headerSnippet.includes('ISO-10303-21')) {
|
|
52
|
+
return { path, error: 'is not a valid IFC/STEP file' };
|
|
53
|
+
}
|
|
54
|
+
const sha256 = createHash('sha256').update(bytes).digest('hex');
|
|
55
|
+
const parser = new IfcParser();
|
|
56
|
+
const origLog = console.log;
|
|
57
|
+
const origWarn = console.warn;
|
|
58
|
+
console.log = () => { };
|
|
59
|
+
console.warn = () => { };
|
|
60
|
+
try {
|
|
61
|
+
const arrayBuffer = bytes.buffer.slice(bytes.byteOffset, bytes.byteOffset + bytes.byteLength);
|
|
62
|
+
const store = await parser.parseColumnar(arrayBuffer, {});
|
|
63
|
+
store.fileSize = bytes.byteLength;
|
|
64
|
+
return { path, sha256, store };
|
|
65
|
+
}
|
|
66
|
+
catch (err) {
|
|
67
|
+
return { path, error: `could not be parsed: ${err.message}` };
|
|
68
|
+
}
|
|
69
|
+
finally {
|
|
70
|
+
console.log = origLog;
|
|
71
|
+
console.warn = origWarn;
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
/** Run the same structural rules `ifc-lite validate` runs. `fail` iff at least one error-severity issue was found. */
|
|
75
|
+
export function runStructuralCheck(modelPath, store) {
|
|
76
|
+
const issues = computeValidationIssues(store);
|
|
77
|
+
const errorCount = issues.filter(i => i.severity === 'error').length;
|
|
78
|
+
const warningCount = issues.filter(i => i.severity === 'warning').length;
|
|
79
|
+
const infoCount = issues.filter(i => i.severity === 'info').length;
|
|
80
|
+
return {
|
|
81
|
+
type: 'structural',
|
|
82
|
+
model: modelPath,
|
|
83
|
+
status: errorCount > 0 ? 'fail' : 'pass',
|
|
84
|
+
errorCount,
|
|
85
|
+
warningCount,
|
|
86
|
+
infoCount,
|
|
87
|
+
issues,
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
/**
|
|
91
|
+
* A specification counts as genuinely evaluated only when its cardinality
|
|
92
|
+
* bound could actually have been violated by SOME entity count, or it
|
|
93
|
+
* checked at least one entity's requirements. `minOccurs="0"` with no
|
|
94
|
+
* finite upper bound (the IDS "optional, if present" cardinality) is
|
|
95
|
+
* satisfied by zero matches for every possible model — no entity count
|
|
96
|
+
* could ever fail it — so treating that as `pass` reports success for a
|
|
97
|
+
* rule that could not possibly have failed and checked nothing. That is
|
|
98
|
+
* distinct from a `maxOccurs="0"` prohibition ("must not exist"), which a
|
|
99
|
+
* nonzero applicable count WOULD have violated, so zero matches there is
|
|
100
|
+
* real evidence, not a vacuous result — `@ifc-lite/ids`'s own corpus fixture
|
|
101
|
+
* `pass-prohibited_specifications_passes_if_the_applicability_does_not_matches.ids`
|
|
102
|
+
* exercises exactly this and must keep reading `pass`.
|
|
103
|
+
*/
|
|
104
|
+
function isVacuousSpecification(spec) {
|
|
105
|
+
if (spec.status === 'not_applicable')
|
|
106
|
+
return true;
|
|
107
|
+
const c = spec.cardinalityResult;
|
|
108
|
+
if (!c || c.actualCount !== 0)
|
|
109
|
+
return false;
|
|
110
|
+
return c.minExpected === 0 && (c.maxExpected === undefined || c.maxExpected === 'unbounded');
|
|
111
|
+
}
|
|
112
|
+
/**
|
|
113
|
+
* Summarize a real `IDSValidationReport` for the delivery checker.
|
|
114
|
+
*
|
|
115
|
+
* This intentionally does NOT call `bim.ids.summarize()` (`@ifc-lite/sdk`)
|
|
116
|
+
* for the specification-level bucketing: that helper defers entirely to
|
|
117
|
+
* the validator's own per-specification `status`, and the validator marks
|
|
118
|
+
* a `minOccurs="0"`/unbounded-`maxOccurs` specification `pass` whenever it
|
|
119
|
+
* matches zero entities (see `isVacuousSpecification` above) — correct IDS
|
|
120
|
+
* semantics for an "optional" clause, but exactly the vacuous-check defect
|
|
121
|
+
* a delivery checker must not fold into `pass`. Entity counts ARE summed
|
|
122
|
+
* directly here for the same reason `bim.ids.summarize()` would need
|
|
123
|
+
* `includePassingEntities: true` to get them right: that option controls
|
|
124
|
+
* whether PASSING entities are even present in `entityResults`, and this
|
|
125
|
+
* file used to call `validate()` with it `false`, which is harmless for the
|
|
126
|
+
* raw validator's own `summary.totalEntitiesChecked` (computed from each
|
|
127
|
+
* specification's `passedCount`/`failedCount`, not from `entityResults`)
|
|
128
|
+
* but would silently undercount any per-entity tally derived from
|
|
129
|
+
* `entityResults` directly, `bim.ids.summarize()` included.
|
|
130
|
+
*/
|
|
131
|
+
function summarizeIdsReport(report) {
|
|
132
|
+
let passedSpecifications = 0;
|
|
133
|
+
let failedSpecifications = 0;
|
|
134
|
+
let notApplicableSpecifications = 0;
|
|
135
|
+
let totalEntities = 0;
|
|
136
|
+
let passedEntities = 0;
|
|
137
|
+
let failedEntities = 0;
|
|
138
|
+
for (const spec of report.specificationResults) {
|
|
139
|
+
for (const entity of spec.entityResults) {
|
|
140
|
+
totalEntities++;
|
|
141
|
+
if (entity.passed)
|
|
142
|
+
passedEntities++;
|
|
143
|
+
else
|
|
144
|
+
failedEntities++;
|
|
145
|
+
}
|
|
146
|
+
if (spec.status === 'fail') {
|
|
147
|
+
failedSpecifications++;
|
|
148
|
+
}
|
|
149
|
+
else if (isVacuousSpecification(spec)) {
|
|
150
|
+
notApplicableSpecifications++;
|
|
151
|
+
}
|
|
152
|
+
else {
|
|
153
|
+
passedSpecifications++;
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
return {
|
|
157
|
+
totalSpecifications: report.specificationResults.length,
|
|
158
|
+
passedSpecifications,
|
|
159
|
+
failedSpecifications,
|
|
160
|
+
notApplicableSpecifications,
|
|
161
|
+
totalEntities,
|
|
162
|
+
passedEntities,
|
|
163
|
+
failedEntities,
|
|
164
|
+
};
|
|
165
|
+
}
|
|
166
|
+
/**
|
|
167
|
+
* Run one IDS rule file against one already-loaded model.
|
|
168
|
+
*
|
|
169
|
+
* `status` is `error` when the IDS file itself could not be read/parsed, or
|
|
170
|
+
* when it declares zero specifications (nothing was actually evaluated —
|
|
171
|
+
* reporting that as `pass` would hide an empty ruleset behind a green
|
|
172
|
+
* check). Otherwise `fail` iff at least one specification failed; a
|
|
173
|
+
* specification whose applicability matched no entities counts as neither
|
|
174
|
+
* pass nor fail (`notApplicableSpecifications`) — if EVERY specification
|
|
175
|
+
* lands there (nothing was definitively satisfied OR violated), the check
|
|
176
|
+
* reports `error` rather than a `pass` that would rest on zero actual
|
|
177
|
+
* evidence.
|
|
178
|
+
*/
|
|
179
|
+
export async function runIdsCheck(modelPath, store, idsPath) {
|
|
180
|
+
const base = { type: 'ids', model: modelPath, source: idsPath };
|
|
181
|
+
let idsContent;
|
|
182
|
+
try {
|
|
183
|
+
idsContent = await readFile(idsPath, 'utf-8');
|
|
184
|
+
}
|
|
185
|
+
catch (err) {
|
|
186
|
+
return { ...base, status: 'error', error: `could not be read: ${err.message}` };
|
|
187
|
+
}
|
|
188
|
+
const ids = new IDSNamespace();
|
|
189
|
+
let idsDoc;
|
|
190
|
+
try {
|
|
191
|
+
idsDoc = await ids.parse(idsContent);
|
|
192
|
+
}
|
|
193
|
+
catch (err) {
|
|
194
|
+
return { ...base, status: 'error', error: `could not be parsed: ${err.message}` };
|
|
195
|
+
}
|
|
196
|
+
const accessor = createDataAccessor(store);
|
|
197
|
+
let report;
|
|
198
|
+
try {
|
|
199
|
+
report = (await ids.validate(idsDoc, {
|
|
200
|
+
accessor,
|
|
201
|
+
modelInfo: { schemaVersion: store.schemaVersion },
|
|
202
|
+
locale: 'en',
|
|
203
|
+
includePassingEntities: true,
|
|
204
|
+
}));
|
|
205
|
+
}
|
|
206
|
+
catch (err) {
|
|
207
|
+
return { ...base, status: 'error', error: `validation failed: ${err.message}` };
|
|
208
|
+
}
|
|
209
|
+
const s = summarizeIdsReport(report);
|
|
210
|
+
if (s.totalSpecifications === 0) {
|
|
211
|
+
return { ...base, status: 'error', error: 'declares zero specifications', ...s };
|
|
212
|
+
}
|
|
213
|
+
const evaluated = s.passedSpecifications + s.failedSpecifications;
|
|
214
|
+
const status = s.failedSpecifications > 0 ? 'fail' : evaluated > 0 ? 'pass' : 'error';
|
|
215
|
+
return {
|
|
216
|
+
...base,
|
|
217
|
+
status,
|
|
218
|
+
...s,
|
|
219
|
+
...(status === 'error' ? { error: 'every specification was not-applicable — nothing was evaluated' } : {}),
|
|
220
|
+
};
|
|
221
|
+
}
|
|
222
|
+
//# sourceMappingURL=delivery-checks.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"delivery-checks.js","sourceRoot":"","sources":["../../src/commands/delivery-checks.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/** The recipe fields as declared in the JSON file (paths not yet resolved). */
|
|
2
|
+
export interface DeliveryRecipe {
|
|
3
|
+
models: string[];
|
|
4
|
+
structural: boolean;
|
|
5
|
+
ids: string[];
|
|
6
|
+
}
|
|
7
|
+
/** A recipe with every `models`/`ids` path resolved to an absolute path. */
|
|
8
|
+
export interface ResolvedDeliveryRecipe extends DeliveryRecipe {
|
|
9
|
+
/** Absolute path to the recipe file itself. */
|
|
10
|
+
recipePath: string;
|
|
11
|
+
/** `models`, resolved to absolute paths, in declared order. */
|
|
12
|
+
resolvedModels: string[];
|
|
13
|
+
/** `ids`, resolved to absolute paths, in declared order. */
|
|
14
|
+
resolvedIds: string[];
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Load and validate a delivery recipe, resolving every declared path
|
|
18
|
+
* relative to the recipe file's own directory. Every failure mode is a
|
|
19
|
+
* `fatal(...)` with a specific reason — never a silently empty or
|
|
20
|
+
* partially-applied recipe.
|
|
21
|
+
*/
|
|
22
|
+
export declare function loadDeliveryRecipe(path: string): Promise<ResolvedDeliveryRecipe>;
|
|
23
|
+
//# sourceMappingURL=delivery-recipe.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"delivery-recipe.d.ts","sourceRoot":"","sources":["../../src/commands/delivery-recipe.ts"],"names":[],"mappings":"AAuCA,+EAA+E;AAC/E,MAAM,WAAW,cAAc;IAC7B,MAAM,EAAE,MAAM,EAAE,CAAC;IACjB,UAAU,EAAE,OAAO,CAAC;IACpB,GAAG,EAAE,MAAM,EAAE,CAAC;CACf;AAED,4EAA4E;AAC5E,MAAM,WAAW,sBAAuB,SAAQ,cAAc;IAC5D,+CAA+C;IAC/C,UAAU,EAAE,MAAM,CAAC;IACnB,+DAA+D;IAC/D,cAAc,EAAE,MAAM,EAAE,CAAC;IACzB,4DAA4D;IAC5D,WAAW,EAAE,MAAM,EAAE,CAAC;CACvB;AAQD;;;;;GAKG;AACH,wBAAsB,kBAAkB,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,sBAAsB,CAAC,CAiEtF"}
|
|
@@ -0,0 +1,104 @@
|
|
|
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 saved recipe `ifc-lite delivery` runs.
|
|
6
|
+
*
|
|
7
|
+
* A recipe is a plain JSON file naming one or more model files and the
|
|
8
|
+
* checks to run against every one of them — `structural` (the same rules
|
|
9
|
+
* `ifc-lite validate` runs) and/or one or more `ids` rule files (the same
|
|
10
|
+
* validator `ifc-lite ids` runs). It exists so a delivery check is a single,
|
|
11
|
+
* versioned, reviewable artifact a team commits and re-runs, rather than a
|
|
12
|
+
* remembered shell invocation.
|
|
13
|
+
*
|
|
14
|
+
* ```json
|
|
15
|
+
* {
|
|
16
|
+
* "models": ["model.ifc"],
|
|
17
|
+
* "structural": true,
|
|
18
|
+
* "ids": ["door-rules.ids"]
|
|
19
|
+
* }
|
|
20
|
+
* ```
|
|
21
|
+
*
|
|
22
|
+
* Every `models`/`ids` path is resolved relative to the DIRECTORY CONTAINING
|
|
23
|
+
* THE RECIPE FILE, not the process's current working directory — a recipe
|
|
24
|
+
* committed alongside its fixtures stays runnable from anywhere.
|
|
25
|
+
*
|
|
26
|
+
* A malformed recipe is a `fatal(...)`, matching every other saved-definition
|
|
27
|
+
* loader in this CLI (`schedule-spec.ts`): not valid JSON, not a JSON object,
|
|
28
|
+
* an unrecognised field, a field of the wrong type, an empty/missing
|
|
29
|
+
* `models` list, or declaring NEITHER `structural: true` NOR a non-empty
|
|
30
|
+
* `ids` list (a recipe with zero applicable checks can never produce a
|
|
31
|
+
* meaningful delivery verdict, so it is rejected up front rather than
|
|
32
|
+
* silently reporting an empty "pass").
|
|
33
|
+
*/
|
|
34
|
+
import { readFile } from 'node:fs/promises';
|
|
35
|
+
import { dirname, resolve } from 'node:path';
|
|
36
|
+
import { fatal } from '../output.js';
|
|
37
|
+
const RECIPE_FIELDS = ['models', 'structural', 'ids'];
|
|
38
|
+
function isStringArray(value) {
|
|
39
|
+
return Array.isArray(value) && value.every(v => typeof v === 'string');
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Load and validate a delivery recipe, resolving every declared path
|
|
43
|
+
* relative to the recipe file's own directory. Every failure mode is a
|
|
44
|
+
* `fatal(...)` with a specific reason — never a silently empty or
|
|
45
|
+
* partially-applied recipe.
|
|
46
|
+
*/
|
|
47
|
+
export async function loadDeliveryRecipe(path) {
|
|
48
|
+
let content;
|
|
49
|
+
try {
|
|
50
|
+
content = await readFile(path, 'utf-8');
|
|
51
|
+
}
|
|
52
|
+
catch (err) {
|
|
53
|
+
fatal(`Recipe "${path}" could not be read: ${err.message}`);
|
|
54
|
+
}
|
|
55
|
+
let parsed;
|
|
56
|
+
try {
|
|
57
|
+
parsed = JSON.parse(content);
|
|
58
|
+
}
|
|
59
|
+
catch (err) {
|
|
60
|
+
fatal(`Recipe "${path}" is not valid JSON: ${err.message}`);
|
|
61
|
+
}
|
|
62
|
+
if (typeof parsed !== 'object' || parsed === null || Array.isArray(parsed)) {
|
|
63
|
+
fatal(`Recipe "${path}" must be a JSON object with "models" and at least one of "structural"/"ids", got ${Array.isArray(parsed) ? 'an array' : typeof parsed}.`);
|
|
64
|
+
}
|
|
65
|
+
const obj = parsed;
|
|
66
|
+
const unknownKeys = Object.keys(obj).filter(k => !RECIPE_FIELDS.includes(k));
|
|
67
|
+
if (unknownKeys.length > 0) {
|
|
68
|
+
fatal(`Recipe "${path}" has unrecognised field(s): ${unknownKeys.join(', ')}. Valid fields: ${RECIPE_FIELDS.join(', ')}.`);
|
|
69
|
+
}
|
|
70
|
+
if (!('models' in obj) || !isStringArray(obj.models) || obj.models.length === 0) {
|
|
71
|
+
fatal(`Recipe "${path}": "models" must be a non-empty array of file paths.`);
|
|
72
|
+
}
|
|
73
|
+
const models = obj.models;
|
|
74
|
+
if ('structural' in obj && typeof obj.structural !== 'boolean') {
|
|
75
|
+
fatal(`Recipe "${path}": "structural" must be a boolean, got ${typeof obj.structural}.`);
|
|
76
|
+
}
|
|
77
|
+
const structural = obj.structural ?? false;
|
|
78
|
+
if ('ids' in obj && !isStringArray(obj.ids)) {
|
|
79
|
+
fatal(`Recipe "${path}": "ids" must be an array of file paths.`);
|
|
80
|
+
}
|
|
81
|
+
const ids = obj.ids ?? [];
|
|
82
|
+
if (!structural && ids.length === 0) {
|
|
83
|
+
fatal(`Recipe "${path}" declares no applicable checks: set "structural": true and/or a non-empty "ids" list. A zero-check recipe can never produce a delivery verdict.`);
|
|
84
|
+
}
|
|
85
|
+
// Duplicate model/ids entries are rejected rather than silently
|
|
86
|
+
// deduplicated or run twice under one collapsed key: a repeated path is
|
|
87
|
+
// almost certainly a copy-paste mistake in the recipe, and either
|
|
88
|
+
// response (silent dedup, or a doubled report entry) would misrepresent
|
|
89
|
+
// what the recipe actually says it checks.
|
|
90
|
+
const dupModels = models.filter((m, i) => models.indexOf(m) !== i);
|
|
91
|
+
if (dupModels.length > 0) {
|
|
92
|
+
fatal(`Recipe "${path}": "models" lists the same path more than once: ${[...new Set(dupModels)].join(', ')}.`);
|
|
93
|
+
}
|
|
94
|
+
const dupIds = ids.filter((m, i) => ids.indexOf(m) !== i);
|
|
95
|
+
if (dupIds.length > 0) {
|
|
96
|
+
fatal(`Recipe "${path}": "ids" lists the same path more than once: ${[...new Set(dupIds)].join(', ')}.`);
|
|
97
|
+
}
|
|
98
|
+
const recipePath = resolve(path);
|
|
99
|
+
const baseDir = dirname(recipePath);
|
|
100
|
+
const resolvedModels = models.map(m => resolve(baseDir, m));
|
|
101
|
+
const resolvedIds = ids.map(i => resolve(baseDir, i));
|
|
102
|
+
return { recipePath, models, structural, ids, resolvedModels, resolvedIds };
|
|
103
|
+
}
|
|
104
|
+
//# sourceMappingURL=delivery-recipe.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"delivery-recipe.js","sourceRoot":"","sources":["../../src/commands/delivery-recipe.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Consolidated report assembly and HTML rendering for `ifc-lite delivery`.
|
|
3
|
+
*
|
|
4
|
+
* Determinism contract: `buildDeliveryReport` never reads the wall clock,
|
|
5
|
+
* never iterates a `Map`/object in insertion-order-dependent ways, and never
|
|
6
|
+
* keys a result by a derived string (model path, IDS file name, check type)
|
|
7
|
+
* — every check is one entry in a flat, declared-order ARRAY. Two collisions
|
|
8
|
+
* this codebase has actually shipped (a subtotal silently overwritten in
|
|
9
|
+
* `schedule`'s output, a duplicate `--columns` header collapsing a row in
|
|
10
|
+
* JSON) both came from keying a result object by a name that two distinct
|
|
11
|
+
* inputs could share; an array keeps every check's own model/type/source
|
|
12
|
+
* fields as data instead of forcing them into a Map/object key that could
|
|
13
|
+
* collide. The result: the SAME recipe run twice, with no model or IDS file
|
|
14
|
+
* changed on disk, produces byte-identical JSON (`tool.version` and the
|
|
15
|
+
* `sha256` fingerprints are the only fields that could ever legitimately
|
|
16
|
+
* differ between two runs, and neither depends on wall-clock time or
|
|
17
|
+
* iteration order).
|
|
18
|
+
*/
|
|
19
|
+
import type { StructuralCheckResult, IdsCheckResult } from './delivery-checks.js';
|
|
20
|
+
import type { ResolvedDeliveryRecipe } from './delivery-recipe.js';
|
|
21
|
+
export type DeliveryCheckResult = StructuralCheckResult | IdsCheckResult;
|
|
22
|
+
export interface DeliveryModelEntry {
|
|
23
|
+
/** Path exactly as declared in the recipe (relative, for a readable report). */
|
|
24
|
+
path: string;
|
|
25
|
+
/** SHA-256 of the model's bytes, or undefined when the model could not be loaded. */
|
|
26
|
+
sha256?: string;
|
|
27
|
+
/** Set when the model could not be loaded; every check for this model then reports `error`. */
|
|
28
|
+
loadError?: string;
|
|
29
|
+
}
|
|
30
|
+
export interface DeliveryReport {
|
|
31
|
+
tool: {
|
|
32
|
+
name: 'ifc-lite';
|
|
33
|
+
version: string;
|
|
34
|
+
};
|
|
35
|
+
recipe: string;
|
|
36
|
+
models: DeliveryModelEntry[];
|
|
37
|
+
checks: DeliveryCheckResult[];
|
|
38
|
+
/** `pass` iff every check in `checks` is `pass` and `checks` is non-empty. */
|
|
39
|
+
verdict: 'pass' | 'fail';
|
|
40
|
+
summary: {
|
|
41
|
+
total: number;
|
|
42
|
+
pass: number;
|
|
43
|
+
fail: number;
|
|
44
|
+
error: number;
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
/** Build the consolidated report from a resolved recipe, its per-model load outcomes, and every check result. */
|
|
48
|
+
export declare function buildDeliveryReport(recipe: ResolvedDeliveryRecipe, version: string, models: DeliveryModelEntry[], checks: DeliveryCheckResult[]): DeliveryReport;
|
|
49
|
+
/** Render the report as a standalone HTML document — no external assets, safe to open from disk. */
|
|
50
|
+
export declare function renderDeliveryHtml(report: DeliveryReport): string;
|
|
51
|
+
//# sourceMappingURL=delivery-report.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"delivery-report.d.ts","sourceRoot":"","sources":["../../src/commands/delivery-report.ts"],"names":[],"mappings":"AAIA;;;;;;;;;;;;;;;;;GAiBG;AAEH,OAAO,KAAK,EAAE,qBAAqB,EAAE,cAAc,EAAe,MAAM,sBAAsB,CAAC;AAC/F,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,sBAAsB,CAAC;AAEnE,MAAM,MAAM,mBAAmB,GAAG,qBAAqB,GAAG,cAAc,CAAC;AAEzE,MAAM,WAAW,kBAAkB;IACjC,gFAAgF;IAChF,IAAI,EAAE,MAAM,CAAC;IACb,qFAAqF;IACrF,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,+FAA+F;IAC/F,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED,MAAM,WAAW,cAAc;IAC7B,IAAI,EAAE;QAAE,IAAI,EAAE,UAAU,CAAC;QAAC,OAAO,EAAE,MAAM,CAAA;KAAE,CAAC;IAC5C,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,kBAAkB,EAAE,CAAC;IAC7B,MAAM,EAAE,mBAAmB,EAAE,CAAC;IAC9B,8EAA8E;IAC9E,OAAO,EAAE,MAAM,GAAG,MAAM,CAAC;IACzB,OAAO,EAAE;QACP,KAAK,EAAE,MAAM,CAAC;QACd,IAAI,EAAE,MAAM,CAAC;QACb,IAAI,EAAE,MAAM,CAAC;QACb,KAAK,EAAE,MAAM,CAAC;KACf,CAAC;CACH;AAED,iHAAiH;AACjH,wBAAgB,mBAAmB,CACjC,MAAM,EAAE,sBAAsB,EAC9B,OAAO,EAAE,MAAM,EACf,MAAM,EAAE,kBAAkB,EAAE,EAC5B,MAAM,EAAE,mBAAmB,EAAE,GAC5B,cAAc,CAchB;AA+BD,oGAAoG;AACpG,wBAAgB,kBAAkB,CAAC,MAAM,EAAE,cAAc,GAAG,MAAM,CAoEjE"}
|
|
@@ -0,0 +1,114 @@
|
|
|
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
|
+
/** Build the consolidated report from a resolved recipe, its per-model load outcomes, and every check result. */
|
|
5
|
+
export function buildDeliveryReport(recipe, version, models, checks) {
|
|
6
|
+
const counts = { total: checks.length, pass: 0, fail: 0, error: 0 };
|
|
7
|
+
for (const c of checks)
|
|
8
|
+
counts[c.status]++;
|
|
9
|
+
const verdict = checks.length > 0 && counts.fail === 0 && counts.error === 0 ? 'pass' : 'fail';
|
|
10
|
+
return {
|
|
11
|
+
tool: { name: 'ifc-lite', version },
|
|
12
|
+
recipe: recipe.recipePath,
|
|
13
|
+
models,
|
|
14
|
+
checks,
|
|
15
|
+
verdict,
|
|
16
|
+
summary: counts,
|
|
17
|
+
};
|
|
18
|
+
}
|
|
19
|
+
function escapeHtml(value) {
|
|
20
|
+
return value
|
|
21
|
+
.replace(/&/g, '&')
|
|
22
|
+
.replace(/</g, '<')
|
|
23
|
+
.replace(/>/g, '>')
|
|
24
|
+
.replace(/"/g, '"')
|
|
25
|
+
.replace(/'/g, ''');
|
|
26
|
+
}
|
|
27
|
+
function statusBadge(status) {
|
|
28
|
+
const label = status.toUpperCase();
|
|
29
|
+
return `<span class="badge badge-${status}">${label}</span>`;
|
|
30
|
+
}
|
|
31
|
+
function describeCheck(check) {
|
|
32
|
+
if (check.type === 'structural') {
|
|
33
|
+
return `${check.errorCount} error(s), ${check.warningCount} warning(s), ${check.infoCount} info`;
|
|
34
|
+
}
|
|
35
|
+
const parts = [];
|
|
36
|
+
if (check.totalSpecifications !== undefined) {
|
|
37
|
+
parts.push(`${check.passedSpecifications ?? 0}/${check.totalSpecifications} specification(s) passed`);
|
|
38
|
+
}
|
|
39
|
+
if (check.failedEntities !== undefined && check.totalEntities !== undefined) {
|
|
40
|
+
parts.push(`${check.failedEntities}/${check.totalEntities} entities failed`);
|
|
41
|
+
}
|
|
42
|
+
if (check.error)
|
|
43
|
+
parts.push(check.error);
|
|
44
|
+
return parts.join(' — ') || (check.error ?? '');
|
|
45
|
+
}
|
|
46
|
+
/** Render the report as a standalone HTML document — no external assets, safe to open from disk. */
|
|
47
|
+
export function renderDeliveryHtml(report) {
|
|
48
|
+
const rows = report.checks
|
|
49
|
+
.map(check => {
|
|
50
|
+
const source = check.type === 'structural' ? 'structural' : check.source;
|
|
51
|
+
return ` <tr class="row-${check.status}">
|
|
52
|
+
<td>${escapeHtml(check.model)}</td>
|
|
53
|
+
<td>${escapeHtml(check.type)}</td>
|
|
54
|
+
<td>${escapeHtml(source)}</td>
|
|
55
|
+
<td>${statusBadge(check.status)}</td>
|
|
56
|
+
<td>${escapeHtml(describeCheck(check))}</td>
|
|
57
|
+
</tr>`;
|
|
58
|
+
})
|
|
59
|
+
.join('\n');
|
|
60
|
+
const modelRows = report.models
|
|
61
|
+
.map(m => ` <tr>
|
|
62
|
+
<td>${escapeHtml(m.path)}</td>
|
|
63
|
+
<td><code>${escapeHtml(m.sha256 ?? '')}</code></td>
|
|
64
|
+
<td>${m.loadError ? `<span class="badge badge-error">ERROR</span> ${escapeHtml(m.loadError)}` : 'OK'}</td>
|
|
65
|
+
</tr>`)
|
|
66
|
+
.join('\n');
|
|
67
|
+
return `<!doctype html>
|
|
68
|
+
<html lang="en">
|
|
69
|
+
<head>
|
|
70
|
+
<meta charset="utf-8">
|
|
71
|
+
<title>IFC Delivery Check Report</title>
|
|
72
|
+
<style>
|
|
73
|
+
body { font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; margin: 2rem; color: #1a1a1a; }
|
|
74
|
+
h1 { font-size: 1.4rem; }
|
|
75
|
+
.verdict { font-size: 1.1rem; font-weight: 600; padding: 0.5rem 1rem; border-radius: 4px; display: inline-block; }
|
|
76
|
+
.verdict-pass { background: #e6f4ea; color: #1e7e34; }
|
|
77
|
+
.verdict-fail { background: #fce8e6; color: #c5221f; }
|
|
78
|
+
table { border-collapse: collapse; width: 100%; margin-top: 1rem; }
|
|
79
|
+
th, td { border: 1px solid #ddd; padding: 0.4rem 0.6rem; text-align: left; font-size: 0.9rem; }
|
|
80
|
+
th { background: #f5f5f5; }
|
|
81
|
+
.badge { display: inline-block; padding: 0.1rem 0.5rem; border-radius: 3px; font-size: 0.75rem; font-weight: 600; }
|
|
82
|
+
.badge-pass { background: #e6f4ea; color: #1e7e34; }
|
|
83
|
+
.badge-fail { background: #fce8e6; color: #c5221f; }
|
|
84
|
+
.badge-error { background: #fef7e0; color: #8a6d00; }
|
|
85
|
+
.row-error td { background: #fffdf5; }
|
|
86
|
+
</style>
|
|
87
|
+
</head>
|
|
88
|
+
<body>
|
|
89
|
+
<h1>IFC Delivery Check Report</h1>
|
|
90
|
+
<p><strong>Tool:</strong> ${escapeHtml(report.tool.name)} ${escapeHtml(report.tool.version)}<br>
|
|
91
|
+
<strong>Recipe:</strong> <code>${escapeHtml(report.recipe)}</code></p>
|
|
92
|
+
<p class="verdict verdict-${report.verdict}">Verdict: ${report.verdict.toUpperCase()}</p>
|
|
93
|
+
<p>${report.summary.pass} pass, ${report.summary.fail} fail, ${report.summary.error} error (of ${report.summary.total} check(s))</p>
|
|
94
|
+
|
|
95
|
+
<h2>Models</h2>
|
|
96
|
+
<table>
|
|
97
|
+
<thead><tr><th>Path</th><th>SHA-256</th><th>Status</th></tr></thead>
|
|
98
|
+
<tbody>
|
|
99
|
+
${modelRows}
|
|
100
|
+
</tbody>
|
|
101
|
+
</table>
|
|
102
|
+
|
|
103
|
+
<h2>Checks</h2>
|
|
104
|
+
<table>
|
|
105
|
+
<thead><tr><th>Model</th><th>Type</th><th>Source</th><th>Status</th><th>Evidence</th></tr></thead>
|
|
106
|
+
<tbody>
|
|
107
|
+
${rows}
|
|
108
|
+
</tbody>
|
|
109
|
+
</table>
|
|
110
|
+
</body>
|
|
111
|
+
</html>
|
|
112
|
+
`;
|
|
113
|
+
}
|
|
114
|
+
//# sourceMappingURL=delivery-report.js.map
|