示例#1
0
    def test_set_context_arg(self):
        # Tests the case where .set() is called correctly, using
        # the way of calling .set() where we pass in a Context
        # object. Verify the data is stored and can be retrieved
        # using .get().
        doc = data_model.OBInstance("CutSheet", self.taxonomy)
        ctx = data_model.Context(duration="forever",
                      entity="JUPITER",
                      ProductIdentifierAxis= "placeholder",
                      TestConditionAxis = "solar:StandardTestConditionMember")
        doc.set("solar:TypeOfDevice", "ModuleMember", context=ctx)

        now = datetime.now(),
        ctx = data_model.Context(instant= now,
                      entity="JUPITER",
                      ProductIdentifierAxis= "placeholder",
                      TestConditionAxis = "solar:StandardTestConditionMember")
        doc.set("solar:DeviceCost", 100, context=ctx, unit_name="USD")

        # Get the data bacK:
        typeFact = doc.get("solar:TypeOfDevice",
                           data_model.Context(duration="forever",
                                entity="JUPITER",
                                ProductIdentifierAxis= "placeholder",
                                TestConditionAxis = "solar:StandardTestConditionMember"))
        self.assertEqual( typeFact.value,  "ModuleMember")
        costFact = doc.get("solar:DeviceCost",
                           data_model.Context(instant = now,
                                entity="JUPITER",
                                ProductIdentifierAxis= "placeholder",
                                TestConditionAxis = "solar:StandardTestConditionMember"))
        self.assertEqual( costFact.value, 100)
示例#2
0
    def test_facts_stored_with_context(self):
        # Test we can store 2 facts of the same concept but with different
        # contexts, and pull them both back out.
        doc = data_model.OBInstance("CutSheet", self.taxonomy)
        concept = "solar:InverterCutSheetNotes"

        ctx_jan = data_model.Context(duration={"start": datetime(year=2018, month=1, day=1),
                                   "end": datetime(year=2018, month=2, day=1)},
                          entity="JUPITER",
                          ProductIdentifierAxis= "placeholder",
                          TestConditionAxis = "solar:StandardTestConditionMember")
        ctx_feb = data_model.Context(duration={"start": datetime(year=2018, month=2, day=1),
                                   "end": datetime(year=2018, month=3, day=1)},
                          entity="JUPITER",
                          ProductIdentifierAxis= "placeholder",
                          TestConditionAxis = "solar:StandardTestConditionMember")
    
        doc.set(concept, "Jan Value", context=ctx_jan)
        doc.set(concept, "Feb Value", context=ctx_feb)

        jan_fact = doc.get(concept, context=ctx_jan)
        feb_fact = doc.get(concept, context=ctx_feb)

        self.assertEqual(jan_fact.value, "Jan Value")
        self.assertEqual(feb_fact.value, "Feb Value")
示例#3
0
    def test_set_separate_dimension_args(self):
        # Tests the case where .set() is called correctly.  Use the
        # way of calling .set() where we pass in every dimension
        # separately. Verify the data is stored and can be retrieved
        # using .get().
        doc = data_model.OBInstance("CutSheet", self.taxonomy)

        # Write a TypeOfDevice and a DeviceCost:

        doc.set("solar:TypeOfDevice", "ModuleMember",
                duration="forever",
                ProductIdentifierAxis= "placeholder",
                TestConditionAxis = "solar:StandardTestConditionMember"
                )
        now = datetime.now()
        doc.set("solar:DeviceCost", 100,
                instant= now,
                ProductIdentifierAxis= "placeholder",
                TestConditionAxis= "solar:StandardTestConditionMember",
                unit_name="USD")

        typeFact = doc.get("solar:TypeOfDevice",
                           data_model.Context(duration="forever",
                                ProductIdentifierAxis= "placeholder",
                                TestConditionAxis = "solar:StandardTestConditionMember"))
        self.assertEqual( typeFact.value,  "ModuleMember")
        costFact = doc.get("solar:DeviceCost",
                           data_model.Context(instant = now,
                                ProductIdentifierAxis= "placeholder",
                                TestConditionAxis = "solar:StandardTestConditionMember"))
        self.assertEqual( costFact.value, 100)
        self.assertEqual( costFact.unit, "USD")
