@disclosureos/scoring 1.0.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/CHANGELOG.md ADDED
@@ -0,0 +1,17 @@
1
+ # Changelog
2
+
3
+ All notable changes to `@disclosureos/scoring` are documented here. The format is
4
+ based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/) and this project
5
+ adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
6
+
7
+ ## 1.0.0
8
+
9
+ Initial public release — the Scoring layer.
10
+
11
+ ### Added
12
+ - `getCompleteness` (how well-documented a record is) and `score` /
13
+ `rankByCompellingness` (how anomalous a case is).
14
+ - Scoring over arrays of competing claims: results include a `range` (spread across
15
+ claims) and a `contested` flag when evaluators disagree.
16
+ - `DEFAULT_WEIGHTS` and configurable evaluator weighting.
17
+ - Committed JSON Schema artifact (`schema/scoring.schema.json`, draft 2020-12).
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Disclosure Foundation
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,89 @@
1
+ # @disclosureos/scoring
2
+
3
+ [![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](https://opensource.org/licenses/MIT)
4
+
5
+ Reference scoring for the [DisclosureOS](https://github.com/disclosurefoundation)
6
+ ecosystem — the **Scoring** layer. It turns an enriched `Observation` into two
7
+ orthogonal measures:
8
+
9
+ - **Completeness** — *is it well-documented?* What fraction of the record's fields are
10
+ present.
11
+ - **Compellingness** — *is it anomalous / non-mundane?* Derived from the observable
12
+ assessments and origin classification.
13
+
14
+ ## The DisclosureOS model
15
+
16
+ DisclosureOS is a **five-part standard**. Each part answers one question about an
17
+ observation; `@disclosureos/schema` binds the package-owned pieces into one portable
18
+ contract (a single TS type + JSON Schema + non-stripping parse).
19
+
20
+ | Part | Package surface | Question it answers |
21
+ |---|---|---|
22
+ | **Records** | `@disclosureos/records` | *What was observed?* |
23
+ | **Observables** | `@disclosureos/observables` | *What anomalous characteristics did it show?* |
24
+ | **Origins** | `@disclosureos/origins` | *What might explain it?* |
25
+ | **Claims** | `@disclosureos/records/shared` | *Who assessed it, why, and on what evidence?* |
26
+ | **Scoring** | `@disclosureos/scoring` | *How complete / compelling is the case?* |
27
+
28
+ Supporting packages: **`@disclosureos/schema`** (the portable contract that composes the
29
+ package-owned pieces), **`@disclosureos/cli`** (tooling), and **`@disclosureos/examples`**
30
+ (the runnable golden path). *(You are here: **Scoring**.)*
31
+
32
+ ## Consumes the foundation (does not augment it)
33
+
34
+ Unlike `observables` and `origins`, scoring **only consumes** the foundation — it
35
+ reads `observableAssessments` and `origin` but adds no slot to `Observation`. It
36
+ depends on `@disclosureos/records`, `@disclosureos/observables`, and
37
+ `@disclosureos/origins`, so importing it gives you those slots typed automatically.
38
+
39
+ > **See it end to end.** [`examples/golden-path.ts`](../../examples/golden-path.ts)
40
+ > takes one observation through every layer — records → observables → origins → scoring,
41
+ > validated as a whole by `@disclosureos/schema` — in a single type-checked file. Run it
42
+ > with `pnpm --filter @disclosureos/examples golden-path`.
43
+ >
44
+ > **Migrating an existing dataset?** See [`examples/migration-path.ts`](../../examples/migration-path.ts)
45
+ > and the [onboarding workspace](https://os.disclosure.org/onboard).
46
+
47
+ ## Install
48
+
49
+ ```bash
50
+ pnpm add @disclosureos/scoring @disclosureos/records @disclosureos/observables @disclosureos/origins zod
51
+ ```
52
+
53
+ ## Quick start
54
+
55
+ ```typescript
56
+ import { score, getCompleteness, rankByCompellingness } from '@disclosureos/scoring';
57
+
58
+ const result = score(observation);
59
+ // { score, range: { low, high }, contested, ... }
60
+
61
+ const completeness = getCompleteness(observation);
62
+ // { percentage, requiredPercentage, missing, ... }
63
+
64
+ const ranked = rankByCompellingness([obsA, obsB, obsC]); // most-compelling first
65
+ ```
66
+
67
+ Because evaluative slots hold **arrays of competing claims**, `score()` reports a
68
+ `range` (the spread across claims) and a `contested` flag when evaluators disagree —
69
+ not just a single point estimate.
70
+
71
+ ## Subpath exports
72
+
73
+ | Subpath | Contents |
74
+ |---|---|
75
+ | `@disclosureos/scoring` | `score`, `rankByCompellingness`, `getCompleteness`, `DEFAULT_WEIGHTS` |
76
+ | `@disclosureos/scoring/completeness` | Completeness scoring + `deriveFieldPaths` |
77
+ | `@disclosureos/scoring/compellingness` | Compellingness scoring + weights |
78
+ | `@disclosureos/scoring/schema` | The committed JSON Schema (`scoring.schema.json`) |
79
+
80
+ ## Standard Schema
81
+
82
+ The Zod schemas here (`ScoreResultSchema`, `CompletenessResultSchema`, …) implement
83
+ [Standard Schema v1](https://standardschema.dev) via their `~standard` property — a
84
+ guarantee of Zod 4. Any Standard-Schema-compatible validator or framework can consume
85
+ them directly, with no DisclosureOS-specific adapter.
86
+
87
+ ## License
88
+
89
+ MIT © [Disclosure Foundation](https://disclosure.org)
@@ -0,0 +1,99 @@
1
+ import { z } from 'zod';
2
+ import { Observation } from '@disclosureos/records';
3
+ import { AssessmentLevel } from '@disclosureos/observables';
4
+
5
+ /** Methodology version stamped on every {@link ScoreResult}. Bump on weight/formula changes. */
6
+ declare const SCORING_VERSION: "2.0.0";
7
+ /**
8
+ * Ordinal anomaly strength per evidentiary tier. A `confirmed` signal counts
9
+ * fully; `not_indicated` contributes nothing. Multiplied by the assessment's
10
+ * confidence (default 1 when unstated). Exported so consumers can render the
11
+ * methodology; override semantics may come in a future minor release.
12
+ */
13
+ declare const ASSESSMENT_LEVEL_WEIGHTS: Record<AssessmentLevel, number>;
14
+ /**
15
+ * OCS id prefixes treated as prosaic (well-explained). A confident *prosaic*
16
+ * classification means the case is explained, so it contributes no anomaly
17
+ * signal. Overridable per {@link score} call. Default is the OCS "Prosaic"
18
+ * branch (`1.1.1`).
19
+ */
20
+ declare const DEFAULT_PROSAIC_OCS_PREFIXES: readonly string[];
21
+ declare const CompellingnessWeightsSchema: z.ZodObject<{
22
+ technology: z.ZodNumber;
23
+ biologics: z.ZodNumber;
24
+ origin: z.ZodNumber;
25
+ }, z.core.$strip>;
26
+ type CompellingnessWeights = z.infer<typeof CompellingnessWeightsSchema>;
27
+ /**
28
+ * Fixed default weights, exposed as a transparent, overridable data object (not a
29
+ * config system). Partners can propose alternate weight sets without an API change.
30
+ *
31
+ * @experimental The *shape* (`CompellingnessWeights`) is stable, but these specific
32
+ * default values are a reference methodology that may be re-tuned in a minor release.
33
+ * Pin your own `weights` via {@link ScoreOptions} if you need stable numbers.
34
+ */
35
+ declare const DEFAULT_WEIGHTS: CompellingnessWeights;
36
+ declare const ScoreResultSchema: z.ZodObject<{
37
+ score: z.ZodNumber;
38
+ range: z.ZodObject<{
39
+ low: z.ZodNumber;
40
+ high: z.ZodNumber;
41
+ }, z.core.$strip>;
42
+ contested: z.ZodBoolean;
43
+ components: z.ZodObject<{
44
+ technology: z.ZodNumber;
45
+ biologics: z.ZodNumber;
46
+ origin: z.ZodNumber;
47
+ }, z.core.$strip>;
48
+ weights: z.ZodObject<{
49
+ technology: z.ZodNumber;
50
+ biologics: z.ZodNumber;
51
+ origin: z.ZodNumber;
52
+ }, z.core.$strip>;
53
+ scoringVersion: z.ZodString;
54
+ }, z.core.$strip>;
55
+ type ScoreResult = z.infer<typeof ScoreResultSchema>;
56
+ /** The minimal claim shape the trust hook sees (it never needs the full body). */
57
+ type EvaluatorWeightInput = {
58
+ evaluatedBy?: string | undefined;
59
+ evidenceRefs?: string[] | undefined;
60
+ };
61
+ interface ScoreOptions {
62
+ weights?: CompellingnessWeights;
63
+ /** Override the prosaic OCS prefixes (defaults to {@link DEFAULT_PROSAIC_OCS_PREFIXES}). */
64
+ prosaicPrefixes?: readonly string[];
65
+ /**
66
+ * Trust applied to each claim's vote in the consensus point estimate (default
67
+ * 1.0 — uniform). This is where a future trust policy (e.g.
68
+ * weighting verified/official evaluators higher) plugs in here without changing
69
+ * the formula. Range/contestedness are NOT affected — they report the honest
70
+ * spread of what evaluators actually asserted.
71
+ *
72
+ * @experimental The hook signature is stable, but the default trust *policy*
73
+ * (uniform 1.0) is deliberately unopinionated and is a separate, ongoing design
74
+ * task; a future built-in policy may change the default.
75
+ */
76
+ evaluatorWeight?: (claim: EvaluatorWeightInput) => number;
77
+ }
78
+ /**
79
+ * Reference compellingness scorer. Combines the strongest technology- and
80
+ * biologics-anomaly signals (from `observableAssessments`) with the confidence in
81
+ * a *non-prosaic* origin classification (from `origin`), under transparent,
82
+ * overridable weights. Each slot is a *list* of competing claims, so the scorer
83
+ * reports a consensus `score`, the `range` of readings across claims, and whether
84
+ * the case is `contested` (evaluators disagree on direction). This answers "how
85
+ * strongly does this case bear on the two public questions — anomalous
86
+ * tech/biologics, and a non-mundane origin?" — distinct from completeness.
87
+ */
88
+ declare function score(observation: Observation, options?: ScoreOptions): ScoreResult;
89
+ interface RankedObservation<T extends Observation = Observation> {
90
+ observation: T;
91
+ result: ScoreResult;
92
+ }
93
+ /**
94
+ * Score a set of observations and return them sorted most-compelling first — the
95
+ * "float the strongest cases to the top" use case.
96
+ */
97
+ declare function rankByCompellingness<T extends Observation>(observations: readonly T[], options?: ScoreOptions): RankedObservation<T>[];
98
+
99
+ export { ASSESSMENT_LEVEL_WEIGHTS, type CompellingnessWeights, CompellingnessWeightsSchema, DEFAULT_PROSAIC_OCS_PREFIXES, DEFAULT_WEIGHTS, type EvaluatorWeightInput, type RankedObservation, SCORING_VERSION, type ScoreOptions, type ScoreResult, ScoreResultSchema, rankByCompellingness, score };
@@ -0,0 +1,136 @@
1
+ import { z } from 'zod';
2
+ import '@disclosureos/observables';
3
+ import '@disclosureos/origins';
4
+
5
+ // src/compellingness/index.ts
6
+ var SCORING_VERSION = "2.0.0";
7
+ var ASSESSMENT_LEVEL_WEIGHTS = {
8
+ not_indicated: 0,
9
+ reported: 0.25,
10
+ documented: 0.5,
11
+ measured: 0.75,
12
+ confirmed: 1
13
+ };
14
+ var DEFAULT_PROSAIC_OCS_PREFIXES = ["1.1.1"];
15
+ var CompellingnessWeightsSchema = z.object({
16
+ technology: z.number().min(0).describe("Weight of the technology-anomaly signal."),
17
+ biologics: z.number().min(0).describe("Weight of the biologics-anomaly signal."),
18
+ origin: z.number().min(0).describe("Weight of the (non-prosaic) origin-confidence signal.")
19
+ }).meta({ id: "CompellingnessWeights" }).describe("Relative weights for the compellingness components. Need not sum to 1 \u2014 normalized internally.");
20
+ var DEFAULT_WEIGHTS = {
21
+ technology: 0.45,
22
+ biologics: 0.35,
23
+ origin: 0.2
24
+ };
25
+ var ScoreResultSchema = z.object({
26
+ score: z.number().min(0).max(1).describe("Overall compellingness (consensus point estimate), 0\u20131."),
27
+ range: z.object({
28
+ low: z.number().min(0).max(1).describe("Most-skeptical reading of the strongest-anomaly signal across competing claims."),
29
+ high: z.number().min(0).max(1).describe("Most-anomalous reading of the strongest-anomaly signal across competing claims.")
30
+ }).describe(
31
+ "Spread of the score across competing claims \u2014 wide range signals disagreement. Bounds are computed per-signal under the same strongest-anomaly (max) reduction as the point estimate, not a global min/max over every claim."
32
+ ),
33
+ contested: z.boolean().describe("True when claims disagree on *direction* (some assert anomaly, some assert none)."),
34
+ components: z.object({
35
+ technology: z.number().min(0).max(1),
36
+ biologics: z.number().min(0).max(1),
37
+ origin: z.number().min(0).max(1)
38
+ }).describe("Per-signal consensus contributions before weighting."),
39
+ weights: CompellingnessWeightsSchema,
40
+ scoringVersion: z.string().describe("Methodology version that produced this score.")
41
+ }).meta({ id: "ScoreResult" }).describe("A reference compellingness score for an observation, with a contestedness range.");
42
+ var UNIFORM_TRUST = () => 1;
43
+ function isProsaic(id, prefixes) {
44
+ return prefixes.some((p) => id === p || id.startsWith(`${p}.`));
45
+ }
46
+ function round(n) {
47
+ return Math.round(n * 1e4) / 1e4;
48
+ }
49
+ var EMPTY_STATS = { point: 0, low: 0, high: 0, contested: false };
50
+ function reduceClaims(weighted) {
51
+ if (weighted.length === 0) return EMPTY_STATS;
52
+ let wsum = 0;
53
+ let msum = 0;
54
+ let low = Infinity;
55
+ let high = -Infinity;
56
+ let anyAnomaly = false;
57
+ let anyNone = false;
58
+ for (const { magnitude, weight } of weighted) {
59
+ wsum += weight;
60
+ msum += magnitude * weight;
61
+ if (magnitude < low) low = magnitude;
62
+ if (magnitude > high) high = magnitude;
63
+ if (magnitude > 0) anyAnomaly = true;
64
+ else anyNone = true;
65
+ }
66
+ return {
67
+ point: wsum > 0 ? msum / wsum : 0,
68
+ low,
69
+ high,
70
+ contested: anyAnomaly && anyNone
71
+ };
72
+ }
73
+ function observableMagnitude(claim) {
74
+ return ASSESSMENT_LEVEL_WEIGHTS[claim.level] * (claim.confidence ?? 1);
75
+ }
76
+ function domainStats(group, evaluatorWeight) {
77
+ if (!group) return EMPTY_STATS;
78
+ let point = 0;
79
+ let low = 0;
80
+ let high = 0;
81
+ let contested = false;
82
+ for (const claims of Object.values(group)) {
83
+ if (!claims || claims.length === 0) continue;
84
+ const weighted = claims.map((c) => ({
85
+ magnitude: observableMagnitude(c),
86
+ weight: (c.confidence ?? 1) * evaluatorWeight(c)
87
+ }));
88
+ const stats = reduceClaims(weighted);
89
+ if (stats.point > point) point = stats.point;
90
+ if (stats.low > low) low = stats.low;
91
+ if (stats.high > high) high = stats.high;
92
+ if (stats.contested) contested = true;
93
+ }
94
+ return { point, low, high, contested };
95
+ }
96
+ function originStats(origin, prefixes, evaluatorWeight) {
97
+ if (!origin || origin.length === 0) return EMPTY_STATS;
98
+ const weighted = origin.map((c) => ({
99
+ magnitude: isProsaic(c.primaryHypothesis, prefixes) ? 0 : c.confidence,
100
+ weight: c.confidence * evaluatorWeight(c)
101
+ }));
102
+ return reduceClaims(weighted);
103
+ }
104
+ function score(observation, options) {
105
+ const weights = options?.weights ?? DEFAULT_WEIGHTS;
106
+ const prosaicPrefixes = options?.prosaicPrefixes ?? DEFAULT_PROSAIC_OCS_PREFIXES;
107
+ const evaluatorWeight = options?.evaluatorWeight ?? UNIFORM_TRUST;
108
+ const assessments = observation.observableAssessments;
109
+ const technology = domainStats(assessments?.technology, evaluatorWeight);
110
+ const biologics = domainStats(assessments?.biologics, evaluatorWeight);
111
+ const origin = originStats(observation.origin, prosaicPrefixes, evaluatorWeight);
112
+ const totalWeight = weights.technology + weights.biologics + weights.origin;
113
+ const combine = (t, b, o) => totalWeight > 0 ? (t * weights.technology + b * weights.biologics + o * weights.origin) / totalWeight : 0;
114
+ return {
115
+ score: round(combine(technology.point, biologics.point, origin.point)),
116
+ range: {
117
+ low: round(combine(technology.low, biologics.low, origin.low)),
118
+ high: round(combine(technology.high, biologics.high, origin.high))
119
+ },
120
+ contested: technology.contested || biologics.contested || origin.contested,
121
+ components: {
122
+ technology: round(technology.point),
123
+ biologics: round(biologics.point),
124
+ origin: round(origin.point)
125
+ },
126
+ weights,
127
+ scoringVersion: SCORING_VERSION
128
+ };
129
+ }
130
+ function rankByCompellingness(observations, options) {
131
+ return observations.map((observation) => ({ observation, result: score(observation, options) })).sort((a, b) => b.result.score - a.result.score);
132
+ }
133
+
134
+ export { ASSESSMENT_LEVEL_WEIGHTS, CompellingnessWeightsSchema, DEFAULT_PROSAIC_OCS_PREFIXES, DEFAULT_WEIGHTS, SCORING_VERSION, ScoreResultSchema, rankByCompellingness, score };
135
+ //# sourceMappingURL=index.js.map
136
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/compellingness/index.ts"],"names":[],"mappings":"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","file":"index.js","sourcesContent":["import { z } from 'zod';\nimport type { Observation } from '@disclosureos/records';\nimport type { AssessmentLevel, ObservableClaim } from '@disclosureos/observables';\nimport type { OriginClaim } from '@disclosureos/origins';\n// Side-effect imports register the `observableAssessments` and `origin` slots on\n// `Observation` via module augmentation, so they are typed below.\nimport '@disclosureos/observables';\nimport '@disclosureos/origins';\n\n/** Methodology version stamped on every {@link ScoreResult}. Bump on weight/formula changes. */\nexport const SCORING_VERSION = '2.0.0' as const;\n\n/**\n * Ordinal anomaly strength per evidentiary tier. A `confirmed` signal counts\n * fully; `not_indicated` contributes nothing. Multiplied by the assessment's\n * confidence (default 1 when unstated). Exported so consumers can render the\n * methodology; override semantics may come in a future minor release.\n */\nexport const ASSESSMENT_LEVEL_WEIGHTS: Record<AssessmentLevel, number> = {\n not_indicated: 0,\n reported: 0.25,\n documented: 0.5,\n measured: 0.75,\n confirmed: 1,\n};\n\n/**\n * OCS id prefixes treated as prosaic (well-explained). A confident *prosaic*\n * classification means the case is explained, so it contributes no anomaly\n * signal. Overridable per {@link score} call. Default is the OCS \"Prosaic\"\n * branch (`1.1.1`).\n */\nexport const DEFAULT_PROSAIC_OCS_PREFIXES: readonly string[] = ['1.1.1'];\n\nexport const CompellingnessWeightsSchema = z\n .object({\n technology: z.number().min(0).describe('Weight of the technology-anomaly signal.'),\n biologics: z.number().min(0).describe('Weight of the biologics-anomaly signal.'),\n origin: z.number().min(0).describe('Weight of the (non-prosaic) origin-confidence signal.'),\n })\n .meta({ id: 'CompellingnessWeights' })\n .describe('Relative weights for the compellingness components. Need not sum to 1 — normalized internally.');\n\nexport type CompellingnessWeights = z.infer<typeof CompellingnessWeightsSchema>;\n\n/**\n * Fixed default weights, exposed as a transparent, overridable data object (not a\n * config system). Partners can propose alternate weight sets without an API change.\n *\n * @experimental The *shape* (`CompellingnessWeights`) is stable, but these specific\n * default values are a reference methodology that may be re-tuned in a minor release.\n * Pin your own `weights` via {@link ScoreOptions} if you need stable numbers.\n */\nexport const DEFAULT_WEIGHTS: CompellingnessWeights = {\n technology: 0.45,\n biologics: 0.35,\n origin: 0.2,\n};\n\nexport const ScoreResultSchema = z\n .object({\n score: z.number().min(0).max(1).describe('Overall compellingness (consensus point estimate), 0–1.'),\n range: z\n .object({\n low: z.number().min(0).max(1).describe('Most-skeptical reading of the strongest-anomaly signal across competing claims.'),\n high: z.number().min(0).max(1).describe('Most-anomalous reading of the strongest-anomaly signal across competing claims.'),\n })\n .describe(\n 'Spread of the score across competing claims — wide range signals disagreement. Bounds are '\n + 'computed per-signal under the same strongest-anomaly (max) reduction as the point estimate, '\n + 'not a global min/max over every claim.',\n ),\n contested: z\n .boolean()\n .describe('True when claims disagree on *direction* (some assert anomaly, some assert none).'),\n components: z\n .object({\n technology: z.number().min(0).max(1),\n biologics: z.number().min(0).max(1),\n origin: z.number().min(0).max(1),\n })\n .describe('Per-signal consensus contributions before weighting.'),\n weights: CompellingnessWeightsSchema,\n scoringVersion: z.string().describe('Methodology version that produced this score.'),\n })\n .meta({ id: 'ScoreResult' })\n .describe('A reference compellingness score for an observation, with a contestedness range.');\n\nexport type ScoreResult = z.infer<typeof ScoreResultSchema>;\n\n/** The minimal claim shape the trust hook sees (it never needs the full body). */\nexport type EvaluatorWeightInput = {\n evaluatedBy?: string | undefined;\n evidenceRefs?: string[] | undefined;\n};\n\nexport interface ScoreOptions {\n weights?: CompellingnessWeights;\n /** Override the prosaic OCS prefixes (defaults to {@link DEFAULT_PROSAIC_OCS_PREFIXES}). */\n prosaicPrefixes?: readonly string[];\n /**\n * Trust applied to each claim's vote in the consensus point estimate (default\n * 1.0 — uniform). This is where a future trust policy (e.g.\n * weighting verified/official evaluators higher) plugs in here without changing\n * the formula. Range/contestedness are NOT affected — they report the honest\n * spread of what evaluators actually asserted.\n *\n * @experimental The hook signature is stable, but the default trust *policy*\n * (uniform 1.0) is deliberately unopinionated and is a separate, ongoing design\n * task; a future built-in policy may change the default.\n */\n evaluatorWeight?: (claim: EvaluatorWeightInput) => number;\n}\n\nconst UNIFORM_TRUST = (): number => 1;\n\nfunction isProsaic(id: string, prefixes: readonly string[]): boolean {\n return prefixes.some((p) => id === p || id.startsWith(`${p}.`));\n}\n\nfunction round(n: number): number {\n return Math.round(n * 1e4) / 1e4;\n}\n\ntype Weighted = { magnitude: number; weight: number };\n\ninterface ClaimStats {\n /** Trust-weighted mean magnitude (the consensus point). */\n point: number;\n /** Min asserted magnitude (most skeptical). */\n low: number;\n /** Max asserted magnitude (most anomalous). */\n high: number;\n /** Claims disagree on direction (some > 0, some === 0). */\n contested: boolean;\n}\n\nconst EMPTY_STATS: ClaimStats = { point: 0, low: 0, high: 0, contested: false };\n\nfunction reduceClaims(weighted: Weighted[]): ClaimStats {\n if (weighted.length === 0) return EMPTY_STATS;\n let wsum = 0;\n let msum = 0;\n let low = Infinity;\n let high = -Infinity;\n let anyAnomaly = false;\n let anyNone = false;\n for (const { magnitude, weight } of weighted) {\n wsum += weight;\n msum += magnitude * weight;\n if (magnitude < low) low = magnitude;\n if (magnitude > high) high = magnitude;\n if (magnitude > 0) anyAnomaly = true;\n else anyNone = true;\n }\n return {\n point: wsum > 0 ? msum / wsum : 0,\n low,\n high,\n contested: anyAnomaly && anyNone,\n };\n}\n\n/** Asserted anomaly magnitude of one observable claim, in [0,1]. */\nfunction observableMagnitude(claim: ObservableClaim): number {\n return ASSESSMENT_LEVEL_WEIGHTS[claim.level] * (claim.confidence ?? 1);\n}\n\n/**\n * Strongest established anomaly within a domain — the \"is there *any* anomaly?\" reduction.\n * `point`/`low`/`high` are each a **max across observables** of that observable's claim-list\n * stat. So `low` is \"the most-skeptical reading of the strongest-anomaly observable,\" not a\n * global floor across all observables — consistent with the point estimate's max reduction.\n */\nfunction domainStats(\n group: Partial<Record<string, ObservableClaim[]>> | undefined,\n evaluatorWeight: (claim: EvaluatorWeightInput) => number,\n): ClaimStats {\n if (!group) return EMPTY_STATS;\n let point = 0;\n let low = 0;\n let high = 0;\n let contested = false;\n for (const claims of Object.values(group)) {\n if (!claims || claims.length === 0) continue;\n const weighted = claims.map<Weighted>((c) => ({\n magnitude: observableMagnitude(c),\n weight: (c.confidence ?? 1) * evaluatorWeight(c),\n }));\n const stats = reduceClaims(weighted);\n if (stats.point > point) point = stats.point;\n if (stats.low > low) low = stats.low;\n if (stats.high > high) high = stats.high;\n if (stats.contested) contested = true;\n }\n return { point, low, high, contested };\n}\n\n/** Confidence in a *non-prosaic* origin, reduced across competing classifications. */\nfunction originStats(\n origin: OriginClaim[] | undefined,\n prefixes: readonly string[],\n evaluatorWeight: (claim: EvaluatorWeightInput) => number,\n): ClaimStats {\n if (!origin || origin.length === 0) return EMPTY_STATS;\n const weighted = origin.map<Weighted>((c) => ({\n magnitude: isProsaic(c.primaryHypothesis, prefixes) ? 0 : c.confidence,\n weight: c.confidence * evaluatorWeight(c),\n }));\n return reduceClaims(weighted);\n}\n\n/**\n * Reference compellingness scorer. Combines the strongest technology- and\n * biologics-anomaly signals (from `observableAssessments`) with the confidence in\n * a *non-prosaic* origin classification (from `origin`), under transparent,\n * overridable weights. Each slot is a *list* of competing claims, so the scorer\n * reports a consensus `score`, the `range` of readings across claims, and whether\n * the case is `contested` (evaluators disagree on direction). This answers \"how\n * strongly does this case bear on the two public questions — anomalous\n * tech/biologics, and a non-mundane origin?\" — distinct from completeness.\n */\nexport function score(observation: Observation, options?: ScoreOptions): ScoreResult {\n const weights = options?.weights ?? DEFAULT_WEIGHTS;\n const prosaicPrefixes = options?.prosaicPrefixes ?? DEFAULT_PROSAIC_OCS_PREFIXES;\n const evaluatorWeight = options?.evaluatorWeight ?? UNIFORM_TRUST;\n\n const assessments = observation.observableAssessments;\n const technology = domainStats(assessments?.technology, evaluatorWeight);\n const biologics = domainStats(assessments?.biologics, evaluatorWeight);\n const origin = originStats(observation.origin, prosaicPrefixes, evaluatorWeight);\n\n const totalWeight = weights.technology + weights.biologics + weights.origin;\n const combine = (t: number, b: number, o: number): number =>\n totalWeight > 0\n ? (t * weights.technology + b * weights.biologics + o * weights.origin) / totalWeight\n : 0;\n\n return {\n score: round(combine(technology.point, biologics.point, origin.point)),\n range: {\n low: round(combine(technology.low, biologics.low, origin.low)),\n high: round(combine(technology.high, biologics.high, origin.high)),\n },\n contested: technology.contested || biologics.contested || origin.contested,\n components: {\n technology: round(technology.point),\n biologics: round(biologics.point),\n origin: round(origin.point),\n },\n weights,\n scoringVersion: SCORING_VERSION,\n };\n}\n\nexport interface RankedObservation<T extends Observation = Observation> {\n observation: T;\n result: ScoreResult;\n}\n\n/**\n * Score a set of observations and return them sorted most-compelling first — the\n * \"float the strongest cases to the top\" use case.\n */\nexport function rankByCompellingness<T extends Observation>(\n observations: readonly T[],\n options?: ScoreOptions,\n): RankedObservation<T>[] {\n return observations\n .map((observation) => ({ observation, result: score(observation, options) }))\n .sort((a, b) => b.result.score - a.result.score);\n}\n"]}
@@ -0,0 +1,41 @@
1
+ import { z } from 'zod';
2
+ import { Observation } from '@disclosureos/records';
3
+
4
+ /** A leaf field path derived from the records JSON Schema. */
5
+ interface FieldPath {
6
+ /** Dot-path into an `Observation` (e.g. `location.latitude`). */
7
+ path: string;
8
+ /** True only when every segment along the path is schema-required. */
9
+ required: boolean;
10
+ }
11
+ /**
12
+ * Walk the committed records JSON Schema and enumerate every leaf field path on
13
+ * `Observation`. Objects are recursed (resolving `$ref` into `$defs`); arrays,
14
+ * enums, scalars, and `record`/`anyOf` nodes are treated as leaves. This is the
15
+ * single source of completeness coverage — there is no hand-maintained path list
16
+ * (so adding a field to the schema automatically extends completeness).
17
+ */
18
+ declare function deriveFieldPaths(schema?: Record<string, unknown>): FieldPath[];
19
+
20
+ declare const CompletenessResultSchema: z.ZodObject<{
21
+ total: z.ZodNumber;
22
+ present: z.ZodNumber;
23
+ percentage: z.ZodNumber;
24
+ missing: z.ZodArray<z.ZodString>;
25
+ requiredTotal: z.ZodNumber;
26
+ requiredPresent: z.ZodNumber;
27
+ requiredPercentage: z.ZodNumber;
28
+ }, z.core.$strip>;
29
+ type CompletenessResult = z.infer<typeof CompletenessResultSchema>;
30
+ interface CompletenessOptions {
31
+ /** Override the derived field-path set (defaults to the records schema). */
32
+ paths?: FieldPath[];
33
+ }
34
+ /**
35
+ * Measure how fully an observation populates the records schema. This is the
36
+ * "is it well-documented?" axis — deliberately kept separate from compellingness
37
+ * ("is it anomalous?"), so a thin-but-anomalous case never masquerades as complete.
38
+ */
39
+ declare function getCompleteness(observation: Observation | Record<string, unknown>, options?: CompletenessOptions): CompletenessResult;
40
+
41
+ export { type CompletenessOptions, type CompletenessResult, CompletenessResultSchema, type FieldPath, deriveFieldPaths, getCompleteness };
@@ -0,0 +1,109 @@
1
+ import { z } from 'zod';
2
+ import { recordsJsonSchema } from '@disclosureos/records';
3
+
4
+ // src/completeness/index.ts
5
+ function refName(ref) {
6
+ return ref.replace("#/$defs/", "");
7
+ }
8
+ function deriveFieldPaths(schema) {
9
+ const root = schema ?? recordsJsonSchema();
10
+ const defs = root.$defs ?? {};
11
+ const out = [];
12
+ function resolve(node) {
13
+ let cur = node;
14
+ const seen = /* @__PURE__ */ new Set();
15
+ while (cur.$ref) {
16
+ const name = refName(cur.$ref);
17
+ if (seen.has(name)) break;
18
+ seen.add(name);
19
+ cur = defs[name] ?? {};
20
+ }
21
+ return cur;
22
+ }
23
+ function walk(node, prefix, required, visited) {
24
+ const refn = node.$ref ? refName(node.$ref) : void 0;
25
+ if (refn && visited.has(refn)) {
26
+ if (prefix) out.push({ path: prefix, required });
27
+ return;
28
+ }
29
+ const resolved = resolve(node);
30
+ const props = resolved.properties;
31
+ if (props && Object.keys(props).length > 0) {
32
+ const nextVisited = refn ? /* @__PURE__ */ new Set([...visited, refn]) : visited;
33
+ const req = new Set(resolved.required ?? []);
34
+ for (const [key, child] of Object.entries(props)) {
35
+ const childPath = prefix ? `${prefix}.${key}` : key;
36
+ walk(child, childPath, required && req.has(key), nextVisited);
37
+ }
38
+ return;
39
+ }
40
+ if (prefix) out.push({ path: prefix, required });
41
+ }
42
+ if (root.$ref) {
43
+ walk({ $ref: root.$ref }, "", true, /* @__PURE__ */ new Set());
44
+ }
45
+ return out;
46
+ }
47
+
48
+ // src/completeness/index.ts
49
+ var CompletenessResultSchema = z.object({
50
+ total: z.number().int().describe("Total leaf field paths defined by the records schema."),
51
+ present: z.number().int().describe("How many of those paths carry a value."),
52
+ percentage: z.number().int().describe("present / total, as a 0\u2013100 integer."),
53
+ missing: z.array(z.string()).describe("Paths that are absent or empty."),
54
+ requiredTotal: z.number().int().describe("Count of schema-required leaf paths."),
55
+ requiredPresent: z.number().int().describe("How many required paths carry a value."),
56
+ requiredPercentage: z.number().int().describe("requiredPresent / requiredTotal, as a 0\u2013100 integer.")
57
+ }).meta({ id: "CompletenessResult" }).describe("How fully an observation populates the records schema.");
58
+ function getNested(obj, path) {
59
+ let current = obj;
60
+ for (const part of path.split(".")) {
61
+ if (current == null || typeof current !== "object") return void 0;
62
+ current = current[part];
63
+ }
64
+ return current;
65
+ }
66
+ function isPresent(value) {
67
+ if (value == null) return false;
68
+ if (typeof value === "string") return value.length > 0;
69
+ if (Array.isArray(value)) return value.length > 0;
70
+ if (typeof value === "object") return Object.keys(value).length > 0;
71
+ return true;
72
+ }
73
+ var cachedPaths = null;
74
+ function defaultPaths() {
75
+ return cachedPaths ?? (cachedPaths = deriveFieldPaths());
76
+ }
77
+ function pct(present, total) {
78
+ return total > 0 ? Math.round(present / total * 100) : 0;
79
+ }
80
+ function getCompleteness(observation, options) {
81
+ const record = observation;
82
+ const paths = options?.paths ?? defaultPaths();
83
+ const missing = [];
84
+ let present = 0;
85
+ let requiredTotal = 0;
86
+ let requiredPresent = 0;
87
+ for (const field of paths) {
88
+ const ok = isPresent(getNested(record, field.path));
89
+ if (ok) present++;
90
+ else missing.push(field.path);
91
+ if (field.required) {
92
+ requiredTotal++;
93
+ if (ok) requiredPresent++;
94
+ }
95
+ }
96
+ return {
97
+ total: paths.length,
98
+ present,
99
+ percentage: pct(present, paths.length),
100
+ missing,
101
+ requiredTotal,
102
+ requiredPresent,
103
+ requiredPercentage: pct(requiredPresent, requiredTotal)
104
+ };
105
+ }
106
+
107
+ export { CompletenessResultSchema, deriveFieldPaths, getCompleteness };
108
+ //# sourceMappingURL=index.js.map
109
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/completeness/field-paths.ts","../../src/completeness/index.ts"],"names":[],"mappings":"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","file":"index.js","sourcesContent":["import { recordsJsonSchema } from '@disclosureos/records';\n\n/** A leaf field path derived from the records JSON Schema. */\nexport interface FieldPath {\n /** Dot-path into an `Observation` (e.g. `location.latitude`). */\n path: string;\n /** True only when every segment along the path is schema-required. */\n required: boolean;\n}\n\ninterface SchemaNode {\n $ref?: string;\n properties?: Record<string, SchemaNode>;\n required?: string[];\n type?: string | string[];\n}\n\nfunction refName(ref: string): string {\n return ref.replace('#/$defs/', '');\n}\n\n/**\n * Walk the committed records JSON Schema and enumerate every leaf field path on\n * `Observation`. Objects are recursed (resolving `$ref` into `$defs`); arrays,\n * enums, scalars, and `record`/`anyOf` nodes are treated as leaves. This is the\n * single source of completeness coverage — there is no hand-maintained path list\n * (so adding a field to the schema automatically extends completeness).\n */\nexport function deriveFieldPaths(schema?: Record<string, unknown>): FieldPath[] {\n const root = (schema ?? recordsJsonSchema()) as Record<string, unknown> & {\n $ref?: string;\n $defs?: Record<string, SchemaNode>;\n };\n const defs = root.$defs ?? {};\n const out: FieldPath[] = [];\n\n function resolve(node: SchemaNode): SchemaNode {\n let cur: SchemaNode = node;\n const seen = new Set<string>();\n while (cur.$ref) {\n const name = refName(cur.$ref);\n if (seen.has(name)) break;\n seen.add(name);\n cur = defs[name] ?? {};\n }\n return cur;\n }\n\n function walk(node: SchemaNode, prefix: string, required: boolean, visited: Set<string>): void {\n const refn = node.$ref ? refName(node.$ref) : undefined;\n // Cycle guard: a recursive $ref re-entering an in-progress def becomes a leaf.\n if (refn && visited.has(refn)) {\n if (prefix) out.push({ path: prefix, required });\n return;\n }\n const resolved = resolve(node);\n const props = resolved.properties;\n if (props && Object.keys(props).length > 0) {\n const nextVisited = refn ? new Set([...visited, refn]) : visited;\n const req = new Set(resolved.required ?? []);\n for (const [key, child] of Object.entries(props)) {\n const childPath = prefix ? `${prefix}.${key}` : key;\n walk(child, childPath, required && req.has(key), nextVisited);\n }\n return;\n }\n if (prefix) out.push({ path: prefix, required });\n }\n\n if (root.$ref) {\n walk({ $ref: root.$ref }, '', true, new Set());\n }\n return out;\n}\n","import { z } from 'zod';\nimport type { Observation } from '@disclosureos/records';\nimport { deriveFieldPaths } from './field-paths';\nimport type { FieldPath } from './field-paths';\n\nexport type { FieldPath } from './field-paths';\nexport { deriveFieldPaths } from './field-paths';\n\nexport const CompletenessResultSchema = z\n .object({\n total: z.number().int().describe('Total leaf field paths defined by the records schema.'),\n present: z.number().int().describe('How many of those paths carry a value.'),\n percentage: z.number().int().describe('present / total, as a 0–100 integer.'),\n missing: z.array(z.string()).describe('Paths that are absent or empty.'),\n requiredTotal: z.number().int().describe('Count of schema-required leaf paths.'),\n requiredPresent: z.number().int().describe('How many required paths carry a value.'),\n requiredPercentage: z\n .number()\n .int()\n .describe('requiredPresent / requiredTotal, as a 0–100 integer.'),\n })\n .meta({ id: 'CompletenessResult' })\n .describe('How fully an observation populates the records schema.');\n\nexport type CompletenessResult = z.infer<typeof CompletenessResultSchema>;\n\nexport interface CompletenessOptions {\n /** Override the derived field-path set (defaults to the records schema). */\n paths?: FieldPath[];\n}\n\nfunction getNested(obj: Record<string, unknown>, path: string): unknown {\n let current: unknown = obj;\n for (const part of path.split('.')) {\n if (current == null || typeof current !== 'object') return undefined;\n current = (current as Record<string, unknown>)[part];\n }\n return current;\n}\n\nfunction isPresent(value: unknown): boolean {\n if (value == null) return false;\n if (typeof value === 'string') return value.length > 0;\n if (Array.isArray(value)) return value.length > 0;\n if (typeof value === 'object') return Object.keys(value).length > 0;\n return true;\n}\n\n// Field paths are stable for a given schema version, so derive once and reuse.\nlet cachedPaths: FieldPath[] | null = null;\nfunction defaultPaths(): FieldPath[] {\n return (cachedPaths ??= deriveFieldPaths());\n}\n\nfunction pct(present: number, total: number): number {\n return total > 0 ? Math.round((present / total) * 100) : 0;\n}\n\n/**\n * Measure how fully an observation populates the records schema. This is the\n * \"is it well-documented?\" axis — deliberately kept separate from compellingness\n * (\"is it anomalous?\"), so a thin-but-anomalous case never masquerades as complete.\n */\nexport function getCompleteness(\n observation: Observation | Record<string, unknown>,\n options?: CompletenessOptions,\n): CompletenessResult {\n const record = observation as Record<string, unknown>;\n const paths = options?.paths ?? defaultPaths();\n const missing: string[] = [];\n let present = 0;\n let requiredTotal = 0;\n let requiredPresent = 0;\n\n for (const field of paths) {\n const ok = isPresent(getNested(record, field.path));\n if (ok) present++;\n else missing.push(field.path);\n if (field.required) {\n requiredTotal++;\n if (ok) requiredPresent++;\n }\n }\n\n return {\n total: paths.length,\n present,\n percentage: pct(present, paths.length),\n missing,\n requiredTotal,\n requiredPresent,\n requiredPercentage: pct(requiredPresent, requiredTotal),\n };\n}\n"]}
@@ -0,0 +1,16 @@
1
+ export { CompletenessOptions, CompletenessResult, CompletenessResultSchema, FieldPath, deriveFieldPaths, getCompleteness } from './completeness/index.js';
2
+ export { ASSESSMENT_LEVEL_WEIGHTS, CompellingnessWeights, CompellingnessWeightsSchema, DEFAULT_PROSAIC_OCS_PREFIXES, DEFAULT_WEIGHTS, EvaluatorWeightInput, RankedObservation, SCORING_VERSION, ScoreOptions, ScoreResult, ScoreResultSchema, rankByCompellingness, score } from './compellingness/index.js';
3
+ import 'zod';
4
+ import '@disclosureos/records';
5
+ import '@disclosureos/observables';
6
+
7
+ declare const SCORING_SCHEMA_VERSION = "2.0.0";
8
+ declare const SCORING_SCHEMA_ID: string;
9
+ /**
10
+ * Emit the JSON Schema (draft 2020-12) for the scoring output shapes
11
+ * ({@link CompletenessResultSchema}, {@link ScoreResultSchema}) so downstream
12
+ * consumers (and other languages) can validate stored scores.
13
+ */
14
+ declare function scoringJsonSchema(): Record<string, unknown>;
15
+
16
+ export { SCORING_SCHEMA_ID, SCORING_SCHEMA_VERSION, scoringJsonSchema };
package/dist/index.js ADDED
@@ -0,0 +1,252 @@
1
+ import { z } from 'zod';
2
+ import { recordsJsonSchema } from '@disclosureos/records';
3
+ import '@disclosureos/observables';
4
+ import '@disclosureos/origins';
5
+ import { schemaId, jsonSchemaEnvelope } from '@disclosureos/records/shared';
6
+
7
+ // src/completeness/index.ts
8
+ function refName(ref) {
9
+ return ref.replace("#/$defs/", "");
10
+ }
11
+ function deriveFieldPaths(schema) {
12
+ const root = schema ?? recordsJsonSchema();
13
+ const defs = root.$defs ?? {};
14
+ const out = [];
15
+ function resolve(node) {
16
+ let cur = node;
17
+ const seen = /* @__PURE__ */ new Set();
18
+ while (cur.$ref) {
19
+ const name = refName(cur.$ref);
20
+ if (seen.has(name)) break;
21
+ seen.add(name);
22
+ cur = defs[name] ?? {};
23
+ }
24
+ return cur;
25
+ }
26
+ function walk(node, prefix, required, visited) {
27
+ const refn = node.$ref ? refName(node.$ref) : void 0;
28
+ if (refn && visited.has(refn)) {
29
+ if (prefix) out.push({ path: prefix, required });
30
+ return;
31
+ }
32
+ const resolved = resolve(node);
33
+ const props = resolved.properties;
34
+ if (props && Object.keys(props).length > 0) {
35
+ const nextVisited = refn ? /* @__PURE__ */ new Set([...visited, refn]) : visited;
36
+ const req = new Set(resolved.required ?? []);
37
+ for (const [key, child] of Object.entries(props)) {
38
+ const childPath = prefix ? `${prefix}.${key}` : key;
39
+ walk(child, childPath, required && req.has(key), nextVisited);
40
+ }
41
+ return;
42
+ }
43
+ if (prefix) out.push({ path: prefix, required });
44
+ }
45
+ if (root.$ref) {
46
+ walk({ $ref: root.$ref }, "", true, /* @__PURE__ */ new Set());
47
+ }
48
+ return out;
49
+ }
50
+
51
+ // src/completeness/index.ts
52
+ var CompletenessResultSchema = z.object({
53
+ total: z.number().int().describe("Total leaf field paths defined by the records schema."),
54
+ present: z.number().int().describe("How many of those paths carry a value."),
55
+ percentage: z.number().int().describe("present / total, as a 0\u2013100 integer."),
56
+ missing: z.array(z.string()).describe("Paths that are absent or empty."),
57
+ requiredTotal: z.number().int().describe("Count of schema-required leaf paths."),
58
+ requiredPresent: z.number().int().describe("How many required paths carry a value."),
59
+ requiredPercentage: z.number().int().describe("requiredPresent / requiredTotal, as a 0\u2013100 integer.")
60
+ }).meta({ id: "CompletenessResult" }).describe("How fully an observation populates the records schema.");
61
+ function getNested(obj, path) {
62
+ let current = obj;
63
+ for (const part of path.split(".")) {
64
+ if (current == null || typeof current !== "object") return void 0;
65
+ current = current[part];
66
+ }
67
+ return current;
68
+ }
69
+ function isPresent(value) {
70
+ if (value == null) return false;
71
+ if (typeof value === "string") return value.length > 0;
72
+ if (Array.isArray(value)) return value.length > 0;
73
+ if (typeof value === "object") return Object.keys(value).length > 0;
74
+ return true;
75
+ }
76
+ var cachedPaths = null;
77
+ function defaultPaths() {
78
+ return cachedPaths ?? (cachedPaths = deriveFieldPaths());
79
+ }
80
+ function pct(present, total) {
81
+ return total > 0 ? Math.round(present / total * 100) : 0;
82
+ }
83
+ function getCompleteness(observation, options) {
84
+ const record = observation;
85
+ const paths = options?.paths ?? defaultPaths();
86
+ const missing = [];
87
+ let present = 0;
88
+ let requiredTotal = 0;
89
+ let requiredPresent = 0;
90
+ for (const field of paths) {
91
+ const ok = isPresent(getNested(record, field.path));
92
+ if (ok) present++;
93
+ else missing.push(field.path);
94
+ if (field.required) {
95
+ requiredTotal++;
96
+ if (ok) requiredPresent++;
97
+ }
98
+ }
99
+ return {
100
+ total: paths.length,
101
+ present,
102
+ percentage: pct(present, paths.length),
103
+ missing,
104
+ requiredTotal,
105
+ requiredPresent,
106
+ requiredPercentage: pct(requiredPresent, requiredTotal)
107
+ };
108
+ }
109
+ var SCORING_VERSION = "2.0.0";
110
+ var ASSESSMENT_LEVEL_WEIGHTS = {
111
+ not_indicated: 0,
112
+ reported: 0.25,
113
+ documented: 0.5,
114
+ measured: 0.75,
115
+ confirmed: 1
116
+ };
117
+ var DEFAULT_PROSAIC_OCS_PREFIXES = ["1.1.1"];
118
+ var CompellingnessWeightsSchema = z.object({
119
+ technology: z.number().min(0).describe("Weight of the technology-anomaly signal."),
120
+ biologics: z.number().min(0).describe("Weight of the biologics-anomaly signal."),
121
+ origin: z.number().min(0).describe("Weight of the (non-prosaic) origin-confidence signal.")
122
+ }).meta({ id: "CompellingnessWeights" }).describe("Relative weights for the compellingness components. Need not sum to 1 \u2014 normalized internally.");
123
+ var DEFAULT_WEIGHTS = {
124
+ technology: 0.45,
125
+ biologics: 0.35,
126
+ origin: 0.2
127
+ };
128
+ var ScoreResultSchema = z.object({
129
+ score: z.number().min(0).max(1).describe("Overall compellingness (consensus point estimate), 0\u20131."),
130
+ range: z.object({
131
+ low: z.number().min(0).max(1).describe("Most-skeptical reading of the strongest-anomaly signal across competing claims."),
132
+ high: z.number().min(0).max(1).describe("Most-anomalous reading of the strongest-anomaly signal across competing claims.")
133
+ }).describe(
134
+ "Spread of the score across competing claims \u2014 wide range signals disagreement. Bounds are computed per-signal under the same strongest-anomaly (max) reduction as the point estimate, not a global min/max over every claim."
135
+ ),
136
+ contested: z.boolean().describe("True when claims disagree on *direction* (some assert anomaly, some assert none)."),
137
+ components: z.object({
138
+ technology: z.number().min(0).max(1),
139
+ biologics: z.number().min(0).max(1),
140
+ origin: z.number().min(0).max(1)
141
+ }).describe("Per-signal consensus contributions before weighting."),
142
+ weights: CompellingnessWeightsSchema,
143
+ scoringVersion: z.string().describe("Methodology version that produced this score.")
144
+ }).meta({ id: "ScoreResult" }).describe("A reference compellingness score for an observation, with a contestedness range.");
145
+ var UNIFORM_TRUST = () => 1;
146
+ function isProsaic(id, prefixes) {
147
+ return prefixes.some((p) => id === p || id.startsWith(`${p}.`));
148
+ }
149
+ function round(n) {
150
+ return Math.round(n * 1e4) / 1e4;
151
+ }
152
+ var EMPTY_STATS = { point: 0, low: 0, high: 0, contested: false };
153
+ function reduceClaims(weighted) {
154
+ if (weighted.length === 0) return EMPTY_STATS;
155
+ let wsum = 0;
156
+ let msum = 0;
157
+ let low = Infinity;
158
+ let high = -Infinity;
159
+ let anyAnomaly = false;
160
+ let anyNone = false;
161
+ for (const { magnitude, weight } of weighted) {
162
+ wsum += weight;
163
+ msum += magnitude * weight;
164
+ if (magnitude < low) low = magnitude;
165
+ if (magnitude > high) high = magnitude;
166
+ if (magnitude > 0) anyAnomaly = true;
167
+ else anyNone = true;
168
+ }
169
+ return {
170
+ point: wsum > 0 ? msum / wsum : 0,
171
+ low,
172
+ high,
173
+ contested: anyAnomaly && anyNone
174
+ };
175
+ }
176
+ function observableMagnitude(claim) {
177
+ return ASSESSMENT_LEVEL_WEIGHTS[claim.level] * (claim.confidence ?? 1);
178
+ }
179
+ function domainStats(group, evaluatorWeight) {
180
+ if (!group) return EMPTY_STATS;
181
+ let point = 0;
182
+ let low = 0;
183
+ let high = 0;
184
+ let contested = false;
185
+ for (const claims of Object.values(group)) {
186
+ if (!claims || claims.length === 0) continue;
187
+ const weighted = claims.map((c) => ({
188
+ magnitude: observableMagnitude(c),
189
+ weight: (c.confidence ?? 1) * evaluatorWeight(c)
190
+ }));
191
+ const stats = reduceClaims(weighted);
192
+ if (stats.point > point) point = stats.point;
193
+ if (stats.low > low) low = stats.low;
194
+ if (stats.high > high) high = stats.high;
195
+ if (stats.contested) contested = true;
196
+ }
197
+ return { point, low, high, contested };
198
+ }
199
+ function originStats(origin, prefixes, evaluatorWeight) {
200
+ if (!origin || origin.length === 0) return EMPTY_STATS;
201
+ const weighted = origin.map((c) => ({
202
+ magnitude: isProsaic(c.primaryHypothesis, prefixes) ? 0 : c.confidence,
203
+ weight: c.confidence * evaluatorWeight(c)
204
+ }));
205
+ return reduceClaims(weighted);
206
+ }
207
+ function score(observation, options) {
208
+ const weights = options?.weights ?? DEFAULT_WEIGHTS;
209
+ const prosaicPrefixes = options?.prosaicPrefixes ?? DEFAULT_PROSAIC_OCS_PREFIXES;
210
+ const evaluatorWeight = options?.evaluatorWeight ?? UNIFORM_TRUST;
211
+ const assessments = observation.observableAssessments;
212
+ const technology = domainStats(assessments?.technology, evaluatorWeight);
213
+ const biologics = domainStats(assessments?.biologics, evaluatorWeight);
214
+ const origin = originStats(observation.origin, prosaicPrefixes, evaluatorWeight);
215
+ const totalWeight = weights.technology + weights.biologics + weights.origin;
216
+ const combine = (t, b, o) => totalWeight > 0 ? (t * weights.technology + b * weights.biologics + o * weights.origin) / totalWeight : 0;
217
+ return {
218
+ score: round(combine(technology.point, biologics.point, origin.point)),
219
+ range: {
220
+ low: round(combine(technology.low, biologics.low, origin.low)),
221
+ high: round(combine(technology.high, biologics.high, origin.high))
222
+ },
223
+ contested: technology.contested || biologics.contested || origin.contested,
224
+ components: {
225
+ technology: round(technology.point),
226
+ biologics: round(biologics.point),
227
+ origin: round(origin.point)
228
+ },
229
+ weights,
230
+ scoringVersion: SCORING_VERSION
231
+ };
232
+ }
233
+ function rankByCompellingness(observations, options) {
234
+ return observations.map((observation) => ({ observation, result: score(observation, options) })).sort((a, b) => b.result.score - a.result.score);
235
+ }
236
+ var SCORING_SCHEMA_VERSION = "2.0.0";
237
+ var SCORING_SCHEMA_ID = schemaId("scoring", "scoring", SCORING_SCHEMA_VERSION);
238
+ function scoringJsonSchema() {
239
+ const bundle = z.toJSONSchema(
240
+ z.object({
241
+ completeness: CompletenessResultSchema,
242
+ compellingness: ScoreResultSchema,
243
+ weights: CompellingnessWeightsSchema
244
+ }),
245
+ { target: "draft-2020-12" }
246
+ );
247
+ return jsonSchemaEnvelope(bundle, { id: SCORING_SCHEMA_ID, version: SCORING_SCHEMA_VERSION });
248
+ }
249
+
250
+ export { ASSESSMENT_LEVEL_WEIGHTS, CompellingnessWeightsSchema, CompletenessResultSchema, DEFAULT_PROSAIC_OCS_PREFIXES, DEFAULT_WEIGHTS, SCORING_SCHEMA_ID, SCORING_SCHEMA_VERSION, SCORING_VERSION, ScoreResultSchema, deriveFieldPaths, getCompleteness, rankByCompellingness, score, scoringJsonSchema };
251
+ //# sourceMappingURL=index.js.map
252
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/completeness/field-paths.ts","../src/completeness/index.ts","../src/compellingness/index.ts","../src/schema.ts"],"names":["z"],"mappings":"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","file":"index.js","sourcesContent":["import { recordsJsonSchema } from '@disclosureos/records';\n\n/** A leaf field path derived from the records JSON Schema. */\nexport interface FieldPath {\n /** Dot-path into an `Observation` (e.g. `location.latitude`). */\n path: string;\n /** True only when every segment along the path is schema-required. */\n required: boolean;\n}\n\ninterface SchemaNode {\n $ref?: string;\n properties?: Record<string, SchemaNode>;\n required?: string[];\n type?: string | string[];\n}\n\nfunction refName(ref: string): string {\n return ref.replace('#/$defs/', '');\n}\n\n/**\n * Walk the committed records JSON Schema and enumerate every leaf field path on\n * `Observation`. Objects are recursed (resolving `$ref` into `$defs`); arrays,\n * enums, scalars, and `record`/`anyOf` nodes are treated as leaves. This is the\n * single source of completeness coverage — there is no hand-maintained path list\n * (so adding a field to the schema automatically extends completeness).\n */\nexport function deriveFieldPaths(schema?: Record<string, unknown>): FieldPath[] {\n const root = (schema ?? recordsJsonSchema()) as Record<string, unknown> & {\n $ref?: string;\n $defs?: Record<string, SchemaNode>;\n };\n const defs = root.$defs ?? {};\n const out: FieldPath[] = [];\n\n function resolve(node: SchemaNode): SchemaNode {\n let cur: SchemaNode = node;\n const seen = new Set<string>();\n while (cur.$ref) {\n const name = refName(cur.$ref);\n if (seen.has(name)) break;\n seen.add(name);\n cur = defs[name] ?? {};\n }\n return cur;\n }\n\n function walk(node: SchemaNode, prefix: string, required: boolean, visited: Set<string>): void {\n const refn = node.$ref ? refName(node.$ref) : undefined;\n // Cycle guard: a recursive $ref re-entering an in-progress def becomes a leaf.\n if (refn && visited.has(refn)) {\n if (prefix) out.push({ path: prefix, required });\n return;\n }\n const resolved = resolve(node);\n const props = resolved.properties;\n if (props && Object.keys(props).length > 0) {\n const nextVisited = refn ? new Set([...visited, refn]) : visited;\n const req = new Set(resolved.required ?? []);\n for (const [key, child] of Object.entries(props)) {\n const childPath = prefix ? `${prefix}.${key}` : key;\n walk(child, childPath, required && req.has(key), nextVisited);\n }\n return;\n }\n if (prefix) out.push({ path: prefix, required });\n }\n\n if (root.$ref) {\n walk({ $ref: root.$ref }, '', true, new Set());\n }\n return out;\n}\n","import { z } from 'zod';\nimport type { Observation } from '@disclosureos/records';\nimport { deriveFieldPaths } from './field-paths';\nimport type { FieldPath } from './field-paths';\n\nexport type { FieldPath } from './field-paths';\nexport { deriveFieldPaths } from './field-paths';\n\nexport const CompletenessResultSchema = z\n .object({\n total: z.number().int().describe('Total leaf field paths defined by the records schema.'),\n present: z.number().int().describe('How many of those paths carry a value.'),\n percentage: z.number().int().describe('present / total, as a 0–100 integer.'),\n missing: z.array(z.string()).describe('Paths that are absent or empty.'),\n requiredTotal: z.number().int().describe('Count of schema-required leaf paths.'),\n requiredPresent: z.number().int().describe('How many required paths carry a value.'),\n requiredPercentage: z\n .number()\n .int()\n .describe('requiredPresent / requiredTotal, as a 0–100 integer.'),\n })\n .meta({ id: 'CompletenessResult' })\n .describe('How fully an observation populates the records schema.');\n\nexport type CompletenessResult = z.infer<typeof CompletenessResultSchema>;\n\nexport interface CompletenessOptions {\n /** Override the derived field-path set (defaults to the records schema). */\n paths?: FieldPath[];\n}\n\nfunction getNested(obj: Record<string, unknown>, path: string): unknown {\n let current: unknown = obj;\n for (const part of path.split('.')) {\n if (current == null || typeof current !== 'object') return undefined;\n current = (current as Record<string, unknown>)[part];\n }\n return current;\n}\n\nfunction isPresent(value: unknown): boolean {\n if (value == null) return false;\n if (typeof value === 'string') return value.length > 0;\n if (Array.isArray(value)) return value.length > 0;\n if (typeof value === 'object') return Object.keys(value).length > 0;\n return true;\n}\n\n// Field paths are stable for a given schema version, so derive once and reuse.\nlet cachedPaths: FieldPath[] | null = null;\nfunction defaultPaths(): FieldPath[] {\n return (cachedPaths ??= deriveFieldPaths());\n}\n\nfunction pct(present: number, total: number): number {\n return total > 0 ? Math.round((present / total) * 100) : 0;\n}\n\n/**\n * Measure how fully an observation populates the records schema. This is the\n * \"is it well-documented?\" axis — deliberately kept separate from compellingness\n * (\"is it anomalous?\"), so a thin-but-anomalous case never masquerades as complete.\n */\nexport function getCompleteness(\n observation: Observation | Record<string, unknown>,\n options?: CompletenessOptions,\n): CompletenessResult {\n const record = observation as Record<string, unknown>;\n const paths = options?.paths ?? defaultPaths();\n const missing: string[] = [];\n let present = 0;\n let requiredTotal = 0;\n let requiredPresent = 0;\n\n for (const field of paths) {\n const ok = isPresent(getNested(record, field.path));\n if (ok) present++;\n else missing.push(field.path);\n if (field.required) {\n requiredTotal++;\n if (ok) requiredPresent++;\n }\n }\n\n return {\n total: paths.length,\n present,\n percentage: pct(present, paths.length),\n missing,\n requiredTotal,\n requiredPresent,\n requiredPercentage: pct(requiredPresent, requiredTotal),\n };\n}\n","import { z } from 'zod';\nimport type { Observation } from '@disclosureos/records';\nimport type { AssessmentLevel, ObservableClaim } from '@disclosureos/observables';\nimport type { OriginClaim } from '@disclosureos/origins';\n// Side-effect imports register the `observableAssessments` and `origin` slots on\n// `Observation` via module augmentation, so they are typed below.\nimport '@disclosureos/observables';\nimport '@disclosureos/origins';\n\n/** Methodology version stamped on every {@link ScoreResult}. Bump on weight/formula changes. */\nexport const SCORING_VERSION = '2.0.0' as const;\n\n/**\n * Ordinal anomaly strength per evidentiary tier. A `confirmed` signal counts\n * fully; `not_indicated` contributes nothing. Multiplied by the assessment's\n * confidence (default 1 when unstated). Exported so consumers can render the\n * methodology; override semantics may come in a future minor release.\n */\nexport const ASSESSMENT_LEVEL_WEIGHTS: Record<AssessmentLevel, number> = {\n not_indicated: 0,\n reported: 0.25,\n documented: 0.5,\n measured: 0.75,\n confirmed: 1,\n};\n\n/**\n * OCS id prefixes treated as prosaic (well-explained). A confident *prosaic*\n * classification means the case is explained, so it contributes no anomaly\n * signal. Overridable per {@link score} call. Default is the OCS \"Prosaic\"\n * branch (`1.1.1`).\n */\nexport const DEFAULT_PROSAIC_OCS_PREFIXES: readonly string[] = ['1.1.1'];\n\nexport const CompellingnessWeightsSchema = z\n .object({\n technology: z.number().min(0).describe('Weight of the technology-anomaly signal.'),\n biologics: z.number().min(0).describe('Weight of the biologics-anomaly signal.'),\n origin: z.number().min(0).describe('Weight of the (non-prosaic) origin-confidence signal.'),\n })\n .meta({ id: 'CompellingnessWeights' })\n .describe('Relative weights for the compellingness components. Need not sum to 1 — normalized internally.');\n\nexport type CompellingnessWeights = z.infer<typeof CompellingnessWeightsSchema>;\n\n/**\n * Fixed default weights, exposed as a transparent, overridable data object (not a\n * config system). Partners can propose alternate weight sets without an API change.\n *\n * @experimental The *shape* (`CompellingnessWeights`) is stable, but these specific\n * default values are a reference methodology that may be re-tuned in a minor release.\n * Pin your own `weights` via {@link ScoreOptions} if you need stable numbers.\n */\nexport const DEFAULT_WEIGHTS: CompellingnessWeights = {\n technology: 0.45,\n biologics: 0.35,\n origin: 0.2,\n};\n\nexport const ScoreResultSchema = z\n .object({\n score: z.number().min(0).max(1).describe('Overall compellingness (consensus point estimate), 0–1.'),\n range: z\n .object({\n low: z.number().min(0).max(1).describe('Most-skeptical reading of the strongest-anomaly signal across competing claims.'),\n high: z.number().min(0).max(1).describe('Most-anomalous reading of the strongest-anomaly signal across competing claims.'),\n })\n .describe(\n 'Spread of the score across competing claims — wide range signals disagreement. Bounds are '\n + 'computed per-signal under the same strongest-anomaly (max) reduction as the point estimate, '\n + 'not a global min/max over every claim.',\n ),\n contested: z\n .boolean()\n .describe('True when claims disagree on *direction* (some assert anomaly, some assert none).'),\n components: z\n .object({\n technology: z.number().min(0).max(1),\n biologics: z.number().min(0).max(1),\n origin: z.number().min(0).max(1),\n })\n .describe('Per-signal consensus contributions before weighting.'),\n weights: CompellingnessWeightsSchema,\n scoringVersion: z.string().describe('Methodology version that produced this score.'),\n })\n .meta({ id: 'ScoreResult' })\n .describe('A reference compellingness score for an observation, with a contestedness range.');\n\nexport type ScoreResult = z.infer<typeof ScoreResultSchema>;\n\n/** The minimal claim shape the trust hook sees (it never needs the full body). */\nexport type EvaluatorWeightInput = {\n evaluatedBy?: string | undefined;\n evidenceRefs?: string[] | undefined;\n};\n\nexport interface ScoreOptions {\n weights?: CompellingnessWeights;\n /** Override the prosaic OCS prefixes (defaults to {@link DEFAULT_PROSAIC_OCS_PREFIXES}). */\n prosaicPrefixes?: readonly string[];\n /**\n * Trust applied to each claim's vote in the consensus point estimate (default\n * 1.0 — uniform). This is where a future trust policy (e.g.\n * weighting verified/official evaluators higher) plugs in here without changing\n * the formula. Range/contestedness are NOT affected — they report the honest\n * spread of what evaluators actually asserted.\n *\n * @experimental The hook signature is stable, but the default trust *policy*\n * (uniform 1.0) is deliberately unopinionated and is a separate, ongoing design\n * task; a future built-in policy may change the default.\n */\n evaluatorWeight?: (claim: EvaluatorWeightInput) => number;\n}\n\nconst UNIFORM_TRUST = (): number => 1;\n\nfunction isProsaic(id: string, prefixes: readonly string[]): boolean {\n return prefixes.some((p) => id === p || id.startsWith(`${p}.`));\n}\n\nfunction round(n: number): number {\n return Math.round(n * 1e4) / 1e4;\n}\n\ntype Weighted = { magnitude: number; weight: number };\n\ninterface ClaimStats {\n /** Trust-weighted mean magnitude (the consensus point). */\n point: number;\n /** Min asserted magnitude (most skeptical). */\n low: number;\n /** Max asserted magnitude (most anomalous). */\n high: number;\n /** Claims disagree on direction (some > 0, some === 0). */\n contested: boolean;\n}\n\nconst EMPTY_STATS: ClaimStats = { point: 0, low: 0, high: 0, contested: false };\n\nfunction reduceClaims(weighted: Weighted[]): ClaimStats {\n if (weighted.length === 0) return EMPTY_STATS;\n let wsum = 0;\n let msum = 0;\n let low = Infinity;\n let high = -Infinity;\n let anyAnomaly = false;\n let anyNone = false;\n for (const { magnitude, weight } of weighted) {\n wsum += weight;\n msum += magnitude * weight;\n if (magnitude < low) low = magnitude;\n if (magnitude > high) high = magnitude;\n if (magnitude > 0) anyAnomaly = true;\n else anyNone = true;\n }\n return {\n point: wsum > 0 ? msum / wsum : 0,\n low,\n high,\n contested: anyAnomaly && anyNone,\n };\n}\n\n/** Asserted anomaly magnitude of one observable claim, in [0,1]. */\nfunction observableMagnitude(claim: ObservableClaim): number {\n return ASSESSMENT_LEVEL_WEIGHTS[claim.level] * (claim.confidence ?? 1);\n}\n\n/**\n * Strongest established anomaly within a domain — the \"is there *any* anomaly?\" reduction.\n * `point`/`low`/`high` are each a **max across observables** of that observable's claim-list\n * stat. So `low` is \"the most-skeptical reading of the strongest-anomaly observable,\" not a\n * global floor across all observables — consistent with the point estimate's max reduction.\n */\nfunction domainStats(\n group: Partial<Record<string, ObservableClaim[]>> | undefined,\n evaluatorWeight: (claim: EvaluatorWeightInput) => number,\n): ClaimStats {\n if (!group) return EMPTY_STATS;\n let point = 0;\n let low = 0;\n let high = 0;\n let contested = false;\n for (const claims of Object.values(group)) {\n if (!claims || claims.length === 0) continue;\n const weighted = claims.map<Weighted>((c) => ({\n magnitude: observableMagnitude(c),\n weight: (c.confidence ?? 1) * evaluatorWeight(c),\n }));\n const stats = reduceClaims(weighted);\n if (stats.point > point) point = stats.point;\n if (stats.low > low) low = stats.low;\n if (stats.high > high) high = stats.high;\n if (stats.contested) contested = true;\n }\n return { point, low, high, contested };\n}\n\n/** Confidence in a *non-prosaic* origin, reduced across competing classifications. */\nfunction originStats(\n origin: OriginClaim[] | undefined,\n prefixes: readonly string[],\n evaluatorWeight: (claim: EvaluatorWeightInput) => number,\n): ClaimStats {\n if (!origin || origin.length === 0) return EMPTY_STATS;\n const weighted = origin.map<Weighted>((c) => ({\n magnitude: isProsaic(c.primaryHypothesis, prefixes) ? 0 : c.confidence,\n weight: c.confidence * evaluatorWeight(c),\n }));\n return reduceClaims(weighted);\n}\n\n/**\n * Reference compellingness scorer. Combines the strongest technology- and\n * biologics-anomaly signals (from `observableAssessments`) with the confidence in\n * a *non-prosaic* origin classification (from `origin`), under transparent,\n * overridable weights. Each slot is a *list* of competing claims, so the scorer\n * reports a consensus `score`, the `range` of readings across claims, and whether\n * the case is `contested` (evaluators disagree on direction). This answers \"how\n * strongly does this case bear on the two public questions — anomalous\n * tech/biologics, and a non-mundane origin?\" — distinct from completeness.\n */\nexport function score(observation: Observation, options?: ScoreOptions): ScoreResult {\n const weights = options?.weights ?? DEFAULT_WEIGHTS;\n const prosaicPrefixes = options?.prosaicPrefixes ?? DEFAULT_PROSAIC_OCS_PREFIXES;\n const evaluatorWeight = options?.evaluatorWeight ?? UNIFORM_TRUST;\n\n const assessments = observation.observableAssessments;\n const technology = domainStats(assessments?.technology, evaluatorWeight);\n const biologics = domainStats(assessments?.biologics, evaluatorWeight);\n const origin = originStats(observation.origin, prosaicPrefixes, evaluatorWeight);\n\n const totalWeight = weights.technology + weights.biologics + weights.origin;\n const combine = (t: number, b: number, o: number): number =>\n totalWeight > 0\n ? (t * weights.technology + b * weights.biologics + o * weights.origin) / totalWeight\n : 0;\n\n return {\n score: round(combine(technology.point, biologics.point, origin.point)),\n range: {\n low: round(combine(technology.low, biologics.low, origin.low)),\n high: round(combine(technology.high, biologics.high, origin.high)),\n },\n contested: technology.contested || biologics.contested || origin.contested,\n components: {\n technology: round(technology.point),\n biologics: round(biologics.point),\n origin: round(origin.point),\n },\n weights,\n scoringVersion: SCORING_VERSION,\n };\n}\n\nexport interface RankedObservation<T extends Observation = Observation> {\n observation: T;\n result: ScoreResult;\n}\n\n/**\n * Score a set of observations and return them sorted most-compelling first — the\n * \"float the strongest cases to the top\" use case.\n */\nexport function rankByCompellingness<T extends Observation>(\n observations: readonly T[],\n options?: ScoreOptions,\n): RankedObservation<T>[] {\n return observations\n .map((observation) => ({ observation, result: score(observation, options) }))\n .sort((a, b) => b.result.score - a.result.score);\n}\n","import { z } from 'zod';\nimport { jsonSchemaEnvelope, schemaId } from '@disclosureos/records/shared';\nimport { CompletenessResultSchema } from './completeness';\nimport { ScoreResultSchema, CompellingnessWeightsSchema } from './compellingness';\n\nexport const SCORING_SCHEMA_VERSION = '2.0.0';\n\nexport const SCORING_SCHEMA_ID = schemaId('scoring', 'scoring', SCORING_SCHEMA_VERSION);\n\n/**\n * Emit the JSON Schema (draft 2020-12) for the scoring output shapes\n * ({@link CompletenessResultSchema}, {@link ScoreResultSchema}) so downstream\n * consumers (and other languages) can validate stored scores.\n */\nexport function scoringJsonSchema(): Record<string, unknown> {\n const bundle = z.toJSONSchema(\n z.object({\n completeness: CompletenessResultSchema,\n compellingness: ScoreResultSchema,\n weights: CompellingnessWeightsSchema,\n }),\n { target: 'draft-2020-12' },\n ) as Record<string, unknown>;\n return jsonSchemaEnvelope(bundle, { id: SCORING_SCHEMA_ID, version: SCORING_SCHEMA_VERSION });\n}\n"]}
package/package.json ADDED
@@ -0,0 +1,81 @@
1
+ {
2
+ "name": "@disclosureos/scoring",
3
+ "version": "1.0.0",
4
+ "description": "Reference scoring for the DisclosureOS ecosystem — record completeness and case compellingness across the Observables and Origin layers",
5
+ "license": "MIT",
6
+ "repository": {
7
+ "type": "git",
8
+ "url": "https://github.com/disclosurefoundation/disclosureos"
9
+ },
10
+ "homepage": "https://github.com/disclosurefoundation/disclosureos#readme",
11
+ "bugs": {
12
+ "url": "https://github.com/disclosurefoundation/disclosureos/issues"
13
+ },
14
+ "keywords": [
15
+ "disclosureos",
16
+ "UAP",
17
+ "scoring",
18
+ "completeness",
19
+ "compellingness",
20
+ "typescript"
21
+ ],
22
+ "sideEffects": false,
23
+ "type": "module",
24
+ "engines": {
25
+ "node": ">=20.0.0"
26
+ },
27
+ "exports": {
28
+ ".": {
29
+ "types": "./dist/index.d.ts",
30
+ "import": "./dist/index.js"
31
+ },
32
+ "./completeness": {
33
+ "types": "./dist/completeness/index.d.ts",
34
+ "import": "./dist/completeness/index.js"
35
+ },
36
+ "./compellingness": {
37
+ "types": "./dist/compellingness/index.d.ts",
38
+ "import": "./dist/compellingness/index.js"
39
+ },
40
+ "./schema": "./schema/scoring.schema.json",
41
+ "./package.json": "./package.json"
42
+ },
43
+ "main": "./dist/index.js",
44
+ "types": "./dist/index.d.ts",
45
+ "files": [
46
+ "dist",
47
+ "schema",
48
+ "README.md",
49
+ "CHANGELOG.md",
50
+ "LICENSE"
51
+ ],
52
+ "dependencies": {
53
+ "zod": "^4.4.3",
54
+ "@disclosureos/records": "1.0.0",
55
+ "@disclosureos/origins": "1.0.0",
56
+ "@disclosureos/observables": "1.0.0"
57
+ },
58
+ "devDependencies": {
59
+ "@types/node": "^20",
60
+ "tsup": "^8.0.0",
61
+ "tsx": "^4.21.0",
62
+ "typescript": "^5.7.2",
63
+ "vitest": "^4.0.18",
64
+ "@arethetypeswrong/cli": "^0.18.3",
65
+ "publint": "^0.3.21",
66
+ "@repo/typescript-config": "0.0.0"
67
+ },
68
+ "publishConfig": {
69
+ "access": "public"
70
+ },
71
+ "scripts": {
72
+ "build": "tsup",
73
+ "dev": "tsup --watch",
74
+ "lint": "eslint .",
75
+ "type-check": "tsc --noEmit",
76
+ "emit:schema": "tsx scripts/emit-schema.ts",
77
+ "clean": "rm -rf dist",
78
+ "test": "vitest",
79
+ "lint:publish": "publint --strict && attw --pack . --profile esm-only"
80
+ }
81
+ }
@@ -0,0 +1,194 @@
1
+ {
2
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
3
+ "$id": "https://os.disclosure.org/schema/scoring/2.0.0/scoring.json",
4
+ "x-schema-version": "2.0.0",
5
+ "type": "object",
6
+ "properties": {
7
+ "completeness": {
8
+ "description": "How fully an observation populates the records schema.",
9
+ "$ref": "#/$defs/CompletenessResult"
10
+ },
11
+ "compellingness": {
12
+ "description": "A reference compellingness score for an observation, with a contestedness range.",
13
+ "$ref": "#/$defs/ScoreResult"
14
+ },
15
+ "weights": {
16
+ "description": "Relative weights for the compellingness components. Need not sum to 1 — normalized internally.",
17
+ "$ref": "#/$defs/CompellingnessWeights"
18
+ }
19
+ },
20
+ "required": [
21
+ "completeness",
22
+ "compellingness",
23
+ "weights"
24
+ ],
25
+ "additionalProperties": false,
26
+ "$defs": {
27
+ "CompletenessResult": {
28
+ "type": "object",
29
+ "properties": {
30
+ "total": {
31
+ "type": "integer",
32
+ "minimum": -9007199254740991,
33
+ "maximum": 9007199254740991,
34
+ "description": "Total leaf field paths defined by the records schema."
35
+ },
36
+ "present": {
37
+ "type": "integer",
38
+ "minimum": -9007199254740991,
39
+ "maximum": 9007199254740991,
40
+ "description": "How many of those paths carry a value."
41
+ },
42
+ "percentage": {
43
+ "type": "integer",
44
+ "minimum": -9007199254740991,
45
+ "maximum": 9007199254740991,
46
+ "description": "present / total, as a 0–100 integer."
47
+ },
48
+ "missing": {
49
+ "type": "array",
50
+ "items": {
51
+ "type": "string"
52
+ },
53
+ "description": "Paths that are absent or empty."
54
+ },
55
+ "requiredTotal": {
56
+ "type": "integer",
57
+ "minimum": -9007199254740991,
58
+ "maximum": 9007199254740991,
59
+ "description": "Count of schema-required leaf paths."
60
+ },
61
+ "requiredPresent": {
62
+ "type": "integer",
63
+ "minimum": -9007199254740991,
64
+ "maximum": 9007199254740991,
65
+ "description": "How many required paths carry a value."
66
+ },
67
+ "requiredPercentage": {
68
+ "type": "integer",
69
+ "minimum": -9007199254740991,
70
+ "maximum": 9007199254740991,
71
+ "description": "requiredPresent / requiredTotal, as a 0–100 integer."
72
+ }
73
+ },
74
+ "required": [
75
+ "total",
76
+ "present",
77
+ "percentage",
78
+ "missing",
79
+ "requiredTotal",
80
+ "requiredPresent",
81
+ "requiredPercentage"
82
+ ],
83
+ "additionalProperties": false
84
+ },
85
+ "CompellingnessWeights": {
86
+ "type": "object",
87
+ "properties": {
88
+ "technology": {
89
+ "type": "number",
90
+ "minimum": 0,
91
+ "description": "Weight of the technology-anomaly signal."
92
+ },
93
+ "biologics": {
94
+ "type": "number",
95
+ "minimum": 0,
96
+ "description": "Weight of the biologics-anomaly signal."
97
+ },
98
+ "origin": {
99
+ "type": "number",
100
+ "minimum": 0,
101
+ "description": "Weight of the (non-prosaic) origin-confidence signal."
102
+ }
103
+ },
104
+ "required": [
105
+ "technology",
106
+ "biologics",
107
+ "origin"
108
+ ],
109
+ "additionalProperties": false
110
+ },
111
+ "ScoreResult": {
112
+ "type": "object",
113
+ "properties": {
114
+ "score": {
115
+ "type": "number",
116
+ "minimum": 0,
117
+ "maximum": 1,
118
+ "description": "Overall compellingness (consensus point estimate), 0–1."
119
+ },
120
+ "range": {
121
+ "type": "object",
122
+ "properties": {
123
+ "low": {
124
+ "type": "number",
125
+ "minimum": 0,
126
+ "maximum": 1,
127
+ "description": "Most-skeptical reading of the strongest-anomaly signal across competing claims."
128
+ },
129
+ "high": {
130
+ "type": "number",
131
+ "minimum": 0,
132
+ "maximum": 1,
133
+ "description": "Most-anomalous reading of the strongest-anomaly signal across competing claims."
134
+ }
135
+ },
136
+ "required": [
137
+ "low",
138
+ "high"
139
+ ],
140
+ "additionalProperties": false,
141
+ "description": "Spread of the score across competing claims — wide range signals disagreement. Bounds are computed per-signal under the same strongest-anomaly (max) reduction as the point estimate, not a global min/max over every claim."
142
+ },
143
+ "contested": {
144
+ "type": "boolean",
145
+ "description": "True when claims disagree on *direction* (some assert anomaly, some assert none)."
146
+ },
147
+ "components": {
148
+ "type": "object",
149
+ "properties": {
150
+ "technology": {
151
+ "type": "number",
152
+ "minimum": 0,
153
+ "maximum": 1
154
+ },
155
+ "biologics": {
156
+ "type": "number",
157
+ "minimum": 0,
158
+ "maximum": 1
159
+ },
160
+ "origin": {
161
+ "type": "number",
162
+ "minimum": 0,
163
+ "maximum": 1
164
+ }
165
+ },
166
+ "required": [
167
+ "technology",
168
+ "biologics",
169
+ "origin"
170
+ ],
171
+ "additionalProperties": false,
172
+ "description": "Per-signal consensus contributions before weighting."
173
+ },
174
+ "weights": {
175
+ "description": "Relative weights for the compellingness components. Need not sum to 1 — normalized internally.",
176
+ "$ref": "#/$defs/CompellingnessWeights"
177
+ },
178
+ "scoringVersion": {
179
+ "type": "string",
180
+ "description": "Methodology version that produced this score."
181
+ }
182
+ },
183
+ "required": [
184
+ "score",
185
+ "range",
186
+ "contested",
187
+ "components",
188
+ "weights",
189
+ "scoringVersion"
190
+ ],
191
+ "additionalProperties": false
192
+ }
193
+ }
194
+ }