예제 #1
0
class SolarEdgeUsagePoint(db.Model):
    __tablename__ = 'solar_edge_usage_points'
    id = db.Column(db.Integer, primary_key=True)
    energy_account_id = db.Column(db.Integer,
                                  db.ForeignKey('energy_accounts.id'))
    time_unit = db.Column(db.String(50))
    unit_of_measure = db.Column(db.String(50))
    date = db.Column(db.Date)
    value = db.Column(db.Numeric(20, 5))

    def __repr__(self):
        return '<SolarEdgeUsagePoint {}>'.format(self.id)
예제 #2
0
class Permission(db.Model):
    __tablename__ = 'permissions'
    id = db.Column(db.Integer, primary_key=True)
    permission = db.Column(db.String(50))

    def __repr__(self):
        return '<Permission {}>'.format(self.id)
예제 #3
0
class EnergyEvent(db.Model):
    __tablename__ = 'energy_events'
    id = db.Column(db.Integer, primary_key=True)
    energy_account_id = db.Column(db.Integer,
                                  db.ForeignKey('energy_accounts.id'))
    date = db.Column(db.Date)
    event_type = db.Column(db.Integer)
    info = db.Column(db.String(355))
예제 #4
0
class AppEvent(db.Model):
    __tablename__ = 'app_events'
    id = db.Column(db.Integer, primary_key=True)
    date_time = db.Column(db.DateTime)
    event_type = db.Column(db.Integer)
    level = db.Column(db.Integer)
    info = db.Column(db.String(1300))
    user_id = db.Column(db.Integer, db.ForeignKey('users.id'))
예제 #5
0
class Role(db.Model):
    __tablename__ = 'roles'
    id = db.Column(db.Integer, primary_key=True)
    role = db.Column(db.String(50))
    permissions = relationship("Permission",
                               secondary=permission_associations,
                               backref="roles")

    def __repr__(self):
        return '<Role {}>'.format(self.id)
예제 #6
0
class CeleryTask(db.Model):
    __tablename__ = 'celery_tasks'
    id = db.Column(db.Integer, primary_key=True)
    energy_account_id = db.Column(db.Integer,
                                  db.ForeignKey('energy_accounts.id'))
    task_id = db.Column(db.String(50))
    task_status = db.Column(db.Integer)

    def __repr__(self):
        return '<CeleryTask {}>'.format(self.id)