示例#4
0
    def test_set_default_context_values(self):
        # Test setting default values, for example something like:
        doc = data_model.OBInstance("CutSheet", self.taxonomy)
        now = datetime.now()
        doc.set_default_context({
            "entity": "JUPITER",
            "solar:TestConditionAxis": "solar:StandardTestConditionMember",
            taxonomy.PeriodType.instant: now,
            taxonomy.PeriodType.duration: "forever"
           })
        # Could also support setting default unit, even though that's not part of context:

        # If we set a fact that wants an instant context, it should use 'now':
        doc.set("solar:DeviceCost", "100", unit_name="USD", ProductIdentifierAxis = "placeholder")
        # This would normally raise an exception because it's missing instant, entity, and
        # TestConditionAxis. But we set defaults for those, so they should be filled in:
        fact = doc.get("solar:DeviceCost", data_model.Context(
            ProductIdentifierAxis = "placeholder",
            TestConditionAxis = "solar:StandardTestConditionMember",
            entity = "JUPITER",
            instant = now))
        self.assertEqual(fact.value, "100")
        self.assertEqual(fact.unit, "USD")
        self.assertEqual(fact.context.entity, "JUPITER")
        self.assertEqual(fact.context.instant, now)

        # TODO test method of calling set() where we pass in Context object.

        # If we set a fact that wants a duration context, it should use jan 1 - jan 31:
        doc.set(
            "solar:ModuleNameplateCapacity", "0.3",
            unit_name="W",
            ProductIdentifierAxis = "placeholder")
        # Would normally raise an exception because missing duration, entity, and
        # TestConditionAxis. But we set defaults for those, so they should be filled in:
        fact = doc.get("solar:ModuleNameplateCapacity", data_model.Context(
            ProductIdentifierAxis = "placeholder",
            TestConditionAxis = "solar:StandardTestConditionMember",
            entity = "JUPITER",
            duration = "forever"))
        self.assertEqual(fact.value, "0.3")
        self.assertEqual(fact.unit, "W")
        self.assertEqual(fact.context.entity, "JUPITER")
        self.assertEqual(fact.context.duration, "forever")

        # Try setting ALL the fields in set_default_context and then pass in NO context fields,
        # that should work too:
        doc.set_default_context({"entity": "JUPITER",
                               "solar:TestConditionAxis": "solar:StandardTestConditionMember",
                               taxonomy.PeriodType.instant: now,
                               "solar:ProductIdentifierAxis": "placeholder"
                               })
        doc.set("solar:DeviceCost", "99", unit_name="USD")
        fact = doc.get("solar:DeviceCost", data_model.Context(
            ProductIdentifierAxis = "placeholder",
            TestConditionAxis = "solar:StandardTestConditionMember",
            entity = "JUPITER",
            instant = now))
        self.assertEqual(fact.value, "99")
