@cavelang/fusion 0.27.14 → 0.29.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/Authors.md +5 -0
- package/License.md +6 -0
- package/README.md +10 -2
- package/dist/src/fuse.d.ts +9 -0
- package/dist/src/fuse.d.ts.map +1 -1
- package/dist/src/fuse.js +53 -12
- package/dist/src/fuse.js.map +1 -1
- package/dist/src/index.d.ts +1 -1
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/index.js +1 -1
- package/dist/src/index.js.map +1 -1
- package/package.json +15 -4
- package/src/fuse.ts +58 -12
- package/src/index.ts +1 -1
package/Authors.md
ADDED
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
# Authors
|
|
2
|
+
|
|
3
|
+
By adding their name to this file, all listed authors confirm they have copyright over their contributions to this project and are waiving these rights, placing their work in the public domain in accordance with the CC0 1.0 Universal Public Domain Dedication.
|
|
4
|
+
|
|
5
|
+
- Mirek Rusin (Switzerland)
|
package/License.md
ADDED
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
# CC0 1.0 Universal (CC0 1.0) Public Domain Dedication
|
|
2
|
+
|
|
3
|
+
To the extent possible under law, the authors listed in [Authors.md](./Authors.md) have waived all copyright and related or neighboring rights to this software and associated documentation files (the "Software").
|
|
4
|
+
|
|
5
|
+
For more information, please see:
|
|
6
|
+
https://creativecommons.org/publicdomain/zero/1.0/
|
package/README.md
CHANGED
|
@@ -26,8 +26,16 @@ For normally distributed estimates with `+/- Δ` at kσ: σ = Δ/k, precision
|
|
|
26
26
|
|
|
27
27
|
`estimateOf(claim)` extracts `{ mean, sigma, conf }` from any numeric claim
|
|
28
28
|
carrying `+/-` uncertainty (attribute or metric payload); claims without a
|
|
29
|
-
usable estimate
|
|
30
|
-
|
|
29
|
+
usable estimate and zero-confidence estimates are skipped. Invalid zero,
|
|
30
|
+
negative, or non-finite σ values throw the same typed core uncertainty error
|
|
31
|
+
used by parsed and constructed claims. The spec's worked example is a test
|
|
32
|
+
case verbatim.
|
|
33
|
+
|
|
34
|
+
Fusion preserves units at the library boundary. Missing units combine only
|
|
35
|
+
with missing units, arbitrary units combine only by exact equality, and the
|
|
36
|
+
fixed-duration units `ms`, `s`, `min`, and `h` convert to the first estimate's
|
|
37
|
+
unit before weighting. Every other mixture throws a typed `FusionUnitError`;
|
|
38
|
+
adapters surface that same failure instead of maintaining separate checks.
|
|
31
39
|
|
|
32
40
|
## Conditional confidence (§10.2)
|
|
33
41
|
|
package/dist/src/fuse.d.ts
CHANGED
|
@@ -17,6 +17,8 @@ import { Claim } from '@cavelang/core';
|
|
|
17
17
|
export type Estimate = {
|
|
18
18
|
readonly mean: number;
|
|
19
19
|
readonly sigma: number;
|
|
20
|
+
/** Unit shared by the mean and σ. Missing is a distinct dimension. */
|
|
21
|
+
readonly unit?: string;
|
|
20
22
|
/** Epistemic confidence used as a precision multiplier (default 1). */
|
|
21
23
|
readonly conf?: number;
|
|
22
24
|
};
|
|
@@ -25,7 +27,14 @@ export type Posterior = {
|
|
|
25
27
|
readonly sigma: number;
|
|
26
28
|
/** Total weighted precision Σ wᵢ. */
|
|
27
29
|
readonly precision: number;
|
|
30
|
+
/** Unit in which the posterior mean and σ are expressed. */
|
|
31
|
+
readonly unit?: string;
|
|
28
32
|
};
|
|
33
|
+
/** Typed boundary failure for estimates that do not share a compatible unit. */
|
|
34
|
+
export declare class FusionUnitError extends Error {
|
|
35
|
+
readonly units: readonly (string | undefined)[];
|
|
36
|
+
constructor(units: readonly (string | undefined)[]);
|
|
37
|
+
}
|
|
29
38
|
/**
|
|
30
39
|
* Fuses estimates into a posterior. Estimates with `conf` 0 or a
|
|
31
40
|
* non-positive σ contribute nothing and are skipped.
|
package/dist/src/fuse.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"fuse.d.ts","sourceRoot":"","sources":["../../src/fuse.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAEH,OAAO,EAAE,KAAK,
|
|
1
|
+
{"version":3,"file":"fuse.d.ts","sourceRoot":"","sources":["../../src/fuse.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAEH,OAAO,EAAE,KAAK,EAAe,MAAM,gBAAgB,CAAA;AAEnD,uEAAuE;AACvE,MAAM,MAAM,QAAQ,GAAG;IACrB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;IACrB,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAA;IACtB,sEAAsE;IACtE,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,CAAA;IACtB,uEAAuE;IACvE,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,CAAA;CACvB,CAAA;AAED,MAAM,MAAM,SAAS,GAAG;IACtB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;IACrB,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAA;IACtB,qCAAqC;IACrC,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAA;IAC1B,4DAA4D;IAC5D,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,CAAA;CACvB,CAAA;AAiBD,gFAAgF;AAChF,qBAAa,eAAgB,SAAQ,KAAK;IACxC,QAAQ,CAAC,KAAK,EAAE,SAAS,CAAC,MAAM,GAAG,SAAS,CAAC,EAAE,CAAA;gBAEnC,KAAK,EAAE,SAAS,CAAC,MAAM,GAAG,SAAS,CAAC,EAAE;CAOnD;AAED;;;;GAIG;AACH,eAAO,MAAM,IAAI,GAAI,WAAW,SAAS,QAAQ,EAAE,KAAG,SAAS,GAAG,SAyBjE,CAAA;AAED;;;;GAIG;AACH,eAAO,MAAM,UAAU,GAAI,OAAO,KAAK,CAAC,CAAC,KAAG,SAAS,GAAG,QAuBvD,CAAA;AAED;;;;GAIG;AACH,eAAO,MAAM,UAAU,GAAI,QAAQ,SAAS,KAAK,CAAC,CAAC,EAAE,KAAG,SAAS,GAAG,SAI/D,CAAA"}
|
package/dist/src/fuse.js
CHANGED
|
@@ -12,31 +12,63 @@
|
|
|
12
12
|
* The math is an implementation layer, not required syntax (spec §10) —
|
|
13
13
|
* CAVE itself only stores claims and metadata.
|
|
14
14
|
*/
|
|
15
|
-
import { Claim } from '@cavelang/core';
|
|
15
|
+
import { Claim, Uncertainty } from '@cavelang/core';
|
|
16
|
+
const durationScale = {
|
|
17
|
+
ms: 0.001,
|
|
18
|
+
s: 1,
|
|
19
|
+
min: 60,
|
|
20
|
+
h: 3_600
|
|
21
|
+
};
|
|
22
|
+
const conversionFactor = (from, to) => {
|
|
23
|
+
if (from === to)
|
|
24
|
+
return 1;
|
|
25
|
+
if (from === undefined || to === undefined)
|
|
26
|
+
return undefined;
|
|
27
|
+
const fromScale = durationScale[from];
|
|
28
|
+
const toScale = durationScale[to];
|
|
29
|
+
return fromScale === undefined || toScale === undefined ? undefined : fromScale / toScale;
|
|
30
|
+
};
|
|
31
|
+
/** Typed boundary failure for estimates that do not share a compatible unit. */
|
|
32
|
+
export class FusionUnitError extends Error {
|
|
33
|
+
units;
|
|
34
|
+
constructor(units) {
|
|
35
|
+
const unique = [...new Set(units)].sort((a, b) => (a ?? '').localeCompare(b ?? ''));
|
|
36
|
+
super('cannot fuse mixed units: ' +
|
|
37
|
+
`${unique.map(unit => unit ?? '(none)').join(', ')} — convert the estimates to one unit first`);
|
|
38
|
+
this.name = 'FusionUnitError';
|
|
39
|
+
this.units = unique;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
16
42
|
/**
|
|
17
43
|
* Fuses estimates into a posterior. Estimates with `conf` 0 or a
|
|
18
44
|
* non-positive σ contribute nothing and are skipped.
|
|
19
45
|
* @returns `undefined` when no estimate carries usable weight.
|
|
20
46
|
*/
|
|
21
47
|
export const fuse = (estimates) => {
|
|
48
|
+
estimates.forEach(estimate => Uncertainty.validateSigma(estimate.sigma));
|
|
49
|
+
const usable = estimates.filter(estimate => (estimate.conf ?? 1) > 0);
|
|
50
|
+
if (usable.length === 0)
|
|
51
|
+
return undefined;
|
|
52
|
+
const unit = usable[0].unit;
|
|
53
|
+
const factors = usable.map(estimate => conversionFactor(estimate.unit, unit));
|
|
54
|
+
if (factors.some(factor => factor === undefined)) {
|
|
55
|
+
throw new FusionUnitError(usable.map(estimate => estimate.unit));
|
|
56
|
+
}
|
|
22
57
|
let totalWeight = 0;
|
|
23
58
|
let weightedSum = 0;
|
|
24
|
-
for (const estimate of
|
|
59
|
+
for (const [index, estimate] of usable.entries()) {
|
|
25
60
|
const conf = estimate.conf ?? 1;
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
const weight = conf / (estimate.sigma * estimate.sigma);
|
|
61
|
+
const factor = factors[index];
|
|
62
|
+
const sigma = estimate.sigma * factor;
|
|
63
|
+
const weight = conf / (sigma * sigma);
|
|
30
64
|
totalWeight += weight;
|
|
31
|
-
weightedSum += weight * estimate.mean;
|
|
32
|
-
}
|
|
33
|
-
if (totalWeight === 0) {
|
|
34
|
-
return undefined;
|
|
65
|
+
weightedSum += weight * estimate.mean * factor;
|
|
35
66
|
}
|
|
36
67
|
return {
|
|
37
68
|
mean: weightedSum / totalWeight,
|
|
38
69
|
sigma: 1 / Math.sqrt(totalWeight),
|
|
39
|
-
precision: totalWeight
|
|
70
|
+
precision: totalWeight,
|
|
71
|
+
...unit === undefined ? {} : { unit }
|
|
40
72
|
};
|
|
41
73
|
};
|
|
42
74
|
/**
|
|
@@ -56,7 +88,16 @@ export const estimateOf = (claim) => {
|
|
|
56
88
|
if (sigma === undefined || !(sigma > 0)) {
|
|
57
89
|
return undefined;
|
|
58
90
|
}
|
|
59
|
-
|
|
91
|
+
const factor = conversionFactor(claim.delta?.unit, value.unit);
|
|
92
|
+
if (factor === undefined) {
|
|
93
|
+
throw new FusionUnitError([value.unit, claim.delta?.unit]);
|
|
94
|
+
}
|
|
95
|
+
return {
|
|
96
|
+
mean: value.num,
|
|
97
|
+
sigma: sigma * factor,
|
|
98
|
+
...value.unit === undefined ? {} : { unit: value.unit },
|
|
99
|
+
conf: claim.conf
|
|
100
|
+
};
|
|
60
101
|
};
|
|
61
102
|
/**
|
|
62
103
|
* Fuses the numeric estimates found in `claims` (spec §10.1's worked
|
package/dist/src/fuse.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"fuse.js","sourceRoot":"","sources":["../../src/fuse.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAEH,OAAO,EAAE,KAAK,EAAE,MAAM,gBAAgB,CAAA;
|
|
1
|
+
{"version":3,"file":"fuse.js","sourceRoot":"","sources":["../../src/fuse.ts"],"names":[],"mappings":"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"}
|
package/dist/src/index.d.ts
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
* independence assumption (§10.2), and competing-hypothesis helpers
|
|
7
7
|
* (§10.3). No storage, no I/O.
|
|
8
8
|
*/
|
|
9
|
-
export { fuse, fuseClaims, estimateOf } from './fuse.ts';
|
|
9
|
+
export { FusionUnitError, fuse, fuseClaims, estimateOf } from './fuse.ts';
|
|
10
10
|
export type { Estimate, Posterior } from './fuse.ts';
|
|
11
11
|
export { noisyAndIndependent, normalizeHypotheses, hypothesisGap } from './conditional.ts';
|
|
12
12
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/src/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,EAAE,IAAI,EAAE,UAAU,EAAE,UAAU,EAAE,MAAM,WAAW,CAAA;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,EAAE,eAAe,EAAE,IAAI,EAAE,UAAU,EAAE,UAAU,EAAE,MAAM,WAAW,CAAA;AACzE,YAAY,EAAE,QAAQ,EAAE,SAAS,EAAE,MAAM,WAAW,CAAA;AACpD,OAAO,EAAE,mBAAmB,EAAE,mBAAmB,EAAE,aAAa,EAAE,MAAM,kBAAkB,CAAA"}
|
package/dist/src/index.js
CHANGED
|
@@ -6,6 +6,6 @@
|
|
|
6
6
|
* independence assumption (§10.2), and competing-hypothesis helpers
|
|
7
7
|
* (§10.3). No storage, no I/O.
|
|
8
8
|
*/
|
|
9
|
-
export { fuse, fuseClaims, estimateOf } from "./fuse.js";
|
|
9
|
+
export { FusionUnitError, fuse, fuseClaims, estimateOf } from "./fuse.js";
|
|
10
10
|
export { noisyAndIndependent, normalizeHypotheses, hypothesisGap } from "./conditional.js";
|
|
11
11
|
//# sourceMappingURL=index.js.map
|
package/dist/src/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,EAAE,IAAI,EAAE,UAAU,EAAE,UAAU,EAAE,MAAM,WAAW,CAAA;
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,EAAE,eAAe,EAAE,IAAI,EAAE,UAAU,EAAE,UAAU,EAAE,MAAM,WAAW,CAAA;AAEzE,OAAO,EAAE,mBAAmB,EAAE,mBAAmB,EAAE,aAAa,EAAE,MAAM,kBAAkB,CAAA"}
|
package/package.json
CHANGED
|
@@ -1,8 +1,18 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cavelang/fusion",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.29.1",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "CAVE probabilistic layer — Bayesian fusion of numeric estimates, noisy-AND conditional confidence, hypothesis helpers.",
|
|
6
|
+
"keywords": [
|
|
7
|
+
"cave",
|
|
8
|
+
"knowledge-graph",
|
|
9
|
+
"knowledge-representation",
|
|
10
|
+
"typescript"
|
|
11
|
+
],
|
|
12
|
+
"homepage": "https://github.com/mirek/cave#readme",
|
|
13
|
+
"bugs": {
|
|
14
|
+
"url": "https://github.com/mirek/cave/issues"
|
|
15
|
+
},
|
|
6
16
|
"license": "CC0-1.0",
|
|
7
17
|
"author": "Mirek Rusin (https://github.com/mirek)",
|
|
8
18
|
"repository": {
|
|
@@ -26,17 +36,18 @@
|
|
|
26
36
|
"src",
|
|
27
37
|
"!src/**/*.test.ts",
|
|
28
38
|
"README.md",
|
|
29
|
-
"License.md"
|
|
39
|
+
"License.md",
|
|
40
|
+
"Authors.md"
|
|
30
41
|
],
|
|
31
42
|
"publishConfig": {
|
|
32
43
|
"access": "public"
|
|
33
44
|
},
|
|
34
45
|
"dependencies": {
|
|
35
|
-
"@cavelang/core": "0.
|
|
46
|
+
"@cavelang/core": "0.29.1"
|
|
36
47
|
},
|
|
37
48
|
"scripts": {
|
|
38
49
|
"build": "tsc -b",
|
|
39
|
-
"test": "node --disable-warning=ExperimentalWarning --test
|
|
50
|
+
"test": "node --disable-warning=ExperimentalWarning --test \"test/*.test.ts\"",
|
|
40
51
|
"typecheck": "tsc -b"
|
|
41
52
|
}
|
|
42
53
|
}
|
package/src/fuse.ts
CHANGED
|
@@ -13,12 +13,14 @@
|
|
|
13
13
|
* CAVE itself only stores claims and metadata.
|
|
14
14
|
*/
|
|
15
15
|
|
|
16
|
-
import { Claim } from '@cavelang/core'
|
|
16
|
+
import { Claim, Uncertainty } from '@cavelang/core'
|
|
17
17
|
|
|
18
18
|
/** One numeric estimate: mean, σ, and confidence weight p ∈ (0, 1]. */
|
|
19
19
|
export type Estimate = {
|
|
20
20
|
readonly mean: number
|
|
21
21
|
readonly sigma: number
|
|
22
|
+
/** Unit shared by the mean and σ. Missing is a distinct dimension. */
|
|
23
|
+
readonly unit?: string
|
|
22
24
|
/** Epistemic confidence used as a precision multiplier (default 1). */
|
|
23
25
|
readonly conf?: number
|
|
24
26
|
}
|
|
@@ -28,6 +30,36 @@ export type Posterior = {
|
|
|
28
30
|
readonly sigma: number
|
|
29
31
|
/** Total weighted precision Σ wᵢ. */
|
|
30
32
|
readonly precision: number
|
|
33
|
+
/** Unit in which the posterior mean and σ are expressed. */
|
|
34
|
+
readonly unit?: string
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
const durationScale: Readonly<Record<string, number>> = {
|
|
38
|
+
ms: 0.001,
|
|
39
|
+
s: 1,
|
|
40
|
+
min: 60,
|
|
41
|
+
h: 3_600
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
const conversionFactor = (from: undefined | string, to: undefined | string): undefined | number => {
|
|
45
|
+
if (from === to) return 1
|
|
46
|
+
if (from === undefined || to === undefined) return undefined
|
|
47
|
+
const fromScale = durationScale[from]
|
|
48
|
+
const toScale = durationScale[to]
|
|
49
|
+
return fromScale === undefined || toScale === undefined ? undefined : fromScale / toScale
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/** Typed boundary failure for estimates that do not share a compatible unit. */
|
|
53
|
+
export class FusionUnitError extends Error {
|
|
54
|
+
readonly units: readonly (string | undefined)[]
|
|
55
|
+
|
|
56
|
+
constructor(units: readonly (string | undefined)[]) {
|
|
57
|
+
const unique = [...new Set(units)].sort((a, b) => (a ?? '').localeCompare(b ?? ''))
|
|
58
|
+
super('cannot fuse mixed units: ' +
|
|
59
|
+
`${unique.map(unit => unit ?? '(none)').join(', ')} — convert the estimates to one unit first`)
|
|
60
|
+
this.name = 'FusionUnitError'
|
|
61
|
+
this.units = unique
|
|
62
|
+
}
|
|
31
63
|
}
|
|
32
64
|
|
|
33
65
|
/**
|
|
@@ -36,24 +68,29 @@ export type Posterior = {
|
|
|
36
68
|
* @returns `undefined` when no estimate carries usable weight.
|
|
37
69
|
*/
|
|
38
70
|
export const fuse = (estimates: readonly Estimate[]): undefined | Posterior => {
|
|
71
|
+
estimates.forEach(estimate => Uncertainty.validateSigma(estimate.sigma))
|
|
72
|
+
const usable = estimates.filter(estimate => (estimate.conf ?? 1) > 0)
|
|
73
|
+
if (usable.length === 0) return undefined
|
|
74
|
+
const unit = usable[0]!.unit
|
|
75
|
+
const factors = usable.map(estimate => conversionFactor(estimate.unit, unit))
|
|
76
|
+
if (factors.some(factor => factor === undefined)) {
|
|
77
|
+
throw new FusionUnitError(usable.map(estimate => estimate.unit))
|
|
78
|
+
}
|
|
39
79
|
let totalWeight = 0
|
|
40
80
|
let weightedSum = 0
|
|
41
|
-
for (const estimate of
|
|
81
|
+
for (const [index, estimate] of usable.entries()) {
|
|
42
82
|
const conf = estimate.conf ?? 1
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
const weight = conf / (estimate.sigma * estimate.sigma)
|
|
83
|
+
const factor = factors[index]!
|
|
84
|
+
const sigma = estimate.sigma * factor
|
|
85
|
+
const weight = conf / (sigma * sigma)
|
|
47
86
|
totalWeight += weight
|
|
48
|
-
weightedSum += weight * estimate.mean
|
|
49
|
-
}
|
|
50
|
-
if (totalWeight === 0) {
|
|
51
|
-
return undefined
|
|
87
|
+
weightedSum += weight * estimate.mean * factor
|
|
52
88
|
}
|
|
53
89
|
return {
|
|
54
90
|
mean: weightedSum / totalWeight,
|
|
55
91
|
sigma: 1 / Math.sqrt(totalWeight),
|
|
56
|
-
precision: totalWeight
|
|
92
|
+
precision: totalWeight,
|
|
93
|
+
...unit === undefined ? {} : { unit }
|
|
57
94
|
}
|
|
58
95
|
}
|
|
59
96
|
|
|
@@ -75,7 +112,16 @@ export const estimateOf = (claim: Claim.t): undefined | Estimate => {
|
|
|
75
112
|
if (sigma === undefined || !(sigma > 0)) {
|
|
76
113
|
return undefined
|
|
77
114
|
}
|
|
78
|
-
|
|
115
|
+
const factor = conversionFactor(claim.delta?.unit, value.unit)
|
|
116
|
+
if (factor === undefined) {
|
|
117
|
+
throw new FusionUnitError([value.unit, claim.delta?.unit])
|
|
118
|
+
}
|
|
119
|
+
return {
|
|
120
|
+
mean: value.num,
|
|
121
|
+
sigma: sigma * factor,
|
|
122
|
+
...value.unit === undefined ? {} : { unit: value.unit },
|
|
123
|
+
conf: claim.conf
|
|
124
|
+
}
|
|
79
125
|
}
|
|
80
126
|
|
|
81
127
|
/**
|
package/src/index.ts
CHANGED
|
@@ -7,6 +7,6 @@
|
|
|
7
7
|
* (§10.3). No storage, no I/O.
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
|
-
export { fuse, fuseClaims, estimateOf } from './fuse.ts'
|
|
10
|
+
export { FusionUnitError, fuse, fuseClaims, estimateOf } from './fuse.ts'
|
|
11
11
|
export type { Estimate, Posterior } from './fuse.ts'
|
|
12
12
|
export { noisyAndIndependent, normalizeHypotheses, hypothesisGap } from './conditional.ts'
|