예제 #7
0
class EnergyAccount(db.Model):
    __tablename__ = 'energy_accounts'
    id = db.Column(db.Integer, primary_key=True)
    energy_events = db.relationship('EnergyEvent',
                                    backref="energy_account",
                                    lazy='dynamic',
                                    cascade="all, delete-orphan")
    nick_name = db.Column(db.String(255))
    address_one = db.Column(db.String(255))
    address_two = db.Column(db.String(255))
    city = db.Column(db.String(50))
    state = db.Column(db.String(50))
    zip_code = db.Column(db.String(50))
    pge_nag = db.Column(db.Integer, default=1)
    pge_bulk_id = db.Column(db.String(50))
    pge_subscription_id = db.Column(db.String(50))
    pge_usage_point = db.Column(db.String(50))
    pge_access_token = db.Column(db.String(255))
    pge_refresh_token = db.Column(db.String(255))
    pge_last_date = db.Column(db.DateTime)
    pge_refresh_token_expiration = db.Column(db.DateTime)
    pge_first_date = db.Column(db.DateTime)
    solar_nag = db.Column(db.Integer, default=1)
    solar_first_date = db.Column(db.DateTime)
    solar_last_date = db.Column(db.DateTime)
    solar_install_date = db.Column(db.DateTime)
    solar_edge_site_id = db.Column(db.String(255))
    solar_edge_api_key = db.Column(db.String(255))
    enphase_user_id = db.Column(db.String(255))
    enphase_system_id = db.Column(db.String(255))
    pge_usage_points = db.relationship('PGEUsagePoint',
                                       backref="energy_account",
                                       cascade="all, delete-orphan",
                                       lazy='dynamic')
    solar_edge_usage_points = db.relationship('SolarEdgeUsagePoint',
                                              backref="energy_account",
                                              cascade="all, delete-orphan",
                                              lazy='dynamic')
    celery_tasks = db.relationship('CeleryTask',
                                   backref="energy_account",
                                   cascade="all, delete-orphan",
                                   lazy='dynamic')

    def energy_events(self,
                      start_date=seven_days_ago,
                      end_date=today,
                      serialize=False):
        events = EnergyEvent.query.filter(
            (EnergyEvent.date <= end_date) & (EnergyEvent.date >= start_date)
            & (EnergyEvent.energy_account_id == self.id)).order_by(
                EnergyEvent.date.asc()).all()

        if serialize:
            serialized_events = []
            for event in events:
                event_dict = {}
                event_dict['date'] = event.date.strftime('%m/%d/%Y')
                event_dict[
                    'event_type'] = 'Weather Related' if event.event_type == 1 else 'Panel Related'
                event_dict['info'] = event.info
                event_dict['id'] = event.id
                serialized_events.append(event_dict)
            return json.dumps(serialized_events)

        return events

    def cumulative_usage_graph(self,
                               start_date=seven_days_ago,
                               end_date=today):
        """Graph that shows net usage.
        Ex.
        3) Graph the daily cumulative usage from a date (1/1), on any graph. So if I am graphing 9/1 - 9/19, we do this:

        a) Pull the usage from 1/1 to 8/31 and sum it (either with sum SQL or just adding up the data)

        b) Starting the graph range, start and the cumulative and show each days change.
        This is a line graph similar to the SE production.

        c) Later we can move to a "caching" style db that saves us processing a year of data, but for now,
        lets just pull the usage data live and see how bad it is in terms of timing.
        for flow_direction 1 means delivered electric and 19 means reversed and sold back to grid. """
        today = datetime.datetime.now()
        first_of_year = datetime.datetime(year=start_date.year, month=1, day=1)

        if self.solar_install_date and (self.solar_install_date >
                                        first_of_year):
            historical_start = self.solar_install_date
        else:
            historical_start = first_of_year

        print historical_start
        print self.solar_install_date
        print start_date

        # sold back to grid
        negative_usage = PGEUsagePoint.query.with_entities(
            func.sum(PGEUsagePoint.interval_value)).filter(
                (PGEUsagePoint.flow_direction == 19)
                & (PGEUsagePoint.interval_start < start_date)
                & (PGEUsagePoint.energy_account_id == self.id)
                & (PGEUsagePoint.interval_start >= historical_start)).scalar()

        # used electric
        positive_usage = PGEUsagePoint.query.with_entities(
            func.sum(PGEUsagePoint.interval_value)).filter(
                (PGEUsagePoint.flow_direction == 1)
                & (PGEUsagePoint.interval_start < start_date)
                & (PGEUsagePoint.energy_account_id == self.id)
                & (PGEUsagePoint.interval_start >= historical_start)).scalar()

        if (negative_usage is None) and (positive_usage is None):
            return None

        print positive_usage, negative_usage

        starting_point = positive_usage - negative_usage

        print starting_point

        original_graph = self.pge_incoming_outgoing_combined_graph(
            start_date, end_date)

        new_data = []
        for data, label in original_graph:
            starting_point = data + starting_point
            new_data.append(starting_point)

        labels = [y for x, y in original_graph]

        new_graph = zip(new_data, labels)

        return new_graph

    def comparison_graph(self, start_date=seven_days_ago, end_date=today):
        """This graph accepts a date range and outputs a line chart with
        the combined incoming/outgoing energy usage for the date and range
        in addition to the same graph for the date range exactly one year prior."""
        net_usage_year1 = self.pge_incoming_outgoing_combined_graph(
            start_date, end_date)
        net_usage_year2 = self.pge_incoming_outgoing_combined_graph(
            (start_date - relativedelta(years=1)),
            (end_date - relativedelta(years=1)))
        if not net_usage_year2:
            net_usage_year2 = [(0, 'fake_label')
                               for x in range(len(net_usage_year1))]
        y1_data = [x[0] for x in net_usage_year1]
        y2_data = [x[0] for x in net_usage_year2]
        labels = [x[1] for x in net_usage_year1]
        return zip(y1_data, y2_data, labels)

    def comparison_graph_solar(self,
                               start_date=seven_days_ago,
                               end_date=today):
        """This graph accepts a date range and outputs a line chart with
        the combined incoming/outgoing energy usage for the date and range
        in addition to the same graph for the date range exactly one year prior."""
        net_usage_year1 = self.solar_edge_production_graph(
            start_date, end_date)
        net_usage_year2 = self.solar_edge_production_graph(
            (start_date - relativedelta(years=1)),
            (end_date - relativedelta(years=1)))
        print net_usage_year1, net_usage_year2
        if not net_usage_year2:
            net_usage_year2 = [(0, 'fake_label')
                               for x in range(len(net_usage_year1))]
        y1_data = [x[0] for x in net_usage_year1]
        y2_data = [x[0] for x in net_usage_year2]
        labels = [x[1] for x in net_usage_year1]
        print zip(y1_data, y2_data, labels)
        return zip(y1_data, y2_data, labels)

    def production_net_usage_graph(self,
                                   start_date=seven_days_ago,
                                   end_date=today):
        """Solar Edge production vs Combined PGE data"""
        production = self.solar_edge_production_graph(start_date, end_date)
        net_usage = self.pge_incoming_outgoing_combined_graph(
            start_date, end_date)
        """Before we can output the data, we need to push the data with matching dates together.
        The goal is to have the same data ranges available from both solar edge and PGE but sometimes
        we will only have 10 days of data for pge and 15 days of data for solar edge. Data for the same date
        ranges need to be displayed together, otherwise funky things happen with the chart."""

        # get all the dates for both lists and combine them into one list in date order, removing duplicates, convert to dict
        date_dict = {
            key: None
            for key in set([p[1]
                            for p in production] + [n[1] for n in net_usage])
        }

        for value, date in net_usage:
            date_dict[date] = (value, )

        for value, date in production:
            if not date_dict[date]:
                date_dict[date] = (value, 0)
            else:
                date_dict[date] = (value, ) + date_dict[date]

        date_list = sorted(date_dict.items(), key=lambda x: x[0])

        date_list = [(values[0], values[1], labels) if len(values) == 2 else
                     (0, 0, labels) for labels, values in date_list]

        # Returns a list of tuples e.g. [(production, net usage, label)]
        return date_list

    def production_net_usage_percentage_graph(self,
                                              start_date=seven_days_ago,
                                              end_date=today):
        """Solar Edge Production vs Combined PGE data normalized to 100%"""
        p = self.production_net_usage_graph(start_date, end_date)

        labels = [labels for production, net_usage, labels in p]
        production_percentage = [(x / (x + y) * 100) if (x + y) != 0 else 0
                                 for x, y, l in p]
        production_percentage = [
            x if x <= 100 else 100 for x in production_percentage
        ]

        net_usage_percentage = [
            100 - x if x != 0 else 0 for x in production_percentage
        ]

        net_input = [((x / (x + y)) - 1) if (x + y) != 0 else 0
                     for x, y, l in p]
        net_input = [x * 100 if x > 0 else 0 for x in net_input]

        return zip(production_percentage, net_input, net_usage_percentage,
                   labels)

    def pge_incoming_outgoing_graph(self,
                                    start_date=seven_days_ago,
                                    end_date=today):
        from solarmonitor.pge.pge_helpers import PGEHelper
        pge_helper = PGEHelper(start_date, end_date, self.id)
        """Set the variables """
        incoming_data, incoming_labels, outgoing_data, outgoing_labels = pge_helper.get_daily_data_and_labels(
        )

        return zip(incoming_data, outgoing_data, outgoing_labels)

    def pge_incoming_outgoing_combined_graph(self,
                                             start_date=seven_days_ago,
                                             end_date=today,
                                             separate=None,
                                             financial=False):
        """Uses the PGEHelper class to return a zipped list of kWh values and datetime objects as a list of tuples.
        If no data is found for a particular day, a value of zero is returned as the kWh part of the tuple."""
        from solarmonitor.pge.pge_helpers import PGEHelper
        pge_helper = PGEHelper(start_date, end_date, self.id)
        """Set the variables """
        incoming_data, incoming_labels, outgoing_data, outgoing_labels = pge_helper.get_daily_data_and_labels(
        )

        net_usage = [x - y for x, y in zip(incoming_data, outgoing_data)]

        positive_usage = [x if x > 0 else 0 for x in net_usage]
        negative_usage = [x if x < 0 else 0 for x in net_usage]

        cost_per_kWh = 0.028780

        if financial:
            net_usage = [((x - y) * cost_per_kWh)
                         for x, y in zip(incoming_data, outgoing_data)]

        if separate:
            return zip(positive_usage, negative_usage, incoming_labels)

        return zip(net_usage, incoming_labels)

    def solar_edge_production_graph(self,
                                    start_date=seven_days_ago,
                                    end_date=today):
        """Pulls all solar edge data (by day) and returns a zipped list of kWh values and datetime objects as a list of tuples."""
        solar_edge_data_pull = SolarEdgeUsagePoint.query.filter(
            (SolarEdgeUsagePoint.date >= start_date)
            & (SolarEdgeUsagePoint.energy_account_id == self.id)
            & (SolarEdgeUsagePoint.date <= end_date)).order_by(
                SolarEdgeUsagePoint.date.asc()).all()

        se_energy_data = []
        se_energy_labels = []

        for each in solar_edge_data_pull:
            se_energy_data.append(each.value)
            se_energy_labels.append(each.date)

        se_energy_data = [float(x) * 1000 for x in se_energy_data]

        return zip(se_energy_data, se_energy_labels)

    def serialize(self):
        if not self.solar_install_date:
            install_date = None
        else:
            install_date = self.solar_install_date.strftime('%m/%d/%Y')
        return {
            'nick_name': self.nick_name,
            'address_one': self.address_one,
            'address_two': self.address_two,
            'city': self.city,
            'state': self.state,
            'zip_code': self.zip_code,
            'pge_bulk_id': self.pge_bulk_id,
            'solar_edge_site_id': self.solar_edge_site_id,
            'solar_edge_api_key': self.solar_edge_api_key,
            'solar_install_date': install_date
        }

    def serialize_charts(self,
                         chart,
                         start_date=seven_days_ago,
                         end_date=today,
                         date_format='%m/%d',
                         separate=False,
                         financial=False):
        if chart == 'comparison_graph_solar':
            comparison_graph_solar = self.comparison_graph_solar(
                start_date, end_date)
            return {
                'y1_data': [
                    convert_to_kWh(y1_data)
                    for y1_data, y2_data, labels in comparison_graph_solar
                ],
                'y2_data': [
                    convert_to_kWh(y2_data)
                    for y1_data, y2_data, labels in comparison_graph_solar
                ],
                'labels': [
                    labels.strftime(date_format)
                    for y1_data, y2_data, labels in comparison_graph_solar
                ]
            }
        elif chart == 'comparison_graph':
            comparison_graph = self.comparison_graph(start_date, end_date)
            return {
                'y1_data': [
                    convert_to_kWh(y1_data)
                    for y1_data, y2_data, labels in comparison_graph
                ],
                'y2_data': [
                    convert_to_kWh(y2_data)
                    for y1_data, y2_data, labels in comparison_graph
                ],
                'labels': [
                    labels.strftime(date_format)
                    for y1_data, y2_data, labels in comparison_graph
                ]
            }
        elif chart == 'solar_edge_production_graph':
            solar_edge_production_graph = self.solar_edge_production_graph(
                start_date, end_date)
            return {
                'se_energy_data': [
                    convert_to_kWh(data)
                    for data, labels in solar_edge_production_graph
                ],
                'labels': [
                    labels.strftime(date_format)
                    for data, labels in solar_edge_production_graph
                ]
            }

        elif chart == 'pge_incoming_outgoing_combined_graph':
            if separate:
                pge_incoming_outgoing_combined_graph = self.pge_incoming_outgoing_combined_graph(
                    start_date, end_date, separate)
                return {
                    'net_usage_positive': [
                        convert_to_kWh(pos_data) for pos_data, neg_data, labels
                        in pge_incoming_outgoing_combined_graph
                    ],
                    'net_usage_negative': [
                        convert_to_kWh(neg_data) for pos_data, neg_data, labels
                        in pge_incoming_outgoing_combined_graph
                    ],
                    'labels': [
                        labels.strftime(date_format) for pos_data, neg_data,
                        labels in pge_incoming_outgoing_combined_graph
                    ]
                }
            if financial:
                pge_incoming_outgoing_combined_graph = self.pge_incoming_outgoing_combined_graph(
                    start_date, end_date, financial=financial)
                return {
                    'net_usage': [
                        convert_to_kWh(data) for data, labels in
                        pge_incoming_outgoing_combined_graph
                    ],
                    'labels': [
                        labels.strftime(date_format) for data, labels in
                        pge_incoming_outgoing_combined_graph
                    ]
                }
            pge_incoming_outgoing_combined_graph = self.pge_incoming_outgoing_combined_graph(
                start_date, end_date)
            return {
                'net_usage': [
                    convert_to_kWh(data)
                    for data, labels in pge_incoming_outgoing_combined_graph
                ],
                'labels': [
                    labels.strftime(date_format)
                    for data, labels in pge_incoming_outgoing_combined_graph
                ]
            }

        elif chart == 'pge_incoming_outgoing_graph':
            pge_incoming_outgoing_graph = self.pge_incoming_outgoing_graph(
                start_date, end_date)
            return {
                'incoming_data': [
                    convert_to_kWh(inc_data) for inc_data, out_data, labels in
                    pge_incoming_outgoing_graph
                ],
                'outgoing_data': [
                    convert_to_kWh(out_data) for inc_data, out_data, labels in
                    pge_incoming_outgoing_graph
                ],
                'labels': [
                    labels.strftime(date_format) for inc_data, out_data, labels
                    in pge_incoming_outgoing_graph
                ]
            }

        elif chart == 'cumulative_usage_graph':
            cumulative_usage_graph = self.cumulative_usage_graph(
                start_date, end_date)
            if cumulative_usage_graph is None:
                return None

            net_usage = [
                convert_to_kWh(data) for data, labels in cumulative_usage_graph
            ]

            financial_cumulative = [int(float(x) * 0.23627) for x in net_usage]
            financial_min = round(financial_cumulative[0] * 2, -1) / 2
            financial_max = round(financial_cumulative[-1] * 2, -1) / 2
            financial_step_value = (financial_max - financial_min) / 10

            return {
                'net_usage':
                net_usage,
                'labels': [
                    labels.strftime(date_format)
                    for data, labels in cumulative_usage_graph
                ],
                'financial_min':
                financial_min,
                'financial_max':
                financial_max,
                'financial_step_value':
                financial_step_value
            }

        elif chart == 'production_net_usage_percentage_graph':
            production_net_usage_percentage_graph = self.production_net_usage_percentage_graph(
                start_date, end_date)
            return {
                'production_percentage': [
                    production_percentage
                    for production_percentage, net_input, net_usage_percentage,
                    labels in production_net_usage_percentage_graph
                ],
                'net_input': [
                    net_input
                    for production_percentage, net_input, net_usage_percentage,
                    labels in production_net_usage_percentage_graph
                ],
                'net_usage_percentage': [
                    net_usage_percentage
                    for production_percentage, net_input, net_usage_percentage,
                    labels in production_net_usage_percentage_graph
                ],
                'labels': [
                    labels.strftime(date_format)
                    for production_percentage, net_input, net_usage_percentage,
                    labels in production_net_usage_percentage_graph
                ]
            }

        elif chart == 'production_net_usage_graph':
            production_net_usage_graph = self.production_net_usage_graph(
                start_date, end_date)
            return {
                'production': [
                    convert_to_kWh(production) for production, net_usage,
                    labels in production_net_usage_graph
                ],
                'net_usage': [
                    convert_to_kWh(net_usage) for production, net_usage, labels
                    in production_net_usage_graph
                ],
                'labels': [
                    labels.strftime(date_format) for production, net_usage,
                    labels in production_net_usage_graph
                ]
            }

    def __repr__(self):
        return '<EnergyAccount {}>'.format(self.id)