示例#5
0
    def test_ids_in_xml_and_json(self):
        # facts should have IDs in both exported JSON and exported XML, and they
        # should be the same ID either way.
        doc = data_model.OBInstance("CutSheet", self.taxonomy)
        now = datetime.now()
        doc.set_default_context({"entity": "JUPITER",
                               "solar:TestConditionAxis": "solar:StandardTestConditionMember",
                               taxonomy.PeriodType.instant: now,
                               taxonomy.PeriodType.duration: "forever"
                               })

        doc.set("solar:ModuleNameplateCapacity", "6.25", unit_name="W",
                ProductIdentifierAxis = 1)

        fact = doc.get("solar:ModuleNameplateCapacity",
                       data_model.Context(
                           ProductIdentifierAxis = 1,
                           TestConditionAxis = "solar:StandardTestConditionMember",
                           entity = "JUPITER",
                           duration="forever"))
        # Read the fact ID that was automatically assigned when we set the fact:
        fact_id = fact.id

        # Look for fact ID in JSON:
        jsonstring = doc.to_JSON_string()
        facts = json.loads(jsonstring)["facts"]
        self.assertEqual(len(list(facts.keys())), 1)
        self.assertEqual(list(facts.keys())[0], fact_id)

        # Look for fact ID in XML:
        xml = doc.to_XML_string()
        root = etree.fromstring(xml)
        fact = root.find("{http://xbrl.us/Solar/v1.2/2018-03-31/solar}ModuleNameplateCapacity")
        self.assertEqual(fact.attrib["id"], fact_id)
示例#6
0
    def test_hypercube_store_context(self):
        doc = data_model.OBInstance("CutSheet", self.taxonomy)
        table = doc.get_table("solar:InverterPowerLevelTable")

        c1 = table.store_context(data_model.Context(duration = "forever",
                                         entity = "JUPITER",
                                         ProductIdentifierAxis = "ABCD",
                                         InverterPowerLevelPercentAxis = 'solar:InverterPowerLevel50PercentMember'))
        self.assertEqual(c1.get_id(), "solar:InverterPowerLevelTable_0")
        c2 = table.store_context(data_model.Context(duration = "forever",
                                         entity = "JUPITER",
                                         ProductIdentifierAxis = "ABCD",
                                         InverterPowerLevelPercentAxis = 'solar:InverterPowerLevel50PercentMember')) # Same
        self.assertIs(c1, c2)
        c3 = table.store_context(data_model.Context(duration = "forever",
                                         entity = "JUPITER",
                                         ProductIdentifierAxis = "ABCD",
                                         InverterPowerLevelPercentAxis = 'solar:InverterPowerLevel75PercentMember')) # Different
        self.assertIsNot(c1, c3)
示例#7
0
    def test_hypercube_rejects_context_with_unwanted_axes(self):
        # Test that giving a context an *extra* axis that is invalid for the table
        # causes it to be rejected as well.
        doc = data_model.OBInstance("CutSheet", self.taxonomy)

        twoAxisContext = data_model.Context(
            ProductIdentifierAxis = "placeholder",
            TestConditionAxis = "solar:StandardTestConditionMember",
            instant = datetime.now())
        self.assertTrue( doc.validate_context("solar:DeviceCost",
                                              twoAxisContext))

        threeAxisContext = data_model.Context(
            ProductIdentifierAxis = "placeholder",
            InverterPowerLevelPercentAxis = 'solar:InverterPowerLevel50PercentMember',
            TestConditionAxis = "solar:StandardTestConditionMember",
            instant = datetime.now())
        # InverterPowerLevelPercentAxis is a valid axis and this is a valid value for it,
        # but the table that holds DeviceCost doesn't want this axis:
        with self.assertRaises(data_model.OBContextException):
            doc.validate_context("solar:DeviceCost", threeAxisContext)
示例#8
0
    def test_sufficient_context_instant_vs_duration(self):
        doc = data_model.OBInstance("CutSheet", self.taxonomy)

        # in order to set a concept value, sufficient context must be
        # provided. what is sufficient context varies by concept.
        # in general the context must provide the correct time information
        # (either duration or instant)

        # We shouldn't even be able to instantiate a context with no time info:
        with self.assertRaises(Exception):
            noTimeContext = data_model.Context(ProductIdentifierAxis="placeholder",
                                               TestConditionAxis="solar:StandardTestConditionMember")

        # solar:DeviceCost has period_type instant
        # so it requires a context with an instant. A context without an instant
        # should be insufficient:
        instantContext = data_model.Context(ProductIdentifierAxis = "placeholder",
                                            TestConditionAxis = "solar:StandardTestConditionMember",
                                            instant = datetime.now())
        durationContext = data_model.Context(ProductIdentifierAxis = "placeholder",
                                             TestConditionAxis = "solar:StandardTestConditionMember",
                                             duration = "forever")

        self.assertTrue( doc.validate_context("solar:DeviceCost",
                                              instantContext))
        # A context with a duration instead of an instant should also be
        # rejected:
        with self.assertRaises(Exception):
            doc.validate_context("solar:DeviceCost", durationContext)

        # solar:ModuleNameplateCapacity has period_type duration.
        # A context with an instant instead of a duration should also be
        # rejected:
        with self.assertRaises(Exception):
            doc.validate_context("solar:ModuleNameplateCapacity", instantContext)
        self.assertTrue( doc.validate_context("solar:ModuleNameplateCapacity",
                                              durationContext))
