3
\$\begingroup\$

Problem Statement

Following are the various scenarios to be considered

  1. Track energy - Track consumption of water, gas and electricity. We should be able extend to capture other type of energies as well (e.g., fuel for vehicles). Apart from storing the consumption and time interval, also look into possibilities of capturing additional attributes for each type and extend it with ease.

  2. Sustainability goals - Build a construct to provide an ability for users to capture sustainability goals, such as use x amount of alternate energy (wind or solar), cut down the usage by x, shift usage of certain energy to a non-peak time (e.g., running washing machine).

  3. Savings - Based on goals, project savings ahead of time as well compute them for every given timeframe.

  4. Suggest goals - Design a mechanism to suggest goals so that folks can pick from existing goals/template and tweak if needed to create their own.

  5. Other scenarios to keep in mind

    1. Sustainability score - to build a score for every home
    2. Badges - Provide badges or incentives for people when they achieve goals
    3. Incentives - Provide incentives when people achieve certain goals

Evaluation Criteria

Pay attention to the following for this exercise for design

  1. Scalable design
  2. Data extensibility - ability to quickly extend attributes to consider additional scenarios.
  3. For all devices - build the application to scale into using any devices or integrate with third party systems

My Implementation

Sequence Diagram Sequence diagram

Code Structure

Directory listing

Code

Git repository

Appliance

package heca;

import java.sql.Time;

public class Appliance{
      String applianceCategory; //e.g Electricity
      ApplianceType applianceType; //e.g StrictAppliance like Refrigerator
      String applianceName; //e.g WashingMachine
      int usageTime;
      Time scheduledTime;

    public Appliance(String applianceCategory, ApplianceType applianceType, String applianceName, int usageTime,Time scheduledTime) {
        this.applianceCategory = applianceCategory;
        this.applianceType = applianceType;
        this.applianceName = applianceName;
        this.usageTime = usageTime;
        this.scheduledTime = scheduledTime;
    }


}

Attribute

package heca;

public class Attribute{
    String attibuteName; //e.g WindPower
    double perUnitWeight; 
    int consumed;
    int   limit;

        public static class Builder {
            //required
            private  String attibuteName; 
            //optional
            private double perUnitWeight; 
            private  int consumed;
            private  int   limit;

            public Builder(String size) {
              this.attibuteName = size;
            }

            public Builder perUnitWeight(double value) {
                perUnitWeight = value;
              return this;
            }

            public Builder consumed(int value) {
                consumed = value;
              return this;
            }

            public Builder limit(int value) {
                limit = value;
              return this;
            }

            public Attribute build() {
              return new Attribute(this);
            }
        }

    private Attribute(Builder builder) {
        attibuteName = builder.attibuteName;
        perUnitWeight = builder.perUnitWeight;
        consumed = builder.consumed;
        limit = builder.limit;
    }

    public Attribute(String a){
        this(a, 0.0, 0, (int)1e6);

    }
    public Attribute(String a, double   w, int   m, int l){
        attibuteName = a;
        perUnitWeight = w;
        consumed  = m;
        limit = l;

    }
    public String toString(){
        StringBuilder sb = new StringBuilder();
        sb.append("\n\tattibute : "+this.attibuteName+"\tConsumed :"+ this.consumed + "\tLimit : "+ this.limit);        
        return sb.toString();   
    }

    @Override
    public int hashCode() {
        final int prime = 31;
        int result = 1;
        result = prime * result + ((attibuteName == null) ? 0 : attibuteName.hashCode());
        return result;
    }

    @Override
    public boolean equals(Object obj) {
        if (this == obj)
            return true;
        if (obj == null)
            return false;
        if (getClass() != obj.getClass())
            return false;
        Attribute other = (Attribute) obj;
        if (attibuteName == null) {
            if (other.attibuteName != null)
                return false;
        } else if (!attibuteName.equals(other.attibuteName))
            return false;
        return true;
    }   
}

Controller

package heca;

import java.sql.Time;
import java.util.List;
import java.util.Map;

