新增统一力读数会话 ForceSession
把同帧测量、空载归零、模型推断与共用后处理收进一个 SDK 会话,Studio 显示与记录使用同一样本;旧 Sensor 读数接口保持兼容。以 force session 与受影响 SDK 套件、lint 与文档门禁验证,硬件与真实载荷精度未验证。 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01CpCfJF6SvonMKmPZhzKZHb
This commit is contained in:
@@ -628,3 +628,85 @@ def test_every_preset_reports_what_it_does(
|
||||
assert fp["measurement"]["flow_ema_alpha"] == pytest.approx(alpha)
|
||||
assert fp["readout"]["force_median_window"] == median
|
||||
assert fp["readout"]["force_lowpass_cutoff_hz"] == pytest.approx(cutoff)
|
||||
|
||||
|
||||
# ------------------------------------------------- session reference adapter
|
||||
def _nonlinear_scalar_calibration(schema_version: int = 2):
|
||||
return parse(
|
||||
{
|
||||
"schema_version": schema_version,
|
||||
"unit": "N",
|
||||
"channels": {
|
||||
"normal": {
|
||||
"method": "piecewise_linear",
|
||||
"points": [[0.0, 0.0], [100.0, 1.0], [200.0, 3.0]],
|
||||
"extrapolate": True,
|
||||
"clamp_output_min": None,
|
||||
},
|
||||
"shear_x": {
|
||||
"method": "piecewise_linear",
|
||||
"signed": True,
|
||||
"input": "components",
|
||||
"points": [[0.0, 0.0], [100.0, 2.0]],
|
||||
"extrapolate": True,
|
||||
"clamp_output_min": None,
|
||||
},
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.parametrize("schema_version", [1, 2])
|
||||
def test_convert_referenced_subtracts_the_session_baseline_once(schema_version: int):
|
||||
calibration = _nonlinear_scalar_calibration(schema_version)
|
||||
curve = calibration.get("fn")
|
||||
assert curve is not None
|
||||
reference = {"fn": 150.0}
|
||||
converted = calibration.convert_referenced((250.0, 0.0, 0.0), reference)
|
||||
# curve(250) - curve(150) = 4.0 - 2.0: one subtraction, and the curve's own
|
||||
# offset/zero point is not removed a second time.
|
||||
assert converted[0] == pytest.approx(4.0 - 2.0)
|
||||
assert converted[0] != pytest.approx(4.0 - 2.0 * 2.0)
|
||||
# A raw-domain delta would convert 250-150 = 100 (1.0) instead of 4.0-2.0.
|
||||
assert converted[0] != pytest.approx(1.0)
|
||||
# The historical entry point is unchanged.
|
||||
assert calibration.convert(250.0, 0.0, 0.0)[0] == pytest.approx(4.0)
|
||||
|
||||
|
||||
def test_convert_referenced_keeps_signed_shear_and_missing_axes_unavailable():
|
||||
calibration = _nonlinear_scalar_calibration()
|
||||
converted = calibration.convert_referenced(
|
||||
(250.0, -50.0, 12.0), {"fn": 150.0, "fx": 10.0}
|
||||
)
|
||||
assert converted[0] == pytest.approx(2.0)
|
||||
# signed curve: convert_component(-50) - convert_component(10) = -1.0 - 0.2.
|
||||
assert converted[1] == pytest.approx(-1.2)
|
||||
# FY has no curve, no reference: unavailable, never a zero.
|
||||
assert converted[2] is None
|
||||
missing_reference = calibration.convert_referenced((250.0, 0.0, 0.0), None)
|
||||
assert missing_reference == (None, None, None)
|
||||
invalid_raw = calibration.convert_referenced(
|
||||
(float("nan"), 0.0, 0.0), {"fn": 150.0}
|
||||
)
|
||||
assert invalid_raw[0] is None
|
||||
|
||||
|
||||
def test_legacy_fingerprint_is_content_identity():
|
||||
calibration = _nonlinear_scalar_calibration()
|
||||
assert len(calibration.fingerprint) == 64
|
||||
assert calibration.fingerprint == _nonlinear_scalar_calibration().fingerprint
|
||||
changed = parse(
|
||||
{
|
||||
"schema_version": 2,
|
||||
"unit": "N",
|
||||
"channels": {
|
||||
"normal": {
|
||||
"method": "piecewise_linear",
|
||||
"points": [[0.0, 0.0], [100.0, 1.0], [200.0, 4.0]],
|
||||
"extrapolate": True,
|
||||
"clamp_output_min": None,
|
||||
}
|
||||
},
|
||||
}
|
||||
)
|
||||
assert changed.fingerprint != calibration.fingerprint
|
||||
|
||||
Reference in New Issue
Block a user