示例#9
0
    def test_tableless_facts(self):
        # Some entry points, like MonthlyOperatingReport, seem to have concepts
        # in them that are not part of any table:
        doc = data_model.OBInstance("MonthlyOperatingReport", self.taxonomy)

        doc.set("solar:MonthlyOperatingReportEffectiveDate",
                date(year=2018,month=6,day=1),
                entity = "JUPITER",
                duration="forever")

        fact = doc.get("solar:MonthlyOperatingReportEffectiveDate",
                       data_model.Context(
                           entity = "JUPITER",
                           duration="forever"))

        self.assertEqual(fact.value, date(year=2018,month=6,day=1))
示例#10
0
    def test_validate_context_axes(self):
        doc = data_model.OBInstance("CutSheet", self.taxonomy)

        # The context must also provide all of the axes needed to place the
        # fact within the right table.

        # DeviceCost is on the CutSheetDetailsTable so it needs a value
        # for ProductIdentifierAxis and TestConditionAxis.
        with self.assertRaises(Exception):
            doc.validate_context("solar:DeviceCost", {})

        context = data_model.Context(instant = datetime.now(),
                          ProductIdentifierAxis = "placeholder",
                          TestConditionAxis = "solar:StandardTestConditionMember")
        self.assertTrue(doc.validate_context("solar:DeviceCost", context))

        badContext = data_model.Context(instant = datetime.now(),
                             TestConditionAxis = "solar:StandardTestConditionMember")
        with self.assertRaises(Exception):
            doc.validate_context("solar:DeviceCost", badContext)

        badContext = data_model.Context(instant = datetime.now(),
                             ProductIdentifierAxis = "placeholder")
        with self.assertRaises(Exception):
            doc.validate_context("solar:DeviceCost", badContext)

        # How do we know what are valid values for ProductIdentifierAxis and
        # TestConditionAxis?  (I think they are meant to be UUIDs.)

        # Note: TestConditionAxis is part of the following relationships:
        # solar:TestConditionAxis -> dimension-domain -> solar:TestConditionDomain
        # solar:TestConditionAxis -> dimension-default -> solar:TestConditionDomain
        # i wonder what that "dimension-default" means

        # 'solar:InverterOutputRatedPowerAC' is on the 'solar:InverterPowerLevelTable'
        # which requires axes: [u'solar:ProductIdentifierAxis', u'solar:InverterPowerLevelPercentAxis'].
        # it's a duration.
        concept = 'solar:InverterOutputRatedPowerAC'
        context = data_model.Context(duration = "forever",
                          ProductIdentifierAxis = "placeholder",
                          InverterPowerLevelPercentAxis = 'solar:InverterPowerLevel100PercentMember')

        self.assertTrue(doc.validate_context(concept, context))

        badContext = data_model.Context(instant = datetime.now(),
                             InverterPowerLevelPercentAxis = 'solar:InverterPowerLevel100PercentMember')
        with self.assertRaises(Exception):
            doc.validate_context(concept, badContext)

        badContext = data_model.Context(instant = datetime.now(),
                             ProductIdentifierAxis = "placeholder")
        with self.assertRaises(Exception):
            doc.validate_context(concept, badContext)