public interface Controller {
    public String createUser(String userName);
    public boolean addAppliance(String userId,String applianceCategory);
    public boolean addAppliance(String userId, String applianceCategory,List<Attribute> attribs);
    public void addAttribute(String userId,String applianceCategory,String attributeName);
    public void updateConsumption(String userId,String applianceCategory,String attributeName,int updatedValue);
    public List<Attribute> getMAXExpenses(String userId);
    public List<Attribute> getMINExpenses(String userId);
    public Map<String,List<Attribute>> getAllConsumptionDetails(String userId);
    public List<Attribute> getSpecificConsumptionDetails(String userId,String applianceCategory);
    public Score getScore();
    public Badge getBadge();
    public List<Attribute> getSuggestedOptimizedGoal(String userId,String applianceCategory,int target);
    public boolean modifyGoal(String userId,String applianceCategory,String attributeName,int targetValue);
    public boolean scheduleFlexibleAppliance(String userId,String applianceCategory, Appliance applianceName,Time schedule);

}

CostComparator

package heca;

import java.util.Comparator;

public class CostComparator implements Comparator<Attribute>{

    // fractional knapsack comparator having only Weight(weight per unit) but all items are unbounded..same Value...Hence value ignored
    @Override
    public int compare(Attribute o1, Attribute o2) {
        return ((o1.limit -o1.consumed)*(int)o1.perUnitWeight )      -  ((o2.limit -o2.consumed)*(int)o2.perUnitWeight ) > 0 ? 1:0  ;
    }

}

EnergyTracker

package heca;

import java.util.ArrayList;
import java.util.List;
import java.util.PriorityQueue;
import java.util.Queue;
import java.util.concurrent.ConcurrentHashMap;

public class EnergyTracker{ 

    ConcurrentHashMap<String,List<Attribute>> appliances = new ConcurrentHashMap<>();   //AdjacencyMatrix   
    Queue<Attribute> maxExpenseHeap = new PriorityQueue<>(20, new CostComparator());
    Queue<Attribute> minExpenseHeap = new PriorityQueue<>(20, new CostComparator().reversed());

    public List<Attribute> getApplianceDetails(String aplianceName){
        if(appliances.containsKey(aplianceName))return appliances.get(aplianceName);
        else return new ArrayList<>(); 
    }
    public void setApplianceDetails(String aplianceName,List<Attribute> attribs){
        List<Attribute> renewedAttribs = appliances.get(aplianceName);
            if(renewedAttribs==null || renewedAttribs.isEmpty())
                renewedAttribs = attribs;
            else if(appliances.containsKey(aplianceName)){
                renewedAttribs.addAll(attribs);
            }
            appliances.put(aplianceName,renewedAttribs);
            renewedAttribs.forEach((attrib )-> this.maxExpenseHeap.offer(attrib));
            renewedAttribs.forEach((attrib )-> this.minExpenseHeap.offer(attrib));

    }

    public ConcurrentHashMap<String,List<Attribute>> getALLApplianceDetails() {     
        return appliances;
    }

    public List<Attribute> get_TopK_MINConsumptionAppliance(int K){
        ArrayList<Attribute> top5minConsumption = new ArrayList<>(K);
        Attribute temp =null;
        for( int i =0; i<K && K< minExpenseHeap.size() && i < minExpenseHeap.size(); ){
            temp = minExpenseHeap.poll();
            top5minConsumption.add(temp);
            minExpenseHeap.offer(temp);
            i++;
        }       
        return top5minConsumption;
    }

    public List<Attribute> get_TopK_MAXConsumptionAppliance(int K){
        ArrayList<Attribute> top5minConsumption = new ArrayList<>(K);
        Attribute temp =null;
        for( int i =0; i<K && K< maxExpenseHeap.size() && i < maxExpenseHeap.size(); ){
            temp = maxExpenseHeap.poll();
            top5minConsumption.add(temp);
            maxExpenseHeap.offer(temp);
        }       
        return top5minConsumption;
    }
}

HomeUser

package heca;

public class HomeUser{

    String userId;

    EnergyTracker targetExpenseTracker  = new EnergyTracker();
    EnergyTracker actualExpenseTracker  = new EnergyTracker();

    int targetExpenseGoal, monthlyBudget, tillNowExpense;

    public HomeUser(String uID){
        userId = uID;
    }

    /* <TODO> calculate based on :
                            getSavings() 
                            getGoalAchieved() 
    */
    public Badge showBadgesAndIncentives(){     
        return Badge.SILVER;
    }
    /* <TODO> judge based on 
                           total consumption cost of all Appliances -> Attribute -> consumed*perUnitWeight
    */
    public Score getScore(){        
        return Score.CONSUMES_MEDIUM;
    }


