Weiqi ZhaoHerminio GarciaPierre Antoine
Published

Alfred

Alfred is a smart water consumption solution for Hotels that improves guest water usage habits through awareness.

ExpertProtipOver 4 days545
Alfred

Things used in this project

Story

Read more

Schematics

Schematics

Code

Using the sense hat to simulate the water flow meter

Python
#!/usr/bin/python
from sense_hat import SenseHat
import os

sense = SenseHat()
while True:
   for event in sense.stick.get_events():
     if (event.action == "pressed"):    
    try:
        with open( 'counter.txt', 'r' ) as fle:
               counter = int( fle.readline() ) + 1
    except IOError:
           counter = 0

    with open( 'counter.txt', 'w' ) as fle:
          fle.write( str(counter) )

Send meter value to AWS Cloud every 10 minutes

Java
aws-iot-device-sdk-python/samples/basicPubSub/basicPubSub.py
# Publish to the same topic in a loop forever
loopCount = 0
while True:
       if (loopCount == 600):
               try:
                    with open( 'counter.txt', 'r' ) as fle:
                       counter = int( fle.readline() ) + 1
               except IOError:
                    counter = 0
               myAWSIoTMQTTClient.publish("sdk/test/Python2", "Value: " + str(counter), 1)
               loopCount = 0
       loopCount += 1
       time.sleep(1)

Connecting the AWS IOT Broker with SenseHat

Python
 from sense_hat import SenseHat

# Custom MQTT message callback
def customCallback(client, userdata, message):
       sense = SenseHat()
       sense.clear()
       sense.load_image(message.payload)

Send Text Message to maintenance guy by Twilio

Python
from twilio.rest 
import TwilioRestClient

account_sid = A##########################"
auth_token = ##########################"
client = TwilioRestClient(account_sid, auth_token)
message = client.messages.create(to="+17655436533", from_="+18558054858", body="Please check the Jacuzzi in room number 212 for Water Leakage.")

Alexa Lambda Functions for VUI

JavaScript
The function for handling the interaction interface between Alexa and users
'use strict';
var https = require('https')

var sessionAttributes={};
// --------------- Helpers that build all of the responses -----------------------

function buildSpeechletResponse(title, output, repromptText, shouldEndSession) {
    return {
        outputSpeech: {
            type: 'PlainText',
            text: output,
        },
        card: {
            type: 'Simple',
            title: 'SessionSpeechlet - ${title}',
            content: 'SessionSpeechlet - ${output}',
        },
        reprompt: {
            outputSpeech: {
                type: 'PlainText',
                text: repromptText,
            },
        },
        shouldEndSession,
    };
}

function buildResponse(sessionAttributes, speechletResponse) {
    return {
        version: '1.0',
        sessionAttributes: sessionAttributes,
        response: speechletResponse,
    };
}


// --------------- Functions that control the skill's behavior -----------------------

function getWelcomeResponse(callback) {
    // If we wanted to initialize the session to have some attributes we could add those here.
    const sessionAttributes = {};
    const cardTitle = 'Welcome';
    const speechOutput = 'Hey, I\'m Alfred. Your water usage in the living room and jacuzzi, are below the average level. But bathroom has the higher water usage than average. Would you like to investigate any problems?';
        //'Please tell me your unit number by saying something such as' + ', my unit number is 101';
    // If the user either does not reply to the welcome message or says something that is not
    // understood, they will be prompted again with this text.
    const repromptText = 'What can I do for you?';
    const shouldEndSession = false;

    callback(sessionAttributes,
        buildSpeechletResponse(cardTitle, speechOutput, repromptText, shouldEndSession));
}

function handleSessionEndRequest(callback) {
    const cardTitle = 'Session Ended';
    const speechOutput = 'Thank you for talking to Alfred. Call me anytime. Have a nice day!';
    // Setting this to true ends the session and exits the skill.
    const shouldEndSession = true;

    callback({}, buildSpeechletResponse(cardTitle, speechOutput, null, shouldEndSession));
}

function createGuestStartActionAttributes(guestStartValue) {
	return {
		guestStartValue,
	};
}

function createFinalThankYouAttributes(finalThankYouValue) {
  return {
     finalThankYouValue,
  };
}

function createNoProblemAttributes(noProblemValue) {
  return {
     noProblemValue,
  };
}

function createNoNeedAttributes(noNeedValue) {
  return {
     noNeedValue,
  };
}

function createNoProblemAttributes(noProblemValue) {
    return {
      noProblemValue,  
    };
}

