Skip to content

Commit

Permalink
OB TV Support and Bug Fixes (#184)
Browse files Browse the repository at this point in the history
Added additional support for OBTV and fixed issues 180 and 181.
  • Loading branch information
joelebwf committed Jan 3, 2020
1 parent 5423233 commit af197dc
Show file tree
Hide file tree
Showing 6 changed files with 473 additions and 34 deletions.
4 changes: 3 additions & 1 deletion oblib/constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -76,4 +76,6 @@

SOLAR_ALL_PRE_XML = "solar_all_2019-09-20_pre.xml"

SOLAR_LAB_XML = "solar_2019-09-20_lab.xml"
SOLAR_LAB_XML = "solar_2019-09-20_lab.xml"

SOLAR_CALCULATION_XML = "solar_all_2019-09-20_cal.xml"
207 changes: 197 additions & 10 deletions oblib/taxonomy.py
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,16 @@ class UnitStatus(enum.Enum):
cr = "CR"


class EntrypointType(enum.Enum):
"""
Legal vaues for Entrypoint types.
"""

data = "Data"
documents = "Documents"
process = "Process"


class RelationshipRole(enum.Enum):
"""
Legal values for Relationship roles.
Expand All @@ -72,6 +82,43 @@ class RelationshipRole(enum.Enum):
hypercube_dimension = "hypercube-dimension"


class CalculationRole(enum.Enum):
"""
Legal values for Calculation roles.
"""

summation_item = "summation-item"


class Entrypoint(object):
"""
Entrypoint models a entrypoint element within the Taxonomy.
Attributes:
name: str
Name
full_name: str
Full name includng number and type (Data, Document, Process)
number: str
Entrypoint number (usually used for sorting)
entrypoint_type: str
Data, Document, or Process
description: str
Description of the entrypoint
_path: str
Path to base filename (used by loader - not externally exposed)
"""

def __init__(self):
self.name = None
self.full_name = None
self.number = None
self.entrypoint_type = None
self.number = None
self.description = None
self._path = None


class ConceptDetails(object):
"""
ConceptDetails models a data element within a Taxonomy Concept.
Expand Down Expand Up @@ -158,6 +205,43 @@ def __repr__(self):
"}"


class Calculation(object):
"""
Calculation holds a taxonomy calculation record.
Attributes:
role: str
XBRL Arcrole
from_: str
Models a calculation between two concepts in
conjunction with to.
to: str
Models a calculation between two concpets in conjunction
with from_.
order: int
The order of the calculations for a single entrypoint
weight: int
The weight (-1 or 1) for calcuations.
"""

def __init__(self):
"""Relationship constructor."""
self.role = None
self.from_ = None
self.to = None
self.order = None
self.weight = None

def __repr__(self):
"""Return a printable representation of an calculation."""
return "{" + str(self.role) + \
"," + str(self.from_) + \
"," + str(self.to) + \
"," + str(self.order) + \
"," + str(self.weight) + \
"}"


class Unit(object):
"""
Unit holds the definition of a Unit from the Unit Registry.
Expand Down Expand Up @@ -561,9 +645,10 @@ class TaxonomySemantic(object):
def __init__(self, tl):
"""Constructor."""

self._concepts_details = tl._load_elements()
self._entrypoints, self._concepts_details = tl._load_entrypoints_concept_details()
self._concepts_by_entrypoint = tl._load_concepts()
self._relationships_by_entrypoint = tl._load_relationships()
self._calculations = tl._load_calculations()
self._reduce_unused_semantic_data()

def _reduce_unused_semantic_data(self):
Expand Down Expand Up @@ -607,7 +692,7 @@ def get_all_concepts(self, details=False):
dict of concept details if details=True
"""
if not details:
return list(self._concepts_by_entrypoint)
return list(self._concepts_details.keys())
else:
return self._concepts_details

Expand Down Expand Up @@ -708,15 +793,43 @@ def get_entrypoint_relationships(self, entrypoint):
else:
return None

def get_all_entrypoints(self):
def get_all_entrypoints(self, details=False):
"""
Used to access a list of all entry points (data, documents, and processes) in the Taxonomy.
Args:
details: boolean, default False
if True return details for each concept
Returns:
A list of entrypoint names (strings).
entrypoints: list
elements of the list are entrypoint names
details: dict
primary key is name from entrypoints, value is dict of entrypoints
details (only returned if details=True
"""

return list(self._concepts_by_entrypoint)
if details:
return list(self._concepts_by_entrypoint), self._entrypoints
else:
return list(self._concepts_by_entrypoint)

def get_entrypoint_details(self, entrypoint):
"""
Used to acces a single entry point details
Args:
entrypoint: str
Entrypoint to return details for
Returns:
The details for the entrypoint or None if not found.
"""

if entrypoint in self._entrypoints:
return self._entrypoints[entrypoint]
else:
return None

def get_concept_details(self, concept):
"""
Expand All @@ -727,17 +840,57 @@ def get_concept_details(self, concept):
concept name
Returns:
dict containing concept attributes
Raises:
KeyError if concept is not found
A single ConceptDetail object if found or None if not found.
"""
if self.is_concept(concept):
return self._concepts_details[concept]
else:
raise KeyError('{} is not a concept in the taxonomy'.format(concept))
return None

def get_concept_calculation(self, concept):
"""
Return information on a concepts calculations.
Args:
concept: str
concept name
Returns:
An array of arrays where each tuple contains a calculated field and the second value
is either +1 or -1 to specify whether the field should be added or subtracted as part of
the calculation. If the concept is not a calcuated field an empty array is returned.
If the concept name is not valid None will be returned.
"""
if not self.is_concept(concept):
return None

data = []
for calculation in self._calculations:
if concept == calculation.from_:
data.append([calculation.to, int(calculation.weight)])
return data

def get_concept_calculated_usage(self, concept):
"""
Return information on what calcuations a concept is used in.
Args:
concept: str
concept name
Returns:
An array of all concepts that this concepet is used as part of a calcuation in. If there is
no calcuated usage an empty array is returned. If the concept name is not found then none is
returned.
"""
if not self.is_concept(concept):
return None
data = []
for calculation in self._calculations:
if concept == calculation.to:
data.append(calculation.from_)
return data


class Taxonomy(object):
"""
Expand All @@ -746,6 +899,9 @@ class Taxonomy(object):
Use this class to load and access all elements of the Taxonomy. Taxonomy
supplies a single import location and is better than loading a portion
of the Taxonomy unless there is a specific need to save memory.
This class also contains methods for cases where more than one child is
required to fulfill the method results.
"""

def __init__(self):
Expand All @@ -761,3 +917,34 @@ def __init__(self):
self.generic_roles = TaxonomyGenericRoles(tl)
self.ref_parts = TaxonomyRefParts(tl)
self.documentation = TaxonomyDocumentation(tl)

def get_concept_units(self, concept):
"""
Args:
concept : str
concept name
Returns:
list containing valid unit ids if any or None if the concept type does not support units. Please note
that an empty list is possible if the concept type is supportive of units but not units are defined
in the taxonomy (this would technically be a taxonomy issue and a standards change request should be
submitted but it does happen occasionally).
Raises:
KeyError if concept is not found
"""

details = self.semantic.get_concept_details(concept)
if details.type_name.startswith("num:") or details.type_name.startswith("num-us:"):
if details.type_name in ["num:percentItemType"]:
return ["Pure"]
else:
u = []
for unit in self.units.get_all_units():
ud = self.units.get_unit(unit)
if details.type_name.lower().find(ud.item_type.lower()) != -1:
u.append(ud.unit_name)
return u
else:
return None

0 comments on commit af197dc

Please sign in to comment.