예제 #1
0
    def test_instantiate_empty_entrypoint(self):
        doc = data_model.OBInstance("CutSheet", self.taxonomy)

        # The newly initialized CutSheet should have a correct list of
        # allowable concepts as defined by the taxonomy for CutSheets.

        # TypeOfDevice is allowed in CutSheets:
        self.assertTrue(doc.is_concept_writable('solar:TypeOfDevice'))
        # AppraisalCounterparties is not allowed in CutSheets:
        self.assertFalse(doc.is_concept_writable('solar:AppraisalCounterparties'))

        # The newly initialized CutSheet should have a correct list of tables
        # and each table should have a correct list of axes, as defined by
        # the taxonomy for CutSheets:
        tables = doc.get_table_names()
        self._check_arrays_equivalent(tables,
                                      ["solar:InverterPowerLevelTable",
                                       "solar:CutSheetDetailsTable"])
        self._check_arrays_equivalent(
            doc.get_table("solar:InverterPowerLevelTable").get_axes(),
            ["solar:ProductIdentifierAxis",
             "solar:InverterPowerLevelPercentAxis"])
        self._check_arrays_equivalent(
            doc.get_table("solar:CutSheetDetailsTable").get_axes(),
            ["solar:ProductIdentifierAxis",
             "solar:TestConditionAxis"])
예제 #2
0
    def test_is_unit_method(self):
        doc = data_model.OBInstance("CutSheet", self.taxonomy)
        # Basic data types:
        self.assertTrue(doc._is_valid_unit("solar:TrackerNumberOfControllers", None))  # pure integer
        self.assertTrue(doc._is_valid_unit("solar:TransformerStyle", None))  # string
        self.assertTrue(doc._is_valid_unit("solar:TransformerDesignFactor", None))  # decimal
        self.assertTrue(doc._is_valid_unit("solar:MeterRevenueGrade", None))  # boolean

        # Advanced data types:
        self.assertTrue(doc._is_valid_unit("solar:DeviceCost", "USD"))  # xbrli:monetaryItemType
        self.assertTrue(doc._is_valid_unit("solar:CutSheetDocumentLink", None))  # :xbrlianyURIItemType
        self.assertTrue(doc._is_valid_unit("solar:CECListingDate", None))  # xbrli:dateItemType
        self.assertTrue(doc._is_valid_unit("solar:InverterHarmonicsTheshold", None))  # num:percentItemType

        # Physics data types:
        self.assertTrue(doc._is_valid_unit("solar:RevenueMeterFrequency", "Hz"))  # num-us:frequencyItemType
        self.assertTrue(doc._is_valid_unit("solar:InverterWidth", "cm"))  # :num:lengthItemType
        self.assertTrue(doc._is_valid_unit("solar:BatteryRating", "kW"))  # :num:powerItemType
        self.assertTrue(doc._is_valid_unit("solar:InverterInputMaximumOperatingCurrentDC", "A"))  # :num-us:electricCurrentItemType
        self.assertTrue(doc._is_valid_unit("solar:InverterInputMaximumVoltageDC", "V"))  # :num-us:voltageItemType
        self.assertTrue(doc._is_valid_unit("solar:InverterOperatingTemperatureRangeMaximum", "Cel"))  # :num-us:temperatureItemType
        # self.assertTrue( doc.valid_unit("solar:TrackerStowWindSpeed:num-us:speedItemType", "???"))
        self.assertTrue(doc._is_valid_unit("solar:OrientationMaximumTrackerRotationLimit", "Degree"))  # :num-us:planeAngleItemType
        
        self.assertTrue(doc._is_valid_unit("solar:TrackerStyle", None))  # solar-types:trackerItemType
        self.assertTrue(doc._is_valid_unit("solar:BatteryStyle", None))  # solar-types:batteryChemistryItemType
        self.assertTrue(doc._is_valid_unit("solar:TypeOfDevice", None))  # solar-types:deviceItemType