function createUnitNumberAttributes(unitNumber) {
    return {
        unitNumber,
    };
}

function createWaterReport(waterReportValue) {
    return {
        waterReportValue,
    };
}

function createWaterReportDetails(waterReportDetailValue) {
    return {
        waterReportDetailValue,
    };
}

function createIssueTypeAttributes(issueType) {
    return {
        issueType,
    };
}

function createIssueNameAttributes(issueName) {
    return {
        issueName,
    };
}

function createEmergencyNameAttributes(emergencyName){
    return {
        emergencyName,
    };
}

/**
 * Sets the unit number in the session and prepares the speech to reply to the user.
 */
function setUnitNumInSession(intent, session, callback) {
    const cardTitle = intent.name;
    const unitNumberSlot = intent.slots.unitNumber;
    let repromptText = '';
    let sessionAttributes = {};
    const shouldEndSession = false;
    let speechOutput = '';

    if (unitNumberSlot) {
        const unitNumber = unitNumberSlot.value;
        sessionAttributes = createUnitNumberAttributes(unitNumber);
        speechOutput = 'OK. What can I do for you? Sweetie.' ;
        repromptText = 'What can I do for you? Sweetie.';
    } else {
        speechOutput = 'I\'m not sure what your unit number is. Please try again.';
        repromptText = 'I\'m not sure what your unit number is. You can tell me your ' +
            'unit number by saying, my unit number is 101';
    }

    callback(sessionAttributes,
         buildSpeechletResponse(cardTitle, speechOutput, repromptText, shouldEndSession));
}

function handleTokenIsNull(intent, session, callback) {
    const cardTitle = intent.name;
    let repromptText = '';
    let sessionAttributes = {};
    const shouldEndSession = true;
    let speechOutput = '';

    speechOutput = 'Your account doesn\'t work now. Please link your Alfred Account in the Alexa app. Thank you!' ;
    callback(sessionAttributes,
         buildSpeechletResponse(cardTitle, speechOutput, repromptText, shouldEndSession));
}

function setIssueTypeFromSession(intent, session, callback) {
    const cardTitle = intent.name;
    const issueTypeSlot = intent.slots.issuetypeslot;
    let repromptText = '';
    let sessionAttributes = {};
    //var sessionAttributes={};
	const shouldEndSession = false;
    let speechOutput = '';

    if (issueTypeSlot) {
        const issueType = issueTypeSlot.value;
        sessionAttributes=session.attributes;
		sessionAttributes['issueType']= issueType;
		//sessionAttributes = createIssueTypeAttributes(issueType);
    //I now know your issue type is ' + issueType + '
        speechOutput = 'I\'m sorry to hear that. What seems to be the problem? Sweetie.' ;
        repromptText = 'I\'m not sure what the problem is. Would you tell me one more time? Honey';
    } else {
        speechOutput = 'I\'m not sure what the problem is. Would you tell me one more time? Honey.';
        repromptText = 'I\'m not sure what the problem is. Would you tell me one more time? Honey.';
    }

    callback(sessionAttributes,
         buildSpeechletResponse(cardTitle, speechOutput, repromptText, shouldEndSession));

		 //closing of setIssueTypeFromSession
		 }


function getWaterReportSession(intent, session, callback) {
    const cardTitle = 'get the water report';
    const waterReportSlot = intent.slots.waterreportslot;
    let repromptText = '';
    let sessionAttributes = {};
    //var sessionAttributes={};
	const shouldEndSession = false;
    let speechOutput = '';

    if (waterReportSlot) {
        const waterReportValue = waterReportSlot.value;
        sessionAttributes = createWaterReport(waterReportValue);
        speechOutput = 'Okay. 90% of rooms are within the normal range of usage. 5 rooms requrie critical attention. More details? ' ;
        repromptText = 'Would like to email you or message the plumber directly?';
    } else {
        speechOutput = 'I\'m not sure what you want. Please try again.';
        repromptText = 'I\'m not sure what you want. Please try again.';
    }

    callback(sessionAttributes,
         buildSpeechletResponse(cardTitle, speechOutput, repromptText, shouldEndSession));

		 //closing of setIssueTypeFromSession
}

