| 
 | 1 | +package net.laprun.sustainability.power.analysis;  | 
 | 2 | + | 
 | 3 | +import java.util.Arrays;  | 
 | 4 | +import java.util.Objects;  | 
 | 5 | +import java.util.function.Function;  | 
 | 6 | +import java.util.stream.Collectors;  | 
 | 7 | + | 
 | 8 | +import net.laprun.sustainability.power.Errors;  | 
 | 9 | +import net.laprun.sustainability.power.SensorMetadata;  | 
 | 10 | +import net.laprun.sustainability.power.SensorUnit;  | 
 | 11 | + | 
 | 12 | +public class TotalSyntheticComponent implements SyntheticComponent {  | 
 | 13 | +    private final Function<double[], Double> formula;  | 
 | 14 | +    private final SensorUnit expectedResultUnit;  | 
 | 15 | +    private final SensorMetadata.ComponentMetadata metadata;  | 
 | 16 | + | 
 | 17 | +    public TotalSyntheticComponent(SensorMetadata metadata, SensorUnit expectedResultUnit, int... totalComponentIndices) {  | 
 | 18 | +        Objects.requireNonNull(totalComponentIndices, "Must specify component indices that will aggregated in a total");  | 
 | 19 | +        this.expectedResultUnit = Objects.requireNonNull(expectedResultUnit, "Must specify expected result unit");  | 
 | 20 | + | 
 | 21 | +        final var errors = new Errors();  | 
 | 22 | +        final var totalComponents = Arrays.stream(totalComponentIndices)  | 
 | 23 | +                .mapToObj(i -> toTotalComponent(metadata, i, errors))  | 
 | 24 | +                .toArray(TotalComponent[]::new);  | 
 | 25 | +        final String description = Arrays.stream(totalComponents)  | 
 | 26 | +                .map(TotalComponent::name)  | 
 | 27 | +                .collect(Collectors.joining(" + ", "Aggregated total from (", ")"));  | 
 | 28 | +        final String name = Arrays.stream(totalComponents)  | 
 | 29 | +                .map(TotalComponent::name)  | 
 | 30 | +                .collect(Collectors.joining("_", "total", ""));  | 
 | 31 | +        final var isAttributed = metadata.components().values().stream()  | 
 | 32 | +                .map(SensorMetadata.ComponentMetadata::isAttributed)  | 
 | 33 | +                .reduce(Boolean::logicalAnd).orElse(false);  | 
 | 34 | +        formula = components -> {  | 
 | 35 | +            double result = 0;  | 
 | 36 | +            for (var totalComponent : totalComponents) {  | 
 | 37 | +                result += components[totalComponent.index] * totalComponent.factor;  | 
 | 38 | +            }  | 
 | 39 | +            return result;  | 
 | 40 | +        };  | 
 | 41 | + | 
 | 42 | +        if (metadata.exists(name)) {  | 
 | 43 | +            errors.addError("Component " + name + " already exists");  | 
 | 44 | +        }  | 
 | 45 | + | 
 | 46 | +        if (errors.hasErrors()) {  | 
 | 47 | +            throw new IllegalArgumentException(errors.formatErrors());  | 
 | 48 | +        }  | 
 | 49 | + | 
 | 50 | +        this.metadata = new SensorMetadata.ComponentMetadata(name, description, isAttributed, expectedResultUnit);  | 
 | 51 | +    }  | 
 | 52 | + | 
 | 53 | +    private double convertToExpectedUnit(double value) {  | 
 | 54 | +        return value * expectedResultUnit.base().conversionFactorTo(expectedResultUnit);  | 
 | 55 | +    }  | 
 | 56 | + | 
 | 57 | +    private record TotalComponent(String name, int index, double factor) {  | 
 | 58 | +    }  | 
 | 59 | + | 
 | 60 | +    private TotalComponent toTotalComponent(SensorMetadata metadata, int index, Errors errors) {  | 
 | 61 | +        final var cm = metadata.metadataFor(index);  | 
 | 62 | +        final var name = cm.name();  | 
 | 63 | +        final var unit = cm.unit();  | 
 | 64 | +        if (!unit.isCommensurableWith(expectedResultUnit)) {  | 
 | 65 | +            errors.addError("Component " + name  | 
 | 66 | +                    + " is not commensurable with the expected base unit: " + expectedResultUnit);  | 
 | 67 | +        }  | 
 | 68 | + | 
 | 69 | +        final var factor = unit.factor();  | 
 | 70 | +        return new TotalComponent(name, index, factor);  | 
 | 71 | +    }  | 
 | 72 | + | 
 | 73 | +    @Override  | 
 | 74 | +    public SensorMetadata.ComponentMetadata metadata() {  | 
 | 75 | +        return metadata;  | 
 | 76 | +    }  | 
 | 77 | + | 
 | 78 | +    @Override  | 
 | 79 | +    public double synthesizeFrom(double[] components, long timestamp) {  | 
 | 80 | +        return convertToExpectedUnit(formula.apply(components));  | 
 | 81 | +    }  | 
 | 82 | +}  | 
0 commit comments