예제 #3
0
    def test_concepts_can_type_check(self):
        # Try passing in wrong data type to a typed concept:
        doc = data_model.OBInstance("CutSheet", self.taxonomy)
        concept = doc.get_concept("solar:TrackerNumberOfControllers") # integer
        self.assertTrue(concept.validate_datatype("3"))
        self.assertFalse(concept.validate_datatype("3.5"))
        self.assertFalse(concept.validate_datatype("a few"))

        concept = doc.get_concept("solar:TransformerStyle") # string
        self.assertTrue(concept.validate_datatype("Autobot"))
        self.assertTrue(concept.validate_datatype("Decepticon"))
        # TODO: 99.99 can be converted to valid string
        self.assertTrue(concept.validate_datatype(99.99))

        concept = doc.get_concept("solar:TransformerDesignFactor") # decimal
        self.assertTrue(concept.validate_datatype("0.99"))
        self.assertTrue(concept.validate_datatype("1"))
        self.assertFalse(concept.validate_datatype("pretty good"))

        concept = doc.get_concept("solar:MeterRevenueGrade") # boolean
        self.assertTrue(concept.validate_datatype("True"))
        self.assertTrue(concept.validate_datatype("False"))
        self.assertTrue(concept.validate_datatype(True))
        self.assertTrue(concept.validate_datatype(False))
        self.assertFalse(concept.validate_datatype("yes"))
        self.assertFalse(concept.validate_datatype("7"))
예제 #4
0
    def test_reject_invalid_datatype(self):
        doc = data_model.OBInstance("CutSheet", self.taxonomy)
        with self.assertRaises(Exception):
            # A non-integer is given, this should fail:
            doc.set(
                "solar:TrackerNumberOfControllers", 0.5,
                entity="JUPITER",
                duration="forever",
                ProductIdentifierAxis= "placeholder",
                TestConditionAxis= "solar:StandardTestConditionMember")

        with self.assertRaises(Exception):
            # A string that can't be parsed into an integer should fail:
            doc.set(
                "solar:TrackerNumberOfControllers", "abcdefg",
                entity="JUPITER",
                duration="forever",
                ProductIdentifierAxis= "placeholder",
                TestConditionAxis= "solar:StandardTestConditionMember")

        # But a string that can be parsed into an integer should succeed:
        doc.set("solar:TrackerNumberOfControllers", "2",
                entity="JUPITER",
                duration="forever",
                ProductIdentifierAxis= "placeholder",
                TestConditionAxis= "solar:StandardTestConditionMember")
예제 #5
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")
예제 #6
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)
예제 #7
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")
예제 #8
0
    def test_json_fields_are_strings(self):
        # Issue #77 - all json fields should be strings other than None which should convert
        # a JSON null literal.
        # e.g. numbers should be "100" not 100
        # booleans should be "true" not true

        
        doc = data_model.OBInstance("System", self.taxonomy, dev_validation_off=True)
        now = datetime.now()
        doc.set_default_context({
            "entity": "JUPITER",
            "solar:InverterPowerLevelPercentAxis": "solar:InverterPowerLevel100PercentMember",
            taxonomy.PeriodType.instant: now,
            taxonomy.PeriodType.duration: "forever"
        })

        # Set fact using a numeric type as value:
        doc.set("solar:InverterOutputRatedPowerAC", 1.25, unit_name="kW",
                ProductIdentifierAxis = 1)

        # Set fact using a boolean type as value:
        doc.set("solar:ModuleHasCertificationIEC61646", True, ProductIdentifierAxis = 1)

        jsonstring = doc.to_JSON_string()
        facts = json.loads(jsonstring)["facts"]

        self.assertEqual(len(facts), 2)

        for fact in list(facts.values()):
            self.assertTrue( isinstance( fact['value'], string_types) )
            self.assertTrue( isinstance( fact['aspects']['solar:ProductIdentifierAxis'], string_types))