function getWaterReportDetailsSession(intent, session, callback) {
    const cardTitle = 'get the water report details';
    const waterReportDetailSlot = intent.slots.waterreportdetailslot;
    let repromptText = '';
    let sessionAttributes = {};
    //var sessionAttributes={};
	const shouldEndSession = false;
    let speechOutput = '';

    if (waterReportDetailSlot) {
        const waterReportDetailValue = waterReportDetailSlot.value;
        sessionAttributes = createWaterReportDetails(waterReportDetailValue);
        speechOutput = 'No problem. Room two one two has a possible leak in the jacuzzi. Room five one three is showing higher than normal water usage in bathroom. Rooms five one enght, five one nine, five two zero show water usage despite vacancies. Would you like me to send a maintenance team to those rooms? ';
    } else {
        speechOutput = 'I\'m not sure what you want. Please try again.';
        repromptText = 'I\'m not sure what you want. Please try again.';
    }

    callback(sessionAttributes,
         buildSpeechletResponse(cardTitle, speechOutput, repromptText, shouldEndSession));

		 //closing of setIssueTypeFromSession
}

function sendMeEMailSession(intent, session, callback) {
    const cardTitle = 'send email';
    const sendMeEmailSlot = intent.slots.emailmeslot;
    let repromptText = '';
    let sessionAttributes = {};
    //var sessionAttributes={};
	const shouldEndSession = false;
    let speechOutput = '';

    if (sendMeEmailSlot) {
        const sendMeEmailValue = sendMeEmailSlot.value;
        sessionAttributes = createWaterReport(sendMeEmailValue);
        speechOutput = 'Great. I have notified the teams by text message and sent you a summary of this conversation by email for your records.' ;
    } else {
        speechOutput = 'I\'m not sure what you want. Please try again.';
        repromptText = 'I\'m not sure what you want. Please try again.';
    }

    callback(sessionAttributes,
         buildSpeechletResponse(cardTitle, speechOutput, repromptText, shouldEndSession));

		 //closing of setIssueTypeFromSession
}


function setIssueNameFromSession(intent, session, callback) {
    const cardTitle = intent.name;
	//const issueTypeSlot = intent.slots.issuetypeslot;
    const issueNameSlot = intent.slots.issuenameslot;
    let repromptText = '';
    let sessionAttributes = {};
    const shouldEndSession = false;
    let speechOutput = '';
    console.log("issueNameSlot:" + issueNameSlot.value);
    if (issueNameSlot) {
		sessionAttributes=session.attributes;
        const issueName = issueNameSlot.value;
        sessionAttributes.issueName = createIssueNameAttributes(issueName);
        //const issueType = issueTypeSlot.value;

        console.log("issue name:" + issueName);
        // I now know your issue is '+ issueName + '
        speechOutput = 'That’s not good. I’ve recorded this and a helper will be there in 5 minutes. Is there anything else I can help you with?' ;
        // TODO Add time durations

		var endpoint = 'https://glacial-shore-74405.herokuapp.com/srequest?issuetype=' + sessionAttributes.issueType + '&issuename=' + issueName + "&issuedesp=" + issueName + " has an issue." // ENDPOINT GOES HERE
        var body = ""
        https.get(endpoint, (response) => {
          response.on('data', (chunk) => { body += chunk })
          response.on('end', () => {
            // var data = JSON.parse(body)
            // var textstr =
            // var subscriberCount = data.items[0].statistics.subscriberCount
            // context.succeed(
            //   generateResponse(
            //     buildSpeechletResponse(`Current subscriber count is ${subscriberCount}`, true),
            //     {}
            //   )
            // )
          })
        })
        repromptText = 'What I can do for you? Sweetie.';
    } else {
        speechOutput = 'I\'m not sure what the problem is. Would you tell me one more time? Honey.';
        repromptText = 'I\'m not sure what the problem is. Would you tell me one more time? Honey.';
    }

    callback(sessionAttributes,
         buildSpeechletResponse(cardTitle, speechOutput, repromptText, shouldEndSession));
}

function guestStartAction(intent, session, callback) {
    const cardTitle = intent.name;
    const guestStartSlot = intent.slots.gueststartslot;
    let repromptText = '';
    let sessionAttributes = {};
    const shouldEndSession = false;
    let speechOutput = '';

    if (guestStartSlot) {
        const guestStartValue = guestStartSlot.value;
        sessionAttributes = createGuestStartActionAttributes(guestStartValue);
        speechOutput = 'OK. You are part of an initiative through the Water Conservation Foundation.  The smiley face turned from green to yellow indicating higher than average water usage.  Would you like tips on how to reduce your water consumption?';
        //shouldEndSession = true;
        //repromptText = 'I\'m not sure what the problem is. Would you tell me one more time? Honey';
    } else {
        speechOutput = '';
        //repromptText = 'I\'m not sure what the problem is. Would you tell me one more time? Honey.';
    }

    callback(sessionAttributes,
         buildSpeechletResponse(cardTitle, speechOutput, repromptText, shouldEndSession));
}