    private int getGoalAchieved(){ 
        return targetExpenseGoal;
    }
    private int getSavings(){
        return monthlyBudget - tillNowExpense;
    }



    //setters

    public void setTargetExpenseGoal(int targetExpenseGoal) {
        this.targetExpenseGoal = targetExpenseGoal;
    }

    public void setMonthlyBudget(int monthlyBudget) {
        this.monthlyBudget = monthlyBudget;
    }

    public void setTillNowExpense(int tillNowExpense) {
        this.tillNowExpense = tillNowExpense;
    }

}

UserDB

package heca;

import java.util.HashMap;

public class UserDB{
    HashMap<String,HomeUser> usserMap = new HashMap<>();

    public void addUser(String userId,HomeUser u){
        usserMap.put(userId, u);
    }

    public HomeUser getUser(String userId){
        return usserMap.get(userId);
    }
}

DesignHECA

package heca;


import java.sql.Time;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;



public class DesignHECA implements Controller {

    private UserDB userDB = new UserDB();

    @Override
    public String createUser(String userName) {
        String userId = userName+ new java.util.Random();
        HomeUser u = new HomeUser(userId);
        userDB.addUser(userId,u);
        return userId ;
    }

    public HomeUser getUser(String userId) {
        return userDB.getUser(userId);
    }


    /*----------------------- addAppliance ----------------------------------------*/
        @Override
        public boolean addAppliance(String userId, String applianceCategory) {
            if(applianceCategory == null || applianceCategory.isEmpty()) return false;
            else getUser(userId).actualExpenseTracker.setApplianceDetails(applianceCategory,new ArrayList<>()); //telescoping
            return true;

        }
        @Override
        public boolean addAppliance(String userId, String applianceCategory,final List<Attribute> attribs){     // not to be leaked to Client 

            HomeUser user  = getUser(userId);

            if(applianceCategory == null || applianceCategory.isEmpty()) return false;
            else 
                if(!user.actualExpenseTracker.appliances.containsKey(applianceCategory)){
                    user.actualExpenseTracker.setApplianceDetails(applianceCategory,attribs);  //defensive copy                     
                }else{
                    List<Attribute> prev = user.actualExpenseTracker.appliances.getOrDefault(applianceCategory, new ArrayList<>());
                    prev.addAll(attribs); //handling override left for brevity
                    user.actualExpenseTracker.setApplianceDetails(applianceCategory,prev);
                }
            return true;
        }


    /*----------------------- addAttribute ----------------------------------------*/
    @Override
    public void addAttribute(String userId, String applianceCategory, String attributeName) {
        List<Attribute> attribs = new ArrayList<>();
        Attribute attribute = new Attribute(attributeName);
        attribs.add(attribute);

        addAppliance(userId, applianceCategory,attribs);

    }

    /*----------------------- updateConsumption ----------------------------------------*/
    @Override
    public void updateConsumption(String userId, String applianceCategory, String attributeName, int tillNowConsumed) {
        //left intentionally for brevity        
    }

    /*----------------------- getMAXExpenses ----------------------------------------*/
    @Override
    public List<Attribute> getMAXExpenses(String userId) {
        HomeUser user  = getUser(userId);       
        return user.actualExpenseTracker.get_TopK_MAXConsumptionAppliance(10);
    }

    /*----------------------- getMINExpenses ----------------------------------------*/
    @Override
    public List<Attribute> getMINExpenses(String userId) {
        HomeUser user  = getUser(userId);       
        return user.actualExpenseTracker.get_TopK_MINConsumptionAppliance(10);
    }
    /*----------------------- getAllConsumptionDetails ----------------------------------------*/
    @Override
    public Map<String, List<Attribute>> getAllConsumptionDetails(String userId) {
        HomeUser user  = getUser(userId);
        return user.actualExpenseTracker.getALLApplianceDetails();
    }


    /*----------------------- getSpecificConsumptionDetails ----------------------------------------*/
    @Override
    public List<Attribute> getSpecificConsumptionDetails(String userId, String applianceCategory) {
        HomeUser user  = getUser(userId);
        //checks omitted for brevity
        return user.actualExpenseTracker.getApplianceDetails(applianceCategory);

    }

