@bradtech/sensor 1.1.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/HOWTO.md ADDED
@@ -0,0 +1,121 @@
1
+ # HOWTO: Using `@bradtech/sensor`
2
+
3
+ This guide presents common usage scenarios, custom converter implementations, and historical data replay with `@bradtech/sensor`.
4
+
5
+ ---
6
+
7
+ ## 1. Implementing a Custom Sensor Converter
8
+
9
+ To implement a new sensor conversion algorithm, extend `BaseSensorConverter`:
10
+
11
+ ```typescript
12
+ import { BaseSensorConverter, type ConversionOutput } from '@bradtech/sensor'
13
+
14
+ interface CropContext {
15
+ canopyHeightMeters?: number
16
+ leafAreaIndex?: number
17
+ }
18
+
19
+ export class CanopyVpdConverter extends BaseSensorConverter<{ airTemp: number; rh: number; leafTemp: number }, CropContext> {
20
+ readonly sensorFamily = 'microclimate'
21
+ readonly modelCode = 'canopy-vpd-calculator'
22
+ readonly modelVersion = '1.0.0'
23
+ readonly description = 'Calculates foliar vapor pressure deficit (VPD) in kPa'
24
+
25
+ convert(
26
+ raw: { airTemp: number; rh: number; leafTemp: number },
27
+ context?: CropContext
28
+ ): ConversionOutput[] {
29
+ // 1. Validate / clamp input signals
30
+ const clampedAir = this.clampWithConfidence(raw.airTemp, -30, 60)
31
+ const clampedRh = this.clampWithConfidence(raw.rh, 0, 100)
32
+ const clampedLeaf = this.clampWithConfidence(raw.leafTemp, -30, 60)
33
+
34
+ // 2. Compute saturation and actual vapor pressures
35
+ const vpsLeaf = 0.61078 * Math.exp((17.27 * clampedLeaf.value) / (clampedLeaf.value + 237.3))
36
+ const vpsAir = 0.61078 * Math.exp((17.27 * clampedAir.value) / (clampedAir.value + 237.3))
37
+ const vpaAir = vpsAir * (clampedRh.value / 100.0)
38
+
39
+ const vpd = Math.max(0, vpsLeaf - vpaAir)
40
+ const confidence = Math.min(clampedAir.confidence, clampedRh.confidence, clampedLeaf.confidence)
41
+
42
+ return [
43
+ {
44
+ metric: 'okf:agronomy/plant/foliar_vpd',
45
+ value: Number(vpd.toFixed(3)),
46
+ unit: 'kPa',
47
+ qudtUri: 'qudt:unit/KiloPA',
48
+ confidence,
49
+ metadata: {
50
+ algorithm: 'Tetens-Monteith',
51
+ canopyHeight: context?.canopyHeightMeters ?? 1.2,
52
+ },
53
+ },
54
+ ]
55
+ }
56
+ }
57
+ ```
58
+
59
+ ---
60
+
61
+ ## 2. Registering and Accessing Adapters via the `Sensor` Singleton Facade
62
+
63
+ ```typescript
64
+ import { Sensor } from '@bradtech/sensor'
65
+
66
+ // 1. Register adapter
67
+ Sensor.addAdapter('canopyVpd', new CanopyVpdConverter())
68
+
69
+ // 2. Check and retrieve adapter
70
+ if (Sensor.hasAdapter('canopyVpd')) {
71
+ const adapter = Sensor.getAdapter<CanopyVpdConverter>('canopyVpd')
72
+ const outputs = adapter.convert({ airTemp: 24.5, rh: 65, leafTemp: 23.8 })
73
+ console.log('Computed VPD:', outputs[0].value, outputs[0].unit)
74
+ }
75
+
76
+ // 3. Or invoke directly via default adapter
77
+ Sensor.defaultAdapter = Sensor.getAdapter('canopyVpd')
78
+ const results = Sensor.convert({ airTemp: 28.0, rh: 45, leafTemp: 26.5 })
79
+ ```
80
+
81
+ ---
82
+
83
+ ## 3. Replaying Historical Data with `ReplayEngine`
84
+
85
+ The `ReplayEngine` allows backfilling new agronomic algorithms over years of historical raw sensor readings:
86
+
87
+ ```typescript
88
+ import { ConverterRegistry, ReplayEngine, type RawDataPointInput } from '@bradtech/sensor'
89
+
90
+ // Register target converter
91
+ ConverterRegistry.register(new CanopyVpdConverter())
92
+
93
+ // Historical raw records loaded from database
94
+ const rawPoints: RawDataPointInput[] = [
95
+ {
96
+ id: '8f0a20a6-1234-4567-89ab-cdef01234567',
97
+ device: 'probes/b25s004',
98
+ plot: 'plots/parcelle-nord',
99
+ company: 'companies/domaine-dupont',
100
+ metric: 'okf:agronomy/microclimate/canopy_temperature',
101
+ value: 22.4,
102
+ timestamp: '2026-06-15T14:00:00Z',
103
+ },
104
+ ]
105
+
106
+ // Execute batch deterministic replay
107
+ const computedPoints = ReplayEngine.replay(
108
+ rawPoints,
109
+ 'microclimate',
110
+ 'canopy-vpd-calculator',
111
+ { canopyHeightMeters: 1.5 }
112
+ )
113
+
114
+ console.log(`Generated ${computedPoints.length} computed points with full audit lineage!`)
115
+ ```
116
+
117
+ ---
118
+
119
+ ## 📄 License & Copyright
120
+
121
+ GNU AGPL-v3 — Copyright (C) 2026 Olivier Lépine <olivier@lepine.fr>