function setFinalThankYouSession(intent, session, callback) {
    const cardTitle = intent.name;
    const finalThankYouSlot = intent.slots.finalthankyouslot;
    let repromptText = '';
    let sessionAttributes = {};
    const shouldEndSession = true;
    let speechOutput = '';

    if (finalThankYouSlot) {
        const finalThankYouValue = finalThankYouSlot.value;
        sessionAttributes = createFinalThankYouAttributes(finalThankYouValue);
        speechOutput = 'Anytime.' ;
        //shouldEndSession = true;
        //repromptText = 'I\'m not sure what the problem is. Would you tell me one more time? Honey';
    } else {
        speechOutput = '';
        //repromptText = 'I\'m not sure what the problem is. Would you tell me one more time? Honey.';
    }

    callback(sessionAttributes,
         buildSpeechletResponse(cardTitle, speechOutput, repromptText, shouldEndSession));
}



function sendMessageToManagerSession(intent, session, callback) {
    const cardTitle = 'Send Message to Manager';
    const noProblemSlot = intent.slots.noproblemslot;
    let sessionAttributes = {};
    const shouldEndSession = true;
    let speechOutput = '';

     if (noProblemSlot) {
        const noProblemValue = noProblemSlot.value;
        sessionAttributes = createNoProblemAttributes(noProblemValue);
        speechOutput = 'Great! Lets start. For example, when you are in the shower...you can ';

     }
    
	const repromptText = 'Would you like to investigate any problems?';

    callback(sessionAttributes,
         buildSpeechletResponse(cardTitle, speechOutput, repromptText, shouldEndSession));
}


function setEmergencyIssueFromSession(intent, session, callback) {
    const cardTitle = intent.name;
	  const emgIssueSlot = intent.slots.emgslot;
    let repromptText = '';
    let sessionAttributes = {};
    const shouldEndSession = false;
    let speechOutput = '';

    if (emgIssueSlot) {
		sessionAttributes=session.attributes;
        const emgIssueName = emgIssueSlot.value;
        //sessionAttributes.issueName = createIssueNameAttributes(issueName);

        // console.log("issue name:" + issueName);
        // I now know your issue is '+ issueName + '
        speechOutput = 'I\'ve contacted your loved ones and someone from the community is on their way to help. Don\'t worry. Is there anything else I can help you with?' ;
        // TODO Add time durations
        var endpoint = 'https://deary123.herokuapp.com/emergency' // ENDPOINT GOES HERE
            var body = ""
            https.get(endpoint, (response) => {
              response.on('data', (chunk) => { body += chunk })
              response.on('end', () => {
                // var data = JSON.parse(body)
                // var textstr =
                // var subscriberCount = data.items[0].statistics.subscriberCount
                // context.succeed(
                //   generateResponse(
                //     buildSpeechletResponse(`Current subscriber count is ${subscriberCount}`, true),
                //     {}
                //   )
                // )
              })
            })
            repromptText = 'What I can do for you? Sweetie.';
      } else {
        speechOutput = 'I\'m not sure what the problem is. Would you tell me one more time? Honey.';
        repromptText = 'I\'m not sure what the problem is. Would you tell me one more time? Honey.';
      }

    callback(sessionAttributes,
         buildSpeechletResponse(cardTitle, speechOutput, repromptText, shouldEndSession));
}

function getUnitNumFromSession(intent, session, callback) {
    let unitNumber;
    const repromptText = null;
    const sessionAttributes = {};
    let shouldEndSession = false;
    let speechOutput = '';

    if (session.attributes) {
        unitNumber = session.attributes.unitNumber;
    }

    if (unitNumber) {
        speechOutput = 'Your unit number is ${unitNumber}. Goodbye.';
        shouldEndSession = false;
    } else {
        speechOutput = 'What can I do for you? Sweetie.';
    }


    callback(sessionAttributes,
         buildSpeechletResponse(intent.name, speechOutput, repromptText, shouldEndSession));
}

function setIssueType(intent, session, callback) {
  let issueType;
}

function setIssueName(intent, session, callback) {
  let issueName;
}


// --------------- Events -----------------------

/**
 * Called when the session starts.
 */
function onSessionStarted(sessionStartedRequest, session) {
    console.log('onSessionStarted requestId=${sessionStartedRequest.requestId}, sessionId=${session.sessionId}');
}

