project-ghost 0.1.1__py3-none-any.whl

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.
Files changed (105) hide show
  1. project_ghost/__init__.py +10 -0
  2. project_ghost/actuators/__init__.py +4 -0
  3. project_ghost/analysis/__init__.py +233 -0
  4. project_ghost/analysis/belief_consistency.py +318 -0
  5. project_ghost/analysis/belief_traceability.py +370 -0
  6. project_ghost/analysis/calibration.py +425 -0
  7. project_ghost/analysis/comparison.py +702 -0
  8. project_ghost/analysis/decision_trace.py +534 -0
  9. project_ghost/analysis/models.py +80 -0
  10. project_ghost/analysis/report.py +69 -0
  11. project_ghost/analysis/self_assessment.py +345 -0
  12. project_ghost/analysis/summary.py +203 -0
  13. project_ghost/app/__init__.py +25 -0
  14. project_ghost/app/main.py +1802 -0
  15. project_ghost/cli.py +1227 -0
  16. project_ghost/core/__init__.py +15 -0
  17. project_ghost/core/actuation/__init__.py +43 -0
  18. project_ghost/core/actuation/attitude_hold_policy.py +152 -0
  19. project_ghost/core/actuation/orchestration.py +37 -0
  20. project_ghost/core/actuation/protocols.py +68 -0
  21. project_ghost/core/actuation/reference_policy.py +80 -0
  22. project_ghost/core/actuation/sinks.py +53 -0
  23. project_ghost/core/actuation/types.py +125 -0
  24. project_ghost/core/clock/__init__.py +53 -0
  25. project_ghost/core/clock/error_sink.py +78 -0
  26. project_ghost/core/clock/random_source.py +109 -0
  27. project_ghost/core/clock/sim_clock.py +225 -0
  28. project_ghost/core/clock/types.py +101 -0
  29. project_ghost/core/decisions/__init__.py +59 -0
  30. project_ghost/core/decisions/orchestration.py +108 -0
  31. project_ghost/core/decisions/protocols.py +67 -0
  32. project_ghost/core/decisions/reference_policy.py +110 -0
  33. project_ghost/core/decisions/sinks.py +66 -0
  34. project_ghost/core/decisions/types.py +286 -0
  35. project_ghost/core/feedback/__init__.py +44 -0
  36. project_ghost/core/feedback/orchestration.py +107 -0
  37. project_ghost/core/feedback/protocols.py +52 -0
  38. project_ghost/core/feedback/reference_policy.py +142 -0
  39. project_ghost/core/feedback/types.py +237 -0
  40. project_ghost/core/fusion/__init__.py +44 -0
  41. project_ghost/core/fusion/orchestration.py +28 -0
  42. project_ghost/core/fusion/protocols.py +46 -0
  43. project_ghost/core/fusion/reference_policy.py +189 -0
  44. project_ghost/core/fusion/sinks.py +39 -0
  45. project_ghost/core/fusion/types.py +191 -0
  46. project_ghost/core/prediction/__init__.py +56 -0
  47. project_ghost/core/prediction/divergence.py +345 -0
  48. project_ghost/core/prediction/orchestration.py +40 -0
  49. project_ghost/core/prediction/protocols.py +74 -0
  50. project_ghost/core/prediction/reference_predictor.py +134 -0
  51. project_ghost/core/prediction/sinks.py +53 -0
  52. project_ghost/core/prediction/types.py +205 -0
  53. project_ghost/core/uncertainty/__init__.py +94 -0
  54. project_ghost/core/uncertainty/composition.py +110 -0
  55. project_ghost/core/uncertainty/estimate.py +227 -0
  56. project_ghost/core/uncertainty/inflation.py +136 -0
  57. project_ghost/core/uncertainty/mode_detector.py +411 -0
  58. project_ghost/core/uncertainty/mode_events.py +123 -0
  59. project_ghost/core/uncertainty/sealing.py +116 -0
  60. project_ghost/core/uncertainty/self_assessment.py +469 -0
  61. project_ghost/core/uncertainty/types.py +182 -0
  62. project_ghost/estimation/__init__.py +30 -0
  63. project_ghost/estimation/config.py +139 -0
  64. project_ghost/estimation/noisy_gt.py +267 -0
  65. project_ghost/events/__init__.py +56 -0
  66. project_ghost/events/adapters.py +100 -0
  67. project_ghost/events/bus.py +269 -0
  68. project_ghost/events/types.py +136 -0
  69. project_ghost/examples/__init__.py +24 -0
  70. project_ghost/examples/closed_loop_smoke.py +397 -0
  71. project_ghost/examples/replay_verification.py +377 -0
  72. project_ghost/hal/__init__.py +39 -0
  73. project_ghost/hal/messages/__init__.py +88 -0
  74. project_ghost/hal/messages/actuators.py +506 -0
  75. project_ghost/hal/messages/runtime.py +274 -0
  76. project_ghost/hal/messages/sensors.py +486 -0
  77. project_ghost/hal/protocols.py +158 -0
  78. project_ghost/properties/__init__.py +79 -0
  79. project_ghost/properties/baud.py +468 -0
  80. project_ghost/properties/erur.py +410 -0
  81. project_ghost/properties/fpb.py +342 -0
  82. project_ghost/properties/md.py +248 -0
  83. project_ghost/properties/rlb.py +285 -0
  84. project_ghost/py.typed +0 -0
  85. project_ghost/sensors/__init__.py +4 -0
  86. project_ghost/simulation/__init__.py +8 -0
  87. project_ghost/state/__init__.py +56 -0
  88. project_ghost/state/aggregator.py +149 -0
  89. project_ghost/state/messages.py +466 -0
  90. project_ghost/state/transforms.py +230 -0
  91. project_ghost/telemetry/__init__.py +110 -0
  92. project_ghost/telemetry/adapters.py +565 -0
  93. project_ghost/telemetry/channels.py +110 -0
  94. project_ghost/telemetry/mcap_sink.py +169 -0
  95. project_ghost/telemetry/replay.py +334 -0
  96. project_ghost/telemetry/serialization.py +273 -0
  97. project_ghost/telemetry/sink.py +96 -0
  98. project_ghost/traceability/__init__.py +50 -0
  99. project_ghost/traceability/analysis.py +279 -0
  100. project_ghost/traceability/models.py +101 -0
  101. project_ghost-0.1.1.dist-info/METADATA +262 -0
  102. project_ghost-0.1.1.dist-info/RECORD +105 -0
  103. project_ghost-0.1.1.dist-info/WHEEL +4 -0
  104. project_ghost-0.1.1.dist-info/entry_points.txt +3 -0
  105. project_ghost-0.1.1.dist-info/licenses/LICENSE +201 -0