예제 #9
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)
예제 #10
0
    def test_optional_namespaces_included(self):
        # If no us-gaap concepts are used, there should be no us-gaap namespace
        # definition in header:
        doc = data_model.OBInstance("MonthlyOperatingReport", self.taxonomy)
        doc.set("solar:MonthlyOperatingReportEffectiveDate",
                date(year=2018,month=6,day=1),
                entity = "JUPITER",
                duration="forever")
        xml = doc.to_XML_string()
        root = etree.fromstring(xml)

        self.assertIn("solar", root.nsmap)
        self.assertIn("xlink", root.nsmap)
        self.assertIn("units", root.nsmap)
        self.assertNotIn("us-gaap", root.nsmap)

        # If a us-gaap concept has been set, however, there should be a us-gaap
        # namespace definition in header:
        doc.set("us-gaap:PartnersCapitalAccountReturnOfCapital", 4, unit_name="USD",
                entity = "JUPITER", duration="forever", InvestmentClassAxis="placeholder",
                ProjectIdentifierAxis="1", CashDistributionAxis="placeholder")

        xml = doc.to_XML_string()
        root = etree.fromstring(xml)
        self.assertIn("solar", root.nsmap)
        self.assertIn("xlink", root.nsmap)
        self.assertIn("units", root.nsmap)
        self.assertIn("us-gaap", root.nsmap)
예제 #11
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")
예제 #12
0
    def test_hypercube_can_identify_axis_domains(self):
        doc = data_model.OBInstance("CutSheet", self.taxonomy)
        table = doc.get_table("solar:CutSheetDetailsTable")

        domain = table.get_domain("solar:ProductIdentifierAxis")
        self.assertEqual(domain, "solar:ProductIdentifierDomain")

        domain = table.get_domain("solar:TestConditionAxis")
        self.assertEqual(domain, "solar:TestConditionDomain")
예제 #13
0
    def test_set_raises_exception(self):
        # Tests the case where .set() is called incorrectly. It should
        # raise exceptions if required information is missing.
        doc = data_model.OBInstance("CutSheet", self.taxonomy)
        with self.assertRaises(Exception):
            doc.set("solar:TypeOfDevice", "ModuleMember", {})

        with self.assertRaises(Exception):
            doc.set("solar:DeviceCost", 100, {})
예제 #14
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)
예제 #15
0
    def test_get_table_for_concept(self):
        doc = data_model.OBInstance("CutSheet", self.taxonomy)
        # The CutSheet instance should know that RevenueMeterFrequency
        # is a concept that belongs in the CutSheetDetailsTable
        table = doc.get_table_for_concept("solar:RevenueMeterFrequency")
        self.assertEqual(table.get_name(), "solar:CutSheetDetailsTable")

        table = doc.get_table_for_concept("solar:InverterEfficiencyAtVmaxPercent")
        self.assertEqual(table.get_name(), "solar:InverterPowerLevelTable")

        # but if we ask for something that is not a line item concept,
        # we should get back the non-table:
        table = doc.get_table_for_concept("solar:CutSheetDetailsTable")
        self.assertEqual(table.get_name(), data_model.UNTABLE)
