Skip to content
This repository was archived by the owner on Jul 26, 2018. It is now read-only.
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
56 changes: 42 additions & 14 deletions dqc_us_rules/dqc_us_0006.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,31 +4,38 @@
from collections import defaultdict
from datetime import timedelta
from .util import facts, messages
import csv
import os

CHECK_TYPES = ['textBlockItemType']
CHECK_DEI = ['AmendmentDescription', 'AmendmentFlag', 'CurrentFiscalYearEndDate', 'DocumentPeriodEndDate',
'DocumentFiscalYearFocus', 'DocumentFiscalPeriodFocus', 'DocumentType', 'EntityRegistrantName',
'EntityCentralIndexKey', 'EntityFilerCategory']
DATE_BOUNDS_DICT = {
"FY": {"min": 340, "max": 390},
"Q1": {"min": 65, "max": 115},
"Q2": {"min": 155, "max": 205},
"Q3": {"min": 245, "max": 295}
}
CHECK_DEI = [
'AmendmentDescription', 'AmendmentFlag', 'CurrentFiscalYearEndDate',
'DocumentPeriodEndDate', 'DocumentFiscalYearFocus',
'DocumentFiscalPeriodFocus', 'DocumentType', 'EntityRegistrantName',
'EntityCentralIndexKey', 'EntityFilerCategory'
]
_CODE_NAME = 'DQC.US.0006'
_RULE_VERSION = '1.0'
_DEFAULT_DATE_BOUNDS_FILE = os.path.join(
os.path.dirname(__file__),
'resources',
'DQC_US_0006',
'dqc_06_date_bounds.csv'
)