@@ -0,0 +1,10 @@
1
+ """Project Ghost — autonomous GPS-denied drone navigation.
2
+
3
+ This package is intentionally empty in Fase 0. Module implementations land
4
+ in Fase 1 according to `docs/roadmaps/phase1.md`.
5
+ """
6
+
7
+ from __future__ import annotations
8
+
9
+ __version__ = "0.0.1"
10
+ __all__ = ["__version__"]
@@ -0,0 +1,4 @@
1
+ """Actuator sinks genericos, safety envelope, mixers.
2
+
3
+ Implementacion en Fase 1+. Ver docs/specs/actuators.md.
4
+ """
@@ -0,0 +1,233 @@
1
+ """`analysis` — derived run analysis artifacts.
2
+
3
+ T5 / ADR-0013 (`RunSummary`), ADR-0016 (`BeliefTraceabilityReport`),
4
+ ADR-0017 (`BeliefConsistencySummary`), ADR-0018
5
+ (`RunManifest` + `ComparativeBeliefReport`), ADR-0019
6
+ (`BeliefCalibrationReport`).
7
+
8
+ Offline only. Deterministic. JSON-only output. No databases, dashboards,
9
+ services, threads, async, ML, or anomaly detection.
10
+
11
+ Public API:
12
+
13
+ - ``RunSummary`` (frozen dataclass): the derived artifact (T5).
14
+ - ``build_run_summary(*, run_id, reader, final_state) -> RunSummary``.
15
+ - ``generate_run_report(summary, output_path)``: writes a JSON sidecar.
16
+ - ``encode_report_to_bytes(summary) -> bytes``: pure encoder.
17
+ - ``SUMMARY_SCHEMA_VERSION`` / ``REPORT_SCHEMA_VERSION``: versioned
18
+ contracts.
19
+ - ``BeliefTraceRecord`` / ``BeliefTraceabilityReport`` (frozen
20
+ dataclasses): per-sample and aggregated artifact for the
21
+ truth/belief comparison (ADR-0016).
22
+ - ``build_traceability_report(*, truth, belief)``: pure aligner.
23
+ - ``compute_position_error`` / ``compute_orientation_error``: pure
24
+ helpers.
25
+ - ``encode_belief_report_to_bytes`` / ``generate_belief_report``:
26
+ canonical JSON encoder + file writer for ADR-0016.
27
+ - ``BELIEF_TRACEABILITY_ANALYSIS_VERSION`` /
28
+ ``BELIEF_TRACEABILITY_REPORT_SCHEMA_VERSION``: versioned contracts.
29
+ - ``BeliefConsistencySummary`` (frozen dataclass): descriptive
30
+ statistics over a ``BeliefTraceabilityReport`` (ADR-0017).
31
+ - ``summarize_belief_consistency(report)``: pure aggregator.
32
+ - ``decode_belief_report_from_json(data)``: companion deserializer for
33
+ the ADR-0016 JSON envelope.
34
+ - ``encode_consistency_summary_to_bytes`` /
35
+ ``generate_consistency_report``: canonical JSON encoder + file writer
36
+ for ADR-0017.
37
+ - ``BELIEF_CONSISTENCY_ANALYSIS_VERSION`` /
38
+ ``BELIEF_CONSISTENCY_REPORT_SCHEMA_VERSION``: versioned contracts.
39
+ - ``ManifestArtifact`` / ``RunManifest`` (frozen dataclasses):
40
+ content-addressed provenance for a run (ADR-0018).
41
+ - ``LabeledSummary`` / ``MetricDelta`` /
42
+ ``ComparativeBeliefReport`` (frozen dataclasses): N-way structured
43
+ deltas between summaries (ADR-0018).
44
+ - ``build_run_manifest`` / ``verify_run_manifest`` /
45
+ ``build_comparative_report``: pure functions for provenance and
46
+ comparison.
47
+ - ``decode_consistency_summary_from_json`` /
48
+ ``decode_run_manifest_from_json`` /
49
+ ``decode_comparative_report_from_json``: companion decoders.
50
+ - ``encode_run_manifest_to_bytes`` /
51
+ ``encode_comparative_report_to_bytes`` /
52
+ ``generate_run_manifest`` / ``generate_comparative_report``:
53
+ canonical JSON encoders + file writers for ADR-0018.
54
+ - ``BELIEF_COMPARISON_ANALYSIS_VERSION`` /
55
+ ``BELIEF_COMPARISON_REPORT_SCHEMA_VERSION`` /
56
+ ``RUN_MANIFEST_SCHEMA_VERSION``: versioned contracts.
57
+ - ``BeliefCalibrationRecord`` / ``BeliefCalibrationReport`` (frozen
58
+ dataclasses): per-record + aggregate audit of declared-vs-empirical
59
+ uncertainty ratios (ADR-0019). Observational, no verdicts.
60
+ - ``analyze_belief_calibration(report, *, source_belief_report_sha256)``:
61
+ pure auditor over the ADR-0016 traceability report.
62
+ - ``decode_calibration_report_from_json`` /
63
+ ``encode_calibration_report_to_bytes`` /
64
+ ``generate_calibration_report``: canonical JSON IO for ADR-0019.
65
+ - ``BELIEF_CALIBRATION_ANALYSIS_VERSION`` /
66
+ ``BELIEF_CALIBRATION_REPORT_SCHEMA_VERSION``: versioned contracts.
67
+
68
+ CLI: six subcommands live in ``project_ghost.cli``:
69
+
70
+ - ``ghost analyze-run --mcap PATH --state PATH --output PATH``
71
+ - ``ghost analyze-belief --truth-mcap PATH --belief-mcap PATH
72
+ [--output PATH]``
73
+ - ``ghost summarize-belief --report PATH [--output PATH]``
74
+ - ``ghost build-manifest --run-id ID [--config-json PATH]
75
+ [--config-kv KEY=VALUE ...] [--input PATH=KIND ...]
76
+ [--output-artifact PATH=KIND ...] [--output PATH]``
77
+ - ``ghost compare-belief --summary LABEL=PATH ...
78
+ [--manifest LABEL=PATH ...] [--output PATH]``
79
+ - ``ghost analyze-calibration --belief-report PATH [--output PATH]``
80
+ """
81
+
82
+ from __future__ import annotations
83
+
84
+ from .belief_consistency import (
85
+ BELIEF_CONSISTENCY_ANALYSIS_VERSION,
86
+ BELIEF_CONSISTENCY_REPORT_SCHEMA_VERSION,
87
+ BeliefConsistencySummary,
88
+ decode_belief_report_from_json,
89
+ encode_consistency_summary_to_bytes,
90
+ generate_consistency_report,
91
+ summarize_belief_consistency,
92
+ )
93
+ from .belief_traceability import (
94
+ BELIEF_TRACEABILITY_ANALYSIS_VERSION,
95
+ BELIEF_TRACEABILITY_REPORT_SCHEMA_VERSION,
96
+ BeliefTraceabilityReport,
97
+ BeliefTraceRecord,
98
+ build_traceability_report,
99
+ compute_orientation_error,
100
+ compute_position_error,
101
+ encode_belief_report_to_bytes,
102
+ generate_belief_report,
103
+ )
104
+ from .calibration import (
105
+ BELIEF_CALIBRATION_ANALYSIS_VERSION,
106
+ BELIEF_CALIBRATION_REPORT_SCHEMA_VERSION,
107
+ BeliefCalibrationRecord,
108
+ BeliefCalibrationReport,
109
+ analyze_belief_calibration,
110
+ decode_calibration_report_from_json,
111
+ encode_calibration_report_to_bytes,
112
+ generate_calibration_report,
113
+ )
114
+ from .comparison import (
115
+ BELIEF_COMPARISON_ANALYSIS_VERSION,
116
+ BELIEF_COMPARISON_REPORT_SCHEMA_VERSION,
117
+ RUN_MANIFEST_SCHEMA_VERSION,
118
+ ComparativeBeliefReport,
119
+ LabeledSummary,
120
+ ManifestArtifact,
121
+ MetricDelta,
122
+ RunManifest,
123
+ build_comparative_report,
124
+ build_run_manifest,
125
+ decode_comparative_report_from_json,
126
+ decode_consistency_summary_from_json,
127
+ decode_run_manifest_from_json,
128
+ encode_comparative_report_to_bytes,
129
+ encode_run_manifest_to_bytes,
130
+ generate_comparative_report,
131
+ generate_run_manifest,
132
+ verify_run_manifest,
133
+ )
134
+ from .decision_trace import (
135
+ DECISION_TRACE_ANALYSIS_VERSION,
136
+ DECISION_TRACE_REPORT_SCHEMA_VERSION,
137
+ ChainStatus,
138
+ DecisionTraceRecord,
139
+ DecisionTraceReport,
140
+ build_decision_trace_report,
141
+ decode_decision_trace_report_from_json,
142
+ encode_decision_trace_report_to_bytes,
143
+ generate_decision_trace_report,
144
+ verify_decision_chain,
145
+ )
146
+ from .models import SUMMARY_SCHEMA_VERSION, RunSummary
147
+ from .report import (
148
+ REPORT_SCHEMA_VERSION,
149
+ encode_report_to_bytes,
150
+ generate_run_report,
151
+ )
152
+ from .self_assessment import (
153
+ SELF_ASSESSMENT_SUMMARY_ANALYSIS_VERSION,
154
+ SELF_ASSESSMENT_SUMMARY_SCHEMA_VERSION,
155
+ LevelCounts,
156
+ SelfAssessmentSummary,
157
+ decode_self_assessment_summary_from_json,
158
+ encode_self_assessment_summary_to_bytes,
159
+ generate_self_assessment_summary,
160
+ read_self_assessments_from_mcap,
161
+ summarize_self_assessments,
162
+ )
163
+ from .summary import build_run_summary
164
+
165
+ __all__ = [
166
+ "BELIEF_CALIBRATION_ANALYSIS_VERSION",
167
+ "BELIEF_CALIBRATION_REPORT_SCHEMA_VERSION",
168
+ "BELIEF_COMPARISON_ANALYSIS_VERSION",
169
+ "BELIEF_COMPARISON_REPORT_SCHEMA_VERSION",
170
+ "BELIEF_CONSISTENCY_ANALYSIS_VERSION",
171
+ "BELIEF_CONSISTENCY_REPORT_SCHEMA_VERSION",
172
+ "BELIEF_TRACEABILITY_ANALYSIS_VERSION",
173
+ "BELIEF_TRACEABILITY_REPORT_SCHEMA_VERSION",
174
+ "DECISION_TRACE_ANALYSIS_VERSION",
175
+ "DECISION_TRACE_REPORT_SCHEMA_VERSION",
176
+ "REPORT_SCHEMA_VERSION",
177
+ "RUN_MANIFEST_SCHEMA_VERSION",
178
+ "SELF_ASSESSMENT_SUMMARY_ANALYSIS_VERSION",
179
+ "SELF_ASSESSMENT_SUMMARY_SCHEMA_VERSION",
180
+ "SUMMARY_SCHEMA_VERSION",
181
+ "BeliefCalibrationRecord",
182
+ "BeliefCalibrationReport",
183
+ "BeliefConsistencySummary",
184
+ "BeliefTraceRecord",
185
+ "BeliefTraceabilityReport",
186
+ "ChainStatus",
187
+ "ComparativeBeliefReport",
188
+ "DecisionTraceRecord",
189
+ "DecisionTraceReport",
190
+ "LabeledSummary",
191
+ "LevelCounts",
192
+ "ManifestArtifact",
193
+ "MetricDelta",
194
+ "RunManifest",
195
+ "RunSummary",
196
+ "SelfAssessmentSummary",
197
+ "analyze_belief_calibration",
198
+ "build_comparative_report",
199
+ "build_decision_trace_report",
200
+ "build_run_manifest",
201
+ "build_run_summary",
202
+ "build_traceability_report",
203
+ "compute_orientation_error",
204
+ "compute_position_error",
205
+ "decode_belief_report_from_json",
206
+ "decode_calibration_report_from_json",
207
+ "decode_comparative_report_from_json",
208
+ "decode_consistency_summary_from_json",
209
+ "decode_decision_trace_report_from_json",
210
+ "decode_run_manifest_from_json",
211
+ "decode_self_assessment_summary_from_json",
212
+ "encode_belief_report_to_bytes",
213
+ "encode_calibration_report_to_bytes",
214
+ "encode_comparative_report_to_bytes",
215
+ "encode_consistency_summary_to_bytes",
216
+ "encode_decision_trace_report_to_bytes",
217
+ "encode_report_to_bytes",
218
+ "encode_run_manifest_to_bytes",
219
+ "encode_self_assessment_summary_to_bytes",
220
+ "generate_belief_report",
221
+ "generate_calibration_report",
222
+ "generate_comparative_report",
223
+ "generate_consistency_report",
224
+ "generate_decision_trace_report",
225
+ "generate_run_manifest",
226
+ "generate_run_report",
227
+ "generate_self_assessment_summary",
228
+ "read_self_assessments_from_mcap",
229
+ "summarize_belief_consistency",
230
+ "summarize_self_assessments",
231
+ "verify_decision_chain",
232
+ "verify_run_manifest",
233
+ ]
@@ -0,0 +1,318 @@
1
+ """Belief consistency analysis (ADR-0017).
2
+
3
+ Pure, deterministic, descriptive. Ingests a
4
+ ``BeliefTraceabilityReport`` (ADR-0016) and emits a frozen
5
+ ``BeliefConsistencySummary`` with min/max/mean over the report's
6
+ records, plus the timestamp range and finite-metric sub-counts.
7
+
8
+ **Honest framing.** This module produces descriptive statistics over
9
+ already-observed paired samples. It does NOT:
10
+
11
+ - evaluate the belief,
12
+ - compute consistency tests (NEES / NIS / Mahalanobis),
13
+ - classify records,
14
+ - detect anomalies,
15
+ - recommend corrective action,
16
+ - cross-correlate fields.
17
+
18
+ Operators read the numbers and form their own hypotheses. The system
19
+ explicitly refuses to do so.
20
+
21
+ Encoding posture: ``sort_keys=True``, ``indent=2``,
22
+ ``ensure_ascii=False``, trailing newline, UTF-8. Byte determinism
23
+ within a fixed ``(CPython, numpy)`` tuple.
24
+ """
25
+
26
+ from __future__ import annotations
27
+
28
+ import dataclasses
29
+ import json
30
+ from collections.abc import Mapping
31
+ from dataclasses import dataclass
32
+ from typing import TYPE_CHECKING, Any
33
+
34
+ from project_ghost.telemetry import from_json_dict
35
+
36
+ from .belief_traceability import (
37
+ BELIEF_TRACEABILITY_REPORT_SCHEMA_VERSION,
38
+ BeliefTraceabilityReport,
39
+ )
40
+
41
+ if TYPE_CHECKING:
42
+ from pathlib import Path
43
+
44
+
45
+ BELIEF_CONSISTENCY_ANALYSIS_VERSION: int = 1
46
+ BELIEF_CONSISTENCY_REPORT_SCHEMA_VERSION: str = "1"
47
+
48
+
49
+ # ---------------------------------------------------------------------------
50
+ # Summary dataclass
51
+ # ---------------------------------------------------------------------------
52
+
53
+
54
+ @dataclass(frozen=True)
55
+ class BeliefConsistencySummary:
56
+ """Descriptive statistics over a `BeliefTraceabilityReport`.
57
+
58
+ Aggregation rules (frozen per ADR-0017):
59
+
60
+ - Counts: ``total_samples`` is ``len(report.records)``;
61
+ ``samples_with_covariance`` / ``samples_without_covariance`` are
62
+ pass-through from the input report.
63
+ - Timestamps: ``first = min(record.timestamp_ns)``,
64
+ ``last = max(...)``, ``span = last - first``. All three are
65
+ ``None`` iff ``total_samples == 0``.
66
+ - Position / orientation error: ``min``, ``max``, ``mean`` over
67
+ **all** records. Empty input collapses to ``0.0`` (consistent
68
+ with ADR-0016 convention).
69
+ - Covariance trace / condition number: ``min``, ``max``, ``mean``
70
+ over records whose corresponding field is **not** ``None``. If
71
+ no such record exists, all three are ``None``.
72
+ - ``samples_with_finite_trace`` /
73
+ ``samples_with_finite_condition_number`` count only records
74
+ whose computed metric was finite (i.e., not collapsed to
75
+ ``None`` by ADR-0016).
76
+
77
+ The summary intentionally does **not** carry the records
78
+ themselves; the source ``BeliefTraceabilityReport`` is the
79
+ authoritative store for per-sample data.
80
+ """
81
+
82
+ total_samples: int
83
+ samples_with_covariance: int
84
+ samples_without_covariance: int
85
+
86
+ timestamp_first_ns: int | None
87
+ timestamp_last_ns: int | None
88
+ timestamp_span_ns: int | None
89
+
90
+ position_error_min_m: float
91
+ position_error_max_m: float
92
+ position_error_mean_m: float
93
+
94
+ orientation_error_min_rad: float
95
+ orientation_error_max_rad: float
96
+ orientation_error_mean_rad: float
97
+
98
+ covariance_trace_min: float | None
99
+ covariance_trace_max: float | None
100
+ covariance_trace_mean: float | None
101
+
102
+ covariance_condition_number_min: float | None
103
+ covariance_condition_number_max: float | None
104
+ covariance_condition_number_mean: float | None
105
+
106
+ samples_with_finite_trace: int
107
+ samples_with_finite_condition_number: int
108
+
109
+ analysis_version: int = BELIEF_CONSISTENCY_ANALYSIS_VERSION
110
+
111
+
112
+ # ---------------------------------------------------------------------------
113
+ # Summarizer
114
+ # ---------------------------------------------------------------------------
115
+
116
+
117
+ def summarize_belief_consistency(
118
+ report: BeliefTraceabilityReport,
119
+ ) -> BeliefConsistencySummary:
120
+ """Aggregate descriptive statistics over a traceability report.
121
+
122
+ Pure function: reads no clock, performs no I/O, holds no
123
+ thread-local state, uses no random. Single forward pass over
124
+ ``report.records``.
125
+ """
126
+ records = report.records
127
+ n = len(records)
128
+
129
+ if n == 0:
130
+ timestamp_first: int | None = None
131
+ timestamp_last: int | None = None
132
+ timestamp_span: int | None = None
133
+ pos_min = pos_max = pos_mean = 0.0
134
+ ori_min = ori_max = ori_mean = 0.0
135
+ else:
136
+ timestamps = [r.timestamp_ns for r in records]
137
+ timestamp_first = min(timestamps)
138
+ timestamp_last = max(timestamps)
139
+ timestamp_span = timestamp_last - timestamp_first
140
+
141
+ pos_errors = [r.position_error_norm_m for r in records]
142
+ ori_errors = [r.orientation_error_rad for r in records]
143
+ pos_min = min(pos_errors)
144
+ pos_max = max(pos_errors)
145
+ pos_mean = sum(pos_errors) / n
146
+ ori_min = min(ori_errors)
147
+ ori_max = max(ori_errors)
148
+ ori_mean = sum(ori_errors) / n
149
+
150
+ # Covariance trace: include only records whose computed trace was
151
+ # finite (ADR-0016 collapses non-finite metrics to None).
152
+ traces: list[float] = [
153
+ r.covariance_trace
154
+ for r in records
155
+ if r.covariance_trace is not None
156
+ ]
157
+ if traces:
158
+ cov_trace_min: float | None = min(traces)
159
+ cov_trace_max: float | None = max(traces)
160
+ cov_trace_mean: float | None = sum(traces) / len(traces)
161
+ else:
162
+ cov_trace_min = None
163
+ cov_trace_max = None
164
+ cov_trace_mean = None
165
+
166
+ conds: list[float] = [
167
+ r.covariance_condition_number
168
+ for r in records
169
+ if r.covariance_condition_number is not None
170
+ ]
171
+ if conds:
172
+ cov_cond_min: float | None = min(conds)
173
+ cov_cond_max: float | None = max(conds)
174
+ cov_cond_mean: float | None = sum(conds) / len(conds)
175
+ else:
176
+ cov_cond_min = None
177
+ cov_cond_max = None
178
+ cov_cond_mean = None
179
+
180
+ return BeliefConsistencySummary(
181
+ total_samples=n,
182
+ samples_with_covariance=report.samples_with_covariance,
183
+ samples_without_covariance=report.samples_without_covariance,
184
+ timestamp_first_ns=timestamp_first,
185
+ timestamp_last_ns=timestamp_last,
186
+ timestamp_span_ns=timestamp_span,
187
+ position_error_min_m=pos_min,
188
+ position_error_max_m=pos_max,
189
+ position_error_mean_m=pos_mean,
190
+ orientation_error_min_rad=ori_min,
191
+ orientation_error_max_rad=ori_max,
192
+ orientation_error_mean_rad=ori_mean,
193
+ covariance_trace_min=cov_trace_min,
194
+ covariance_trace_max=cov_trace_max,
195
+ covariance_trace_mean=cov_trace_mean,
196
+ covariance_condition_number_min=cov_cond_min,
197
+ covariance_condition_number_max=cov_cond_max,
198
+ covariance_condition_number_mean=cov_cond_mean,
199
+ samples_with_finite_trace=len(traces),
200
+ samples_with_finite_condition_number=len(conds),
201
+ )
202
+
203
+
204
+ # ---------------------------------------------------------------------------
205
+ # Decoder for the ADR-0016 JSON envelope
206
+ # ---------------------------------------------------------------------------
207
+
208
+
209
+ def decode_belief_report_from_json(
210
+ data: Mapping[str, Any],
211
+ ) -> BeliefTraceabilityReport:
212
+ """Reconstruct a ``BeliefTraceabilityReport`` from canonical JSON.
213
+
214
+ Expects the structure produced by
215
+ ``encode_belief_report_to_bytes`` (ADR-0016)::
216
+
217
+ {
218
+ "schema_version": "1",
219
+ "report": { ...fields... }
220
+ }
221
+
222
+ Validates ``schema_version`` against the literal
223
+ ``BELIEF_TRACEABILITY_REPORT_SCHEMA_VERSION``. Reconstruction goes
224
+ through ``telemetry.from_json_dict``, so each nested dataclass's
225
+ ``__post_init__`` re-runs — bad data fails loudly.
226
+ """
227
+ if not isinstance(data, Mapping):
228
+ raise TypeError(
229
+ f"decode_belief_report_from_json: expected mapping; got "
230
+ f"{type(data).__name__}"
231
+ )
232
+ if "schema_version" not in data:
233
+ raise ValueError(
234
+ "decode_belief_report_from_json: missing 'schema_version'"
235
+ )
236
+ schema_version = data["schema_version"]
237
+ if schema_version != BELIEF_TRACEABILITY_REPORT_SCHEMA_VERSION:
238
+ raise ValueError(
239
+ f"decode_belief_report_from_json: incompatible "
240
+ f"schema_version {schema_version!r}; expected "
241
+ f"{BELIEF_TRACEABILITY_REPORT_SCHEMA_VERSION!r}"
242
+ )
243
+ if "report" not in data:
244
+ raise ValueError(
245
+ "decode_belief_report_from_json: missing 'report' field"
246
+ )
247
+ report_dict = data["report"]
248
+ if not isinstance(report_dict, Mapping):
249
+ raise TypeError(
250
+ f"decode_belief_report_from_json: 'report' must be a mapping; "
251
+ f"got {type(report_dict).__name__}"
252
+ )
253
+ decoded = from_json_dict(BeliefTraceabilityReport, report_dict)
254
+ if not isinstance(decoded, BeliefTraceabilityReport): # pragma: no cover
255
+ raise TypeError(
256
+ "decode_belief_report_from_json: decoded object is not a "
257
+ "BeliefTraceabilityReport"
258
+ )
259
+ return decoded
260
+
261
+
262
+ # ---------------------------------------------------------------------------
263
+ # JSON encoder + file writer
264
+ # ---------------------------------------------------------------------------
265
+
266
+
267
+ def encode_consistency_summary_to_bytes(
268
+ summary: BeliefConsistencySummary,
269
+ ) -> bytes:
270
+ """Encode ``summary`` to deterministic UTF-8 JSON bytes.
271
+
272
+ Output structure::
273
+
274
+ {
275
+ "schema_version": "1",
276
+ "summary": { ...alphabetically sorted fields... }
277
+ }
278
+
279
+ Encoding rules (frozen):
280
+
281
+ - ``sort_keys=True``
282
+ - ``indent=2``
283
+ - ``ensure_ascii=False``
284
+ - trailing newline
285
+ - UTF-8
286
+ """
287
+ document = {
288
+ "schema_version": BELIEF_CONSISTENCY_REPORT_SCHEMA_VERSION,
289
+ "summary": dataclasses.asdict(summary),
290
+ }
291
+ serialized = json.dumps(
292
+ document,
293
+ sort_keys=True,
294
+ indent=2,
295
+ ensure_ascii=False,
296
+ )
297
+ return (serialized + "\n").encode("utf-8")
298
+
299
+
300
+ def generate_consistency_report(
301
+ summary: BeliefConsistencySummary, output_path: Path
302
+ ) -> None:
303
+ """Write ``summary`` as canonical JSON to ``output_path``.
304
+
305
+ Overwrites if the file exists. Does not invent parent directories.
306
+ """
307
+ output_path.write_bytes(encode_consistency_summary_to_bytes(summary))
308
+
309
+
310
+ __all__ = [
311
+ "BELIEF_CONSISTENCY_ANALYSIS_VERSION",
312
+ "BELIEF_CONSISTENCY_REPORT_SCHEMA_VERSION",
313
+ "BeliefConsistencySummary",
314
+ "decode_belief_report_from_json",
315
+ "encode_consistency_summary_to_bytes",
316
+ "generate_consistency_report",
317
+ "summarize_belief_consistency",
318
+ ]