    /*----------------------- modifyGoal - lets user to newly calibrate his target expenses ---*/
    @Override
    public boolean modifyGoal(String userId, String applianceCategory, String attributeName, int newlimit) {
        HomeUser user  = getUser(userId);
        List<Attribute> attribs = user.targetExpenseTracker.getApplianceDetails(applianceCategory);
        Attribute temp =null;
        for(int i=0; i<attribs.size();i++){          
            if(attributeName.equals(attribs.get(i).attibuteName)){
                temp =attribs.remove(i);
                temp.limit = newlimit;
                attribs.add(temp);
                user.targetExpenseTracker.setApplianceDetails(applianceCategory,attribs);
                return true;
            }
        }
        return false;
    }

    /*---------------------- getSuggestedOptimizedGoals ------------------------------
     * This method Suggests optimized path(Top 5 MIN Expense) for Goal if calibration set by User predicted to meet target 
     *  */
    @Override
    public List<Attribute> getSuggestedOptimizedGoal(String userId,String applianceCategory,int target) {
        HomeUser user  = getUser(userId);
        List<Attribute>  attribs = user.actualExpenseTracker.getApplianceDetails(applianceCategory);
        /* e.g 
             "PowerSupply",340,880
             "WindPower",120,1
             "SolarEnergy",10,2 

        int[] w ={880,1,2};
        int[] c ={340,120,10};

        if( this.canProduce(c,w,user.monthlyBudget -user.tillNowExpense) > 0){ //optimiseed combination
            return this.getMINExpenses(userId);
        }
        else 
            return new ArrayList<>();   
        */
        return this.getMINExpenses(userId);
    }
    public int canProduce(int[] c,int[] w, int W){
        return min_cost(c.length,W,c,w);
    }

    // Dynamic programming to compute Minimum Cost Path for fixed Weight 
    public int min_cost(int N, int W,int[] c, int[] w){
        // min_cost(i, W) = min(min_cost(i+1, W), min_cost(i, W - w[i]) + c[i])

        int[][] dp = new int[N][W];
        int i=0;

            //base cases
               if(dp[i][0] ==  0 ) return 1;       // We already reached our goal
               if(W < 0 || i > N) dp[i][W]  = Integer.MIN_VALUE;  // if (W < 0 or i > N) then we can't get to W

            dp[i][ W] = Math.min(min_cost(i+1, W,c,w), min_cost(i, W - w[i],c,w) + c[i]); 

        if(dp[N][ W] <= W)
            return dp[N][ W];
        else 
            return 0;//impossible --need to re calibrate
    }


    @Override
    public boolean scheduleFlexibleAppliance(String userId,String applianceCategory, Appliance applianceName,Time schedule) {
        // TODO omitted for brevity
        return false;
    }

    @Override
    public Score getScore() {
        // TODO omitted for brevity
        return null;
    }

    @Override
    public Badge getBadge() {
        // TODO omitted for brevity
        return null;
    }

            /*------------Driver Program -----------------------------------------------------------*/ 
            public static void main(String args[] ) throws Exception {

                    DesignHECA d = new DesignHECA();
                    String userId = d.createUser("Chandra");

             /** Create Attribute */
                    List<Attribute> attributeList1 = new ArrayList<>();
                        attributeList1.add( new Attribute.Builder("CarOil").perUnitWeight(80.0).consumed(20).limit(60).build());
                        attributeList1.add( new Attribute.Builder("CookingOil").perUnitWeight(60.0).consumed(4).limit(12).build());
                        attributeList1.add( new Attribute.Builder("CandleOil").perUnitWeight(12.0).consumed(2).limit(10).build());
                    List<Attribute> attributeList2 = new ArrayList<>();
                        attributeList2.add( new Attribute.Builder("CrudeOil").perUnitWeight(12.0).consumed(10).limit(80).build());

                    List<Attribute> attributeList3 = new ArrayList<>();
                        attributeList3.add( new Attribute("PowerSupply",340.0,880,60));
                        attributeList3.add( new Attribute("WindPower",120.0,1,2));
                    List<Attribute> attributeList4 = new ArrayList<>();
                        attributeList4.add( new Attribute("SolarEnergy",0.0,0,2));

             /** Create ApplapplianceCategorydd Attributes */
                        d.addAppliance(userId,"Fuel",attributeList1); 
                        d.addAppliance(userId,"Fuel",attributeList2); 
                        d.addAppliance(userId,"Electricity",attributeList3);
                        d.addAppliance(userId,"Electricity",attributeList4); 

             /** Optimize Electric Consumption */

                        /*------------show Electric consumption ----------------------------------------------------------------*/ 
                                                d.getAllConsumptionDetails(userId).forEach((k,v)-> System.out.println(k+" : "+v));

                        /*------------show Suggested Paths ----------------------------------------------------------------------*/
                                                System.out.println(d.getSpecificConsumptionDetails(userId,"Electricity")); 
                                                System.out.println(d.getSuggestedOptimizedGoal(userId,"Electricity",9000)); //DP based on Graph 

                        /*------------user can opt to re-calibrating his target ---------------------------------------------------*/   
                                                d.modifyGoal(userId,"Electricity","PowerSupply", 600);

                        /*------------user can opt to schedule Washing Machine ---------------------------------------------------*/
                                                //d.scheduleFlexibleAppliance(userId,"Electricity", "WashingMachine", new Time(11,30,20));


            }