/**
 * Called when the user launches the skill without specifying what they want.
 */
function onLaunch(launchRequest, session, callback) {
    console.log('onLaunch requestId=${launchRequest.requestId}, sessionId=${session.sessionId}');

    // Dispatch to your skill's launch.
    getWelcomeResponse(callback);
}

/**
 * Called when the user specifies an intent for this skill.
 */
function onIntent(intentRequest, session, callback) {
    console.log('onIntent requestId=${intentRequest.requestId}, sessionId=${session.sessionId}');

    const intent = intentRequest.intent;
    const intentName = intentRequest.intent.name;
    
    // const user = session.getUser();
    // const token = user.getAccessToken();
    // if (token === null) {
    //     handleTokenIsNull(intent, session, callback);
    // }
    
    // Dispatch to your skill's intent handlers
    if (intentName === 'MyUnitNumberIsIntent') {
        setUnitNumInSession(intent, session, callback);
    } else if (intentName === 'WhatsMyUnitIntent') {
        getUnitNumFromSession(intent, session, callback);
    } else if (intentName === 'AMAZON.HelpIntent') {
        getWelcomeResponse(callback);
    } else if (intentName === 'AMAZON.StopIntent' || intentName === 'AMAZON.CancelIntent') {
        handleSessionEndRequest(callback);
    } else if (intentName === 'GetMaintenanceissueIssuename') {
        setIssueNameFromSession(intent, session, callback);
    } else if (intentName === 'GetMaintenanceIssueIssuetype') {
        setIssueTypeFromSession(intent, session, callback);
    } else if (intentName === 'FinalThankYouIntent') {
        setFinalThankYouSession(intent, session, callback);
    } else if (intentName === 'Emergencyintent') {
        setEmergencyIssueFromSession(intent, session, callback);
    }  else if (intentName === 'CannotFindProblemIntent') {
    	sendMessageToManagerSession(intent, session, callback);
    } else if (intentName === 'GetWaterReportIntent') {
    	getWaterReportSession(intent, session, callback);
    } else if (intentName === 'GetWaterReportDetailsIntent') {
    	getWaterReportDetailsSession(intent, session, callback);
    } else if (intentName === 'EmailMeIntent') {
    	sendMeEMailSession(intent, session, callback);
    } else if (intentName === 'GuestStartAction') {
    	guestStartAction(intent, session, callback);
    } else {
        throw new Error('Invalid intent');
    }
}

/**
 * Called when the user ends the session.
 * Is not called when the skill returns shouldEndSession=true.
 */
function onSessionEnded(sessionEndedRequest, session) {
    console.log('onSessionEnded requestId=${sessionEndedRequest.requestId}, sessionId=${session.sessionId}');
    // Add cleanup logic here
}


// --------------- Main handler -----------------------

// Route the incoming request based on type (LaunchRequest, IntentRequest,
// etc.) The JSON body of the request is provided in the event parameter.
exports.handler = (event, context, callback) => {
    try {
        console.log('event.session.application.applicationId=${event.session.application.applicationId}');

        /**
         * Uncomment this if statement and populate with your skill's application ID to
         * prevent someone else from configuring a skill that sends requests to this function.
         */
        /*
        if (event.session.application.applicationId !== 'amzn1.echo-sdk-ams.app.[unique-value-here]') {
             callback('Invalid Application ID');
        }
        */

        if (event.session.new) {
            onSessionStarted({ requestId: event.request.requestId }, event.session);
        }

        if (event.request.type === 'LaunchRequest') {
            onLaunch(event.request,
                event.session,
                (sessionAttributes, speechletResponse) => {
                    callback(null, buildResponse(sessionAttributes, speechletResponse));
                });
        } else if (event.request.type === 'IntentRequest') {
            onIntent(event.request,
                event.session,
                (sessionAttributes, speechletResponse) => {
                    callback(null, buildResponse(sessionAttributes, speechletResponse));
                });
        } else if (event.request.type === 'SessionEndedRequest') {
            onSessionEnded(event.request, event.session);
            callback();
        }
    } catch (err) {
        callback(err);
    }
};

Alexa Intent Schema