示例#11
0
    def test_hypercube_rejects_out_of_domain_axis_values(self):
        # Try passing in something as a value for TestConditionAxis that is not
        # one of the enumerated Members; it should be rejected:

        doc = data_model.OBInstance("CutSheet", self.taxonomy)
        table = doc.get_table("solar:CutSheetDetailsTable")

        self.assertTrue( table.is_axis_value_within_domain("solar:TestConditionAxis",
                                                     "solar:StandardTestConditionMember") )

        self.assertFalse( table.is_axis_value_within_domain("solar:TestConditionAxis",
                                                     "solar:InverterPowerLevel100PercentMember"))

        concept = 'solar:InverterOutputRatedPowerAC'
        context = data_model.Context(duration = "forever",
                          ProductIdentifierAxis = "placeholder",
                          InverterPowerLevelPercentAxis = 'solar:StandardTestConditionMember')
        # not a valid value for InverterPowerLevelPercentAxis
        with self.assertRaises(Exception):
            doc.validate_context(concept, context)
示例#12
0
    def test_set_default_multiple_times(self):
        # set default for some fields, then set default again for different
        # fields, assert the non-replaced fields keep old values.
        doc = data_model.OBInstance("CutSheet", self.taxonomy)
        now = datetime.now()
        doc.set_default_context({"entity": "JUPITER",
                                 "solar:TestConditionAxis": "solar:StandardTestConditionMember",
                                 })

        doc.set_default_context({taxonomy.PeriodType.instant: now,
                                 taxonomy.PeriodType.duration: "forever"})

        # The second set_default_context should not erase defaults for entity or
        # TestConditionAxis
        doc.set("solar:DeviceCost", "100", unit_name="USD", ProductIdentifierAxis = "placeholder")
        fact = doc.get("solar:DeviceCost", data_model.Context(
            ProductIdentifierAxis = "placeholder",
            TestConditionAxis = "solar:StandardTestConditionMember",
            entity = "JUPITER",
            instant = now))
        self.assertEqual(fact.value, "100")
        self.assertEqual(fact.unit, "USD")
        self.assertEqual(fact.context.entity, "JUPITER")
        self.assertEqual(fact.context.instant, now)