예제 #16
0
    def test_conversion_to_json(self):
        doc = data_model.OBInstance("CutSheet", self.taxonomy)
        doc.set("solar:TypeOfDevice", "ModuleMember",
                entity="JUPITER",
                duration="forever",
                ProductIdentifierAxis= "placeholder",
                TestConditionAxis = "solar:StandardTestConditionMember",
                )
        now = datetime.now()
        doc.set("solar:DeviceCost", 100,
                entity="JUPITER",
                instant= now,
                ProductIdentifierAxis= "placeholder",
                TestConditionAxis= "solar:StandardTestConditionMember",
                unit_name="USD")
        jsonstring = doc.to_JSON_string()

        root = json.loads(jsonstring)

        # should have 2 facts:
        all_facts = list(root["facts"].values())
        self.assertEqual( len(all_facts), 2)

        # each should have expected 'value' and 'aspects':
        typeFacts = [x for x in all_facts if x['aspects']['concept'] == 'solar:TypeOfDevice']
        self.assertEqual(len(typeFacts), 1)
        typeFact = typeFacts[0]
        self.assertEqual(typeFact['value'], "ModuleMember")
        self.assertEqual(typeFact['aspects']['solar:ProductIdentifierAxis'], 'placeholder')
        self.assertEqual(typeFact['aspects']['solar:TestConditionAxis'],
                             "solar:StandardTestConditionMember")
        self.assertEqual(typeFact['aspects']['entity'], 'JUPITER')
        # period = Forever means we don't write a period aspect:
        self.assertNotIn("period", typeFact['aspects'])
        # TODO if there's no unit is it correct to write 'xbrl:unit':'None' or to leave out
        # xbrl:unit?  Currently assuming we leave it out:
        self.assertNotIn("unit", typeFact["aspects"])

        costFacts = [x for x in all_facts if x['aspects']['concept'] == 'solar:DeviceCost']
        self.assertEqual(len(costFacts), 1)

        costFact = costFacts[0]
        self.assertEqual(costFact['value'], '100')
        self.assertEqual(costFact['aspects']['solar:ProductIdentifierAxis'], 'placeholder')
        self.assertEqual(costFact['aspects']['solar:TestConditionAxis'],
                             "solar:StandardTestConditionMember")
        self.assertEqual(costFact['aspects']['entity'], 'JUPITER')
        self.assertEqual(costFact['aspects']['period'], now.strftime("%Y-%m-%dT%H:%M:%S"))
        self.assertEqual(costFact['aspects']['unit'], 'USD')
예제 #17
0
    def test_can_write_concept(self):
        doc = data_model.OBInstance("CutSheet", self.taxonomy)

        # Not every concept is writable. For instance, we shouldn't be able
        # to write a value for an Abstract concept, a LineItem group, an Axis,
        # a Domain, etc. even though those are part of this entrypoint.
        self.assertFalse(doc.is_concept_writable('solar:ProductIdentifierModuleAbstract'))
        self.assertTrue(doc.is_concept_writable('solar:TypeOfDevice'))
        self.assertFalse(doc.is_concept_writable('solar:CutSheetDetailsLineItems'))

        self.assertFalse(doc.is_concept_writable('solar:CutSheetDetailsTable'))
        self.assertFalse(doc.is_concept_writable('solar:TestConditionDomain'))

        self.assertFalse(doc.is_concept_writable('solar:ProductIdentifierAxis'))
        self.assertTrue(doc.is_concept_writable('solar:ProductIdentifier'))
예제 #18
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))
예제 #19
0
    def test_input_ids(self):
        # Test ID's can be passed into either a Fact or an OBInstance.

        fact = data_model.Fact("solar:MeasuredEnergy", None, "SI:MWh", "3164.80", id="test")
        self.assertEqual(fact.id, "test")

        doc = data_model.OBInstance("System", self.taxonomy, dev_validation_off=True)
        now = datetime.now()
        doc.set_default_context({
            "entity": "JUPITER",
            "solar:InverterPowerLevelPercentAxis": "solar:InverterPowerLevel100PercentMember",
            taxonomy.PeriodType.instant: now,
            taxonomy.PeriodType.duration: "forever"
        })
        doc.set("solar:InverterOutputRatedPowerAC", 1.25, unit_name="kW",
            ProductIdentifierAxis=1, fact_id="obinstance-test")
        fact = doc.get_all_facts()[0]
        self.assertEqual("obinstance-test", fact.id)