JSON
{
   "intents":[
      {
         "intent":"AMAZON.ResumeIntent"
      },
      {
         "intent":"AMAZON.PauseIntent"
      },
      {
         "intent":"CannotFindProblemIntent",
         "slots":[
            {
               "name":"noproblemslot",
               "type":"NOPROBLEMSSLOT"
            }
         ]
      },
     
     {
         "intent":"GuestStartAction",
         "slots":[
            {
               "name":"gueststartslot",
               "type":"GUESTSTARTACTION"
            }
         ]
      },
      {
         "intent":"GetMaintenanceissueIssuename",
         "slots":[
            {
               "name":"issuesayslot",
               "type":"ISSUESAY"
            },
            {
               "name":"issuenameslot",
               "type":"ISSUENAME"
            }
         ]
      },
      {
         "intent":"Emergencyintent",
         "slots":[
            {
               "name":"emgslot",
               "type":"EMISSUE"
            }
         ]
      },
      {
         "intent":"GetMaintenanceIssueIssuetype",
         "slots":[
            {
               "name":"issuetypeslot",
               "type":"ISSUETYPE"
            }
         ]
      },
      {
         "intent":"GetWaterReportIntent",
         "slots":[
            {
               "name":"waterreportslot",
               "type":"WATERREPORTSAY"
            }
         ]
      },
     {
         "intent":"GetWaterReportDetailsIntent",
         "slots":[
            {
               "name":"waterreportdetailslot",
               "type":"WATERREPORTDETAILSAY"
            }
         ]
      },
      {
         "intent":"MyUnitNumberIsIntent",
         "slots":[
            {
               "name":"unitNumber",
               "type":"UNITNUM"
            }
         ]
      },
      {
         "intent":"FinalThankYouIntent",
         "slots":[
            {
               "name":"finalthankyouslot",
               "type":"FINALTHANKYOUSAY"
            }
         ]
      },
     {
         "intent":"EmailMeIntent",
         "slots":[
            {
               "name":"emailmeslot",
               "type":"EMAILMESAY"
            }
         ]
      },
      {
         "intent":"WhatsMyColorIntent"
      },
      {
         "intent":"AMAZON.HelpIntent"
      }
   ]
}

Alexa Sample Utterances help Alexa to better understand the voice input and invoke the intents.

JavaScript
The sample Utterances help the Alexa to better understand the voice input and invoke the intents.
MyUnitNumberIsIntent my unit number is {unitNumber}
MyUnitNumberIsIntent my unit is {unitNumber}
MyUnitNumberIsIntent my number is {unitNumber}
CannotFindProblemIntent {noproblemslot}
GuestStartAction {gueststartslot}
GetWaterReportIntent give me {waterreportslot} from the previous day
GetWaterReportIntent give me {waterreportslot} 
GetWaterReportIntent tell me {waterreportslot}
EmailMeIntent {emailmeslot}
EmailMeIntent {emailmeslot} Thanks.
EmailMeIntent {emailmeslot} Thank you.
FinalThankYouIntent {finalthankyouslot} Alfred
FinalThankYouIntent {finalthankyouslot} 
GetWaterReportDetailsIntent {waterreportdetailslot}

CSS for dashboard Web App

CSS
row {
 width: 100%;

}

.dashboard {
    width: 60%;
    margin-right: 20px;
    position:absolute;
    left:5%;
}
title {
    left: 5%;
}

.room {
    border: .1px solid #000;
    width: 9.5%;
    text-align: center;
     float:left;
     color:white;
     height: 40px;
     padding-top:15px;
}
.room-button {
    border: .1px solid #000;
    width: 9.5%;
    text-align: center;
     float:left;
     color:white;
     height:20px;
}
.bad {
    background-color: red;
}

.good {
    background-color: green;
}

.sideinfo {
    width: 30%;
    border: 1px solid #000;
    float:left;
    position:absolute;
    left:65%;
}

.bathroom {
    left-margin:5px;
    width:15px;
    background-color: green;
    border-radius:50%;
    height:15px;
    font-size: 8px;
    float:left;
    color:white;
    border: 1px solid #000;
}
.kitchen {
    top-padding:3px;
    width:15px;
    background-color: green;
    border-radius:50%;
    height:15px;
    font-size: 8px;
    float:left;
    color:white;
    border: 1px solid #000;    
}
.lr {
    top-padding:3px;
    width:15px;
    background-color: green;
    border-radius:50%;
    height:15px;
    font-size: 8px;
    float:left;
    color:white;
    border: 1px solid #000;
}

Credits

Weiqi Zhao

Weiqi Zhao

1 project • 2 followers
Herminio Garcia

Herminio Garcia

2 projects • 2 followers
Pierre Antoine

Pierre Antoine

2 projects • 3 followers

Comments