# Path: nilmtk/node.py
# class Node(object):
#     """Abstract class defining interface for all Node subclasses,
#     where a 'node' is a module which runs pre-processing or statistics
#     (or, later, maybe NILM training or disaggregation).
#     """
# 
#     requirements = {}
#     postconditions = {}
#     results_class = None
# 
#     def __init__(self, upstream=None, generator=None):
#         """
#         Parameters
#         ----------
#         upstream : an ElecMeter or MeterGroup or a Node subclass
#             Required methods:
#             - dry_run_metadata
#             - get_metadata
#             - process (not required if `generator` supplied)
#         generator : Python generator. Optional
#             Used when `upstream` object is an ElecMeter or MeterGroup.
#             Provides source of data.
#         """
#         self.upstream = upstream
#         self.generator = generator
#         self.results = None
#         self.reset()
# 
#     def reset(self):
#         if self.results_class is not None:
#             self.results = self.results_class()
# 
#     def process(self):
#         return self.generator # usually overridden by subclass
# 
#     def run(self):
#         """Pulls data through the pipeline.  Useful if we just want to calculate 
#         some stats."""
#         for _ in self.process():
#             pass
# 
#     def check_requirements(self):
#         """Checks that `self.upstream.dry_run_metadata` satisfies `self.requirements`.
# 
#         Raises
#         ------
#         UnsatistfiedRequirementsError
#         """
#         # If a subclass has complex rules for preconditions then
#         # override this method.
#         unsatisfied = find_unsatisfied_requirements(self.upstream.dry_run_metadata(),
#                                                     self.requirements)
#         if unsatisfied:
#             msg = str(self) + " not satisfied by:\n" + str(unsatisfied)
#             raise UnsatisfiedRequirementsError(msg)
#             
#     def dry_run_metadata(self):
#         """Does a 'dry run' so we can validate the full pipeline before
#         loading any data.
# 
#         Returns
#         -------
#         dict : dry run metadata
#         """
#         state = deepcopy(self.__class__.postconditions)
#         recursively_update_dict(state, self.upstream.dry_run_metadata())
#         return state
# 
#     def get_metadata(self):
#         if self.results:
#             metadata = deepcopy(self.upstream.get_metadata())
#             results_dict = self.results.to_dict()
#             recursively_update_dict(metadata, results_dict)
#         else:
#             # Don't bother to deepcopy upstream's metadata if 
#             # we aren't going to modify it.
#             metadata = self.upstream.get_metadata()
#         return metadata
# 
#     def required_measurements(self, state):
#         """
#         Returns
#         -------
#         Set of measurements that need to be loaded from disk for this node.
#         """
#         return set()
# 
# Path: nilmtk/utils.py
# def get_index(data):
#     """
#     Parameters
#     ----------
#     data : pandas.DataFrame or Series or DatetimeIndex
# 
#     Returns
#     -------
#     index : the index for the DataFrame or Series
#     """
#     if isinstance(data, (pd.DataFrame, pd.Series)):
#         index = data.index
#     elif isinstance(data, pd.DatetimeIndex):
#         index = data
#     else:
#         raise TypeError('wrong type for `data`.')
#     return index


import numpy as np
from ..node import Node
from ..exceptions import TooFewSamplesError
from ..utils import get_index 
from .dropoutrateresults import DropoutRateResults

class DropoutRate(Node):

    requirements = {'device': {'sample_period': 'ANY VALUE'}}
    postconditions =  {'statistics': {'dropout_rate': None}}
    results_class = DropoutRateResults

    def process(self):
        self.check_requirements()
        metadata = self.upstream.get_metadata()
        sample_period = metadata['device']['sample_period']
        for chunk in self.upstream.process():
            dropout_rate = get_dropout_rate(chunk, sample_period)
            self.results.append(chunk.timeframe, 
                                {'dropout_rate': dropout_rate,
                                 'n_samples': len(chunk)})