예제 #20
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)
예제 #21
0
    def test_reject_missing_or_invalid_units(self):
        # issue #28
        # -- reject attempt to set a fact if it doesn't have a unit and the unit is required
        # -- reject attempt to set a fact using a unit name that doesn't match taxonomy
        # -- reject attempt to set a fact using a unit that is the wrong type
        now = datetime.now()
        doc = data_model.OBInstance("CutSheet", self.taxonomy)
        with self.assertRaises(Exception):
            # Unit is required but not provided, so this should fail:
            doc.set(
                "solar:DeviceCost", 100,
                entity="JUPITER",
                instant= now,
                ProductIdentifierAxis= "placeholder",
                TestConditionAxis= "solar:StandardTestConditionMember")

        with self.assertRaises(Exception):
            # Zorkmids is not a real unit, so this should fail:
            doc.set(
                "solar:DeviceCost", 100,
                entity="JUPITER",
                instant= now,
                ProductIdentifierAxis= "placeholder",
                TestConditionAxis= "solar:StandardTestConditionMember",
                unit_name="zorkmids")

        with self.assertRaises(Exception):
            # kWh is a real unit but the wrong type, so this should fail:
            doc.set(
                "solar:DeviceCost", 100,
                entity="JUPITER",
                instant= now,
                ProductIdentifierAxis= "placeholder",
                TestConditionAxis= "solar:StandardTestConditionMember",
                unit_name="kWh")

        # USD is a valid unit, so this should succeed:
        doc.set("solar:DeviceCost", 100,
                entity="JUPITER",
                instant= now,
                ProductIdentifierAxis= "placeholder",
                TestConditionAxis= "solar:StandardTestConditionMember",
                unit_name="USD")
예제 #22
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)
예제 #23
0
    def test_concepts_load_details(self):
        doc = data_model.OBInstance("CutSheet", self.taxonomy)

        frequency = doc.get_concept("solar:RevenueMeterFrequency")
        device = doc.get_concept("solar:TypeOfDevice")

        # Metadata such as period-type, type-name, and nillable should be available
        # on the concept objects:
        self.assertEqual(frequency.get_details("period_type"), taxonomy.PeriodType.duration)
        self.assertEqual(frequency.get_details("type_name"), "num-us:frequencyItemType")
        self.assertEqual(frequency.get_details("nillable"), True)

        self.assertEqual(device.get_details("period_type"), taxonomy.PeriodType.duration)
        self.assertEqual(device.get_details("type_name"), "solar-types:deviceItemType")
        self.assertEqual(device.get_details("nillable"), True)

        # Parents and children should be correct:
        self.assertEqual(device.parent.name, 'solar:CutSheetDetailsLineItems')
        self.assertEqual(frequency.parent.name, 'solar:ProductIdentifierMeterAbstract')
        self.assertEqual(len(device.children), 0)
        self.assertEqual(len(frequency.children), 0)
예제 #24
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)
예제 #25
0
    def test_decimals_and_precision(self):
        # if we set a fact and pass in a Decimals argument,
        # then when we write out to JSON or XML we should see decimals there.
        # Same with Precision.
        # Trying to set both Decimals and Precision should give an error.
        # If we don't set either, it should default to decimals=2.

        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"
                               })

        # Set fact with precision:
        doc.set("solar:ModuleNameplateCapacity", "6.25", unit_name="W",
                ProductIdentifierAxis = 1, precision = 3)

        jsonstring = doc.to_JSON_string()
        facts = json.loads(jsonstring)["facts"]

        # TODO is supposed to be in aspects or not?
        self.assertEqual(len(facts), 1)
        self.assertEqual(list(facts.values())[0]["aspects"]["precision"], "3")

        # Set fact with decimals:
        doc.set("solar:ModuleNameplateCapacity", "6.25", unit_name="W",
                ProductIdentifierAxis = 1, decimals = 3)
        jsonstring = doc.to_JSON_string()
        facts = json.loads(jsonstring)["facts"]
        self.assertEqual(len(facts), 1)
        self.assertEqual(list(facts.values())[0]["aspects"]["decimals"], "3")

        # Trying to set both decimals and precision should raise an error
        with self.assertRaises(data_model.OBException):
            doc.set("solar:ModuleNameplateCapacity", "6.25", unit_name="W",
                ProductIdentifierAxis = 1, decimals = 3, precision=3)