示例#13
0
    def from_XML_string(self, xml_string, entrypoint_name=None):
        """
        Loads the Entrypoint from an XML string.    If no entrypoint_name
        is given the entrypoint will be derived from the facts.  In some cases this is not
        possible because more than one entrypoint could exist given the list of facts and
        in these cases an entrypoint is required.

        Args:
            xml_string(str): String containing XML.
            entrypoint_name (str): Optional name of the entrypoint.

        Returns:
            OBInstance containing the loaded data.
        """

        # NOTE: The XML parser has much less effort placed into both the coding and testing as
        # opposed to the coding and testing effort performed on the JSON parser.  To some extent
        # this is on purpose since the hope is that the bulk of Orange Button data will be
        # transmitted using JSON.  With this you are invited to (a) refactor the XML parsing code
        # and (b) create XML test cases if you believe that XML parser should receive the same
        # effort level as the JSON parser.

        # Create a validation error which can be used to maintain a list of error messages
        validation_errors = ob.OBValidationErrors(
            "Error(s) found in input JSON")

        try:
            root = ElementTree.fromstring(xml_string)
        except Exception as e:
            validation_errors.append(e)
            raise validation_errors

        # Read all elements that are not a context or a unit:
        if not entrypoint_name:
            fact_names = []
            for child in root:
                if child.tag != _xn("link:schemaRef") and child.tag != _xn(
                        "unit") and child.tag != _xn("context"):

                    tag = child.tag
                    tag = tag.replace(
                        "{http://xbrl.us/Solar/v1.2/2018-03-31/solar}",
                        "solar:")
                    tag = tag.replace("{http://fasb.org/us-gaap/2017-01-31}",
                                      "us-gaap:")
                    tag = tag.replace("{http://xbrl.sec.gov/dei/2014-01-31}",
                                      "dei:")
                    fact_names.append(tag)
            try:
                entrypoint_name = self._entrypoint_name(fact_names)
            except ob.OBValidationError as ve:
                validation_errors.append(ve)
                raise validation_errors

        # Create an entrypoint.
        entrypoint = data_model.OBInstance(entrypoint_name,
                                           self._taxonomy,
                                           dev_validation_off=True)

        # Read in units
        units = {}
        for unit in root.iter(_xn("unit")):
            units[unit.attrib["id"]] = unit[0].text

        # Read in contexts
        contexts = {}
        for context in root.iter(_xn("context")):
            instant = None
            duration = None
            entity = None
            start_date = None
            end_date = None
            axis = {}
            for elem in context.iter():
                if elem.tag == _xn("period"):
                    if elem[0].tag == _xn("forever"):
                        duration = "forever"
                    elif elem[0].tag == _xn("startDate"):
                        start_date = elem[0].text
                    elif elem[0].tag == _xn("endDate"):
                        end_date = elem[0].text
                    elif elem[0].tag == _xn("instant"):
                        instant = elem[0].text
                elif elem.tag == _xn("entity"):
                    for elem2 in elem.iter():
                        if elem2.tag == _xn("identifier"):
                            entity = elem2.text
                        elif elem2.tag == _xn("segment"):
                            for elem3 in elem2.iter():
                                if elem3.tag == _xn("xbrldi:typedMember"):
                                    for elem4 in elem3.iter():
                                        if elem4.tag != _xn(
                                                "xbrldi:typedMember"):
                                            axis[elem3.attrib[
                                                "dimension"]] = elem4.text

            if duration is None and start_date is not None and end_date is not None:
                duration = {"start": start_date, "end": end_date}
            kwargs = {}
            if instant is not None:
                kwargs["instant"] = instant
            if duration is not None:
                kwargs["duration"] = duration
            if entity is not None:
                kwargs["entity"] = entity
            if axis is not None:
                for a in axis:
                    kwargs[a] = axis[a]

            if instant is None and duration is None:
                validation_errors.append(
                    ob.OBValidationError(
                        "Context is missing both a duration and instant tag"))
            if entity is None:
                validation_errors.append("Context is missing an entity tag")

            try:
                dm_ctx = data_model.Context(**kwargs)
                contexts[context.attrib["id"]] = dm_ctx
            except Exception as e:
                validation_errors.append(e)

        # Read all elements that are not a context or a unit:
        for child in root:
            if child.tag != _xn("link:schemaRef") and child.tag != _xn(
                    "unit") and child.tag != _xn("context"):
                kwargs = {}

                fact_id = None
                if "id" in child.attrib:
                    fact_id = child.attrib["id"]

                if "contextRef" in child.attrib:
                    if child.attrib["contextRef"] in contexts:
                        kwargs["context"] = contexts[
                            child.attrib["contextRef"]]
                        kwargs["fact_id"] = fact_id
                        tag = child.tag
                        tag = tag.replace(
                            "{http://xbrl.us/Solar/v1.2/2018-03-31/solar}",
                            "solar:")
                        tag = tag.replace(
                            "{http://fasb.org/us-gaap/2017-01-31}", "us-gaap:")
                        tag = tag.replace(
                            "{http://xbrl.sec.gov/dei/2014-01-31}", "dei:")
                        try:
                            entrypoint.set(tag, child.text, **kwargs)
                        except Exception as e:
                            validation_errors.append(e)
                    else:
                        validation_errors.append(
                            "referenced context is missing")
                else:
                    validation_errors.append("Element is missing a context")

        # Raise the errors if necessary
        if validation_errors.get_errors():
            raise validation_errors

        # Return populated entrypoint
        return entrypoint