feat(battery): plan a periodic full charge of the stationary battery (#1486)

LFP batteries can only recalibrate their state of charge close to full
charge, so many systems expect a full charge every few weeks and some
start one on their own at an arbitrary, possibly expensive time. EOS can
now plan it instead (GENETIC only, off by default).

New battery settings: full_charge_interval_days (unset = off),
full_charge_soc_percentage, full_charge_detect_percentage,
full_charge_allow_grid and the informational cell_chemistry. The last
full charge is read from the measured SoC history; nothing is stored.
When the next one is due within the horizon (or overdue), the maximum
SoC is raised to the target. With full_charge_allow_grid the optimizer
must reach the target once before the deadline, which is at least 24 h
away (penalty battery_full_charge_miss on the best SoC reached), so it
picks the cheapest time itself. Without it, PV surplus may fill the
battery but the target is not enforced.

Signed-off-by: SchaafAlexander <86607129+SchaafAlexander@users.noreply.github.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
SchaafAlexander
2026-11-08 07:40:13 +01:00
committed by GitHub
co-authored by Claude Opus 5.5
parent b33953d445
commit a06473b6d6
12 changed files with 1085 additions and 6 deletions
+207
View File
@@ -3682,6 +3682,92 @@
"x-scope": [
"GENETIC"
]
},
"cell_chemistry": {
"anyOf": [
{
"type": "string",
"enum": [
"LFP",
"NMC",
"NCA",
"LTO"
]
},
{
"type": "null"
}
],
"title": "Cell Chemistry",
"description": "Cell chemistry of the battery. Informational only, it does not enable anything. LFP batteries usually need a periodic full charge to recalibrate their state of charge, see 'full_charge_interval_days'.",
"examples": [
null,
"LFP"
],
"x-scope": [
"GENETIC"
]
},
"full_charge_interval_days": {
"anyOf": [
{
"type": "number",
"exclusiveMinimum": 0.0
},
{
"type": "null"
}
],
"title": "Full Charge Interval Days",
"description": "Maximum number of days between two full charges of a stationary battery [d]. The last full charge is taken from the measured state of charge. When the next one falls due within the optimization horizon, the maximum state of charge is raised to 'full_charge_soc_percentage'. None disables the periodic full charge. Ignored for electric vehicles.",
"examples": [
null,
21
],
"x-scope": [
"GENETIC"
]
},
"full_charge_soc_percentage": {
"type": "integer",
"maximum": 100.0,
"minimum": 1.0,
"title": "Full Charge Soc Percentage",
"description": "State of charge a periodic full charge has to reach [%]. May be above 'max_soc_percentage', which then applies on the other days.",
"default": 100,
"examples": [
100
],
"x-scope": [
"GENETIC"
]
},
"full_charge_detect_percentage": {
"type": "integer",
"maximum": 100.0,
"minimum": 1.0,
"title": "Full Charge Detect Percentage",
"description": "Measured state of charge from which the battery counts as fully charged [%]. Many battery management systems never report exactly 100 %.",
"default": 99,
"examples": [
99
],
"x-scope": [
"GENETIC"
]
},
"full_charge_allow_grid": {
"type": "boolean",
"title": "Full Charge Allow Grid",
"description": "Enforce a due full charge, charging from the grid if needed. False only raises the maximum state of charge, so that PV surplus can fill the battery, without enforcing the target.",
"default": false,
"examples": [
false,
true
],
"x-scope": [
"GENETIC"
]
}
},
"type": "object",
@@ -3930,6 +4016,92 @@
"GENETIC"
]
},
"cell_chemistry": {
"anyOf": [
{
"type": "string",
"enum": [
"LFP",
"NMC",
"NCA",
"LTO"
]
},
{
"type": "null"
}
],
"title": "Cell Chemistry",
"description": "Cell chemistry of the battery. Informational only, it does not enable anything. LFP batteries usually need a periodic full charge to recalibrate their state of charge, see 'full_charge_interval_days'.",
"examples": [
null,
"LFP"
],
"x-scope": [
"GENETIC"
]
},
"full_charge_interval_days": {
"anyOf": [
{
"type": "number",
"exclusiveMinimum": 0.0
},
{
"type": "null"
}
],
"title": "Full Charge Interval Days",
"description": "Maximum number of days between two full charges of a stationary battery [d]. The last full charge is taken from the measured state of charge. When the next one falls due within the optimization horizon, the maximum state of charge is raised to 'full_charge_soc_percentage'. None disables the periodic full charge. Ignored for electric vehicles.",
"examples": [
null,
21
],
"x-scope": [
"GENETIC"
]
},
"full_charge_soc_percentage": {
"type": "integer",
"maximum": 100.0,
"minimum": 1.0,
"title": "Full Charge Soc Percentage",
"description": "State of charge a periodic full charge has to reach [%]. May be above 'max_soc_percentage', which then applies on the other days.",
"default": 100,
"examples": [
100
],
"x-scope": [
"GENETIC"
]
},
"full_charge_detect_percentage": {
"type": "integer",
"maximum": 100.0,
"minimum": 1.0,
"title": "Full Charge Detect Percentage",
"description": "Measured state of charge from which the battery counts as fully charged [%]. Many battery management systems never report exactly 100 %.",
"default": 99,
"examples": [
99
],
"x-scope": [
"GENETIC"
]
},
"full_charge_allow_grid": {
"type": "boolean",
"title": "Full Charge Allow Grid",
"description": "Enforce a due full charge, charging from the grid if needed. False only raises the maximum state of charge, so that PV surplus can fill the battery, without enforcing the target.",
"default": false,
"examples": [
false,
true
],
"x-scope": [
"GENETIC"
]
},
"measurement_key_soc_factor": {
"type": "string",
"title": "Measurement Key Soc Factor",
@@ -16814,6 +16986,41 @@
"examples": [
0.12
]
},
"full_charge_soc_percentage": {
"anyOf": [
{
"type": "integer",
"maximum": 100.0,
"minimum": 1.0
},
{
"type": "null"
}
],
"title": "Full Charge Soc Percentage",
"description": "State of charge a due full charge has to reach at least once before 'full_charge_deadline_datetime' [%]. Must not exceed 'max_soc_percentage'. None means no full charge is due.",
"examples": [
null,
100
]
},
"full_charge_deadline_datetime": {
"anyOf": [
{
"type": "string",
"format": "date-time"
},
{
"type": "null"
}
],
"title": "Full Charge Deadline Datetime",
"description": "Moment by which 'full_charge_soc_percentage' has to be reached. A date time without timezone is read as local time. None means end of the optimization horizon.",
"examples": [
null,
"2026-11-20T07:00:00+01:00"
]
}
},
"additionalProperties": false,