Optimizer: add feasible status (#32632)
This commit is contained in:
parent
b29b49e305
commit
d09a952cba
5 changed files with 32 additions and 17 deletions
|
|
@ -16,7 +16,7 @@
|
|||
import { defineComponent, type PropType } from "vue";
|
||||
import Tooltip from "bootstrap/js/dist/tooltip";
|
||||
|
||||
type Variant = "success" | "warning" | "muted";
|
||||
type Variant = "success" | "warning" | "danger" | "muted";
|
||||
|
||||
export default defineComponent({
|
||||
name: "StatusIndicator",
|
||||
|
|
@ -34,6 +34,8 @@ export default defineComponent({
|
|||
return "bg-success";
|
||||
case "warning":
|
||||
return "bg-warning";
|
||||
case "danger":
|
||||
return "bg-danger";
|
||||
default:
|
||||
return "border border-secondary";
|
||||
}
|
||||
|
|
|
|||
|
|
@ -41,6 +41,10 @@ Payment.args = { ...base, status: OptimizationStatus.OPTIMAL, netCost: 12.4 };
|
|||
export const Credit = Template.bind({});
|
||||
Credit.args = { ...base, status: OptimizationStatus.OPTIMAL, netCost: -3.8 };
|
||||
|
||||
// usable but not proven optimal plan
|
||||
export const Feasible = Template.bind({});
|
||||
Feasible.args = { ...base, status: OptimizationStatus.FEASIBLE, netCost: 12.4 };
|
||||
|
||||
// solver could not produce a plan
|
||||
export const Infeasible = Template.bind({});
|
||||
Infeasible.args = { ...base, status: OptimizationStatus.INFEASIBLE, netCost: 0 };
|
||||
|
|
|
|||
|
|
@ -11,7 +11,9 @@
|
|||
<div class="field-caption small evcc-gray mt-1">always, fixed</div>
|
||||
</div>
|
||||
<div class="field-value gap-2">
|
||||
<span class="large fw-bold text-lowercase evcc-default-text">Lowest cost</span>
|
||||
<span class="value-text fw-bold text-lowercase evcc-default-text"
|
||||
>Lowest cost</span
|
||||
>
|
||||
<LockIcon class="value-icon" />
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -32,7 +34,7 @@
|
|||
@change="onStrategyChange"
|
||||
>
|
||||
<span
|
||||
class="large fw-bold text-lowercase evcc-default-text secondary-goal"
|
||||
class="value-text fw-bold text-lowercase evcc-default-text secondary-goal"
|
||||
>{{ secondaryGoalLabel }}</span
|
||||
>
|
||||
</CustomSelect>
|
||||
|
|
@ -60,7 +62,7 @@
|
|||
</div>
|
||||
<div class="field-value gap-2">
|
||||
<StatusIndicator :variant="statusVariant">
|
||||
<span class="large fw-bold text-lowercase evcc-default-text">{{
|
||||
<span class="value-text fw-bold text-lowercase evcc-default-text">{{
|
||||
status
|
||||
}}</span>
|
||||
</StatusIndicator>
|
||||
|
|
@ -88,7 +90,7 @@
|
|||
</div>
|
||||
<div class="field-value gap-2">
|
||||
<span
|
||||
class="fs-4 fw-bold tabular"
|
||||
class="value-text fw-bold tabular"
|
||||
:class="isCredit ? 'text-primary' : 'evcc-default-text'"
|
||||
>
|
||||
{{ netCostDisplay }}
|
||||
|
|
@ -132,6 +134,7 @@ const STRATEGY_LABELS: Record<string, string> = {
|
|||
const STATUS_TOOLTIP =
|
||||
"The optimizer result:<br><br>" +
|
||||
"<strong>Optimal</strong>: the best plan was found.<br>" +
|
||||
"<strong>Feasible</strong>: a usable plan was found, but not proven optimal.<br>" +
|
||||
"<strong>Infeasible</strong>: the constraints cannot all be met.<br>" +
|
||||
"<strong>Not solved</strong>: optimization hasn't finished yet.";
|
||||
|
||||
|
|
@ -180,13 +183,15 @@ export default defineComponent({
|
|||
if (Math.abs(elapsed) < 60 * 1000) return "just now";
|
||||
return this.fmtTimeAgo(elapsed);
|
||||
},
|
||||
statusVariant(): "success" | "warning" | "muted" {
|
||||
statusVariant(): "success" | "warning" | "danger" | "muted" {
|
||||
switch (this.status) {
|
||||
case OptimizationStatus.OPTIMAL:
|
||||
return "success";
|
||||
case OptimizationStatus.FEASIBLE:
|
||||
return "warning";
|
||||
case OptimizationStatus.INFEASIBLE:
|
||||
case OptimizationStatus.UNBOUNDED:
|
||||
return "warning";
|
||||
return "danger";
|
||||
default:
|
||||
return "muted";
|
||||
}
|
||||
|
|
@ -276,20 +281,22 @@ export default defineComponent({
|
|||
.info-icon {
|
||||
cursor: help;
|
||||
}
|
||||
/* body size on small screens, larger from md up; bold carries the emphasis */
|
||||
.value-text {
|
||||
font-size: 1rem;
|
||||
line-height: 1.2;
|
||||
}
|
||||
@media (min-width: 768px) {
|
||||
.value-text {
|
||||
font-size: 1.25rem;
|
||||
}
|
||||
}
|
||||
.secondary-goal {
|
||||
text-decoration: underline;
|
||||
text-decoration-color: var(--evcc-gray);
|
||||
text-underline-offset: 3px;
|
||||
}
|
||||
|
||||
/* small: slightly reduced value size, rows are tighter */
|
||||
@media (max-width: 767.98px) {
|
||||
.field-value .large,
|
||||
.field-value .fs-4 {
|
||||
font-size: 1.125rem !important;
|
||||
}
|
||||
}
|
||||
|
||||
/* md and up: each field a vertical stack (label, value, caption); columns via Bootstrap grid */
|
||||
@media (min-width: 768px) {
|
||||
.field {
|
||||
|
|
|
|||
|
|
@ -1429,6 +1429,7 @@ export interface TimeSeries {
|
|||
// Solver status enum
|
||||
export enum OptimizationStatus {
|
||||
OPTIMAL = "Optimal",
|
||||
FEASIBLE = "Feasible",
|
||||
INFEASIBLE = "Infeasible",
|
||||
UNBOUNDED = "Unbounded",
|
||||
UNDEFINED = "Undefined",
|
||||
|
|
|
|||
|
|
@ -599,8 +599,9 @@ func (site *Site) optimizerUpdate(battery []types.Measurement) error {
|
|||
Details: details,
|
||||
})
|
||||
|
||||
if resp.JSON200.Status != optimizer.Optimal {
|
||||
return errors.New(string(resp.JSON200.Status))
|
||||
// feasible results are usable, they are just not proven optimal
|
||||
if status := resp.JSON200.Status; status != optimizer.Optimal && status != optimizer.Feasible {
|
||||
return errors.New(string(status))
|
||||
}
|
||||
|
||||
site.applyOptimizerResult(req, details.BatteryDetails, *resp.JSON200)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue