Skip to main content

Overview

The Plan vs Execution module provides tools for:
  • Comparing planned production to actual results
  • Calculating deltas with tolerance strategies
  • Analyzing variance statistics
  • Automated plan modifications based on rules
  • Production matching algorithms

Architecture

Core Concepts

Production Plans

ProductionPlan
public interface ProductionPlan {
    List<PlannedProduction> planned();
}

public record PlannedProduction(
    String productId,
    LocalDate date,
    int quantity,
    String productionLine
) {
    public static PlannedProduction of(
        String productId,
        LocalDate date,
        int quantity
    ) {
        return new PlannedProduction(
            productId,
            date,
            quantity,
            "default"
        );
    }
}

public record ActualProduction(
    String productId,
    LocalDate date,
    int quantity,
    String productionLine
) {}

Configurable Production Plan

ConfigurableProductionPlan
public class ConfigurableProductionPlan 
    implements ProductionPlan {
    
    private List<PlannedProduction> planned;
    
    public ConfigurableProductionPlan(
        List<PlannedProduction> planned
    ) {
        this.planned = new ArrayList<>(planned);
    }
    
    public void updateQuantity(
        String productId,
        LocalDate date,
        int newQuantity
    ) {
        planned = planned.stream()
            .map(p -> {
                if (p.productId().equals(productId) 
                    && p.date().equals(date)) {
                    return new PlannedProduction(
                        p.productId(),
                        p.date(),
                        newQuantity,
                        p.productionLine()
                    );
                }
                return p;
            })
            .toList();
    }
    
    public void addBuffer(
        String productId,
        int bufferPercentage
    ) {
        planned = planned.stream()
            .map(p -> {
                if (p.productId().equals(productId)) {
                    int buffered = (int) Math.ceil(
                        p.quantity() * (1 + bufferPercentage / 100.0)
                    );
                    return new PlannedProduction(
                        p.productId(),
                        p.date(),
                        buffered,
                        p.productionLine()
                    );
                }
                return p;
            })
            .toList();
    }
    
    @Override
    public List<PlannedProduction> planned() {
        return List.copyOf(planned);
    }
}

ProductionAnalysisFacade

Main entry point for analysis:
Facade
public class ProductionAnalysisFacade {
    
    public DeltaResult analyze(
        ProductionPlan planned,
        List<ActualProduction> actual,
        ToleranceStrategy tolerance
    ) {
        DeltaCalculator calculator = 
            new DeltaCalculator(tolerance);
        return calculator.calculate(planned, actual);
    }
}

Delta Calculation

Delta Calculator

DeltaCalculator
public class DeltaCalculator {
    private final ToleranceStrategy tolerance;
    
    public DeltaCalculator(ToleranceStrategy tolerance) {
        this.tolerance = tolerance;
    }
    
    public DeltaResult calculate(
        ProductionPlan plan,
        List<ActualProduction> actual
    ) {
        List<ProductionMatch> matches = 
            matchProductions(plan.planned(), actual);
        
        List<ProductionMatch> overproduction = 
            new ArrayList<>();
        List<ProductionMatch> underproduction = 
            new ArrayList<>();
        List<ProductionMatch> withinTolerance = 
            new ArrayList<>();
        
        for (ProductionMatch match : matches) {
            MatchResult result = 
                tolerance.evaluate(match);
            
            switch (result) {
                case OVER -> overproduction.add(match);
                case UNDER -> underproduction.add(match);
                case WITHIN -> withinTolerance.add(match);
            }
        }
        
        DeltaStatistics stats = 
            calculateStatistics(matches);
        
        return new DeltaResult(
            matches,
            overproduction,
            underproduction,
            withinTolerance,
            stats
        );
    }
    
    private List<ProductionMatch> matchProductions(
        List<PlannedProduction> planned,
        List<ActualProduction> actual
    ) {
        Map<String, Map<LocalDate, PlannedProduction>> 
            plannedMap = planned.stream()
                .collect(Collectors.groupingBy(
                    PlannedProduction::productId,
                    Collectors.toMap(
                        PlannedProduction::date,
                        p -> p
                    )
                ));
        
        return actual.stream()
            .map(a -> {
                PlannedProduction p = plannedMap
                    .getOrDefault(a.productId(), Map.of())
                    .get(a.date());
                
                if (p == null) {
                    return new ProductionMatch(
                        a.productId(),
                        a.date(),
                        0,  // Not planned
                        a.quantity()
                    );
                }
                
                return new ProductionMatch(
                    a.productId(),
                    a.date(),
                    p.quantity(),
                    a.quantity()
                );
            })
            .toList();
    }
}

// Match record
public record ProductionMatch(
    String productId,
    LocalDate date,
    int planned,
    int actual
) {
    public int delta() {
        return actual - planned;
    }
    
    public double variance() {
        if (planned == 0) return 0;
        return ((double) delta() / planned) * 100;
    }
}

Delta Result

DeltaResult
public record DeltaResult(
    List<ProductionMatch> allMatches,
    List<ProductionMatch> overproduction,
    List<ProductionMatch> underproduction,
    List<ProductionMatch> withinTolerance,
    DeltaStatistics statistics
) {
    public boolean hasIssues() {
        return !overproduction.isEmpty() 
            || !underproduction.isEmpty();
    }
    
    public List<ProductionMatch> significantVariances() {
        return allMatches.stream()
            .filter(m -> Math.abs(m.variance()) > 10)
            .toList();
    }
}

Delta Statistics

DeltaStatistics
public record DeltaStatistics(
    int totalPlanned,
    int totalActual,
    double averageVariance,
    double maxVariance,
    double minVariance,
    int matchesWithinTolerance,
    int totalMatches
) {
    public double accuracyPercentage() {
        if (totalMatches == 0) return 0;
        return (double) matchesWithinTolerance 
            / totalMatches * 100;
    }
    
    public int totalDelta() {
        return totalActual - totalPlanned;
    }
}

Tolerance Strategies

Define acceptable variance:
public class ExactMatch implements ToleranceStrategy {
    
    @Override
    public MatchResult evaluate(ProductionMatch match) {
        int delta = match.delta();
        
        if (delta == 0) {
            return MatchResult.WITHIN;
        } else if (delta > 0) {
            return MatchResult.OVER;
        } else {
            return MatchResult.UNDER;
        }
    }
}
Match Result
public enum MatchResult {
    WITHIN,     // Within tolerance
    OVER,       // Overproduction
    UNDER       // Underproduction
}

public interface ToleranceStrategy {
    MatchResult evaluate(ProductionMatch match);
}

Plan Modification

Modification Orchestrator

PlanModificationOrchestrator
public class PlanModificationOrchestrator {
    
    private final List<ModificationRule> rules;
    
    public ConfigurableProductionPlan modify(
        ConfigurableProductionPlan plan,
        DeltaResult deltaResult
    ) {
        ConfigurableProductionPlan modified = plan;
        
        for (ModificationRule rule : rules) {
            if (rule.shouldApply(deltaResult)) {
                modified = rule.apply(modified, deltaResult);
            }
        }
        
        return modified;
    }
}

Modification Rules

ModificationRule
public interface ModificationRule {
    
    boolean shouldApply(DeltaResult deltaResult);
    
    ConfigurableProductionPlan apply(
        ConfigurableProductionPlan plan,
        DeltaResult deltaResult
    );
}

// Condition interface
public interface ScheduleModificationCondition {
    boolean isMet(DeltaResult deltaResult);
}

// Modifier interface
public interface ScheduleModifier {
    ConfigurableProductionPlan modify(
        ConfigurableProductionPlan plan,
        DeltaResult deltaResult
    );
}

Under-Production Rule

UnderProductionCondition
public class UnderProductionCondition 
    implements ScheduleModificationCondition {
    
    private final double threshold;
    
    public UnderProductionCondition(double threshold) {
        this.threshold = threshold;
    }
    
    @Override
    public boolean isMet(DeltaResult deltaResult) {
        return !deltaResult.underproduction().isEmpty()
            && deltaResult.statistics().averageVariance() 
                < -threshold;
    }
}

Increase Buffer Modifier

IncreaseBufferModifier
public class IncreaseBufferModifier 
    implements ScheduleModifier {
    
    private final int bufferPercentage;
    
    public IncreaseBufferModifier(int percentage) {
        this.bufferPercentage = percentage;
    }
    
    @Override
    public ConfigurableProductionPlan modify(
        ConfigurableProductionPlan plan,
        DeltaResult deltaResult
    ) {
        ConfigurableProductionPlan modified = plan;
        
        // Add buffer to underperforming products
        Set<String> underProducts = deltaResult
            .underproduction()
            .stream()
            .map(ProductionMatch::productId)
            .collect(Collectors.toSet());
        
        for (String productId : underProducts) {
            modified.addBuffer(productId, bufferPercentage);
        }
        
        return modified;
    }
}

Real-World Example: Manufacturing Analysis

Complete Analysis
// 1. Define plan
ConfigurableProductionPlan plan = 
    new ConfigurableProductionPlan(List.of(
        PlannedProduction.of("WIDGET-A", 
            LocalDate.of(2024, 3, 15), 1000),
        PlannedProduction.of("WIDGET-B", 
            LocalDate.of(2024, 3, 15), 500),
        PlannedProduction.of("WIDGET-A", 
            LocalDate.of(2024, 3, 16), 1000)
    ));

// 2. Record actual production
List<ActualProduction> actual = List.of(
    new ActualProduction("WIDGET-A", 
        LocalDate.of(2024, 3, 15), 850, "LINE-1"),
    new ActualProduction("WIDGET-B", 
        LocalDate.of(2024, 3, 15), 520, "LINE-2"),
    new ActualProduction("WIDGET-A", 
        LocalDate.of(2024, 3, 16), 900, "LINE-1")
);

// 3. Analyze with tolerance
ToleranceStrategy tolerance = 
    new PercentageTolerance(5.0);  // ±5%

DeltaResult result = facade.analyze(
    plan,
    actual,
    tolerance
);

// 4. Review results
System.out.println("Statistics:");
System.out.println("  Total Planned: " + 
    result.statistics().totalPlanned());
System.out.println("  Total Actual: " + 
    result.statistics().totalActual());
System.out.println("  Average Variance: " + 
    String.format("%.2f%%", 
        result.statistics().averageVariance()));
System.out.println("  Accuracy: " + 
    String.format("%.2f%%", 
        result.statistics().accuracyPercentage()));

System.out.println("\nUnderproduction:");
for (ProductionMatch match : result.underproduction()) {
    System.out.println("  " + match.productId() + 
        " on " + match.date() + 
        ": " + match.planned() + 
        " planned, " + match.actual() + 
        " actual (" + 
        String.format("%.2f%%", match.variance()) + 
        ")");
}

// 5. Automatic plan adjustment
if (result.hasIssues()) {
    ModificationRule rule = new ModificationRule() {
        @Override
        public boolean shouldApply(DeltaResult delta) {
            return new UnderProductionCondition(5.0)
                .isMet(delta);
        }
        
        @Override
        public ConfigurableProductionPlan apply(
            ConfigurableProductionPlan p,
            DeltaResult delta
        ) {
            return new IncreaseBufferModifier(10)
                .modify(p, delta);
        }
    };
    
    PlanModificationOrchestrator orchestrator = 
        new PlanModificationOrchestrator(List.of(rule));
    
    ConfigurableProductionPlan adjusted = 
        orchestrator.modify(plan, result);
    
    System.out.println("\nAdjusted plan:");
    for (PlannedProduction p : adjusted.planned()) {
        System.out.println("  " + p.productId() + 
            " on " + p.date() + 
            ": " + p.quantity());
    }
}

// Output:
// Statistics:
//   Total Planned: 2500
//   Total Actual: 2270
//   Average Variance: -9.20%
//   Accuracy: 33.33%
//
// Underproduction:
//   WIDGET-A on 2024-03-15: 1000 planned, 850 actual (-15.00%)
//   WIDGET-A on 2024-03-16: 1000 planned, 900 actual (-10.00%)
//
// Adjusted plan:
//   WIDGET-A on 2024-03-15: 1100  (10% buffer added)
//   WIDGET-B on 2024-03-15: 500
//   WIDGET-A on 2024-03-16: 1100  (10% buffer added)

Bad Implementation Example

The module includes a “bad implementation” example for learning:
deliveryscheduling/
// This package demonstrates anti-patterns:
- Tight coupling
- Missing abstractions
- Hard-coded business logic
- Difficult to test

// Use as reference for what NOT to do

Best Practices

Define Tolerance

Always specify acceptable variance thresholds

Track Statistics

Monitor trends over time, not just point-in-time

Automate Carefully

Review automated adjustments before applying

Match Precisely

Ensure correct matching of planned vs actual
  • Uses Common for Result pattern
  • Can integrate with Inventory for stock planning
  • Can integrate with Ordering for demand forecasting

Build docs developers (and LLMs) love