# Path: nilmtk/node.py
# class Node(object):
#     """Abstract class defining interface for all Node subclasses,
#     where a 'node' is a module which runs pre-processing or statistics
#     (or, later, maybe NILM training or disaggregation).
#     """
# 
#     requirements = {}
#     postconditions = {}
#     results_class = None
# 
#     def __init__(self, upstream=None, generator=None):
#         """
#         Parameters
#         ----------
#         upstream : an ElecMeter or MeterGroup or a Node subclass
#             Required methods:
#             - dry_run_metadata
#             - get_metadata
#             - process (not required if `generator` supplied)
#         generator : Python generator. Optional
#             Used when `upstream` object is an ElecMeter or MeterGroup.
#             Provides source of data.
#         """
#         self.upstream = upstream
#         self.generator = generator
#         self.results = None
#         self.reset()
# 
#     def reset(self):
#         if self.results_class is not None:
#             self.results = self.results_class()
# 
#     def process(self):
#         return self.generator # usually overridden by subclass
# 
#     def run(self):
#         """Pulls data through the pipeline.  Useful if we just want to calculate 
#         some stats."""
#         for _ in self.process():
#             pass
# 
#     def check_requirements(self):
#         """Checks that `self.upstream.dry_run_metadata` satisfies `self.requirements`.
# 
#         Raises
#         ------
#         UnsatistfiedRequirementsError
#         """
#         # If a subclass has complex rules for preconditions then
#         # override this method.
#         unsatisfied = find_unsatisfied_requirements(self.upstream.dry_run_metadata(),
#                                                     self.requirements)
#         if unsatisfied:
#             msg = str(self) + " not satisfied by:\n" + str(unsatisfied)
#             raise UnsatisfiedRequirementsError(msg)
#             
#     def dry_run_metadata(self):
#         """Does a 'dry run' so we can validate the full pipeline before
#         loading any data.
# 
#         Returns
#         -------
#         dict : dry run metadata
#         """
#         state = deepcopy(self.__class__.postconditions)
#         recursively_update_dict(state, self.upstream.dry_run_metadata())
#         return state
# 
#     def get_metadata(self):
#         if self.results:
#             metadata = deepcopy(self.upstream.get_metadata())
#             results_dict = self.results.to_dict()
#             recursively_update_dict(metadata, results_dict)
#         else:
#             # Don't bother to deepcopy upstream's metadata if 
#             # we aren't going to modify it.
#             metadata = self.upstream.get_metadata()
#         return metadata
# 
#     def required_measurements(self, state):
#         """
#         Returns
#         -------
#         Set of measurements that need to be loaded from disk for this node.
#         """
#         return set()
# 
# Path: nilmtk/utils.py
# def get_index(data):
#     """
#     Parameters
#     ----------
#     data : pandas.DataFrame or Series or DatetimeIndex
# 
#     Returns
#     -------
#     index : the index for the DataFrame or Series
#     """
#     if isinstance(data, (pd.DataFrame, pd.Series)):
#         index = data.index
#     elif isinstance(data, pd.DatetimeIndex):
#         index = data
#     else:
#         raise TypeError('wrong type for `data`.')
#     return index


# Path: nilmtk/stats/dropoutrate.py
import numpy as np
from ..node import Node
from ..exceptions import TooFewSamplesError
from ..utils import get_index 
from .dropoutrateresults import DropoutRateResults


class DropoutRate(Node):

    requirements = {'device': {'sample_period': 'ANY VALUE'}}
    postconditions =  {'statistics': {'dropout_rate': None}}
    results_class = DropoutRateResults

    def process(self):
        self.check_requirements()
        metadata = self.upstream.get_metadata()
        sample_period = metadata['device']['sample_period']
        for chunk in self.upstream.process():
            dropout_rate = get_dropout_rate(chunk, sample_period)
            self.results.append(chunk.timeframe, 
                                {'dropout_rate': dropout_rate,
                                 'n_samples': len(chunk)})

            yield chunk