jay_diff: fix two correctness bugs + add real-data round-trip tests
Bug 1 (crash): jay_merge_delete iterated _new.items() while _old (which
aliases _new sub-dicts due to jay_diff_rev's side-effect writes) was being
mutated. Fixed by snapshotting with list(_new.items()) before iteration.
Bug 2 (wrong round-trip): When new has an empty dict {} at a location where
old had a non-empty dict, jay_merge_delete incorrectly pruned the now-empty
parent key. Root cause: jay_diff_del stored the full old sub-dict in the
delete delta for both "key absent from new" and "key present but empty in
new", making them indistinguishable at merge time.
Fix: jay_diff_del is now a standalone recursive function that stores None
for dict keys entirely absent from new (sentinel for "delete entire key")
vs a sub-dict of specific sub-keys for partial deletions. jay_merge_delete
no longer prunes empty dicts; None (or any non-dict value) in the delta is
the explicit signal to remove the parent key.
Also adds:
- tests/data/ — copy of 25 real JSON log files for integration testing
- tests/test_jay_diff_real_data.py — 2028 parametrised round-trip tests
over 675 real vehicle snapshots; exposed and confirmed both bugs above
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,293 @@
|
||||
{
|
||||
"meta": {
|
||||
"vin": "WVWZZZE1ZMP010760",
|
||||
"created_at": "2026-05-26T11:37:05.156831+00:00",
|
||||
"interval_seconds": 30
|
||||
},
|
||||
"records": [
|
||||
{
|
||||
"ts": "2026-05-26T11:37:08.127749+00:00",
|
||||
"charging": {
|
||||
"chargingStatus": {
|
||||
"chargingState": "ChargingState.CHARGE_PURPOSE_REACHED_CONSERVATION",
|
||||
"chargePower": {
|
||||
"value": 0.0,
|
||||
"unit": "kW"
|
||||
},
|
||||
"remainingChargingTimeToComplete": {
|
||||
"value": 0,
|
||||
"unit": "min"
|
||||
},
|
||||
"chargeType": "ChargeType.AC"
|
||||
},
|
||||
"batteryStatus": {
|
||||
"currentSOC": {
|
||||
"value": 80,
|
||||
"unit": "%"
|
||||
},
|
||||
"cruisingRangeElectric": {
|
||||
"value": 280,
|
||||
"unit": "km"
|
||||
}
|
||||
},
|
||||
"chargingSettings": {
|
||||
"targetSOC": {
|
||||
"value": 80,
|
||||
"unit": "%"
|
||||
},
|
||||
"maxChargeCurrentAC": "MaximumChargeCurrent.MAXIMUM",
|
||||
"autoUnlockPlugWhenCharged": "UnlockPlugState.OFF"
|
||||
},
|
||||
"plugStatus": {
|
||||
"plugConnectionState": "PlugConnectionState.CONNECTED",
|
||||
"plugLockState": "PlugLockState.LOCKED",
|
||||
"externalPower": "ExternalPower.READY"
|
||||
}
|
||||
},
|
||||
"measurements": {
|
||||
"odometerStatus": {
|
||||
"odometer": {
|
||||
"value": 67419,
|
||||
"unit": "km"
|
||||
}
|
||||
},
|
||||
"rangeStatus": {
|
||||
"electricRange": {
|
||||
"value": 280,
|
||||
"unit": "km"
|
||||
},
|
||||
"totalRange": {
|
||||
"value": 280,
|
||||
"unit": "km"
|
||||
}
|
||||
},
|
||||
"temperatureBatteryStatus": {
|
||||
"temperatureHvBatteryMax": {
|
||||
"value": 23.5,
|
||||
"unit": "degC"
|
||||
},
|
||||
"temperatureHvBatteryMin": {
|
||||
"value": 22.0,
|
||||
"unit": "degC"
|
||||
}
|
||||
}
|
||||
},
|
||||
"climatisation": {
|
||||
"climatisationStatus": {
|
||||
"climatisationState": "ClimatizationState.OFF",
|
||||
"remainingClimatisationTime": {
|
||||
"value": 0,
|
||||
"unit": "min"
|
||||
}
|
||||
},
|
||||
"climatisationSettings": {
|
||||
"targetTemperature": {
|
||||
"value": 21.0,
|
||||
"unit": "degC"
|
||||
},
|
||||
"unitInCar": "UnitInCar.CELSIUS"
|
||||
},
|
||||
"windowHeatingStatus": {
|
||||
"front": "WindowHeatingState.OFF",
|
||||
"rear": "WindowHeatingState.OFF"
|
||||
}
|
||||
},
|
||||
"access": {
|
||||
"accessStatus": {
|
||||
"overallStatus": "OverallState.SAFE",
|
||||
"doors": {
|
||||
"bonnet": {
|
||||
"lockState": "LockState.UNKNOWN",
|
||||
"openState": "OpenState.CLOSED"
|
||||
},
|
||||
"trunk": {
|
||||
"lockState": "LockState.LOCKED",
|
||||
"openState": "OpenState.CLOSED"
|
||||
},
|
||||
"rearRight": {
|
||||
"lockState": "LockState.LOCKED",
|
||||
"openState": "OpenState.CLOSED"
|
||||
},
|
||||
"rearLeft": {
|
||||
"lockState": "LockState.LOCKED",
|
||||
"openState": "OpenState.CLOSED"
|
||||
},
|
||||
"frontRight": {
|
||||
"lockState": "LockState.LOCKED",
|
||||
"openState": "OpenState.CLOSED"
|
||||
},
|
||||
"frontLeft": {
|
||||
"lockState": "LockState.LOCKED",
|
||||
"openState": "OpenState.CLOSED"
|
||||
}
|
||||
},
|
||||
"windows": {
|
||||
"sunRoof": {
|
||||
"openState": "OpenState.UNSUPPORTED"
|
||||
},
|
||||
"roofCover": {
|
||||
"openState": "OpenState.UNSUPPORTED"
|
||||
},
|
||||
"sunRoofRear": {
|
||||
"openState": "OpenState.UNSUPPORTED"
|
||||
},
|
||||
"frontLeft": {
|
||||
"openState": "OpenState.CLOSED"
|
||||
},
|
||||
"frontRight": {
|
||||
"openState": "OpenState.CLOSED"
|
||||
},
|
||||
"rearLeft": {
|
||||
"openState": "OpenState.CLOSED"
|
||||
},
|
||||
"rearRight": {
|
||||
"openState": "OpenState.CLOSED"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"ts": "2026-05-26T11:37:43.660250+00:00",
|
||||
"charging": {
|
||||
"chargingStatus": {
|
||||
"chargingState": "ChargingState.CHARGE_PURPOSE_REACHED_CONSERVATION",
|
||||
"chargePower": {
|
||||
"value": 0.0,
|
||||
"unit": "kW"
|
||||
},
|
||||
"remainingChargingTimeToComplete": {
|
||||
"value": 0,
|
||||
"unit": "min"
|
||||
},
|
||||
"chargeType": "ChargeType.AC"
|
||||
},
|
||||
"batteryStatus": {
|
||||
"currentSOC": {
|
||||
"value": 80,
|
||||
"unit": "%"
|
||||
},
|
||||
"cruisingRangeElectric": {
|
||||
"value": 280,
|
||||
"unit": "km"
|
||||
}
|
||||
},
|
||||
"chargingSettings": {
|
||||
"targetSOC": {
|
||||
"value": 80,
|
||||
"unit": "%"
|
||||
},
|
||||
"maxChargeCurrentAC": "MaximumChargeCurrent.MAXIMUM",
|
||||
"autoUnlockPlugWhenCharged": "UnlockPlugState.OFF"
|
||||
},
|
||||
"plugStatus": {
|
||||
"plugConnectionState": "PlugConnectionState.CONNECTED",
|
||||
"plugLockState": "PlugLockState.LOCKED",
|
||||
"externalPower": "ExternalPower.READY"
|
||||
}
|
||||
},
|
||||
"measurements": {
|
||||
"odometerStatus": {
|
||||
"odometer": {
|
||||
"value": 67419,
|
||||
"unit": "km"
|
||||
}
|
||||
},
|
||||
"rangeStatus": {
|
||||
"electricRange": {
|
||||
"value": 280,
|
||||
"unit": "km"
|
||||
},
|
||||
"totalRange": {
|
||||
"value": 280,
|
||||
"unit": "km"
|
||||
}
|
||||
},
|
||||
"temperatureBatteryStatus": {
|
||||
"temperatureHvBatteryMax": {
|
||||
"value": 23.5,
|
||||
"unit": "degC"
|
||||
},
|
||||
"temperatureHvBatteryMin": {
|
||||
"value": 22.0,
|
||||
"unit": "degC"
|
||||
}
|
||||
}
|
||||
},
|
||||
"climatisation": {
|
||||
"climatisationStatus": {
|
||||
"climatisationState": "ClimatizationState.OFF",
|
||||
"remainingClimatisationTime": {
|
||||
"value": 0,
|
||||
"unit": "min"
|
||||
}
|
||||
},
|
||||
"climatisationSettings": {
|
||||
"targetTemperature": {
|
||||
"value": 21.0,
|
||||
"unit": "degC"
|
||||
},
|
||||
"unitInCar": "UnitInCar.CELSIUS"
|
||||
},
|
||||
"windowHeatingStatus": {
|
||||
"front": "WindowHeatingState.OFF",
|
||||
"rear": "WindowHeatingState.OFF"
|
||||
}
|
||||
},
|
||||
"access": {
|
||||
"accessStatus": {
|
||||
"overallStatus": "OverallState.SAFE",
|
||||
"doors": {
|
||||
"bonnet": {
|
||||
"lockState": "LockState.UNKNOWN",
|
||||
"openState": "OpenState.CLOSED"
|
||||
},
|
||||
"trunk": {
|
||||
"lockState": "LockState.LOCKED",
|
||||
"openState": "OpenState.CLOSED"
|
||||
},
|
||||
"rearRight": {
|
||||
"lockState": "LockState.LOCKED",
|
||||
"openState": "OpenState.CLOSED"
|
||||
},
|
||||
"rearLeft": {
|
||||
"lockState": "LockState.LOCKED",
|
||||
"openState": "OpenState.CLOSED"
|
||||
},
|
||||
"frontRight": {
|
||||
"lockState": "LockState.LOCKED",
|
||||
"openState": "OpenState.CLOSED"
|
||||
},
|
||||
"frontLeft": {
|
||||
"lockState": "LockState.LOCKED",
|
||||
"openState": "OpenState.CLOSED"
|
||||
}
|
||||
},
|
||||
"windows": {
|
||||
"sunRoof": {
|
||||
"openState": "OpenState.UNSUPPORTED"
|
||||
},
|
||||
"roofCover": {
|
||||
"openState": "OpenState.UNSUPPORTED"
|
||||
},
|
||||
"sunRoofRear": {
|
||||
"openState": "OpenState.UNSUPPORTED"
|
||||
},
|
||||
"frontLeft": {
|
||||
"openState": "OpenState.CLOSED"
|
||||
},
|
||||
"frontRight": {
|
||||
"openState": "OpenState.CLOSED"
|
||||
},
|
||||
"rearLeft": {
|
||||
"openState": "OpenState.CLOSED"
|
||||
},
|
||||
"rearRight": {
|
||||
"openState": "OpenState.CLOSED"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,485 @@
|
||||
{
|
||||
"meta": {
|
||||
"vin": "WVWZZZE1ZMP010760",
|
||||
"created_at": "2026-05-27T05:53:07.953894+00:00",
|
||||
"interval_seconds": 30
|
||||
},
|
||||
"records": [
|
||||
{
|
||||
"ts": "2026-05-27T05:53:08.103087+00:00",
|
||||
"charging": {
|
||||
"chargingStatus": {
|
||||
"chargingState": "off",
|
||||
"chargeType": "invalid",
|
||||
"chargePower": {
|
||||
"value": 0.0,
|
||||
"unit": "kW"
|
||||
},
|
||||
"remainingChargingTimeToComplete": {
|
||||
"value": 0,
|
||||
"unit": "min"
|
||||
}
|
||||
},
|
||||
"batteryStatus": {
|
||||
"currentSOC": {
|
||||
"value": 58.0,
|
||||
"unit": "%"
|
||||
},
|
||||
"cruisingRangeElectric": {
|
||||
"value": 209.0,
|
||||
"unit": "km"
|
||||
}
|
||||
},
|
||||
"chargingSettings": {
|
||||
"targetSOC": {
|
||||
"value": 80.0,
|
||||
"unit": "%"
|
||||
},
|
||||
"maxChargeCurrentAC": {
|
||||
"value": 16.0,
|
||||
"unit": "A"
|
||||
},
|
||||
"autoUnlockPlugWhenCharged": "False"
|
||||
},
|
||||
"plugStatus": {
|
||||
"plugConnectionState": "disconnected",
|
||||
"plugLockState": "unlocked",
|
||||
"externalPower": "unavailable"
|
||||
}
|
||||
},
|
||||
"measurements": {
|
||||
"odometerStatus": {
|
||||
"odometer": {
|
||||
"value": 67489.0,
|
||||
"unit": "km"
|
||||
}
|
||||
},
|
||||
"rangeStatus": {
|
||||
"electricRange": {
|
||||
"value": 209.0,
|
||||
"unit": "km"
|
||||
},
|
||||
"totalRange": {
|
||||
"value": 209.0,
|
||||
"unit": "km"
|
||||
}
|
||||
},
|
||||
"temperatureBatteryStatus": {
|
||||
"temperatureHvBatteryMax": {
|
||||
"value": 295.15,
|
||||
"unit": "degC"
|
||||
},
|
||||
"temperatureHvBatteryMin": {
|
||||
"value": 294.15,
|
||||
"unit": "degC"
|
||||
}
|
||||
},
|
||||
"temperatureOutsideStatus": {
|
||||
"temperatureOutside": {
|
||||
"value": null,
|
||||
"unit": "degC"
|
||||
}
|
||||
}
|
||||
},
|
||||
"readiness": {
|
||||
"readinessStatus": {
|
||||
"connectionState": {
|
||||
"isOnline": false,
|
||||
"isActive": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"climatisation": {
|
||||
"climatisationStatus": {
|
||||
"climatisationState": "off",
|
||||
"remainingClimatisationTime": {
|
||||
"value": 0,
|
||||
"unit": "min"
|
||||
}
|
||||
},
|
||||
"climatisationSettings": {
|
||||
"targetTemperature": {
|
||||
"value": 21.0,
|
||||
"unit": "degC"
|
||||
}
|
||||
}
|
||||
},
|
||||
"parking": {
|
||||
"parkingPosition": {
|
||||
"lat": null,
|
||||
"lon": null
|
||||
}
|
||||
},
|
||||
"access": {
|
||||
"accessStatus": {
|
||||
"overallStatus": "None",
|
||||
"doors": {},
|
||||
"windows": {}
|
||||
}
|
||||
},
|
||||
"procedural": {
|
||||
"range_at_100": {
|
||||
"value": 360.3,
|
||||
"unit": "km"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"ts": "2026-05-27T05:53:38.257550+00:00",
|
||||
"charging": {
|
||||
"chargingStatus": {
|
||||
"chargingState": "off",
|
||||
"chargeType": "invalid",
|
||||
"chargePower": {
|
||||
"value": 0.0,
|
||||
"unit": "kW"
|
||||
},
|
||||
"remainingChargingTimeToComplete": {
|
||||
"value": 0,
|
||||
"unit": "min"
|
||||
}
|
||||
},
|
||||
"batteryStatus": {
|
||||
"currentSOC": {
|
||||
"value": 58.0,
|
||||
"unit": "%"
|
||||
},
|
||||
"cruisingRangeElectric": {
|
||||
"value": 209.0,
|
||||
"unit": "km"
|
||||
}
|
||||
},
|
||||
"chargingSettings": {
|
||||
"targetSOC": {
|
||||
"value": 80.0,
|
||||
"unit": "%"
|
||||
},
|
||||
"maxChargeCurrentAC": {
|
||||
"value": 16.0,
|
||||
"unit": "A"
|
||||
},
|
||||
"autoUnlockPlugWhenCharged": "False"
|
||||
},
|
||||
"plugStatus": {
|
||||
"plugConnectionState": "disconnected",
|
||||
"plugLockState": "unlocked",
|
||||
"externalPower": "unavailable"
|
||||
}
|
||||
},
|
||||
"measurements": {
|
||||
"odometerStatus": {
|
||||
"odometer": {
|
||||
"value": 67489.0,
|
||||
"unit": "km"
|
||||
}
|
||||
},
|
||||
"rangeStatus": {
|
||||
"electricRange": {
|
||||
"value": 209.0,
|
||||
"unit": "km"
|
||||
},
|
||||
"totalRange": {
|
||||
"value": 209.0,
|
||||
"unit": "km"
|
||||
}
|
||||
},
|
||||
"temperatureBatteryStatus": {
|
||||
"temperatureHvBatteryMax": {
|
||||
"value": 295.15,
|
||||
"unit": "degC"
|
||||
},
|
||||
"temperatureHvBatteryMin": {
|
||||
"value": 294.15,
|
||||
"unit": "degC"
|
||||
}
|
||||
},
|
||||
"temperatureOutsideStatus": {
|
||||
"temperatureOutside": {
|
||||
"value": null,
|
||||
"unit": "degC"
|
||||
}
|
||||
}
|
||||
},
|
||||
"readiness": {
|
||||
"readinessStatus": {
|
||||
"connectionState": {
|
||||
"isOnline": false,
|
||||
"isActive": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"climatisation": {
|
||||
"climatisationStatus": {
|
||||
"climatisationState": "off",
|
||||
"remainingClimatisationTime": {
|
||||
"value": 0,
|
||||
"unit": "min"
|
||||
}
|
||||
},
|
||||
"climatisationSettings": {
|
||||
"targetTemperature": {
|
||||
"value": 21.0,
|
||||
"unit": "degC"
|
||||
}
|
||||
}
|
||||
},
|
||||
"parking": {
|
||||
"parkingPosition": {
|
||||
"lat": null,
|
||||
"lon": null
|
||||
}
|
||||
},
|
||||
"access": {
|
||||
"accessStatus": {
|
||||
"overallStatus": "None",
|
||||
"doors": {},
|
||||
"windows": {}
|
||||
}
|
||||
},
|
||||
"procedural": {
|
||||
"range_at_100": {
|
||||
"value": 360.3,
|
||||
"unit": "km"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"ts": "2026-05-27T05:54:08.438969+00:00",
|
||||
"charging": {
|
||||
"chargingStatus": {
|
||||
"chargingState": "off",
|
||||
"chargeType": "invalid",
|
||||
"chargePower": {
|
||||
"value": 0.0,
|
||||
"unit": "kW"
|
||||
},
|
||||
"remainingChargingTimeToComplete": {
|
||||
"value": 0,
|
||||
"unit": "min"
|
||||
}
|
||||
},
|
||||
"batteryStatus": {
|
||||
"currentSOC": {
|
||||
"value": 58.0,
|
||||
"unit": "%"
|
||||
},
|
||||
"cruisingRangeElectric": {
|
||||
"value": 209.0,
|
||||
"unit": "km"
|
||||
}
|
||||
},
|
||||
"chargingSettings": {
|
||||
"targetSOC": {
|
||||
"value": 80.0,
|
||||
"unit": "%"
|
||||
},
|
||||
"maxChargeCurrentAC": {
|
||||
"value": 16.0,
|
||||
"unit": "A"
|
||||
},
|
||||
"autoUnlockPlugWhenCharged": "False"
|
||||
},
|
||||
"plugStatus": {
|
||||
"plugConnectionState": "disconnected",
|
||||
"plugLockState": "unlocked",
|
||||
"externalPower": "unavailable"
|
||||
}
|
||||
},
|
||||
"measurements": {
|
||||
"odometerStatus": {
|
||||
"odometer": {
|
||||
"value": 67489.0,
|
||||
"unit": "km"
|
||||
}
|
||||
},
|
||||
"rangeStatus": {
|
||||
"electricRange": {
|
||||
"value": 209.0,
|
||||
"unit": "km"
|
||||
},
|
||||
"totalRange": {
|
||||
"value": 209.0,
|
||||
"unit": "km"
|
||||
}
|
||||
},
|
||||
"temperatureBatteryStatus": {
|
||||
"temperatureHvBatteryMax": {
|
||||
"value": 295.15,
|
||||
"unit": "degC"
|
||||
},
|
||||
"temperatureHvBatteryMin": {
|
||||
"value": 294.15,
|
||||
"unit": "degC"
|
||||
}
|
||||
},
|
||||
"temperatureOutsideStatus": {
|
||||
"temperatureOutside": {
|
||||
"value": null,
|
||||
"unit": "degC"
|
||||
}
|
||||
}
|
||||
},
|
||||
"readiness": {
|
||||
"readinessStatus": {
|
||||
"connectionState": {
|
||||
"isOnline": false,
|
||||
"isActive": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"climatisation": {
|
||||
"climatisationStatus": {
|
||||
"climatisationState": "off",
|
||||
"remainingClimatisationTime": {
|
||||
"value": 0,
|
||||
"unit": "min"
|
||||
}
|
||||
},
|
||||
"climatisationSettings": {
|
||||
"targetTemperature": {
|
||||
"value": 21.0,
|
||||
"unit": "degC"
|
||||
}
|
||||
}
|
||||
},
|
||||
"parking": {
|
||||
"parkingPosition": {
|
||||
"lat": null,
|
||||
"lon": null
|
||||
}
|
||||
},
|
||||
"access": {
|
||||
"accessStatus": {
|
||||
"overallStatus": "None",
|
||||
"doors": {},
|
||||
"windows": {}
|
||||
}
|
||||
},
|
||||
"procedural": {
|
||||
"range_at_100": {
|
||||
"value": 360.3,
|
||||
"unit": "km"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"ts": "2026-05-27T05:54:38.603661+00:00",
|
||||
"charging": {
|
||||
"chargingStatus": {
|
||||
"chargingState": "off",
|
||||
"chargeType": "invalid",
|
||||
"chargePower": {
|
||||
"value": 0.0,
|
||||
"unit": "kW"
|
||||
},
|
||||
"remainingChargingTimeToComplete": {
|
||||
"value": 0,
|
||||
"unit": "min"
|
||||
}
|
||||
},
|
||||
"batteryStatus": {
|
||||
"currentSOC": {
|
||||
"value": 58.0,
|
||||
"unit": "%"
|
||||
},
|
||||
"cruisingRangeElectric": {
|
||||
"value": 209.0,
|
||||
"unit": "km"
|
||||
}
|
||||
},
|
||||
"chargingSettings": {
|
||||
"targetSOC": {
|
||||
"value": 80.0,
|
||||
"unit": "%"
|
||||
},
|
||||
"maxChargeCurrentAC": {
|
||||
"value": 16.0,
|
||||
"unit": "A"
|
||||
},
|
||||
"autoUnlockPlugWhenCharged": "False"
|
||||
},
|
||||
"plugStatus": {
|
||||
"plugConnectionState": "disconnected",
|
||||
"plugLockState": "unlocked",
|
||||
"externalPower": "unavailable"
|
||||
}
|
||||
},
|
||||
"measurements": {
|
||||
"odometerStatus": {
|
||||
"odometer": {
|
||||
"value": 67489.0,
|
||||
"unit": "km"
|
||||
}
|
||||
},
|
||||
"rangeStatus": {
|
||||
"electricRange": {
|
||||
"value": 209.0,
|
||||
"unit": "km"
|
||||
},
|
||||
"totalRange": {
|
||||
"value": 209.0,
|
||||
"unit": "km"
|
||||
}
|
||||
},
|
||||
"temperatureBatteryStatus": {
|
||||
"temperatureHvBatteryMax": {
|
||||
"value": 295.15,
|
||||
"unit": "degC"
|
||||
},
|
||||
"temperatureHvBatteryMin": {
|
||||
"value": 294.15,
|
||||
"unit": "degC"
|
||||
}
|
||||
},
|
||||
"temperatureOutsideStatus": {
|
||||
"temperatureOutside": {
|
||||
"value": null,
|
||||
"unit": "degC"
|
||||
}
|
||||
}
|
||||
},
|
||||
"readiness": {
|
||||
"readinessStatus": {
|
||||
"connectionState": {
|
||||
"isOnline": false,
|
||||
"isActive": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"climatisation": {
|
||||
"climatisationStatus": {
|
||||
"climatisationState": "off",
|
||||
"remainingClimatisationTime": {
|
||||
"value": 0,
|
||||
"unit": "min"
|
||||
}
|
||||
},
|
||||
"climatisationSettings": {
|
||||
"targetTemperature": {
|
||||
"value": 21.0,
|
||||
"unit": "degC"
|
||||
}
|
||||
}
|
||||
},
|
||||
"parking": {
|
||||
"parkingPosition": {
|
||||
"lat": null,
|
||||
"lon": null
|
||||
}
|
||||
},
|
||||
"access": {
|
||||
"accessStatus": {
|
||||
"overallStatus": "None",
|
||||
"doors": {},
|
||||
"windows": {}
|
||||
}
|
||||
},
|
||||
"procedural": {
|
||||
"range_at_100": {
|
||||
"value": 360.3,
|
||||
"unit": "km"
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,128 @@
|
||||
{
|
||||
"meta": {
|
||||
"vin": "WVWZZZE1ZMP010760",
|
||||
"created_at": "2026-05-27T05:55:03.027525+00:00",
|
||||
"interval_seconds": 30
|
||||
},
|
||||
"records": [
|
||||
{
|
||||
"ts": "2026-05-27T05:55:03.219238+00:00",
|
||||
"charging": {
|
||||
"chargingStatus": {
|
||||
"chargingState": "off",
|
||||
"chargeType": "invalid",
|
||||
"chargePower": {
|
||||
"value": 0.0,
|
||||
"unit": "kW"
|
||||
},
|
||||
"remainingChargingTimeToComplete": {
|
||||
"value": 0,
|
||||
"unit": "min"
|
||||
}
|
||||
},
|
||||
"batteryStatus": {
|
||||
"currentSOC": {
|
||||
"value": 58.0,
|
||||
"unit": "%"
|
||||
},
|
||||
"cruisingRangeElectric": {
|
||||
"value": 209.0,
|
||||
"unit": "km"
|
||||
}
|
||||
},
|
||||
"chargingSettings": {
|
||||
"targetSOC": {
|
||||
"value": 80.0,
|
||||
"unit": "%"
|
||||
},
|
||||
"maxChargeCurrentAC": {
|
||||
"value": 16.0,
|
||||
"unit": "A"
|
||||
},
|
||||
"autoUnlockPlugWhenCharged": "False"
|
||||
},
|
||||
"plugStatus": {
|
||||
"plugConnectionState": "disconnected",
|
||||
"plugLockState": "unlocked",
|
||||
"externalPower": "unavailable"
|
||||
}
|
||||
},
|
||||
"measurements": {
|
||||
"odometerStatus": {
|
||||
"odometer": {
|
||||
"value": 67489.0,
|
||||
"unit": "km"
|
||||
}
|
||||
},
|
||||
"rangeStatus": {
|
||||
"electricRange": {
|
||||
"value": 209.0,
|
||||
"unit": "km"
|
||||
},
|
||||
"totalRange": {
|
||||
"value": 209.0,
|
||||
"unit": "km"
|
||||
}
|
||||
},
|
||||
"temperatureBatteryStatus": {
|
||||
"temperatureHvBatteryMax": {
|
||||
"value": 295.15,
|
||||
"unit": "degC"
|
||||
},
|
||||
"temperatureHvBatteryMin": {
|
||||
"value": 294.15,
|
||||
"unit": "degC"
|
||||
}
|
||||
},
|
||||
"temperatureOutsideStatus": {
|
||||
"temperatureOutside": {
|
||||
"value": null,
|
||||
"unit": "degC"
|
||||
}
|
||||
}
|
||||
},
|
||||
"readiness": {
|
||||
"readinessStatus": {
|
||||
"connectionState": {
|
||||
"isOnline": false,
|
||||
"isActive": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"climatisation": {
|
||||
"climatisationStatus": {
|
||||
"climatisationState": "off",
|
||||
"remainingClimatisationTime": {
|
||||
"value": 0,
|
||||
"unit": "min"
|
||||
}
|
||||
},
|
||||
"climatisationSettings": {
|
||||
"targetTemperature": {
|
||||
"value": 21.0,
|
||||
"unit": "degC"
|
||||
}
|
||||
}
|
||||
},
|
||||
"parking": {
|
||||
"parkingPosition": {
|
||||
"lat": null,
|
||||
"lon": null
|
||||
}
|
||||
},
|
||||
"access": {
|
||||
"accessStatus": {
|
||||
"overallStatus": "None",
|
||||
"doors": {},
|
||||
"windows": {}
|
||||
}
|
||||
},
|
||||
"procedural": {
|
||||
"range_at_100": {
|
||||
"value": 360.3,
|
||||
"unit": "km"
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,130 @@
|
||||
{
|
||||
"meta": {
|
||||
"vin": "WVWZZZE1ZMP010760",
|
||||
"created_at": "2026-05-27T06:17:18.385566+00:00",
|
||||
"interval_seconds": 30
|
||||
},
|
||||
"records": [
|
||||
{
|
||||
"ts": "2026-05-27T06:17:18.554491+00:00",
|
||||
"charging": {
|
||||
"chargingStatus": {
|
||||
"chargingState": "off",
|
||||
"chargeType": "invalid",
|
||||
"chargePower": {
|
||||
"value": 0.0,
|
||||
"unit": "kW"
|
||||
},
|
||||
"remainingChargingTimeToComplete": {
|
||||
"value": 0,
|
||||
"unit": "min"
|
||||
}
|
||||
},
|
||||
"batteryStatus": {
|
||||
"currentSOC": {
|
||||
"value": 58.0,
|
||||
"unit": "%"
|
||||
},
|
||||
"cruisingRangeElectric": {
|
||||
"value": 209.0,
|
||||
"unit": "km"
|
||||
}
|
||||
},
|
||||
"chargingSettings": {
|
||||
"targetSOC": {
|
||||
"value": 80.0,
|
||||
"unit": "%"
|
||||
},
|
||||
"maxChargeCurrentAC": {
|
||||
"value": 16.0,
|
||||
"unit": "A"
|
||||
},
|
||||
"autoUnlockPlugWhenCharged": "False"
|
||||
},
|
||||
"plugStatus": {
|
||||
"plugConnectionState": "disconnected",
|
||||
"plugLockState": "unlocked",
|
||||
"externalPower": "unavailable"
|
||||
}
|
||||
},
|
||||
"measurements": {
|
||||
"odometerStatus": {
|
||||
"odometer": {
|
||||
"value": 67489.0,
|
||||
"unit": "km"
|
||||
}
|
||||
},
|
||||
"rangeStatus": {
|
||||
"electricRange": {
|
||||
"value": 209.0,
|
||||
"unit": "km"
|
||||
},
|
||||
"totalRange": {
|
||||
"value": 209.0,
|
||||
"unit": "km"
|
||||
}
|
||||
},
|
||||
"temperatureBatteryStatus": {
|
||||
"temperatureHvBatteryMax": {
|
||||
"value": 295.15,
|
||||
"unit": "degC"
|
||||
},
|
||||
"temperatureHvBatteryMin": {
|
||||
"value": 294.15,
|
||||
"unit": "degC"
|
||||
}
|
||||
},
|
||||
"temperatureOutsideStatus": {
|
||||
"temperatureOutside": {
|
||||
"value": null,
|
||||
"unit": "degC"
|
||||
}
|
||||
}
|
||||
},
|
||||
"vehicle": {
|
||||
"vehicle": {
|
||||
"state": "unknown vehicle state"
|
||||
}
|
||||
},
|
||||
"connectivity": {
|
||||
"connection": {
|
||||
"state": "reachable"
|
||||
}
|
||||
},
|
||||
"climatisation": {
|
||||
"climatisationStatus": {
|
||||
"climatisationState": "off",
|
||||
"remainingClimatisationTime": {
|
||||
"value": 0,
|
||||
"unit": "min"
|
||||
}
|
||||
},
|
||||
"climatisationSettings": {
|
||||
"targetTemperature": {
|
||||
"value": 21.0,
|
||||
"unit": "degC"
|
||||
}
|
||||
}
|
||||
},
|
||||
"position": {
|
||||
"position": {
|
||||
"lat": null,
|
||||
"lon": null
|
||||
}
|
||||
},
|
||||
"doors": {
|
||||
"doors": {
|
||||
"overallState": "None",
|
||||
"doors": {},
|
||||
"windows": {}
|
||||
}
|
||||
},
|
||||
"procedural": {
|
||||
"range_at_100": {
|
||||
"value": 360.3,
|
||||
"unit": "km"
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,130 @@
|
||||
{
|
||||
"meta": {
|
||||
"vin": "WVWZZZE1ZMP010760",
|
||||
"created_at": "2026-05-27T06:18:28.783719+00:00",
|
||||
"interval_seconds": 30
|
||||
},
|
||||
"records": [
|
||||
{
|
||||
"ts": "2026-05-27T06:18:28.981339+00:00",
|
||||
"charging": {
|
||||
"chargingStatus": {
|
||||
"chargingState": "off",
|
||||
"chargeType": "invalid",
|
||||
"chargePower": {
|
||||
"value": 0.0,
|
||||
"unit": "kW"
|
||||
},
|
||||
"remainingChargingTimeToComplete": {
|
||||
"value": 0,
|
||||
"unit": "min"
|
||||
}
|
||||
},
|
||||
"batteryStatus": {
|
||||
"currentSOC": {
|
||||
"value": 58.0,
|
||||
"unit": "%"
|
||||
},
|
||||
"cruisingRangeElectric": {
|
||||
"value": 209.0,
|
||||
"unit": "km"
|
||||
}
|
||||
},
|
||||
"chargingSettings": {
|
||||
"targetSOC": {
|
||||
"value": 80.0,
|
||||
"unit": "%"
|
||||
},
|
||||
"maxChargeCurrentAC": {
|
||||
"value": 16.0,
|
||||
"unit": "A"
|
||||
},
|
||||
"autoUnlockPlugWhenCharged": "False"
|
||||
},
|
||||
"plugStatus": {
|
||||
"plugConnectionState": "disconnected",
|
||||
"plugLockState": "unlocked",
|
||||
"externalPower": "unavailable"
|
||||
}
|
||||
},
|
||||
"measurements": {
|
||||
"odometerStatus": {
|
||||
"odometer": {
|
||||
"value": 67489.0,
|
||||
"unit": "km"
|
||||
}
|
||||
},
|
||||
"rangeStatus": {
|
||||
"electricRange": {
|
||||
"value": 209.0,
|
||||
"unit": "km"
|
||||
},
|
||||
"totalRange": {
|
||||
"value": 209.0,
|
||||
"unit": "km"
|
||||
}
|
||||
},
|
||||
"temperatureBatteryStatus": {
|
||||
"temperatureHvBatteryMax": {
|
||||
"value": 295.15,
|
||||
"unit": "degC"
|
||||
},
|
||||
"temperatureHvBatteryMin": {
|
||||
"value": 294.15,
|
||||
"unit": "degC"
|
||||
}
|
||||
},
|
||||
"temperatureOutsideStatus": {
|
||||
"temperatureOutside": {
|
||||
"value": null,
|
||||
"unit": "degC"
|
||||
}
|
||||
}
|
||||
},
|
||||
"vehicle": {
|
||||
"vehicle": {
|
||||
"state": "unknown vehicle state"
|
||||
}
|
||||
},
|
||||
"connectivity": {
|
||||
"connection": {
|
||||
"state": "reachable"
|
||||
}
|
||||
},
|
||||
"climatisation": {
|
||||
"climatisationStatus": {
|
||||
"climatisationState": "off",
|
||||
"remainingClimatisationTime": {
|
||||
"value": 0,
|
||||
"unit": "min"
|
||||
}
|
||||
},
|
||||
"climatisationSettings": {
|
||||
"targetTemperature": {
|
||||
"value": 21.0,
|
||||
"unit": "degC"
|
||||
}
|
||||
}
|
||||
},
|
||||
"position": {
|
||||
"position": {
|
||||
"lat": null,
|
||||
"lon": null
|
||||
}
|
||||
},
|
||||
"doors": {
|
||||
"doors": {
|
||||
"overallState": "None",
|
||||
"doors": {},
|
||||
"windows": {}
|
||||
}
|
||||
},
|
||||
"procedural": {
|
||||
"range_at_100": {
|
||||
"value": 360.3,
|
||||
"unit": "km"
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,130 @@
|
||||
{
|
||||
"meta": {
|
||||
"vin": "WVWZZZE1ZMP010760",
|
||||
"created_at": "2026-05-27T06:21:01.933728+00:00",
|
||||
"interval_seconds": 30
|
||||
},
|
||||
"records": [
|
||||
{
|
||||
"ts": "2026-05-27T06:21:02.150738+00:00",
|
||||
"charging": {
|
||||
"chargingStatus": {
|
||||
"chargingState": "off",
|
||||
"chargeType": "invalid",
|
||||
"chargePower": {
|
||||
"value": 0.0,
|
||||
"unit": "kW"
|
||||
},
|
||||
"remainingChargingTimeToComplete": {
|
||||
"value": 0,
|
||||
"unit": "min"
|
||||
}
|
||||
},
|
||||
"batteryStatus": {
|
||||
"currentSOC": {
|
||||
"value": 58.0,
|
||||
"unit": "%"
|
||||
},
|
||||
"cruisingRangeElectric": {
|
||||
"value": 209.0,
|
||||
"unit": "km"
|
||||
}
|
||||
},
|
||||
"chargingSettings": {
|
||||
"targetSOC": {
|
||||
"value": 80.0,
|
||||
"unit": "%"
|
||||
},
|
||||
"maxChargeCurrentAC": {
|
||||
"value": 16.0,
|
||||
"unit": "A"
|
||||
},
|
||||
"autoUnlockPlugWhenCharged": "False"
|
||||
},
|
||||
"plugStatus": {
|
||||
"plugConnectionState": "disconnected",
|
||||
"plugLockState": "unlocked",
|
||||
"externalPower": "unavailable"
|
||||
}
|
||||
},
|
||||
"measurements": {
|
||||
"odometerStatus": {
|
||||
"odometer": {
|
||||
"value": 67489.0,
|
||||
"unit": "km"
|
||||
}
|
||||
},
|
||||
"rangeStatus": {
|
||||
"electricRange": {
|
||||
"value": 209.0,
|
||||
"unit": "km"
|
||||
},
|
||||
"totalRange": {
|
||||
"value": 209.0,
|
||||
"unit": "km"
|
||||
}
|
||||
},
|
||||
"temperatureBatteryStatus": {
|
||||
"temperatureHvBatteryMax": {
|
||||
"value": 295.15,
|
||||
"unit": "degC"
|
||||
},
|
||||
"temperatureHvBatteryMin": {
|
||||
"value": 294.15,
|
||||
"unit": "degC"
|
||||
}
|
||||
},
|
||||
"temperatureOutsideStatus": {
|
||||
"temperatureOutside": {
|
||||
"value": null,
|
||||
"unit": "degC"
|
||||
}
|
||||
}
|
||||
},
|
||||
"vehicle": {
|
||||
"vehicle": {
|
||||
"state": "unknown vehicle state"
|
||||
}
|
||||
},
|
||||
"connectivity": {
|
||||
"connection": {
|
||||
"state": "reachable"
|
||||
}
|
||||
},
|
||||
"climatisation": {
|
||||
"climatisationStatus": {
|
||||
"climatisationState": "off",
|
||||
"remainingClimatisationTime": {
|
||||
"value": 0,
|
||||
"unit": "min"
|
||||
}
|
||||
},
|
||||
"climatisationSettings": {
|
||||
"targetTemperature": {
|
||||
"value": 21.0,
|
||||
"unit": "degC"
|
||||
}
|
||||
}
|
||||
},
|
||||
"position": {
|
||||
"position": {
|
||||
"lat": null,
|
||||
"lon": null
|
||||
}
|
||||
},
|
||||
"doors": {
|
||||
"doors": {
|
||||
"overallState": "None",
|
||||
"doors": {},
|
||||
"windows": {}
|
||||
}
|
||||
},
|
||||
"procedural": {
|
||||
"range_at_100": {
|
||||
"value": 360.3,
|
||||
"unit": "km"
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,130 @@
|
||||
{
|
||||
"meta": {
|
||||
"vin": "WVWZZZE1ZMP010760",
|
||||
"created_at": "2026-05-27T06:33:08.561803+00:00",
|
||||
"interval_seconds": 30
|
||||
},
|
||||
"records": [
|
||||
{
|
||||
"ts": "2026-05-27T06:33:08.745827+00:00",
|
||||
"charging": {
|
||||
"chargingStatus": {
|
||||
"chargingState": "off",
|
||||
"chargeType": "invalid",
|
||||
"chargePower": {
|
||||
"value": 0.0,
|
||||
"unit": "kW"
|
||||
},
|
||||
"remainingChargingTimeToComplete": {
|
||||
"value": 0,
|
||||
"unit": "min"
|
||||
}
|
||||
},
|
||||
"batteryStatus": {
|
||||
"currentSOC": {
|
||||
"value": 58.0,
|
||||
"unit": "%"
|
||||
},
|
||||
"cruisingRangeElectric": {
|
||||
"value": 209.0,
|
||||
"unit": "km"
|
||||
}
|
||||
},
|
||||
"chargingSettings": {
|
||||
"targetSOC": {
|
||||
"value": 80.0,
|
||||
"unit": "%"
|
||||
},
|
||||
"maxChargeCurrentAC": {
|
||||
"value": 16.0,
|
||||
"unit": "A"
|
||||
},
|
||||
"autoUnlockPlugWhenCharged": "False"
|
||||
},
|
||||
"plugStatus": {
|
||||
"plugConnectionState": "disconnected",
|
||||
"plugLockState": "unlocked",
|
||||
"externalPower": "unavailable"
|
||||
}
|
||||
},
|
||||
"electric_system": {
|
||||
"electric_system": {
|
||||
"range": {
|
||||
"value": 209.0,
|
||||
"unit": "km"
|
||||
},
|
||||
"range_full": {
|
||||
"value": 360.3448275862069,
|
||||
"unit": "km"
|
||||
},
|
||||
"battery": {
|
||||
"temperature_max": {
|
||||
"value": 295.15,
|
||||
"unit": "degC"
|
||||
},
|
||||
"temperature_min": {
|
||||
"value": 294.15,
|
||||
"unit": "degC"
|
||||
}
|
||||
}
|
||||
},
|
||||
"odometer": {
|
||||
"odometer": {
|
||||
"value": 67489.0,
|
||||
"unit": "km"
|
||||
}
|
||||
},
|
||||
"environment": {
|
||||
"temperature_outside": {
|
||||
"value": null,
|
||||
"unit": "degC"
|
||||
}
|
||||
}
|
||||
},
|
||||
"vehicle": {
|
||||
"vehicle": {
|
||||
"state": "unknown vehicle state"
|
||||
}
|
||||
},
|
||||
"connectivity": {
|
||||
"connectivity": {
|
||||
"state": "reachable"
|
||||
}
|
||||
},
|
||||
"climatisation": {
|
||||
"climatisationStatus": {
|
||||
"climatisationState": "off",
|
||||
"remainingClimatisationTime": {
|
||||
"value": 0,
|
||||
"unit": "min"
|
||||
}
|
||||
},
|
||||
"climatisationSettings": {
|
||||
"targetTemperature": {
|
||||
"value": 21.0,
|
||||
"unit": "degC"
|
||||
}
|
||||
}
|
||||
},
|
||||
"position": {
|
||||
"position": {
|
||||
"lat": null,
|
||||
"lon": null
|
||||
}
|
||||
},
|
||||
"doors": {
|
||||
"doors": {
|
||||
"overallState": "None",
|
||||
"doors": {},
|
||||
"windows": {}
|
||||
}
|
||||
},
|
||||
"procedural": {
|
||||
"range_at_100": {
|
||||
"value": 360.3,
|
||||
"unit": "km"
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,627 @@
|
||||
{
|
||||
"meta": {
|
||||
"vin": "WVWZZZE1ZMP010760",
|
||||
"created_at": "2026-05-27T09:31:50.175248+00:00",
|
||||
"interval_seconds": 30
|
||||
},
|
||||
"records": [
|
||||
{
|
||||
"ts": "2026-05-27T09:31:50.333801+00:00",
|
||||
"charging": {
|
||||
"state": "off",
|
||||
"type": "invalid",
|
||||
"power": {
|
||||
"value": 0.0,
|
||||
"unit": "kW"
|
||||
},
|
||||
"remain": {
|
||||
"value": 0,
|
||||
"unit": "min"
|
||||
},
|
||||
"settings": {
|
||||
"target_level": {
|
||||
"value": 80.0,
|
||||
"unit": "%"
|
||||
},
|
||||
"maximum_current": {
|
||||
"value": 16.0,
|
||||
"unit": "A"
|
||||
},
|
||||
"auto_unlock": "False"
|
||||
},
|
||||
"plugStatus": {
|
||||
"connection_state": "disconnected",
|
||||
"lock_state": "unlocked",
|
||||
"external_power": "unavailable"
|
||||
}
|
||||
},
|
||||
"electric_drive": {
|
||||
"range": {
|
||||
"value": 209.0,
|
||||
"unit": "km"
|
||||
},
|
||||
"range_full": {
|
||||
"value": 360.3,
|
||||
"unit": "km"
|
||||
},
|
||||
"battery": {
|
||||
"soc": {
|
||||
"value": 58.0,
|
||||
"unit": "%"
|
||||
},
|
||||
"temperature_max": {
|
||||
"value": 295.15,
|
||||
"unit": "degC"
|
||||
},
|
||||
"temperature_min": {
|
||||
"value": 294.15,
|
||||
"unit": "degC"
|
||||
}
|
||||
},
|
||||
"odometer": {
|
||||
"odometer": {
|
||||
"value": 67489.0,
|
||||
"unit": "km"
|
||||
}
|
||||
}
|
||||
},
|
||||
"vehicle": {
|
||||
"state": "unknown vehicle state"
|
||||
},
|
||||
"connectivity": {
|
||||
"state": "reachable"
|
||||
},
|
||||
"climatisation": {
|
||||
"state": "off",
|
||||
"remain": {
|
||||
"value": 0,
|
||||
"unit": "min"
|
||||
},
|
||||
"settings": {
|
||||
"target_temperature": {
|
||||
"value": 21.0,
|
||||
"unit": "degC"
|
||||
}
|
||||
},
|
||||
"environment": {
|
||||
"temperature_outside": {
|
||||
"value": null,
|
||||
"unit": "degC"
|
||||
}
|
||||
}
|
||||
},
|
||||
"position": {
|
||||
"lat": null,
|
||||
"lon": null
|
||||
},
|
||||
"doors": {
|
||||
"doors": {
|
||||
"overallState": "None",
|
||||
"doors": {},
|
||||
"windows": {}
|
||||
}
|
||||
},
|
||||
"procedural": {
|
||||
"range_at_100": {
|
||||
"value": 360.3,
|
||||
"unit": "km"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"ts": "2026-05-27T09:32:20.484182+00:00",
|
||||
"charging": {
|
||||
"state": "off",
|
||||
"type": "invalid",
|
||||
"power": {
|
||||
"value": 0.0,
|
||||
"unit": "kW"
|
||||
},
|
||||
"remain": {
|
||||
"value": 0,
|
||||
"unit": "min"
|
||||
},
|
||||
"settings": {
|
||||
"target_level": {
|
||||
"value": 80.0,
|
||||
"unit": "%"
|
||||
},
|
||||
"maximum_current": {
|
||||
"value": 16.0,
|
||||
"unit": "A"
|
||||
},
|
||||
"auto_unlock": "False"
|
||||
},
|
||||
"plugStatus": {
|
||||
"connection_state": "disconnected",
|
||||
"lock_state": "unlocked",
|
||||
"external_power": "unavailable"
|
||||
}
|
||||
},
|
||||
"electric_drive": {
|
||||
"range": {
|
||||
"value": 209.0,
|
||||
"unit": "km"
|
||||
},
|
||||
"range_full": {
|
||||
"value": 360.3,
|
||||
"unit": "km"
|
||||
},
|
||||
"battery": {
|
||||
"soc": {
|
||||
"value": 58.0,
|
||||
"unit": "%"
|
||||
},
|
||||
"temperature_max": {
|
||||
"value": 295.15,
|
||||
"unit": "degC"
|
||||
},
|
||||
"temperature_min": {
|
||||
"value": 294.15,
|
||||
"unit": "degC"
|
||||
}
|
||||
},
|
||||
"odometer": {
|
||||
"odometer": {
|
||||
"value": 67489.0,
|
||||
"unit": "km"
|
||||
}
|
||||
}
|
||||
},
|
||||
"vehicle": {
|
||||
"state": "unknown vehicle state"
|
||||
},
|
||||
"connectivity": {
|
||||
"state": "reachable"
|
||||
},
|
||||
"climatisation": {
|
||||
"state": "off",
|
||||
"remain": {
|
||||
"value": 0,
|
||||
"unit": "min"
|
||||
},
|
||||
"settings": {
|
||||
"target_temperature": {
|
||||
"value": 21.0,
|
||||
"unit": "degC"
|
||||
}
|
||||
},
|
||||
"environment": {
|
||||
"temperature_outside": {
|
||||
"value": null,
|
||||
"unit": "degC"
|
||||
}
|
||||
}
|
||||
},
|
||||
"position": {
|
||||
"lat": null,
|
||||
"lon": null
|
||||
},
|
||||
"doors": {
|
||||
"doors": {
|
||||
"overallState": "None",
|
||||
"doors": {},
|
||||
"windows": {}
|
||||
}
|
||||
},
|
||||
"procedural": {
|
||||
"range_at_100": {
|
||||
"value": 360.3,
|
||||
"unit": "km"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"ts": "2026-05-27T09:32:50.642394+00:00",
|
||||
"charging": {
|
||||
"state": "off",
|
||||
"type": "invalid",
|
||||
"power": {
|
||||
"value": 0.0,
|
||||
"unit": "kW"
|
||||
},
|
||||
"remain": {
|
||||
"value": 0,
|
||||
"unit": "min"
|
||||
},
|
||||
"settings": {
|
||||
"target_level": {
|
||||
"value": 80.0,
|
||||
"unit": "%"
|
||||
},
|
||||
"maximum_current": {
|
||||
"value": 16.0,
|
||||
"unit": "A"
|
||||
},
|
||||
"auto_unlock": "False"
|
||||
},
|
||||
"plugStatus": {
|
||||
"connection_state": "disconnected",
|
||||
"lock_state": "unlocked",
|
||||
"external_power": "unavailable"
|
||||
}
|
||||
},
|
||||
"electric_drive": {
|
||||
"range": {
|
||||
"value": 209.0,
|
||||
"unit": "km"
|
||||
},
|
||||
"range_full": {
|
||||
"value": 360.3,
|
||||
"unit": "km"
|
||||
},
|
||||
"battery": {
|
||||
"soc": {
|
||||
"value": 58.0,
|
||||
"unit": "%"
|
||||
},
|
||||
"temperature_max": {
|
||||
"value": 295.15,
|
||||
"unit": "degC"
|
||||
},
|
||||
"temperature_min": {
|
||||
"value": 294.15,
|
||||
"unit": "degC"
|
||||
}
|
||||
},
|
||||
"odometer": {
|
||||
"odometer": {
|
||||
"value": 67489.0,
|
||||
"unit": "km"
|
||||
}
|
||||
}
|
||||
},
|
||||
"vehicle": {
|
||||
"state": "unknown vehicle state"
|
||||
},
|
||||
"connectivity": {
|
||||
"state": "reachable"
|
||||
},
|
||||
"climatisation": {
|
||||
"state": "off",
|
||||
"remain": {
|
||||
"value": 0,
|
||||
"unit": "min"
|
||||
},
|
||||
"settings": {
|
||||
"target_temperature": {
|
||||
"value": 21.0,
|
||||
"unit": "degC"
|
||||
}
|
||||
},
|
||||
"environment": {
|
||||
"temperature_outside": {
|
||||
"value": null,
|
||||
"unit": "degC"
|
||||
}
|
||||
}
|
||||
},
|
||||
"position": {
|
||||
"lat": null,
|
||||
"lon": null
|
||||
},
|
||||
"doors": {
|
||||
"doors": {
|
||||
"overallState": "None",
|
||||
"doors": {},
|
||||
"windows": {}
|
||||
}
|
||||
},
|
||||
"procedural": {
|
||||
"range_at_100": {
|
||||
"value": 360.3,
|
||||
"unit": "km"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"ts": "2026-05-27T09:33:20.808989+00:00",
|
||||
"charging": {
|
||||
"state": "off",
|
||||
"type": "invalid",
|
||||
"power": {
|
||||
"value": 0.0,
|
||||
"unit": "kW"
|
||||
},
|
||||
"remain": {
|
||||
"value": 0,
|
||||
"unit": "min"
|
||||
},
|
||||
"settings": {
|
||||
"target_level": {
|
||||
"value": 80.0,
|
||||
"unit": "%"
|
||||
},
|
||||
"maximum_current": {
|
||||
"value": 16.0,
|
||||
"unit": "A"
|
||||
},
|
||||
"auto_unlock": "False"
|
||||
},
|
||||
"plugStatus": {
|
||||
"connection_state": "disconnected",
|
||||
"lock_state": "unlocked",
|
||||
"external_power": "unavailable"
|
||||
}
|
||||
},
|
||||
"electric_drive": {
|
||||
"range": {
|
||||
"value": 209.0,
|
||||
"unit": "km"
|
||||
},
|
||||
"range_full": {
|
||||
"value": 360.3,
|
||||
"unit": "km"
|
||||
},
|
||||
"battery": {
|
||||
"soc": {
|
||||
"value": 58.0,
|
||||
"unit": "%"
|
||||
},
|
||||
"temperature_max": {
|
||||
"value": 295.15,
|
||||
"unit": "degC"
|
||||
},
|
||||
"temperature_min": {
|
||||
"value": 294.15,
|
||||
"unit": "degC"
|
||||
}
|
||||
},
|
||||
"odometer": {
|
||||
"odometer": {
|
||||
"value": 67489.0,
|
||||
"unit": "km"
|
||||
}
|
||||
}
|
||||
},
|
||||
"vehicle": {
|
||||
"state": "unknown vehicle state"
|
||||
},
|
||||
"connectivity": {
|
||||
"state": "reachable"
|
||||
},
|
||||
"climatisation": {
|
||||
"state": "off",
|
||||
"remain": {
|
||||
"value": 0,
|
||||
"unit": "min"
|
||||
},
|
||||
"settings": {
|
||||
"target_temperature": {
|
||||
"value": 21.0,
|
||||
"unit": "degC"
|
||||
}
|
||||
},
|
||||
"environment": {
|
||||
"temperature_outside": {
|
||||
"value": null,
|
||||
"unit": "degC"
|
||||
}
|
||||
}
|
||||
},
|
||||
"position": {
|
||||
"lat": null,
|
||||
"lon": null
|
||||
},
|
||||
"doors": {
|
||||
"doors": {
|
||||
"overallState": "None",
|
||||
"doors": {},
|
||||
"windows": {}
|
||||
}
|
||||
},
|
||||
"procedural": {
|
||||
"range_at_100": {
|
||||
"value": 360.3,
|
||||
"unit": "km"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"ts": "2026-05-27T09:33:50.960434+00:00",
|
||||
"charging": {
|
||||
"state": "off",
|
||||
"type": "invalid",
|
||||
"power": {
|
||||
"value": 0.0,
|
||||
"unit": "kW"
|
||||
},
|
||||
"remain": {
|
||||
"value": 0,
|
||||
"unit": "min"
|
||||
},
|
||||
"settings": {
|
||||
"target_level": {
|
||||
"value": 80.0,
|
||||
"unit": "%"
|
||||
},
|
||||
"maximum_current": {
|
||||
"value": 16.0,
|
||||
"unit": "A"
|
||||
},
|
||||
"auto_unlock": "False"
|
||||
},
|
||||
"plugStatus": {
|
||||
"connection_state": "disconnected",
|
||||
"lock_state": "unlocked",
|
||||
"external_power": "unavailable"
|
||||
}
|
||||
},
|
||||
"electric_drive": {
|
||||
"range": {
|
||||
"value": 209.0,
|
||||
"unit": "km"
|
||||
},
|
||||
"range_full": {
|
||||
"value": 360.3,
|
||||
"unit": "km"
|
||||
},
|
||||
"battery": {
|
||||
"soc": {
|
||||
"value": 58.0,
|
||||
"unit": "%"
|
||||
},
|
||||
"temperature_max": {
|
||||
"value": 295.15,
|
||||
"unit": "degC"
|
||||
},
|
||||
"temperature_min": {
|
||||
"value": 294.15,
|
||||
"unit": "degC"
|
||||
}
|
||||
},
|
||||
"odometer": {
|
||||
"odometer": {
|
||||
"value": 67489.0,
|
||||
"unit": "km"
|
||||
}
|
||||
}
|
||||
},
|
||||
"vehicle": {
|
||||
"state": "unknown vehicle state"
|
||||
},
|
||||
"connectivity": {
|
||||
"state": "reachable"
|
||||
},
|
||||
"climatisation": {
|
||||
"state": "off",
|
||||
"remain": {
|
||||
"value": 0,
|
||||
"unit": "min"
|
||||
},
|
||||
"settings": {
|
||||
"target_temperature": {
|
||||
"value": 21.0,
|
||||
"unit": "degC"
|
||||
}
|
||||
},
|
||||
"environment": {
|
||||
"temperature_outside": {
|
||||
"value": null,
|
||||
"unit": "degC"
|
||||
}
|
||||
}
|
||||
},
|
||||
"position": {
|
||||
"lat": null,
|
||||
"lon": null
|
||||
},
|
||||
"doors": {
|
||||
"doors": {
|
||||
"overallState": "None",
|
||||
"doors": {},
|
||||
"windows": {}
|
||||
}
|
||||
},
|
||||
"procedural": {
|
||||
"range_at_100": {
|
||||
"value": 360.3,
|
||||
"unit": "km"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"ts": "2026-05-27T09:34:21.107584+00:00",
|
||||
"charging": {
|
||||
"state": "off",
|
||||
"type": "invalid",
|
||||
"power": {
|
||||
"value": 0.0,
|
||||
"unit": "kW"
|
||||
},
|
||||
"remain": {
|
||||
"value": 0,
|
||||
"unit": "min"
|
||||
},
|
||||
"settings": {
|
||||
"target_level": {
|
||||
"value": 80.0,
|
||||
"unit": "%"
|
||||
},
|
||||
"maximum_current": {
|
||||
"value": 16.0,
|
||||
"unit": "A"
|
||||
},
|
||||
"auto_unlock": "False"
|
||||
},
|
||||
"plugStatus": {
|
||||
"connection_state": "disconnected",
|
||||
"lock_state": "unlocked",
|
||||
"external_power": "unavailable"
|
||||
}
|
||||
},
|
||||
"electric_drive": {
|
||||
"range": {
|
||||
"value": 209.0,
|
||||
"unit": "km"
|
||||
},
|
||||
"range_full": {
|
||||
"value": 360.3,
|
||||
"unit": "km"
|
||||
},
|
||||
"battery": {
|
||||
"soc": {
|
||||
"value": 58.0,
|
||||
"unit": "%"
|
||||
},
|
||||
"temperature_max": {
|
||||
"value": 295.15,
|
||||
"unit": "degC"
|
||||
},
|
||||
"temperature_min": {
|
||||
"value": 294.15,
|
||||
"unit": "degC"
|
||||
}
|
||||
},
|
||||
"odometer": {
|
||||
"odometer": {
|
||||
"value": 67489.0,
|
||||
"unit": "km"
|
||||
}
|
||||
}
|
||||
},
|
||||
"vehicle": {
|
||||
"state": "unknown vehicle state"
|
||||
},
|
||||
"connectivity": {
|
||||
"state": "reachable"
|
||||
},
|
||||
"climatisation": {
|
||||
"state": "off",
|
||||
"remain": {
|
||||
"value": 0,
|
||||
"unit": "min"
|
||||
},
|
||||
"settings": {
|
||||
"target_temperature": {
|
||||
"value": 21.0,
|
||||
"unit": "degC"
|
||||
}
|
||||
},
|
||||
"environment": {
|
||||
"temperature_outside": {
|
||||
"value": null,
|
||||
"unit": "degC"
|
||||
}
|
||||
}
|
||||
},
|
||||
"position": {
|
||||
"lat": null,
|
||||
"lon": null
|
||||
},
|
||||
"doors": {
|
||||
"doors": {
|
||||
"overallState": "None",
|
||||
"doors": {},
|
||||
"windows": {}
|
||||
}
|
||||
},
|
||||
"procedural": {
|
||||
"range_at_100": {
|
||||
"value": 360.3,
|
||||
"unit": "km"
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
+17
-7
@@ -60,8 +60,9 @@ class TestJayDiffDel:
|
||||
{'a': 'hamburger', 'b': 'fries'}) == {'c': 'pepsi'}
|
||||
|
||||
def test_nested_deleted_key(self):
|
||||
# 'c' is absent from _new → None signals "delete entire key"
|
||||
assert jay_diff_del({'a': 'hamburger', 'b': 'fries', 'c': {'e': 'coke', 'f': 'coffee'}},
|
||||
{'a': 'hamburger', 'b': 'fries'}) == {'c': {'e': 'coke', 'f': 'coffee'}}
|
||||
{'a': 'hamburger', 'b': 'fries'}) == {'c': None}
|
||||
|
||||
def test_nested_partial_delete(self):
|
||||
assert jay_diff_del({'a': 'hamburger', 'b': 'fries', 'c': {'e': 'coke', 'f': 'coffee'}},
|
||||
@@ -164,7 +165,7 @@ class TestJayDiffFullSeparate:
|
||||
def test_delete_nested(self):
|
||||
assert jay_diff_full({'a': 'hamburger', 'b': 'fries', 'c': {'e': 'coke', 'f': 'coffee'}},
|
||||
{'a': 'hamburger', 'b': 'fries'}, self.F) \
|
||||
== {'delete': {'c': {'e': 'coke', 'f': 'coffee'}}}
|
||||
== {'delete': {'c': None}}
|
||||
|
||||
def test_nested_partial_delete(self):
|
||||
assert jay_diff_full({'a': 'hamburger', 'b': 'fries', 'c': {'e': 'coke', 'f': 'coffee'}},
|
||||
@@ -174,12 +175,12 @@ class TestJayDiffFullSeparate:
|
||||
def test_update_and_delete(self):
|
||||
assert jay_diff_full({'a': 'hamburger', 'b': 'fries', 'c': {'e': 'coke', 'f': 'coffee'}},
|
||||
{'a': 'hotdog', 'b': 'potatoes'}, self.F) \
|
||||
== {'delete': {'c': {'e': 'coke', 'f': 'coffee'}}, 'update': {'a': 'hotdog', 'b': 'potatoes'}}
|
||||
== {'delete': {'c': None}, 'update': {'a': 'hotdog', 'b': 'potatoes'}}
|
||||
|
||||
def test_nested_add_delete_update(self):
|
||||
assert jay_diff_full({'a': 'hamburger', 'b': 'fries', 'c': {'e': 'coke', 'f': 'coffee'}},
|
||||
{'a': 'hotdog', 'b': 'potatoes', 'd': 'icecream'}, self.F) \
|
||||
== {'add': {'d': 'icecream'}, 'delete': {'c': {'e': 'coke', 'f': 'coffee'}}, 'update': {'a': 'hotdog', 'b': 'potatoes'}}
|
||||
== {'add': {'d': 'icecream'}, 'delete': {'c': None}, 'update': {'a': 'hotdog', 'b': 'potatoes'}}
|
||||
|
||||
|
||||
# ── jay_diff_full (combine_upd_add=True) ──────────────────────────────────────
|
||||
@@ -213,12 +214,12 @@ class TestJayDiffFullCombined:
|
||||
def test_delete_nested(self):
|
||||
assert jay_diff_full({'a': 'hamburger', 'b': 'fries', 'c': {'e': 'coke', 'f': 'coffee'}},
|
||||
{'a': 'hamburger', 'b': 'fries'}, self.T) \
|
||||
== {'delete': {'c': {'e': 'coke', 'f': 'coffee'}}}
|
||||
== {'delete': {'c': None}}
|
||||
|
||||
def test_update_and_delete_nested(self):
|
||||
assert jay_diff_full({'a': 'hamburger', 'b': 'fries', 'c': {'e': 'coke', 'f': 'coffee'}},
|
||||
{'a': 'hotdog', 'b': 'potatoes'}, self.T) \
|
||||
== {'delete': {'c': {'e': 'coke', 'f': 'coffee'}}, 'update_add': {'a': 'hotdog', 'b': 'potatoes'}}
|
||||
== {'delete': {'c': None}, 'update_add': {'a': 'hotdog', 'b': 'potatoes'}}
|
||||
|
||||
def test_nested_update_add_and_delete(self):
|
||||
assert jay_diff_full({'a': 'hamburger', 'b': 'fries', 'c': {'e': 'coke', 'f': 'coffee'}},
|
||||
@@ -412,7 +413,7 @@ class TestCompletelyDifferentStructure:
|
||||
diff = jay_diff_full(old, new)
|
||||
assert diff == {
|
||||
'update_add': {'electric_drive': {'range': 300, 'soc': 80}},
|
||||
'delete': {'charging': {'state': 'charging', 'power': 11}},
|
||||
'delete': {'charging': None},
|
||||
}
|
||||
|
||||
def test_merge_round_trip_all_keys_replaced(self):
|
||||
@@ -465,6 +466,15 @@ class TestEmptyContainers:
|
||||
diff = jay_diff_del(old, new)
|
||||
assert diff == {'a': {'b': 'c'}}
|
||||
|
||||
def test_empty_dict_preserved_after_delete_all_subkeys(self):
|
||||
# When new has an empty dict at a location, the key must survive with {}
|
||||
# (not be pruned away). This would break if merge incorrectly prunes empty dicts.
|
||||
old = {'x': {'p': 1, 'q': 2}}
|
||||
new = {'x': {}}
|
||||
diff = jay_diff_full(old, new)
|
||||
result = jay_merge_full(deepcopy(old), diff)
|
||||
assert result == new # 'x' must remain as {}
|
||||
|
||||
|
||||
# ── Corner cases: deep nesting ────────────────────────────────────────────────
|
||||
|
||||
|
||||
@@ -0,0 +1,93 @@
|
||||
"""
|
||||
Round-trip tests for jay_diff using real snapshot records from ./tests/data/.
|
||||
|
||||
For every pair of consecutive records (within a file and across adjacent files)
|
||||
we verify:
|
||||
jay_merge_full(a, jay_diff_full(a, b)) == b
|
||||
|
||||
We also verify that diffing identical records produces an empty diff, and that
|
||||
applying an empty diff is a no-op.
|
||||
"""
|
||||
|
||||
import json
|
||||
import pathlib
|
||||
|
||||
import pytest
|
||||
|
||||
from utils.jay_diff import jay_diff_full, jay_merge_full
|
||||
|
||||
DATA_DIR = pathlib.Path(__file__).parent / "data"
|
||||
|
||||
|
||||
def _load_all_records() -> list[dict]:
|
||||
"""Return all records from all data files, sorted by timestamp."""
|
||||
records: list[dict] = []
|
||||
for path in sorted(DATA_DIR.glob("*.json")):
|
||||
store = json.loads(path.read_text())
|
||||
records.extend(store.get("records", []))
|
||||
records.sort(key=lambda r: r.get("ts", ""))
|
||||
return records
|
||||
|
||||
|
||||
ALL_RECORDS = _load_all_records()
|
||||
CONSECUTIVE_PAIRS = list(zip(ALL_RECORDS, ALL_RECORDS[1:]))
|
||||
|
||||
|
||||
class TestRealDataRoundTrip:
|
||||
"""Diff+merge of every consecutive snapshot pair must reconstruct the target."""
|
||||
|
||||
@pytest.mark.parametrize("pair_index", range(len(CONSECUTIVE_PAIRS)))
|
||||
def test_consecutive_round_trip(self, pair_index: int) -> None:
|
||||
a, b = CONSECUTIVE_PAIRS[pair_index]
|
||||
diff = jay_diff_full(a, b)
|
||||
result = jay_merge_full(a, diff)
|
||||
assert result == b, (
|
||||
f"Round-trip failed for pair #{pair_index} "
|
||||
f"(ts: {a.get('ts')} → {b.get('ts')})"
|
||||
)
|
||||
|
||||
@pytest.mark.parametrize("pair_index", range(len(CONSECUTIVE_PAIRS)))
|
||||
def test_diff_of_identical_is_empty(self, pair_index: int) -> None:
|
||||
a, _ = CONSECUTIVE_PAIRS[pair_index]
|
||||
assert jay_diff_full(a, a) == {}
|
||||
|
||||
@pytest.mark.parametrize("pair_index", range(len(CONSECUTIVE_PAIRS)))
|
||||
def test_empty_diff_is_noop(self, pair_index: int) -> None:
|
||||
a, _ = CONSECUTIVE_PAIRS[pair_index]
|
||||
assert jay_merge_full(a, {}) == a
|
||||
|
||||
|
||||
class TestRealDataProperties:
|
||||
"""Structural invariants on the real snapshot data."""
|
||||
|
||||
def test_data_dir_has_files(self) -> None:
|
||||
assert len(list(DATA_DIR.glob("*.json"))) > 0, "No JSON files in tests/data/"
|
||||
|
||||
def test_all_records_have_timestamp(self) -> None:
|
||||
for rec in ALL_RECORDS:
|
||||
assert "ts" in rec, f"Record missing 'ts': {list(rec.keys())}"
|
||||
|
||||
def test_records_are_sorted(self) -> None:
|
||||
ts_list = [r["ts"] for r in ALL_RECORDS]
|
||||
assert ts_list == sorted(ts_list), "Records are not in ascending timestamp order"
|
||||
|
||||
def test_total_record_count(self) -> None:
|
||||
assert len(ALL_RECORDS) > 100, (
|
||||
f"Expected >100 records across all files, got {len(ALL_RECORDS)}"
|
||||
)
|
||||
|
||||
def test_diff_size_smaller_than_full_snapshot(self) -> None:
|
||||
"""Diffs between similar consecutive records should usually be smaller."""
|
||||
savings = 0
|
||||
for a, b in CONSECUTIVE_PAIRS:
|
||||
diff = jay_diff_full(a, b)
|
||||
if diff: # skip identical pairs
|
||||
diff_len = len(json.dumps(diff))
|
||||
full_len = len(json.dumps(b))
|
||||
if diff_len < full_len:
|
||||
savings += 1
|
||||
ratio = savings / len(CONSECUTIVE_PAIRS) if CONSECUTIVE_PAIRS else 1.0
|
||||
assert ratio > 0.5, (
|
||||
f"Expected diffs to be smaller than full snapshots for >50% of pairs, "
|
||||
f"got {ratio:.0%}"
|
||||
)
|
||||
+34
-28
@@ -25,8 +25,16 @@ def jay_diff_add(_old, _new):
|
||||
|
||||
|
||||
def jay_diff_del(_old, _new):
|
||||
__new = deepcopy(_new)
|
||||
delta = jay_diff_rev(__new, _old)
|
||||
delta = {}
|
||||
for k in _old:
|
||||
if k in _new:
|
||||
if isinstance(_old[k], dict) and isinstance(_new[k], dict):
|
||||
sub = jay_diff_del(_old[k], _new[k])
|
||||
if sub:
|
||||
delta[k] = sub
|
||||
else:
|
||||
# key absent from _new: None signals "delete entire key" for dicts
|
||||
delta[k] = None if isinstance(_old[k], dict) else _old[k]
|
||||
return delta
|
||||
|
||||
|
||||
@@ -100,15 +108,13 @@ def jay_merge_add(_old, _new):
|
||||
|
||||
|
||||
def jay_merge_delete(_old, _new):
|
||||
for k, v in _new.items():
|
||||
for k, v in list(_new.items()):
|
||||
if isinstance(v, dict) and v:
|
||||
jay_merge_delete(_old[k], _new[k])
|
||||
if len(_old[k].keys()) == 0:
|
||||
del _old[k]
|
||||
if k in _old and isinstance(_old[k], dict):
|
||||
jay_merge_delete(_old[k], v)
|
||||
else:
|
||||
if k in _old:
|
||||
del _old[k]
|
||||
|
||||
return _old
|
||||
|
||||
|
||||
@@ -171,7 +177,7 @@ def _legacy_assertions() -> None:
|
||||
assert result == {}
|
||||
|
||||
result = jay_diff_del({'a': 'hamburger', 'b': 'fries', 'c': {'e': 'coke', 'f': 'coffee'}}, {'a': 'hamburger', 'b': 'fries'})
|
||||
assert result == {'c': {'e': 'coke', 'f': 'coffee'}}
|
||||
assert result == {'c': None}
|
||||
|
||||
result = jay_diff_del({'a': 'hamburger', 'b': 'fries', 'c': {'e': 'coke', 'f': 'coffee'}}, {'c': {'e': 'coke'}})
|
||||
assert result == {'a':'hamburger', 'b':'fries', 'c': {'f': 'coffee'}}
|
||||
@@ -275,7 +281,7 @@ def _legacy_assertions() -> None:
|
||||
assert result == {'update': {'c': 'coke'}}
|
||||
|
||||
result = jay_diff_full({'a': 'hamburger', 'b': 'fries', 'c': {'e': 'coke', 'f': 'coffee'}}, {'a': 'hamburger', 'b': 'fries'}, combine_upd_add)
|
||||
assert result == {'delete': {'c': {'e': 'coke', 'f': 'coffee'}}}
|
||||
assert result == {'delete': {'c': None}}
|
||||
|
||||
result = jay_diff_full({'a': 'hamburger', 'b': 'fries', 'c': {'e': 'coke', 'f': 'coffee'}}, {'a': 'hamburger', 'b': 'fries', 'c': {'f': 'coffee'}}, combine_upd_add)
|
||||
assert result == {'delete': {'c': {'e': 'coke'}}}
|
||||
@@ -284,25 +290,25 @@ def _legacy_assertions() -> None:
|
||||
assert result == {'delete': {'a': 'hamburger', 'b': 'fries', 'c': {'f': 'coffee'}}}
|
||||
|
||||
result = jay_diff_full({'a':'hamburger', 'b':'fries', 'c': {'e': 'coke', 'f': 'coffee'}}, {'a':'hotdog', 'b':'potatoes'}, combine_upd_add)
|
||||
assert result == {'delete': {'c': {'e': 'coke', 'f': 'coffee'}}, 'update': {'a': 'hotdog', 'b': 'potatoes'}}
|
||||
assert result == {'delete': {'c': None}, 'update': {'a': 'hotdog', 'b': 'potatoes'}}
|
||||
|
||||
result = jay_diff_full({'a':'hamburger', 'b':'fries', 'c': {'e': 'coke', 'f': 'coffee'}}, {'c': {'e': 'pepsi'}}, combine_upd_add)
|
||||
assert result == {'delete': {'a': 'hamburger', 'b': 'fries', 'c': {'f': 'coffee'}}, 'update': {'c': {'e': 'pepsi'}}}
|
||||
|
||||
result = jay_diff_full({'a':'hamburger', 'b':'fries', 'c': {'e': 'coke', 'f': 'coffee'}}, {'a':'hotdog', 'b':'fries'}, combine_upd_add)
|
||||
assert result == {'delete': {'c': {'e': 'coke', 'f': 'coffee'}}, 'update': {'a': 'hotdog'}}
|
||||
assert result == {'delete': {'c': None}, 'update': {'a': 'hotdog'}}
|
||||
|
||||
result = jay_diff_full({'a':'hamburger', 'b':'fries', 'c': {'e': 'coke', 'f': 'coffee'}}, {'a':'hamburger', 'b':'fries', 'c': {'e': 'pepsi', 'f': 'coffee'}}, combine_upd_add)
|
||||
assert result == {'update': {'c': {'e': 'pepsi'}}}
|
||||
|
||||
result = jay_diff_full({'a':'hamburger', 'b':'fries', 'c': {'e': 'coke', 'f': 'coffee'}}, {'a':'hotdog', 'b':'potatoes', 'd': 'icecream'}, combine_upd_add)
|
||||
assert result == {'add': {'d': 'icecream'}, 'delete': {'c': {'e': 'coke', 'f': 'coffee'}}, 'update': {'a': 'hotdog', 'b': 'potatoes'}}
|
||||
assert result == {'add': {'d': 'icecream'}, 'delete': {'c': None}, 'update': {'a': 'hotdog', 'b': 'potatoes'}}
|
||||
|
||||
result = jay_diff_full({'a':'hamburger', 'b':'fries', 'c': {'e': 'coke', 'f': 'coffee'}}, {'c': {'e': 'pepsi', 'g': 'tea', 'h': 'cake'}, 'b':'potatoes'}, combine_upd_add)
|
||||
assert result == {'add': {'c': {'g': 'tea', 'h': 'cake'}}, 'delete': {'a': 'hamburger', 'c': {'f': 'coffee'}}, 'update': {'b': 'potatoes', 'c': {'e': 'pepsi'}}}
|
||||
|
||||
result = jay_diff_full({'a':'hamburger', 'b':'fries', 'c': {'e': 'coke', 'f': 'coffee'}}, {'a':'hotdog', 'b':'fries'}, combine_upd_add)
|
||||
assert result == {'delete': {'c': {'e': 'coke', 'f': 'coffee'}}, 'update': {'a': 'hotdog'}}
|
||||
assert result == {'delete': {'c': None}, 'update': {'a': 'hotdog'}}
|
||||
|
||||
result = jay_diff_full({'a':'hamburger', 'b':'fries', 'c': {'e': 'coke', 'f': 'coffee'}}, {'a':'hamburger', 'b':'fries', 'c': {'e': 'pepsi', 'f': 'coffee'}}, combine_upd_add)
|
||||
assert result == {'update': {'c': {'e': 'pepsi'}}}
|
||||
@@ -347,7 +353,7 @@ def _legacy_assertions() -> None:
|
||||
assert result == {'update_add': {'c': 'coke'}}
|
||||
|
||||
result = jay_diff_full({'a': 'hamburger', 'b': 'fries', 'c': {'e': 'coke', 'f': 'coffee'}}, {'a': 'hamburger', 'b': 'fries'}, combine_upd_add)
|
||||
assert result == {'delete': {'c': {'e': 'coke', 'f': 'coffee'}}}
|
||||
assert result == {'delete': {'c': None}}
|
||||
|
||||
result = jay_diff_full({'a': 'hamburger', 'b': 'fries', 'c': {'e': 'coke', 'f': 'coffee'}}, {'a': 'hamburger', 'b': 'fries', 'c': {'f': 'coffee'}}, combine_upd_add)
|
||||
assert result == {'delete': {'c': {'e': 'coke'}}}
|
||||
@@ -356,25 +362,25 @@ def _legacy_assertions() -> None:
|
||||
assert result == {'delete': {'a': 'hamburger', 'b': 'fries', 'c': {'f': 'coffee'}}}
|
||||
|
||||
result = jay_diff_full({'a':'hamburger', 'b':'fries', 'c': {'e': 'coke', 'f': 'coffee'}}, {'a':'hotdog', 'b':'potatoes'}, combine_upd_add)
|
||||
assert result == {'delete': {'c': {'e': 'coke', 'f': 'coffee'}}, 'update_add': {'a': 'hotdog', 'b': 'potatoes'}}
|
||||
assert result == {'delete': {'c': None}, 'update_add': {'a': 'hotdog', 'b': 'potatoes'}}
|
||||
|
||||
result = jay_diff_full({'a':'hamburger', 'b':'fries', 'c': {'e': 'coke', 'f': 'coffee'}}, {'c': {'e': 'pepsi'}}, combine_upd_add)
|
||||
assert result == {'delete': {'a': 'hamburger', 'b': 'fries', 'c': {'f': 'coffee'}}, 'update_add': {'c': {'e': 'pepsi'}}}
|
||||
|
||||
result = jay_diff_full({'a':'hamburger', 'b':'fries', 'c': {'e': 'coke', 'f': 'coffee'}}, {'a':'hotdog', 'b':'fries'}, combine_upd_add)
|
||||
assert result == {'delete': {'c': {'e': 'coke', 'f': 'coffee'}}, 'update_add': {'a': 'hotdog'}}
|
||||
assert result == {'delete': {'c': None}, 'update_add': {'a': 'hotdog'}}
|
||||
|
||||
result = jay_diff_full({'a':'hamburger', 'b':'fries', 'c': {'e': 'coke', 'f': 'coffee'}}, {'a':'hamburger', 'b':'fries', 'c': {'e': 'pepsi', 'f': 'coffee'}}, combine_upd_add)
|
||||
assert result == {'update_add': {'c': {'e': 'pepsi'}}}
|
||||
|
||||
result = jay_diff_full({'a':'hamburger', 'b':'fries', 'c': {'e': 'coke', 'f': 'coffee'}}, {'a':'hotdog', 'b':'potatoes', 'd': 'icecream'}, combine_upd_add)
|
||||
assert result == {'delete': {'c': {'e': 'coke', 'f': 'coffee'}}, 'update_add': {'a': 'hotdog', 'b': 'potatoes', 'd': 'icecream'}}
|
||||
assert result == {'delete': {'c': None}, 'update_add': {'a': 'hotdog', 'b': 'potatoes', 'd': 'icecream'}}
|
||||
|
||||
result = jay_diff_full({'a':'hamburger', 'b':'fries', 'c': {'e': 'coke', 'f': 'coffee'}}, {'c': {'e': 'pepsi', 'g': 'tea', 'h': 'cake'}, 'b':'potatoes'}, combine_upd_add)
|
||||
assert result == {'delete': {'a': 'hamburger', 'c': {'f': 'coffee'}}, 'update_add': {'b': 'potatoes', 'c': {'e': 'pepsi', 'g': 'tea', 'h': 'cake'}}}
|
||||
|
||||
result = jay_diff_full({'a':'hamburger', 'b':'fries', 'c': {'e': 'coke', 'f': 'coffee'}}, {'a':'hotdog', 'b':'fries'}, combine_upd_add)
|
||||
assert result == {'delete': {'c': {'e': 'coke', 'f': 'coffee'}}, 'update_add': {'a': 'hotdog'}}
|
||||
assert result == {'delete': {'c': None}, 'update_add': {'a': 'hotdog'}}
|
||||
|
||||
result = jay_diff_full({'a':'hamburger', 'b':'fries', 'c': {'e': 'coke', 'f': 'coffee'}}, {'a':'hamburger', 'b':'fries', 'c': {'e': 'pepsi', 'f': 'coffee'}}, combine_upd_add)
|
||||
assert result == {'update_add': {'c': {'e': 'pepsi'}}}
|
||||
@@ -443,7 +449,7 @@ def _legacy_assertions() -> None:
|
||||
result = jay_merge_full({'a': 'hamburger', 'b': 'fries', 'c': 'pepsi'}, {'update': {'c': 'coke'}})
|
||||
assert result == {'a': 'hamburger', 'b': 'fries', 'c': 'coke'}
|
||||
|
||||
result = jay_merge_full({'a': 'hamburger', 'b': 'fries', 'c': {'e': 'coke', 'f': 'coffee'}}, {'delete': {'c': {'e': 'coke', 'f': 'coffee'}}})
|
||||
result = jay_merge_full({'a': 'hamburger', 'b': 'fries', 'c': {'e': 'coke', 'f': 'coffee'}}, {'delete': {'c': None}})
|
||||
assert result == {'a': 'hamburger', 'b': 'fries'}
|
||||
|
||||
result = jay_merge_full({'a': 'hamburger', 'b': 'fries', 'c': {'e': 'coke', 'f': 'coffee'}}, {'delete': {'c': {'e': 'coke'}}})
|
||||
@@ -452,25 +458,25 @@ def _legacy_assertions() -> None:
|
||||
result = jay_merge_full({'a': 'hamburger', 'b': 'fries', 'c': {'e': 'coke', 'f': 'coffee'}}, {'delete': {'a': 'hamburger', 'b': 'fries', 'c': {'f': 'coffee'}}})
|
||||
assert result == {'c': {'e': 'coke'}}
|
||||
|
||||
result = jay_merge_full({'a':'hamburger', 'b':'fries', 'c': {'e': 'coke', 'f': 'coffee'}}, {'delete': {'c': {'e': 'coke', 'f': 'coffee'}}, 'update': {'a': 'hotdog', 'b': 'potatoes'}})
|
||||
result = jay_merge_full({'a':'hamburger', 'b':'fries', 'c': {'e': 'coke', 'f': 'coffee'}}, {'delete': {'c': None}, 'update': {'a': 'hotdog', 'b': 'potatoes'}})
|
||||
assert result == {'a':'hotdog', 'b':'potatoes'}
|
||||
|
||||
result = jay_merge_full({'a':'hamburger', 'b':'fries', 'c': {'e': 'coke', 'f': 'coffee'}}, {'delete': {'a': 'hamburger', 'b': 'fries', 'c': {'f': 'coffee'}}, 'update': {'c': {'e': 'pepsi'}}})
|
||||
assert result == {'c': {'e': 'pepsi'}}
|
||||
|
||||
result = jay_merge_full({'a':'hamburger', 'b':'fries', 'c': {'e': 'coke', 'f': 'coffee'}}, {'delete': {'c': {'e': 'coke', 'f': 'coffee'}}, 'update': {'a': 'hotdog'}})
|
||||
result = jay_merge_full({'a':'hamburger', 'b':'fries', 'c': {'e': 'coke', 'f': 'coffee'}}, {'delete': {'c': None}, 'update': {'a': 'hotdog'}})
|
||||
assert result == {'a':'hotdog', 'b':'fries'}
|
||||
|
||||
result = jay_merge_full({'a':'hamburger', 'b':'fries', 'c': {'e': 'coke', 'f': 'coffee'}}, {'update': {'c': {'e': 'pepsi'}}})
|
||||
assert result == {'a':'hamburger', 'b':'fries', 'c': {'e': 'pepsi', 'f': 'coffee'}}
|
||||
|
||||
result = jay_merge_full({'a':'hamburger', 'b':'fries', 'c': {'e': 'coke', 'f': 'coffee'}}, {'add': {'d': 'icecream'}, 'delete': {'c': {'e': 'coke', 'f': 'coffee'}}, 'update': {'a': 'hotdog', 'b': 'potatoes'}})
|
||||
result = jay_merge_full({'a':'hamburger', 'b':'fries', 'c': {'e': 'coke', 'f': 'coffee'}}, {'add': {'d': 'icecream'}, 'delete': {'c': None}, 'update': {'a': 'hotdog', 'b': 'potatoes'}})
|
||||
assert result == {'a':'hotdog', 'b':'potatoes', 'd': 'icecream'}
|
||||
|
||||
result = jay_merge_full({'a':'hamburger', 'b':'fries', 'c': {'e': 'coke', 'f': 'coffee'}}, {'add': {'c': {'g': 'tea', 'h': 'cake'}}, 'delete': {'a': 'hamburger', 'c': {'f': 'coffee'}}, 'update': {'b': 'potatoes', 'c': {'e': 'pepsi'}}})
|
||||
assert result == {'c': {'e': 'pepsi', 'g': 'tea', 'h': 'cake'}, 'b':'potatoes'}
|
||||
|
||||
result = jay_merge_full({'a':'hamburger', 'b':'fries', 'c': {'e': 'coke', 'f': 'coffee'}}, {'delete': {'c': {'e': 'coke', 'f': 'coffee'}}, 'update': {'a': 'hotdog'}})
|
||||
result = jay_merge_full({'a':'hamburger', 'b':'fries', 'c': {'e': 'coke', 'f': 'coffee'}}, {'delete': {'c': None}, 'update': {'a': 'hotdog'}})
|
||||
assert result == {'a':'hotdog', 'b':'fries'}
|
||||
|
||||
result = jay_merge_full({'a':'hamburger', 'b':'fries', 'c': {'e': 'coke', 'f': 'coffee'}}, {'update': {'c': {'e': 'pepsi'}}})
|
||||
@@ -515,7 +521,7 @@ def _legacy_assertions() -> None:
|
||||
result = jay_merge_full({'a': 'hamburger', 'b': 'fries', 'c': 'pepsi'}, {'update_add': {'c': 'coke'}})
|
||||
assert result == {'a': 'hamburger', 'b': 'fries', 'c': 'coke'}
|
||||
|
||||
result = jay_merge_full({'a': 'hamburger', 'b': 'fries', 'c': {'e': 'coke', 'f': 'coffee'}}, {'delete': {'c': {'e': 'coke', 'f': 'coffee'}}})
|
||||
result = jay_merge_full({'a': 'hamburger', 'b': 'fries', 'c': {'e': 'coke', 'f': 'coffee'}}, {'delete': {'c': None}})
|
||||
assert result == {'a': 'hamburger', 'b': 'fries'}
|
||||
|
||||
result = jay_merge_full({'a': 'hamburger', 'b': 'fries', 'c': {'e': 'coke', 'f': 'coffee'}}, {'delete': {'c': {'e': 'coke'}}})
|
||||
@@ -524,25 +530,25 @@ def _legacy_assertions() -> None:
|
||||
result = jay_merge_full({'a': 'hamburger', 'b': 'fries', 'c': {'e': 'coke', 'f': 'coffee'}}, {'delete': {'a': 'hamburger', 'b': 'fries', 'c': {'f': 'coffee'}}})
|
||||
assert result == {'c': {'e': 'coke'}}
|
||||
|
||||
result = jay_merge_full({'a':'hamburger', 'b':'fries', 'c': {'e': 'coke', 'f': 'coffee'}}, {'delete': {'c': {'e': 'coke', 'f': 'coffee'}}, 'update_add': {'a': 'hotdog', 'b': 'potatoes'}})
|
||||
result = jay_merge_full({'a':'hamburger', 'b':'fries', 'c': {'e': 'coke', 'f': 'coffee'}}, {'delete': {'c': None}, 'update_add': {'a': 'hotdog', 'b': 'potatoes'}})
|
||||
assert result == {'a':'hotdog', 'b':'potatoes'}
|
||||
|
||||
result = jay_merge_full({'a':'hamburger', 'b':'fries', 'c': {'e': 'coke', 'f': 'coffee'}}, {'delete': {'a': 'hamburger', 'b': 'fries', 'c': {'f': 'coffee'}}, 'update_add': {'c': {'e': 'pepsi'}}})
|
||||
assert result == {'c': {'e': 'pepsi'}}
|
||||
|
||||
result = jay_merge_full({'a':'hamburger', 'b':'fries', 'c': {'e': 'coke', 'f': 'coffee'}}, {'delete': {'c': {'e': 'coke', 'f': 'coffee'}}, 'update_add': {'a': 'hotdog'}})
|
||||
result = jay_merge_full({'a':'hamburger', 'b':'fries', 'c': {'e': 'coke', 'f': 'coffee'}}, {'delete': {'c': None}, 'update_add': {'a': 'hotdog'}})
|
||||
assert result == {'a':'hotdog', 'b':'fries'}
|
||||
|
||||
result = jay_merge_full({'a':'hamburger', 'b':'fries', 'c': {'e': 'coke', 'f': 'coffee'}}, {'update_add': {'c': {'e': 'pepsi'}}})
|
||||
assert result == {'a':'hamburger', 'b':'fries', 'c': {'e': 'pepsi', 'f': 'coffee'}}
|
||||
|
||||
result = jay_merge_full({'a':'hamburger', 'b':'fries', 'c': {'e': 'coke', 'f': 'coffee'}}, {'delete': {'c': {'e': 'coke', 'f': 'coffee'}}, 'update_add': {'a': 'hotdog', 'b': 'potatoes', 'd': 'icecream'}})
|
||||
result = jay_merge_full({'a':'hamburger', 'b':'fries', 'c': {'e': 'coke', 'f': 'coffee'}}, {'delete': {'c': None}, 'update_add': {'a': 'hotdog', 'b': 'potatoes', 'd': 'icecream'}})
|
||||
assert result == {'a':'hotdog', 'b':'potatoes', 'd': 'icecream'}
|
||||
|
||||
result = jay_merge_full({'a':'hamburger', 'b':'fries', 'c': {'e': 'coke', 'f': 'coffee'}}, {'delete': {'a': 'hamburger', 'c': {'f': 'coffee'}}, 'update_add': {'b': 'potatoes', 'c': {'e': 'pepsi', 'g': 'tea', 'h': 'cake'}}})
|
||||
assert result == {'b':'potatoes', 'c': {'e': 'pepsi', 'g': 'tea', 'h': 'cake'}}
|
||||
|
||||
result = jay_merge_full({'a':'hamburger', 'b':'fries', 'c': {'e': 'coke', 'f': 'coffee'}}, {'delete': {'c': {'e': 'coke', 'f': 'coffee'}}, 'update_add': {'a': 'hotdog'}})
|
||||
result = jay_merge_full({'a':'hamburger', 'b':'fries', 'c': {'e': 'coke', 'f': 'coffee'}}, {'delete': {'c': None}, 'update_add': {'a': 'hotdog'}})
|
||||
assert result == {'a':'hotdog', 'b':'fries'}
|
||||
|
||||
result = jay_merge_full({'a':'hamburger', 'b':'fries', 'c': {'e': 'coke', 'f': 'coffee'}}, {'update_add': {'c': {'e': 'pepsi'}}})
|
||||
|
||||
Reference in New Issue
Block a user