def validate_dates_within_periods(val):
"""
Check Date Ranges are within expected values
for the fiscal focus period
"""
date_bounds_dict = _date_bounds_from_csv()
doc_type = facts.lookup_dei_facts('DocumentType', val.modelXbrl)
if len(doc_type) != 1 or 'T' in doc_type[0].xValue:
# If it is a transitional document, or there is more than one document type declared, we will not run this check.
# If it is a transitional document, or there is more than one
# document type declared, we will not run this check.
return
dict_of_facts = _date_range_check(CHECK_TYPES, CHECK_DEI, DATE_BOUNDS_DICT, val.modelXbrl)
dict_of_facts = _date_range_check(CHECK_TYPES, CHECK_DEI, date_bounds_dict, val.modelXbrl)
for document_fiscal_period_focus, fact_list in dict_of_facts.items():
for fact in fact_list:
val.modelXbrl.error('{}.14'.format(_CODE_NAME), messages.get_message(_CODE_NAME), concept=fact.qname,
Expand Down Expand Up @@ -69,17 +76,38 @@ def _date_range_check(check_types, check_dei, date_bounds_dict, modelXbrl):

def _dict_list_update(dict_a, dict_b):
"""
helper for the LEA dictionaries, extends the lists from dict_a with the lists in dict_b.
Helper for the LEA dictionaries, extends the lists from dict_a with the
lists in dict_b.
"""
for key, val in dict_b.items():
dict_a[key].extend(val)
return dict_a

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

however, should add an extra line here to make two blank lines above this function


def _date_bounds_from_csv():
"""
Returns a map of {time_period: {'min':min_value,'max':max_value}}
ex: date_bounds_from_csv()['Q1'] = {'min':65,'max':115}

:rtype: dict
:return: A map of {time_period: {'min':min_value,'max':max_value}}.
"""
with open(_DEFAULT_DATE_BOUNDS_FILE, 'r') as f:
reader = csv.reader(f)
date_bounds_dict = {}
next(reader, None)
for row in reader:
date_bounds_dict[row[0]] = {'min': int(row[1]), 'max': int(row[2])}
return date_bounds_dict

__pluginInfo__ = {
'name': _CODE_NAME,
'version': _RULE_VERSION,
'description': '''Checks all of the specified types and concepts for their date ranges to verify the ranges are within expected paramters for the fiscal periods''',
#Mount points
'description': (
'Checks all of the specified types and concepts for their date '
'ranges to verify the ranges are within expected paramters for the '
'fiscal periods'
),
# Mount points
'Validate.XBRL.Finally': validate_dates_within_periods,
}
5 changes: 5 additions & 0 deletions dqc_us_rules/resources/DQC_US_0006/dqc_06_date_bounds.csv
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
Period,Min,Max
"FY","340","390"
"Q1","65","115"
"Q3","245","295"
"Q2","155","205"
8 changes: 4 additions & 4 deletions dqc_us_rules/util/facts.py
Original file line number Diff line number Diff line change
Expand Up @@ -175,7 +175,7 @@ def LegalEntityAxis_facts_by_member(facts):
results = defaultdict(list)
for fact in facts:
legalDim = LEGALENTITYAXIS_DEFAULT
if fact_components_valid(fact):
if _fact_components_valid(fact):
dims = [dim for dim in fact.context.segDimValues.values() if dim.isExplicit and dim.member is not None]
for dim in dims:
if dim.dimension.qname.localName == 'LegalEntityAxis':
Expand All @@ -185,13 +185,13 @@ def LegalEntityAxis_facts_by_member(facts):
return results


def fact_components_valid(fact):
def _fact_components_valid(fact):
"""
Return true if all of the components in a fact are none
Return true if all of the components in a fact are not none

:param fact: The fact to check if it is valid
:type fact: arelle.ModelInstanceObject.ModelFact
:return: True if none of the components of the fact are None
:return: True if none of the components of the fact are not None
:rtype: bool
"""
if fact is None:
Expand Down
40 changes: 40 additions & 0 deletions tests/unit_tests/test_dqc_us_0006.py
Original file line number Diff line number Diff line change
Expand Up @@ -56,3 +56,43 @@ def test_lea_facts_and_update(self):
expected = defaultdict(list)
expected.update({'': [fact2, fact3], 'Company1': [fact1]})
self.assertEqual(res3, expected)


class TestDateBoundsCSV(unittest.TestCase):
def test_date_bounds_csv_keys_equal(self):
"""
Test to make sure that the dictionary read in from the csv shares
equals the original date_bounds_dict
"""
date_bounds_dict = {
'FY': {'min': 340, 'max': 390},
'Q1': {'min': 65, 'max': 115},
'Q3': {'min': 245, 'max': 295},
'Q2': {'min': 155, 'max': 205}
}

date_bounds_dict_from_csv = dqc_us_0006._date_bounds_from_csv()

self.assertDictEqual(date_bounds_dict, date_bounds_dict_from_csv)

def test_date_bounds_csv_keys_unequal(self):
"""
Test to make sure that the dictionary read is doesn't equal something
other than the original DATE_BOUNDS_DICT
"""
random_date_bounds_dict = {
'FY': {'min': 374, 'max': 489},
'Q1': {'min': 234, 'max': 394},
'Q3': {'min': 890, 'max': 891},
'Q2': {'min': 300, 'max': 790}
}

date_bounds_dict_from_csv = dqc_us_0006._date_bounds_from_csv()
self.assertEqual(sorted(list(random_date_bounds_dict.keys())), sorted(list(date_bounds_dict_from_csv.keys())))

for key in random_date_bounds_dict.keys():
for subkey in random_date_bounds_dict[key].keys():
self.assertNotEqual(
random_date_bounds_dict[key][subkey],
date_bounds_dict_from_csv[key][subkey]
)
14 changes: 8 additions & 6 deletions tests/unit_tests/util/test_facts.py
Original file line number Diff line number Diff line change
Expand Up @@ -622,33 +622,35 @@ def test_member_qnames(self):
expected = ['CashCheckAxis']
self.assertEqual(expected, fact_lib.member_qnames(fact))


class TestFactsAreValid(unittest.TestCase):
def test_fact_components_valid_on_valid_fact(self):
"""
Tests to make sure that a valid fact still works
"""
fact = Mock(decimals='-4', value='869098', xvalue = 869098, precision = None)
self.assertTrue(fact_lib.fact_components_valid(fact))
fact = Mock(decimals='-4', value='869098', xvalue=869098, precision=None)
self.assertTrue(fact_lib._fact_components_valid(fact))

def test_fact_components_valid_on_none_type_fact(self):
"""
Tests to make sure that a None type fact is not valid
"""
fact = None
self.assertFalse(fact_lib.fact_components_valid(fact))
self.assertFalse(fact_lib._fact_components_valid(fact))

def test_fact_components_valid_on_none_type_context(self):
"""
Tests to make sure that a Fact with a None type context is not valid
"""
fact = Mock(decimals='-1', value='670', xValue=670, precision=None)
fact.context = None
self.assertFalse(fact_lib.fact_components_valid(fact))
self.assertFalse(fact_lib._fact_components_valid(fact))

def test_fact_components_valid_on_none_type_segDimValue(self):
"""
Tests to make sure that a Fact.context with a None type segDimValue is not valid
Tests to make sure that a Fact.context with a None type segDimValue is
not valid
"""
fact = Mock(decimals='-2', value='6500', xValue=6500, precision=None)
fact.context.segDimValues = None
self.assertFalse(fact_lib.fact_components_valid(fact))
self.assertFalse(fact_lib._fact_components_valid(fact))