@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 +121 -0
- package/LICENSE +620 -0
- package/README.md +29 -0
- package/dist/BaseSensorConverter.d.ts +42 -0
- package/dist/BaseSensorConverter.d.ts.map +1 -0
- package/dist/BaseSensorConverter.js +47 -0
- package/dist/BaseSensorConverter.js.map +1 -0
- package/dist/ConverterRegistry.d.ts +39 -0
- package/dist/ConverterRegistry.d.ts.map +1 -0
- package/dist/ConverterRegistry.js +53 -0
- package/dist/ConverterRegistry.js.map +1 -0
- package/dist/ReplayEngine.d.ts +78 -0
- package/dist/ReplayEngine.d.ts.map +1 -0
- package/dist/ReplayEngine.js +47 -0
- package/dist/ReplayEngine.js.map +1 -0
- package/dist/Sensor.d.ts +50 -0
- package/dist/Sensor.d.ts.map +1 -0
- package/dist/Sensor.js +75 -0
- package/dist/Sensor.js.map +1 -0
- package/dist/index.d.ts +6 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +22 -0
- package/dist/index.js.map +1 -0
- package/dist/types.d.ts +86 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +3 -0
- package/dist/types.js.map +1 -0
- package/package.json +63 -0
- package/src/BaseSensorConverter.ts +76 -0
- package/src/ConverterRegistry.ts +55 -0
- package/src/ReplayEngine.ts +133 -0
- package/src/Sensor.ts +90 -0
- package/src/index.ts +5 -0
- package/src/types.ts +97 -0
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>
|