예제 #26
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))
예제 #27
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)
예제 #28
0
def process(en):

    # TODO: Change dev_validation_off to false in order to force additional rules
    # on the templates created.  Unfortunatley this program needs additional logic
    # to run correctly in this mode but it will also create more accurate templates
    # once this is done.
    entrypoint = data_model.OBInstance(en, tax, dev_validation_off=True)
    relationships = tax.semantic.get_entrypoint_relationships(en)

    for concept_name in tax.semantic.get_entrypoint_concepts(en):

        # Skip erroneous taxonomy concepts which end with _1.
        if concept_name.endswith("_1"):
            continue

        c = tax.semantic.get_concept_details(concept_name)
        table = entrypoint.get_table_for_concept(c.id)

        # Skip elements that can't be generated - note that the string check on "Abstract"
        # is due to a taxonomy bug.  Once fixed c.abstract will suffice as the check.
        if c.abstract or "Abstract" in c.id or "Axis" in c.id or "Table" in c.id:
            continue

        kwargs = {}
        if c.period_type == taxonomy.PeriodType.instant:
            kwargs["instant"] = util.convert_taxonomy_xsd_date("2018-01-01")
        else:
            kwargs["duration"] = "forever"
        kwargs["entity"] = ENTITY
        if len(table.get_axes()) > 0:
            found = False
            values_to_add = []
            for a in table.get_axes():
                d = None
                for r in relationships:
                    if r.from_ == a:
                        d = r.to
                        break
                if d is not None:
                    for r in relationships:
                        if r.from_ == d:
                            kwargs[a] = r.to
                            value = types[c.type_name]
                            values_to_add.append([c.id, value])

            i = 1
            for a in table.get_axes():
                if a not in kwargs:
                    kwargs[a] = str(i)
                    i = i + 1

            if not found:
                value = types[c.type_name]
                values_to_add.append([c.id, value])

            for v in values_to_add:
                try:
                    entrypoint.set(v[0], v[1], **kwargs)
                except Exception as e:
                    print("Exception processing", c.id)
                    print(e)
        else:
            value = types[c.type_name]
            try:
                entrypoint.set(c.id, value, **kwargs)
            except Exception as e:
                print("Exception processing", c.id)
                print(e)

        entrypoint.to_JSON(out_dn + "/" + en + ".json")
        entrypoint.to_XML(out_dn + "/" + en + ".xml")
예제 #29
0
    def test_conversion_to_xml(self):
        doc = data_model.OBInstance("CutSheet", self.taxonomy)
        doc.set("solar:TypeOfDevice", "ModuleMember",
                entity="JUPITER",
                duration="forever",
                ProductIdentifierAxis= "placeholder",
                TestConditionAxis = "solar:StandardTestConditionMember"
                )
        now = datetime.now()
        doc.set("solar:DeviceCost", 100,
                entity="JUPITER",
                instant= now,
                ProductIdentifierAxis= "placeholder",
                TestConditionAxis= "solar:StandardTestConditionMember",
                unit_name="USD")
        xml = doc.to_XML_string()
        root = etree.fromstring(xml)

        self.assertEqual( len(root.getchildren()), 6)
        # top-level xml should have child <link:schemaRef>, one <unit>, two <context>s and two <fact>s.
        schemaRef = root.getchildren()[0]
        self.assertEqual( schemaRef.tag, "{http://www.xbrl.org/2003/linkbase}schemaRef" )

        # expect to see 2 contexts with id "solar:CutSheetDetailsTable_0" and "solar:CutSheetDetailsTable_1"
        contexts = root.findall('{http://www.xbrl.org/2003/instance}context')
        self.assertEqual(len(contexts), 2)
        for context in contexts:
            # both should have entity tag containing identifier containing text JUPITER
            self.assertTrue( context.attrib["id"] == 'solar:CutSheetDetailsTable_0' or \
                             context.attrib["id"] == 'solar:CutSheetDetailsTable_1' )

            entity = context.find("{http://www.xbrl.org/2003/instance}entity")
            identifier = entity.find("{http://www.xbrl.org/2003/instance}identifier")
            self.assertEqual(identifier.text, "JUPITER")
            
            # both should have segment containing xbrldi:explicitMember dimension="<axis name>"
            # containing text "placeholder"
            # (wait i have segment inside of entity?  is that correct?)
            segment = entity.find("{http://www.xbrl.org/2003/instance}segment")

            axes = segment.findall("{http://xbrl.org/2006/xbrldi}typedMember")
            axis_names = [x.attrib["dimension"] for x in axes]

            self._check_arrays_equivalent(axis_names, ['solar:ProductIdentifierAxis',
                                                       'solar:TestConditionAxis'])
            for axis in axes:
                if axis.attrib["dimension"] == 'solar:ProductIdentifierAxis':
                    self.assertEqual(axis.getchildren()[0].tag, "{http://xbrl.us/Solar/v1.2/2018-03-31/solar}ProductIdentifierDomain")
                    self.assertEqual(axis.getchildren()[0].text, "placeholder")
                elif axis.attrib["dimension"] == 'solar:TestConditionsAxis':
                    self.assertEqual(axis.getchildren()[0].tag, "{http://xbrl.us/Solar/v1.2/2018-03-31/solar}TestConditionDomain")
                    self.assertEqual(axis.getchildren()[0].text, "solar:StandardTestConditionMember")

            # one should have period containing <forever/> other should have period containing <instant> containing today's date.
            period = context.find("{http://www.xbrl.org/2003/instance}period")
            tag = period.getchildren()[0].tag
            self.assertTrue(tag == "{http://www.xbrl.org/2003/instance}instant" or\
                            tag == "{http://www.xbrl.org/2003/instance}forever")

        # Expect one unit tag with id=USD containing <measure>units:USD</measure>
        unit_tag = root.findall("{http://www.xbrl.org/2003/instance}unit")
        self.assertEqual(len(unit_tag), 1)
        self.assertEqual(unit_tag[0].attrib["id"], "USD")
        self.assertEqual(unit_tag[0].getchildren()[0].text, "units:USD")

        # Expect to see two facts solar:DeviceCost and solar:TypeOfDevice,
        # each containing text of the fact value
        costFact = root.find('{http://xbrl.us/Solar/v1.2/2018-03-31/solar}DeviceCost')
        typeFact = root.find('{http://xbrl.us/Solar/v1.2/2018-03-31/solar}TypeOfDevice')
        self.assertEqual(costFact.text, "100")
        self.assertEqual(typeFact.text, "ModuleMember")
        # They should have contextRef and (in the case of cost) unitRef attributes:
        self.assertEqual(typeFact.attrib['contextRef'], "solar:CutSheetDetailsTable_0")
        self.assertEqual(costFact.attrib['unitRef'], "USD")
        self.assertEqual(costFact.attrib['contextRef'], "solar:CutSheetDetailsTable_1")
예제 #30
0
# limitations under the License.
"""
This sample program creates a XBRL JSON Power Purchase Agreement populated with fake data.
It can be used as a starting point for building a program to create real Power Purchase Agreement
Orange Button data.
"""

from oblib import taxonomy, data_model, parser

import datetime

# Initialize oblib and create an Entrypoint for a Power Purchase Agreement
tax = taxonomy.Taxonomy()
ob_parser = parser.Parser(tax)
entrypoint = data_model.OBInstance("PowerPurchaseAgreement",
                                   tax,
                                   dev_validation_off=True)

#
# PowerPurcahseAgreements (PPA's) have two tables, a PowerPurchaseAgremeentContract table and an EnergyRateTable.
# Both tables can be placed in the same set of XBRL JSON (or XML).  They are both included in the code.
#

# Create 5 rows of data in the PowerPurchaseAgreementContract table
for i in range(0, 5):

    # The priamry key will be numbered 1-5 in each row, in a real application it can be set to any string value.
    pk_value = str(i + 1)

    # Most of the elements require the primary key (called an Axis in XBRL terminology) and have a duration of forever.
    # A kwargs value is created which can be applied to this set of elements.