@robotical/webapp-types 3.25.9 → 3.25.10

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.
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "marty-web-app",
3
- "version": "3.25.9",
3
+ "version": "3.25.10",
4
4
  "private": true,
5
5
  "engines": {
6
6
  "node": ">=20"
@@ -6,6 +6,8 @@ import type { RICStateInfo } from "@robotical/roboticaljs";
6
6
  import { type ColourSensorValues } from "./published-data-helpers";
7
7
  interface MartyState {
8
8
  colourSensed: "blue" | "green" | "red" | "yellow" | "purple" | "unclear" | "air";
9
+ /** Rule strength (0–100), not probability; null when no scored reading is available. */
10
+ colourConfidence: number | null;
9
11
  lightSense: "none" | "high" | "mid" | "low";
10
12
  objectSense: "none" | "near";
11
13
  noiseSense: "none" | "detected";
@@ -34,7 +36,7 @@ declare class PublishedDataAnalyser extends EventEmitter {
34
36
  subscribeToPublishedData(): void;
35
37
  unsubscribeFromPublishedData(): void;
36
38
  analyse(data: RICStateInfo): void;
37
- setColourSensed(colour: MartyState["colourSensed"]): void;
39
+ setColourSensed(colour: MartyState["colourSensed"], confidence?: number | null): void;
38
40
  setLightSense(lightSense: MartyState["lightSense"]): void;
39
41
  setObjectSense(objectSense: MartyState["objectSense"]): void;
40
42
  setNoiseSense(noiseSense: MartyState["noiseSense"]): void;
@@ -61,6 +61,7 @@ var PublishedDataAnalyser = /** @class */ (function (_super) {
61
61
  _this.marty = marty;
62
62
  _this.martyState = {
63
63
  colourSensed: "unclear",
64
+ colourConfidence: null,
64
65
  lightSense: "none",
65
66
  objectSense: "none",
66
67
  noiseSense: "none",
@@ -101,8 +102,10 @@ var PublishedDataAnalyser = /** @class */ (function (_super) {
101
102
  this.connectedSensors.objectSensor = !!objectSensor || !!colourSensor;
102
103
  this.connectedSensors.noiseSensor = !!noiseSensor;
103
104
  };
104
- PublishedDataAnalyser.prototype.setColourSensed = function (colour) {
105
+ PublishedDataAnalyser.prototype.setColourSensed = function (colour, confidence) {
106
+ if (confidence === void 0) { confidence = null; }
105
107
  this.martyState.colourSensed = colour;
108
+ this.martyState.colourConfidence = confidence;
106
109
  this.emit(this.eventsMap.colourSensed[colour]);
107
110
  };
108
111
  PublishedDataAnalyser.prototype.setLightSense = function (lightSense) {
@@ -124,7 +127,8 @@ var ColourSensedDetection = /** @class */ (function () {
124
127
  }
125
128
  ColourSensedDetection.prototype.detectColourSensed = function (_a, analyser, profile) {
126
129
  var r = _a.r, g = _a.g, b = _a.b, clear = _a.clear, air = _a.air;
127
- return analyser.setColourSensed(classifyColour({ red: r, green: g, blue: b, clear: clear, air: air }, profile).classifiedColour);
130
+ var reading = classifyColour({ red: r, green: g, blue: b, clear: clear, air: air }, profile);
131
+ return analyser.setColourSensed(reading.classifiedColour, reading.confidence);
128
132
  };
129
133
  return ColourSensedDetection;
130
134
  }());
@@ -16,6 +16,10 @@ declare const colourDiagnosticsEnglish: {
16
16
  prompt: string;
17
17
  airPrompt: string;
18
18
  result: string;
19
+ confidence: string;
20
+ confidenceUnavailable: string;
21
+ confidenceAbout: string;
22
+ confidenceHelp: string;
19
23
  withoutAir: string;
20
24
  firmwareAir: string;
21
25
  waiting: string;
@@ -11,6 +11,10 @@ var colourDiagnosticsEnglish = {
11
11
  prompt: "Place the selected sensor on the {{colour}} card.",
12
12
  airPrompt: "Lift Marty so both feet are at least 10 cm above any card or surface, with the sensors facing down and uncovered. Keep the lighting the same and hold still.",
13
13
  result: "Blocks interpretation: {{result}}",
14
+ confidence: "Confidence {{score}}/100",
15
+ confidenceUnavailable: "Confidence unavailable",
16
+ confidenceAbout: "About confidence",
17
+ confidenceHelp: "Rule strength, not probability. Higher scores mean more room before a colour or air cutoff. Air needs a saved calibration to score.",
14
18
  withoutAir: "Firmware reports air; the RGB signal matches {{result}}.",
15
19
  firmwareAir: "Firmware Air",
16
20
  waiting: "Select a sensor and wait for fresh readings.",
@@ -9,6 +9,13 @@ export type ColourSignalProfile = {
9
9
  margin: RGB;
10
10
  };
11
11
  export type ColourName = "red" | "yellow" | "green" | "blue" | "purple" | "air" | "unclear";
12
+ export type ColourConfidenceFactors = {
13
+ hue?: number;
14
+ chroma?: number;
15
+ saturation?: number;
16
+ air?: number;
17
+ };
18
+ export declare const COLOUR_CONFIDENCE_METHOD: string;
12
19
  /** Published channels are white-normalised by the SDK; do not clamp them for classification. */
13
20
  export declare const classifyColour: (values: RGB & {
14
21
  clear: number;
@@ -18,6 +25,9 @@ export declare const classifyColour: (values: RGB & {
18
25
  colourWithoutAirOverride: ColourName;
19
26
  hue: number | null;
20
27
  chroma: number;
28
+ confidence: number | null;
29
+ confidenceMethod: string;
30
+ confidenceFactors: ColourConfidenceFactors;
21
31
  saturation: number;
22
32
  signalAboveAir: boolean;
23
33
  signalSource: string;
@@ -1,4 +1,40 @@
1
+ var __assign = (this && this.__assign) || function () {
2
+ __assign = Object.assign || function(t) {
3
+ for (var s, i = 1, n = arguments.length; i < n; i++) {
4
+ s = arguments[i];
5
+ for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p))
6
+ t[p] = s[p];
7
+ }
8
+ return t;
9
+ };
10
+ return __assign.apply(this, arguments);
11
+ };
1
12
  import policy from "./marty-colour-policy.json";
13
+ export var COLOUR_CONFIDENCE_METHOD = policy.confidence.method;
14
+ var channels = ["red", "green", "blue"];
15
+ var marginScore = function (fraction) { return Number.isFinite(fraction) ? Math.round(100 * Math.max(0, Math.min(1, fraction))) : 0; };
16
+ var colourConfidenceFactors = function (values, hue, chroma, saturation, match, profile) {
17
+ var _a = match.hue, lowerHue = _a[0], upperHue = _a[1];
18
+ // Red's two table entries are one continuous interval across 360°/0°.
19
+ // That seam is not a decision boundary, so pure red must not get a zero score.
20
+ var sameBand = policy.boundaries.filter(function (band) { return band.name === match.name &&
21
+ band.chroma[0] === match.chroma[0] && band.chroma[1] === match.chroma[1]; });
22
+ var start = sameBand.find(function (band) { return band.hue[0] === 0; });
23
+ var end = sameBand.find(function (band) { return band.hue[1] >= 360; });
24
+ if (start && end) {
25
+ lowerHue = end.hue[0] - 360;
26
+ upperHue = start.hue[1];
27
+ if (hue > 180)
28
+ hue -= 360;
29
+ }
30
+ // Two channels determine chroma. Use the two largest channel margins as a
31
+ // conservative noise scale, in the same white-normalised units as the RGB.
32
+ var noise = channels.map(function (channel) { var _a; return (_a = profile === null || profile === void 0 ? void 0 : profile.margin[channel]) !== null && _a !== void 0 ? _a : policy.confidence.defaultChannelMargin; })
33
+ .sort(function (a, b) { return b - a; });
34
+ return __assign({ hue: marginScore(Math.min(hue - lowerHue, upperHue - hue) / policy.confidence.fullHueMarginDegrees), chroma: marginScore(Math.min(chroma - Math.max(match.chroma[0], policy.signal.minimumChroma), match.chroma[1] - chroma) / (noise[0] + noise[1])), saturation: marginScore((saturation - policy.signal.minimumSaturation) / policy.confidence.fullSaturationMargin) }, (profile && { air: marginScore(Math.max.apply(Math, channels.map(function (channel) {
35
+ return (values[channel] - profile.airMax[channel] - profile.margin[channel]) / profile.margin[channel];
36
+ }))) }));
37
+ };
2
38
  /** Published channels are white-normalised by the SDK; do not clamp them for classification. */
3
39
  export var classifyColour = function (values, profile) {
4
40
  var r = values.red, g = values.green, b = values.blue, clear = values.clear, air = values.air;
@@ -20,7 +56,24 @@ export var classifyColour = function (values, profile) {
20
56
  var colourWithoutAirOverride = confident && match ? match.name : "unclear";
21
57
  var classifiedColour = colourWithoutAirOverride !== "unclear" ? colourWithoutAirOverride :
22
58
  valid && air ? "air" : "unclear";
23
- return { classifiedColour: classifiedColour, colourWithoutAirOverride: colourWithoutAirOverride, hue: Number.isFinite(hue) ? hue : null, chroma: chroma, saturation: saturation, signalAboveAir: exceedsAir, signalSource: profile ? "calibratedAir" : "defaultRules", classificationReason: !valid ? "invalid" :
59
+ // A rule-strength score, not a statistical probability or a new acceptance
60
+ // gate. The weakest decision margin wins; existing colour decisions stay intact.
61
+ var confidence = 0;
62
+ var confidenceFactors = {};
63
+ if (colourWithoutAirOverride !== "unclear" && match) {
64
+ confidenceFactors = colourConfidenceFactors(values, hue, chroma, saturation, match, profile);
65
+ confidence = Math.min.apply(Math, Object.values(confidenceFactors));
66
+ }
67
+ else if (classifiedColour === "air") {
68
+ // Firmware Air alone has no measurable confidence. With calibration, all
69
+ // channels must agree with the air ceiling; contrary RGB evidence scores 0.
70
+ confidence = profile ? marginScore(Math.min.apply(Math, channels.map(function (channel) {
71
+ return (profile.airMax[channel] + profile.margin[channel] - values[channel]) / profile.margin[channel];
72
+ }))) : null;
73
+ if (confidence !== null)
74
+ confidenceFactors = { air: confidence };
75
+ }
76
+ return { classifiedColour: classifiedColour, colourWithoutAirOverride: colourWithoutAirOverride, hue: Number.isFinite(hue) ? hue : null, chroma: chroma, confidence: confidence, confidenceMethod: COLOUR_CONFIDENCE_METHOD, confidenceFactors: confidenceFactors, saturation: saturation, signalAboveAir: exceedsAir, signalSource: profile ? "calibratedAir" : "defaultRules", classificationReason: !valid ? "invalid" :
24
77
  !exceedsAir ? "withinCalibratedAir" : confident && match ? air ? "rgbOverridesFirmwareAir" : "rgbColour" :
25
78
  air ? "firmwareAir" : "weakOrUnknownColour" };
26
79
  };
@@ -1,5 +1,5 @@
1
1
  import colourPolicy from "./marty-colour-policy.json";
2
- import { ColourSignalProfile } from "./colour-classifier";
2
+ import { ColourConfidenceFactors, ColourSignalProfile } from "./colour-classifier";
3
3
  import { ColourSensorProfile } from "./colour-sensor-profile";
4
4
  import type { RaftObserver } from "../application/RAFTs/RaftObserver";
5
5
  import type { RestMessageOptions } from "../types/communication-between-apps/wrapper-communication";
@@ -68,6 +68,9 @@ export type ColourReading = {
68
68
  hue: number | null;
69
69
  chroma: number;
70
70
  classifiedColour: string;
71
+ confidence?: number | null;
72
+ confidenceMethod?: string;
73
+ confidenceFactors?: ColourConfidenceFactors;
71
74
  colourWithoutAirOverride?: string;
72
75
  saturation?: number;
73
76
  signalAboveAir?: boolean;
@@ -93,6 +96,7 @@ export type ColourSnapshot = {
93
96
  signal?: typeof colourPolicy.signal;
94
97
  profile?: ColourSensorProfile;
95
98
  clearBoundsUsedForClassification?: boolean;
99
+ confidence?: typeof colourPolicy.confidence;
96
100
  };
97
101
  device: {
98
102
  id: string;
@@ -148,6 +152,9 @@ export declare const interpretColour: (vals: Record<string, number | boolean>, p
148
152
  colourWithoutAirOverride: import("./colour-classifier").ColourName;
149
153
  hue: number | null;
150
154
  chroma: number;
155
+ confidence: number | null;
156
+ confidenceMethod: string;
157
+ confidenceFactors: ColourConfidenceFactors;
151
158
  saturation: number;
152
159
  signalAboveAir: boolean;
153
160
  signalSource: string;
@@ -263,7 +263,8 @@ var captureUnlocked = function (sensor, expectedColour, session, signal) { retur
263
263
  if (samples.length < 2)
264
264
  throw new ColourDiagnosticError("staleReading");
265
265
  return [2 /*return*/, __assign(__assign({ schemaVersion: 2, sessionId: session.sessionId, sessionStartedAtMs: session.startedAtMs, captureId: createId(), expectedColour: expectedColour, startedAtMs: startedAtMs, finishedAtMs: Date.now(), appVersion: packageInfo.version, classifier: { source: colourPolicy.source, airFlagTakesPrecedence: false, boundaries: BLOCKS_COLOUR_BOUNDARIES,
266
- signal: colourPolicy.signal, profile: getActiveColourSensorProfile(sensor), clearBoundsUsedForClassification: false }, device: {
266
+ signal: colourPolicy.signal, profile: getActiveColourSensorProfile(sensor), clearBoundsUsedForClassification: false,
267
+ confidence: colourPolicy.confidence }, device: {
267
268
  id: sensor.raftId, type: sensor.raft.type, name: ((_c = (_b = sensor.raft).getFriendlyName) === null || _c === void 0 ? void 0 : _c.call(_b)) || sensor.raftId,
268
269
  firmwareVersion: ((_d = sensor.raft.systemInfo) === null || _d === void 0 ? void 0 : _d.SystemVersion) || null, connectionMethod: sensor.raft.connectionMethod || null,
269
270
  }, sensor: { id: sensor.addon.id, name: sensor.addon.name, whoAmI: sensor.addon.whoAmI } }, (((_f = (_e = sensor.raft.raftStateInfo) === null || _e === void 0 ? void 0 : _e.power) === null || _f === void 0 ? void 0 : _f.powerStatus) && { powerStatus: {
@@ -2,6 +2,7 @@
2
2
  "source": "Marty web app RGB signal policy v4",
3
3
  "clearBoundsUsedForClassification": false,
4
4
  "signal": { "minimumChroma": 8, "minimumSaturation": 0.15, "airNoiseMarginRaw": 3 },
5
+ "confidence": { "method": "rule-margin-v1", "fullHueMarginDegrees": 15, "fullSaturationMargin": 0.15, "defaultChannelMargin": 3 },
5
6
  "boundaries": [
6
7
  { "hue": [0, 15], "chroma": [50, 200], "clear": [40, 150], "name": "red" },
7
8
  { "hue": [20, 65], "chroma": [40, 300], "clear": [100, 255], "name": "yellow" },
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@robotical/webapp-types",
3
- "version": "3.25.9",
3
+ "version": "3.25.10",
4
4
  "description": "Type definitions for the Application Manager",
5
5
  "main": "dist/application-manager.d.ts",
6
6
  "types": "dist/application-manager.d.ts",