    /* *********************************************
       <TODO> : Implement following Business Methods 
       =============================================
         --remmoveAppliance() 
         --getTargetGoal()
         --getProjectSavings()
         --getBadgesAndIncentives()
         --calculateDeviation()
         --getSuggestedOptimizedGoals("Electric") DP based on Graph
    */  
    /*----Constructor-------------*/
            public DesignHECA(){ init(); } 


    /*----Utility & Loaders-------*/
            public void init() {    }   //can be populated from File system     


}

enum ApplianceType{
    STRICT, FLEXIBLE;    
}
enum Goal{
    EXCELLENT,GOAL_ACHIEVED,EXCEEDED;    
}
enum Badge{


        // Badge with Incentive
            COPPER(100),SILVER(300),GOLD(800);

            private int intValue;
            private String abbreviation;

            private Badge(final int intValue) {
                this.intValue = intValue;
            }
            private Badge(String value) {
                this.abbreviation = value;
            }
        //lookup a Java enum from its ordinals
            private static Badge[] values = Badge.values();
            public static Badge getByID(int i) {                
                    return (values[i - 1] != null)? values[i - 1] : Badge.values()[i];
            }

}


enum Score{
    CONSUMES_LOW, CONSUMES_MEDIUM, CONSUMES_HIGH, CONSUMES_PEAK;    
}

My Stake

I have tried implementing a very minimal viable program. Its incomplete and I was rejected - obvious isn't it? I know the interviewer was not expecting a full fledged implementation though. I shared my Code and design. I need the help of experts here to help me correct myself, so that in future I have a rough idea of my shortcomings.

What I need help on:

  1. What would be experts Object Oriented Class Design
  2. Where would experts prefer Generics to make polymorphic Algo...
  3. Where would experts might like to use MutliThread / Concurrency here.
  4. What all design patterns (eg. strategy) may be applied to which scenarios
  5. How would expert implement some features like: Track energy, time interval, Savings, Suggest goals, WashingMachine rescheduling...etc
  6. How should this app be designed to facilitate unit testing?
\$\endgroup\$
2
\$\begingroup\$

Initial impressions

These are my initial impressions just reading from top to bottom:

Sequence diagram

I look at your sequence diagram and I think, "this isn't a sequence diagram!"

You have an actor with no identity, interacting with an unidentified first entity, doing various unrelated things. I assume this means you are merging several sequence diagrams into one.

All of your interactions ignore the HomeUser, so why is it even on there?

All of your interactions (save one) are identical. So what is the point of showing them all? Also, if they are all identical, does that not suggest to you that either your design is wrong, or perhaps there is a greater pattern that you are missing?

class Appliance

You defined this class. And from what I can see, you never use it. Everything afterwards is just a list of Attribute? So what's the point of the class?

class Attribute

Every attribute has a name, a weight, consumed, and limit? What do those mean? Especially considering that everything else in the system depends on a list of attributes, could you provide more detail in a class documentation comment or example code?

And is List<Attribute> really the best structure? Is there no minimum requirement for contents, no constructor, no validation for this?

DesignHECA

First, why is there a Controller interface? This seems like the one class which will not have more than a single implementation. So why does it need to conform to any interface?

Next, what is the Single Responsibility of this class? It looks like you're trying to make this class the user interface of your project. But ... there's no UI.

Evaluation

Now let's take a look at the stated objectives:

Problem Statement

