text: from collections.abc import Iterable
  from pydoc import locate
  from abc import ABC, abstractmethod
  import numbers
  import numpy as np
  import pandas as pd
  
  from raymon.globals import (
      Buildable,
      Serializable,
      DataException,
      ProfileStateException,
  )
  from raymon.profiling.stats import Stats, CategoricStats, FloatStats, IntStats, equalize_domains
  from raymon.tags import Tag, CTYPE_TAGTYPES
  from raymon.profiling.extractors import Extractor, NoneExtractor, NoneEvalExtractor
  
  
  class DataType:
      INT = "INT"
      FLOAT = "FLOAT"
      CAT = "CAT"
  
  
  class Component(Serializable, Buildable, ABC):
      def __init__(self, name, extractor, dtype=DataType.FLOAT, stats=None):
          self.name = name
          self.extractor = extractor
          self.dtype = dtype
          self.stats = stats
  
      @property
      def name(self):
          return self._name
  
      @name.setter
      def name(self, value):
          if not isinstance(value, str):
              raise ValueError(f"Component name should be a string")
          self._name = value.lower()
  
      @property
      def extractor(self):
          return self._extractor
  
      @extractor.setter
      def extractor(self, value):
          if not isinstance(value, Extractor):
              raise ValueError(f"Extractor should be a subclass of Extractor")
          self._extractor = value
  
      @property
      def dtype(self):
          return self._dtype
  
      @dtype.setter
      def dtype(self, value):
          if not value in [DataType.FLOAT, DataType.INT, DataType.CAT]:
              raise ValueError(f"Component dtype not valid.")
          self._dtype = value
  
      @property
      def stats(self):
          return self._stats
  
      @stats.setter
      def stats(self, value):
          if value is None:
              if self.dtype == DataType.FLOAT:
                  self._stats = FloatStats()
              elif self.dtype == DataType.INT:
                  self._stats = IntStats()
              else:
                  self._stats = CategoricStats()
  
          elif isinstance(value, FloatStats) and self.dtype == DataType.FLOAT:
              self._stats = value
          elif isinstance(value, IntStats) and self.dtype == DataType.INT:
              self._stats = value
          elif isinstance(value, CategoricStats) and self.dtype == DataType.CAT:
              self._stats = value
          else:
              raise DataException(f"Stats type / dtype mismatch. {self.dtype} <-> {type(value)}")
  
      """Serializable interface """
  
      def to_jcr(self):
  
          data = {
              "class": self.class2str(),
              "state": {
                  "name": self.name,
                  "dtype": self.dtype,
                  "stats": self.stats.to_jcr(),
                  "extractor": self.extractor.to_jcr(),
              },
          }
          return data
  
      @classmethod
      def from_jcr(cls, jcr, mock_extractor=False):
          classpath = jcr["class"]
          compclass = locate(classpath)
          if compclass is None:
              raise NameError(f"Could not locate classpath {classpath}")
          return compclass.from_jcr(jcr["state"])
  
      def build_extractor(self, data):
          self.extractor.build(data)
  
      def build(self, data, domain=None):
          # Compile extractor
          self.build_extractor(data)
          # Configure stats
          return self.build_stats(data, domain=domain)
  
      def is_built(self):
          return self.extractor.is_built() and self.stats.is_built()
  
      def contrast(self, other, thresholds):
  
          invalids_threshold = thresholds.get("invalids", 0.01)
          drift_threshold = thresholds.get("drift", 0.05)
  
          drift_report = self.stats.report_drift(other.stats, threshold=drift_threshold)
          invalids_report = self.stats.report_invalid_diff(other.stats, threshold=invalids_threshold)
  
          return {"drift": drift_report, "invalids": invalids_report}
  
      def __repr__(self):
          return str(self)
  
  
  class InputComponent(Component):
      def __init__(self, name, extractor, dtype=DataType.FLOAT, stats=None):
          super().__init__(name=name, extractor=extractor, dtype=dtype, stats=stats)
  
      def build_stats(self, data, domain=None):
          extracted = self.extractor.extract_multiple(data)
          self.stats.build(extracted, domain=domain)
          return extracted
  
      def validate(self, data):
          component = self.extractor.extract(data)
          # Make a tag from the component
          feat_tag = self.stats.component2tag(name=self.name, value=component, tagtype=CTYPE_TAGTYPES["input"]["tagtype"])
          # Check min, max, nan or None and raise data error
          err_tag = self.stats.check_invalid(
              name=self.name, value=component, tagtype=CTYPE_TAGTYPES["input"]["errortype"]
          )
          tags = [feat_tag, err_tag]
          # Filter Nones
          tags = [tag for tag in tags if tag is not None]
          return tags
  
      @classmethod
      def from_jcr(cls, jcr, mock_extractor=False):
          name = jcr["name"]
          dtype = jcr["dtype"]
          stats = Stats.from_jcr(jcr["stats"])
          if mock_extractor:
              extractor = NoneExtractor()
          else:
              extractor = Extractor.from_jcr(jcr["extractor"])
          component = cls(name=name, extractor=extractor, stats=stats, dtype=dtype)
          return component
  
      def __str__(self):
          return f"InputComponent(name={self.name}, dtype={self.dtype}, extractor={self.extractor})"
  
  
  class OutputComponent(Component):
      def __init__(self, name, extractor, dtype=DataType.FLOAT, stats=None):
          super().__init__(name=name, extractor=extractor, dtype=dtype, stats=stats)
  
      def build_stats(self, data, domain=None):
          extracted = self.extractor.extract_multiple(data)
          self.stats.build(extracted, domain=domain)
          return extracted
  
      def validate(self, data):
          component = self.extractor.extract(data)
          # Make a tag from the component
          feat_tag = self.stats.component2tag(
              name=self.name, value=component, tagtype=CTYPE_TAGTYPES["output"]["tagtype"]
          )
          # Check min, max, nan or None and raise data error
          err_tag = self.stats.check_invalid(
              name=self.name, value=component, tagtype=CTYPE_TAGTYPES["output"]["errortype"]
          )
          tags = [feat_tag, err_tag]
          # Filter Nones
          tags = [tag for tag in tags if tag is not None]
          return tags
  
      @classmethod
      def from_jcr(cls, jcr, mock_extractor=False):
          name = jcr["name"]
          dtype = jcr["dtype"]
          stats = Stats.from_jcr(jcr["stats"])
          if mock_extractor:
              extractor = NoneExtractor()
          else:
              extractor = Extractor.from_jcr(jcr["extractor"])
          component = cls(name=name, extractor=extractor, stats=stats, dtype=dtype)
          return component
  
      def __str__(self):
          return f"OutputComponent(name={self.name}, dtype={self.dtype}, extractor={self.extractor})"
  
  
  class ActualComponent(Component):
      def __init__(self, name, extractor, dtype=DataType.FLOAT, stats=None):
          super().__init__(name=name, extractor=extractor, dtype=dtype, stats=stats)
  
      def build_stats(self, data, domain=None):
          extracted = self.extractor.extract_multiple(data)
          self.stats.build(extracted, domain=domain)
          return extracted
  
      def validate(self, data):
          component = self.extractor.extract(data)
          # Make a tag from the component
          feat_tag = self.stats.component2tag(
              name=self.name, value=component, tagtype=CTYPE_TAGTYPES["actual"]["tagtype"]
          )
          # Check min, max, nan or None and raise data error
          err_tag = self.stats.check_invalid(
              name=self.name, value=component, tagtype=CTYPE_TAGTYPES["actual"]["errortype"]
          )
          tags = [feat_tag, err_tag]
          # Filter Nones
          tags = [tag for tag in tags if tag is not None]
          return tags
  
      @classmethod
      def from_jcr(cls, jcr, mock_extractor=False):
          name = jcr["name"]
          dtype = jcr["dtype"]
          stats = Stats.from_jcr(jcr["stats"])
          if mock_extractor:
              extractor = NoneExtractor()
          else:
              extractor = Extractor.from_jcr(jcr["extractor"])
          component = cls(name=name, extractor=extractor, stats=stats, dtype=dtype)
          return component
  
      def __str__(self):
          return f"ActualComponent(name={self.name}, dtype={self.dtype}, extractor={self.extractor})"
  
  
  class EvalComponent(Component):
      def __init__(self, name, extractor, dtype=DataType.FLOAT, stats=None):
          super().__init__(name=name, extractor=extractor, dtype=dtype, stats=stats)
  
      def build_stats(self, data, domain=None):
          output, actual = data
          extracted = self.extractor.extract_multiple(output=output, actual=actual)
          self.stats.build(extracted, domain=domain)
          return extracted
  
      def validate(self, data):
          output, actual = data
          component = self.extractor.extract(output=output, actual=actual)
          # Make a tag from the component
          feat_tag = self.stats.component2tag(name=self.name, value=component, tagtype=CTYPE_TAGTYPES["eval"]["tagtype"])
          # Check min, max, nan or None and raise data error
          err_tag = self.stats.check_invalid(name=self.name, value=component, tagtype=CTYPE_TAGTYPES["eval"]["errortype"])
          tags = [feat_tag, err_tag]
          # Filter Nones
          tags = [tag for tag in tags if tag is not None]
          return tags
  
      @classmethod
      def from_jcr(cls, jcr, mock_extractor=False):
          name = jcr["name"]
          dtype = jcr["dtype"]
          stats = Stats.from_jcr(jcr["stats"])
          if mock_extractor:
              extractor = NoneEvalExtractor()
          else:
              extractor = Extractor.from_jcr(jcr["extractor"])
          component = cls(name=name, extractor=extractor, stats=stats, dtype=dtype)
          return component
  
      def __str__(self):
          return f"EvalComponent(name={self.name}, dtype={self.dtype}, extractor={self.extractor})"
  