예제 #8
0
class User(UserMixin, db.Model):
    __tablename__ = 'users'
    id = db.Column(db.Integer, primary_key=True)
    email = db.Column(db.String(64), unique=True)
    first_name = db.Column(db.String(64))
    last_name = db.Column(db.String(64))
    address_one = db.Column(db.String(64))
    address_two = db.Column(db.String(64))
    city = db.Column(db.String(64))
    state = db.Column(db.String(64))
    zip_code = db.Column(db.String(5))
    password_hash = db.Column(db.String(128))
    app_events = db.relationship('AppEvent',
                                 backref="user",
                                 lazy='dynamic',
                                 cascade="all, delete-orphan")
    energy_accounts = relationship("EnergyAccount",
                                   secondary=energy_accounts,
                                   backref="users")
    roles = relationship("Role", secondary=role_associations, backref="users")

    def has_role(self, role):
        user_roles = [x.role for x in self.roles]
        return True if role in user_roles else False

    def can(self, permission):
        permissions = []
        for role in self.roles:
            for permission in role:
                list(set(permissions.append(permission.permission)))
        return True if permission in permissions else False

    @property
    def full_address(self):
        if self.address_one and self.city and self.state:
            return '{} {} {}, {} {}'.format(self.address_one, self.address_two,
                                            self.city, self.state,
                                            self.zip_code)
        elif len(self.energy_accounts) > 0:
            first_account = self.energy_accounts[0]
            if first_account.address_one and first_account.city and first_account.state:
                return '{} {} {}, {} {}'.format(first_account.address_one,
                                                first_account.address_two,
                                                first_account.city,
                                                first_account.state,
                                                first_account.zip_code)
            else:
                return ''
        else:
            return ''

    def log_event(self, event_type=None, level=1, info=None):
        event = AppEvent(user_id=self.id,
                         date_time=datetime.datetime.utcnow(),
                         event_type=event_type,
                         level=level,
                         info=info)
        db.session.add(event)
        db.session.commit()

    @property
    def full_name(self):
        return '{} {}'.format(self.first_name, self.last_name)

    @property
    def password(self):
        raise AttributeError('password is not a readable attribute')

    @password.setter
    def password(self, password):
        self.password_hash = bcrypt.generate_password_hash(password, 12)

    def verify_password(self, password):
        return bcrypt.check_password_hash(self.password_hash, password)

    def __repr__(self):
        return '<User {}>'.format(self.first_name)

    def generate_auth_token(self, expiration=3600):
        s = Serializer(app.config['SECRET_KEY'], expiration)
        return s.dumps({'id': self.id})

    @staticmethod
    def verify_auth_token(self, token):
        s = Serializer(app.config['SECRET_KEY'])
        try:
            data = s.loads(token)
        except:
            return False
        if data.get('id') != self.id:
            return False
        self.confirmed = 1
        db.session.commit()
        return True