Following are the various scenarios to be considered

  1. Track energy - Track consumption of water, gas and electricity. We should be able extend to capture other type of energies as well (e.g., fuel for vehicles). Apart from storing the consumption and time interval, also look into possibilities of capturing additional attributes for each type and extend it with ease.

  2. Sustainability goals - Build a construct to provide an ability for users to capture sustainability goals, such as use x amount of alternate energy (wind or solar), cut down the usage by x, shift usage of certain energy to a non-peak time (e.g., running washing machine).

  3. Savings - Based on goals, project savings ahead of time as well compute them for every given timeframe.

  4. Suggest goals - Design a mechanism to suggest goals so that folks can pick from existing goals/template and tweak if needed to create their own.

  5. Other scenarios to keep in mind

    1. Sustainability score - to build a score for every home
    2. Badges - Provide badges or incentives for people when they achieve goals
    3. Incentives - Provide incentives when people achieve certain goals

Evaluation Criteria

Pay attention to the following for this exercise for design

  1. Scalable design
  2. Data extensibility - ability to quickly extend attributes to consider additional scenarios.
  3. For all devices - build the application to scale into using any devices or integrate with third party systems

1. Track Energy

I don't see any mechanism for this. Your Attribute class provides no clear way to do this, since attribute names are totally arbitrary. If someone creates an attribute "CarOil" and someone else creates an attribute "Motor Oil", are they the same or different? Are "Cooking Oil" and "Canola Oil" and "Vegetable Oil" and "Peanut Oil" the same for tracking purposes? Can user "Mom" track "Gas" while user "Dad" tracks "Gasoline" and "LittleSister" tracks "Exxon"?

Tracking

There are two possible mechanisms for this: first, if your application is sitting on an extensible database, you might use the database key from the resources table to classify usage. Otherwise, with no database or a mapping layer above the database, you might have an enum identifying your resources. You don't do either one of these, so that's a fail from me.

Extending

Can you "extend to capture other type of energies as well"? No, since you can't track anything consistently.

Can you explore "capturing additional attributes for each type and extend it with ease"? No. You have no explicit identification of energy or resource types, and no relation between activities and resources. So there's no way you can do this.

Alternate solutions

Create a "table" for your resource types. Look into cost models of all the resources in your area (where you live). How are they billed? In my area, electricity and natural gas are billed in two bands: the initial quantity units used costs differently than additional units over the amount. (There might also be a amount, Y>X, that bills more, but I don't use that much.) There is also a time component: peak hours versus off-peak hours. Using electricity during peak hours costs more. (Gas is the same cost always.)

So how can you model just those details? You'll need to know the time-of-day for any activity. You'll need to know the "monthly usage" prior to an activity, or you'll want to only present cost data in monthly aggregate form. (That's two possibilities- you'll have to decide what makes more sense for your application.)

What about water? What about firewood or charcoal? Consider the possibility that some energy sources might get cheaper in volume: if one is buying small LP gas tanks to power a stove, and switches to larger tanks, the cost might go down! How can you store that data? (Hint: I don't think a single class can support all these options. It might be time for an interface...or two.)

2. Sustainability Goals

Different sources

How can you identify these things? For example, how can I tell if I'm using "wind power" versus "solar" versus "nuclear?" My household doesn't have different outlets for different kinds of electricity!

Again, there are a couple of options: some places support energy markets, so you can buy your electricity from a "solar" provider. The electricity comes from the grid, but you're paying someone to put solar-generated electricity into the grid and pretending that you take out what they put in.

Or maybe you have a solar panel array on your house, producing power during certain hours of the day. There are systems that just feed household demand, and systems that "sell back" power into the grid. For a local-only system, use only counts as solar if it happens when the sun is shining.

Reducing usage

This one you almost have a handle on, running an "actual" vs "target" model. But I don't see any indication of month-on-month or year-on-year tracking. Comparing my heating bill in April with my heating bill in March is going to show dramatic improvements! But it would be better to compare my heating bill in April 2019 with my heating bill from April 2018, since heating is a seasonal thing.

Other things may be seasonal, or not. Laundry is probably not seasonal for some people, and seasonal for others (the kids! the sports! the laundry!).

Time-shifting

Every usage needs to have a timestamp associated, or needs to be an "ongoing" expense. For example, my gas range connects to the gas line, but there's also an electric plug to power the display (and the lights). The display shows the current time, except when I'm programming the oven. So the electric expense is pretty much constant, while the gas usage only happens when I'm using the stovetop or oven.

Tracking that seems to mean there's at least two kinds of usage: on-demand or continuing. Another field.

Alternate solutions

Appliances can consume more than one kind of resource. A washing machine will consume water and electricity. And possibly hot water, which means it might trigger costs from a water heater. A range can consume electricity and natural gas or LP gas. A home heating system can consume electricity and hot water.

Consumption can be fixed: some power is required to run the little green light on the front of every electric device. But consumption can also be on-demand. Some costs might be best modeled as constant even if they are not: a refrigerator usually turns itself on and off as needed, making it a "demand" type appliance, but you don't have any control over when the demand happens (except not opening the door!) so you should model it as a fixed cost.

This says to me that an appliance object would not contain cost data. It's too complicated. Instead, there should be a separate costing mechanism that appliances refer to.

Appliances can vary in "intensity" by use. I can wash dark colors which uses cold water, or I can wash white cotton using hot water. I can wash a small or large load of clothes. I can cook pizza at high heat, or I can re-heat leftovers at low heat. So the activity needs to account for this, and the costing function needs to allow for some variable in its operation.

So you have appliances that use resources in continuing or on-demand costs. You don't specify how you are gathering information. If the information is entered by hand, I'd suggest you provide some kind of activity template for the user: "Took a hot shower. Duration X minutes." This is probably easer than trying to enter "Hot water heater: 2.2 gpm @ X minutes" and "Cold water: 0.4 gpm @ X minutes".

On the other hand, if you are gather data via sensors, there won't necessarily be any association between the activities and the expenses. (The water heater ran for 18 minutes this morning.) That will make it harder to suggest improvement goals.

3. Savings

This is relatively straightforward, if you have concrete, numerical goals and categories. There's no real evidence of this in your design. You have a budget - actual computation, but nothing more.

You would need to have strong support for resource types first, in order to have a resource budget, then resource savings. If I want to reduce my water consumption, I need to be tracking water use. If I just want to reduce my monthly monetary outlay, your mechanism probably works. I give this a 'C'.

4. Suggest Goals

This is interesting, because it's not the goals that are important, but the suggesting. How do you know what goals to suggest?

The spec suggests that a "template" mechanism might exist, with some kind of user editing facility.

I don't see any of that in your design. This is a fail.

How could you implement this?

Create some templates and meta-templates. For example, if a resource has a time-cost element, then time-shifting resource use is a viable goal. If a resource has a cost breakpoint, then reducing or increasing resource usage towards that breakpoint can make sense if the overall costs balance out.

Consider:

  • If electricity has peak/off-peak costing, then it makes sense to suggest time-shifting the use of appliances like the washing machine or electric range.

  • If sustainability is a goal, it makes sense to suggest favoring charcoal or wood over gas or electric.

  • If carbon footprint is a goal, it makes sense to suggest gas over electric over charcoal, wood, and peat.

  • If gas has a low initial consumption charge with a higher charge after some threshold, then it makes sense to suggest lowering gas consumption if the household is close to (but over) that threshold. It makes no sense to suggest lowering consumption if the threshold is 150 but the household is using 2000. But if the household uses 2100 and the threshold is 2000, they might see good savings by reducing that 100 units to get out of the high-cost range.

So you should classify possible goals according to things like "cost-saving", "sustainability", "carbon footprint". Then allow these categories to be prioritized or disabled. Finally, each goal should be evaluated with regard to applicability (do not suggest reducing gas consumption in an all-electric household), reachability, and value. If a goal does not apply, or cannot be reached, or will not provide useful value, don't suggest it.

Otherwise, rank the goals in some order, and suggest them. You might want to include a seasonal component: if you suggest using more charcoal during the summer, I'll grab a beer and some bratwursts and head outside. If you suggest using more charcoal when there's 8 inches of snow on the ground, I'll make fun of you on social media and uninstall the app.

5. Other scenarios

I'm going to skip these.

Evaluation criteria

You haven't picked an application model, as far as I can tell. Is your application intended to run on an embedded device, as an Android app, on a desktop, or on the web?

That choice is going to affect pretty much all the options listed in the spec. Does it make sense to store data in a database versus in-memory? Should you use flat files? Can you add a jar file with some extra classes later, or download an update from the app store?

Alternate solutions

If you have a database, then a lot of items can be referenced using id fields. Maybe those are strings, maybe they are integers. But if they are unique ids, it doesn't matter.

If you don't have a database, should you use enum types? Doing so reduces your ability to extend, since you'd have to rebuild if you add to the enum. That's possible for web and android apps, since the cost of deployment is low.

If you are integrating with 3rd-party systems, what kind? It might be trivial to add support for cost data from the local utility, or incredibly difficult to get usage data from a smart switch.

Also, what kind of integrations will you support. If you have sensors detecting the flow of resources, that's somewhat useful. But "gas is being used" or "electric usage went up" isn't as granular as "I turned on the stove burner high for 20 minutes". You'll want to be careful about what kind of data you collect, and how you use it. I'd suggest listing example integrations you think would obviously work, and leave it at that.

For "complicated" mechanisms, an interface is the go-to solution in Java. It's up to you to define where. Computation of costs seems obvious, but there might be others. You should specify a mechanism for things like plug-ins that can be used to add additional support for these interfaces. Can you add a new electricity-costing plugin for a new provider?

\$\endgroup\$
  • \$\begingroup\$ visiting after a long time...but Really Thankful! \$\endgroup\$ – Arnab Dutta Jul 2 at 20:56
  • \$\begingroup\$ I feel there is a necessity for System-design tag in stackexchange. Would you mind doing that ? \$\endgroup\$ – Arnab Dutta Jul 2 at 20:58
0
\$\begingroup\$

When scanning through your code, the first thing I noticed was:

Your code is inconsistent regarding its formatting.

To fix this in Eclipse, select Window > Preferences from the main menu, search for "Save actions" and activate the "Format Code" and "Organize Imports" items. Then right-click on your complete project and select "Format Source" (or similar).


You do use a version control system like Git or Mercurial, so that you could undo every change quickly and without risk, do you? If not, start using it. It's worth it. Just learn the basics:

  • initialize a repository
  • commit changes

If you only learn these 2 things, you have a fully working backup and can ask knowledgeable people to help you out, whatever happens.


There's a lot of boilerplate code in the Appliance class. You should either use Lombok or Kotlin to reduce the amount of code you have to write yourself.

When using Lombok, the code becomes:

@lombok.Data
public class Appliance {
    private final String applianceCategory; //e.g Electricity
    private final ApplianceType applianceType; //e.g StrictAppliance like Refrigerator
    private final String applianceName; //e.g WashingMachine
    private final int usageTime;
    private final Time scheduledTime;
}

When using Kotlin, the code becomes:

data class Appliance(
    val category: String, //e.g Electricity
    val type: ApplianceType, //e.g StrictAppliance like Refrigerator
    val name: String, //e.g WashingMachine
    val usageTime: int,
    val scheduledTime: Time)

In both styles you don't need to write down the redundant code for the constructor.

It is even more useful for the Attribute type since both of these styles generate the equals and hashCode methods for you, so you don't have to think about them anymore.

When you program the code that creates the Attribute objects in Kotlin, you don't need the builder anymore since in Kotlin you have named parameters. Instead of

new Attribute("WindPower", 3.4, 13, 500)

you would write:

Appliance(
        category = "Electricity",
        type = ApplianceType.StrictAppliance,
        name = "WashingMachine",
        usageTime = 13,
        scheduledTime = Time(1234L))

Now there is no chance anymore to mix up the order of the arguments. No builder needed.


The method addAppliance returns false if it fails to do anything, but you never check that. The established way of signalling programming errors in Java is to throw new IllegalArgumentException("reason"). When you use that style, you cannot accidentally forget to check for errors.


Indeed, you need to write unit tests. Especially for the more tricky algorithms like min_cost. That may seem still simple, but even after 25 years of experience my code contains awfully many bugs when I initially write it, and unit tests help a lot.

You should even extract the interesting algorithms into separate files, to make them easily testable. Currently min_cost is part of DesignHECA, but I doubt that this coupling is necessary. If you place the min_cost in a utility class called Algorithms instead, this makes it immediately clear that the algorithm is independent from your home energy management system.

\$\endgroup\$
  • \$\begingroup\$ Appreciate a lot . I would definitely walk the line you suggested :) Will put those in my git with lombok. Exception handling we can ignore as this is more of a interview just identifying edge cases is adequate I would guess. Meanwhile please keep suggesting. Let me know if you are waiting on something more from me :) \$\endgroup\$ – Arnab Dutta Apr 21 at 22:21
  • \$\begingroup\$ shared my Git repo. Hope You and other knowledgeable pexperts can help me further to refine. \$\endgroup\$ – Arnab Dutta Apr 22 at 11:33

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.