From 6a03664b51058541ee56e1747a77c843543c6493 Mon Sep 17 00:00:00 2001 From: dandds Date: Wed, 17 Oct 2018 15:05:58 -0400 Subject: [PATCH 1/4] get CLIN info from EDA XML --- atst/eda_client.py | 36 ++++- tests/fixtures/eda_contract.xml | 239 ++++++++++++++++++++++++++++++++ tests/test_eda_client.py | 17 ++- 3 files changed, 284 insertions(+), 8 deletions(-) create mode 100644 tests/fixtures/eda_contract.xml diff --git a/atst/eda_client.py b/atst/eda_client.py index 51b25522..06e3f881 100644 --- a/atst/eda_client.py +++ b/atst/eda_client.py @@ -5,6 +5,39 @@ import requests from requests.auth import HTTPBasicAuth +def parse_eda_xml(xml_string): + contract_et = ET.fromstring(xml_string) + handler = EDAXMLHandler(contract_et) + handler.parse() + return { + "clin_0001": handler.clins.get("0001"), + "clin_0003": handler.clins.get("0003"), + "clin_1001": handler.clins.get("1001"), + "clin_1003": handler.clins.get("1003"), + "clin_2001": handler.clins.get("2001"), + "clin_2003": handler.clins.get("2003"), + } + + +class EDAXMLHandler: + def __init__(self, element_tree): + self.element_tree = element_tree + self.clins = {} + + @property + def _line_items(self): + return self.element_tree.findall(".//LineItem[LineItemType='CLIN']/../../..") + + def _parse_clin_data(self): + for line_item in self._line_items: + number = line_item.find(".//LineItemBase").text + amount_details = line_item.find(".//ItemOtherAmounts[AmountDescription='Not to Exceed Amount (Funding)']/Amount") + self.clins[number] = float(amount_details.text) + + def parse(self): + self._parse_clin_data() + + class EDAClientBase(object): def list_contracts( self, @@ -80,9 +113,6 @@ class MockEDAClient(EDAClientBase): MOCK_CONTRACT_NUMBER = "DCA10096D0052" - # TODO: It seems likely that this will have to supply CLIN data form the - # EDA returnclinXML API call, in addition to the basic task order data - # below. See the EDA docs. def get_contract(self, contract_number, status): if contract_number == self.MOCK_CONTRACT_NUMBER and status == "y": return { diff --git a/tests/fixtures/eda_contract.xml b/tests/fixtures/eda_contract.xml new file mode 100644 index 00000000..14dad743 --- /dev/null +++ b/tests/fixtures/eda_contract.xml @@ -0,0 +1,239 @@ + + 2.5 + DD 1155 + + 3244871 + + 00000431 + 704331 + + + + + + Department of Defense + Delivery Order + 70433119F2644 + Represented Contract + + + Department of Defense + Basic Ordering Agreement + W81K0419G0001 + Ordering Instrument + + + false + Original + false + + Firm Fixed Price + + + + + http://farsite.hill.af.mil/reghtml/regs/far2afmcfars/fardfars/far/52_220.htm#P810_149596 + + + FAR + 52.222-50 + Combating Trafficking in Persons. + 2015-05 + + + [ lots of text ] + + +
I
+
+ + FAR + 52.245-1 + Government Property. + 2012-04 + + + [ lots of text ] + + +
I
+
+
+ + 2016-02-04 + + 2016-01-25 + + DALE WOLFE + + Telephone + 520-533-9132 + + + + + + Contractor +
+ + 0Z7K0 + 808152482 + + + CACI TECHNOLOGIES, INC + + + 6933 Gateway Ct + Manassas VA, 20109 + + + +
+
+ + Contract Issuing Office +
+ + 704331 + + + FEMA DISTRIBUTION CENTER + + + 3870 S. SIDE INDUSTRIAL CTR + ATLANTA GA, 30354 + + + +
+ + GENE BARBER + + Telephone + (202) 646-2727 + + +
+ + Contract Administrative Office +
+ + 704331 + + + FEMA DISTRIBUTION CENTER + + + 3870 S. SIDE INDUSTRIAL CTR + ATLANTA GA, 30354 + + + +
+
+ + Paying Office +
+ + HQ0131 + + + DEFENSE FINANCE AND ACCOUNTING SVC + + + P.O. BOX 369016 + COLUMBUS OH, 43236 + + + +
+
+ + Ship To +
+ + S0302A + + + DCMA PHOENIX + + + 40 NORTH CENTRAL AVE, STE 400 + TWO RENAISSANCE SQUARE + PHOENIX AZ, 85004 + + + +
+
+ + + Header Only - Total Contract Value + 192000.00 + + + + + Delivery Requested By + + 2016-01-16 + + + + + + Defense Priorities Allocation System (DPAS) Priority Rating + + DO-A7 +
A
+
+ + + Contractor + Origin (after Loading) + + +
+ + + + + + CLIN + 0001 + + + + + false + + Cost No Fee + + + Real Property + Radio Dishes + 3 + false + Estimated + Each + 64000.00 + + Manufacturer's Part Number + 5L33M7730291DX081 + + + + + + Estimated Cost + 192000.00 + + + Not to Exceed Amount (Funding) + 200000.00 + + + + +
+
diff --git a/tests/test_eda_client.py b/tests/test_eda_client.py index 03380ee1..36619df6 100644 --- a/tests/test_eda_client.py +++ b/tests/test_eda_client.py @@ -1,20 +1,27 @@ -from atst.eda_client import MockEDAClient +from atst.eda_client import MockEDAClient, parse_eda_xml -client = MockEDAClient() +mock_client = MockEDAClient() def test_list_contracts(): - results = client.list_contracts() + results = mock_client.list_contracts() assert len(results) == 3 def test_get_contract(): - result = client.get_contract("DCA10096D0052", "y") + result = mock_client.get_contract("DCA10096D0052", "y") assert result["contract_no"] == "DCA10096D0052" assert result["amount"] == 2_000_000 def test_contract_not_found(): - result = client.get_contract("abc", "y") + result = mock_client.get_contract("abc", "y") assert result is None + + +def test_eda_xml_parser(): + with open("tests/fixtures/eda_contract.xml") as contract: + eda_data = parse_eda_xml(contract.read()) + assert eda_data["clin_0001"] == 200000.00 + assert not eda_data["clin_0003"] From 4fefd017a22a614740d6813e75a0148c3219b63b Mon Sep 17 00:00:00 2001 From: dandds Date: Wed, 17 Oct 2018 16:12:19 -0400 Subject: [PATCH 2/4] use CLIN data from EDA client response --- atst/domain/task_orders.py | 2 +- atst/eda_client.py | 24 +++++++++++------------- tests/test_eda_client.py | 3 +-- 3 files changed, 13 insertions(+), 16 deletions(-) diff --git a/atst/domain/task_orders.py b/atst/domain/task_orders.py index 0b449083..b8653af5 100644 --- a/atst/domain/task_orders.py +++ b/atst/domain/task_orders.py @@ -29,7 +29,7 @@ class TaskOrders(object): to_data = TaskOrders._client().get_contract(order_number, status="y") if to_data: # TODO: we need to determine exactly what we're getting and storing from the EDA client - return TaskOrders.create(number=to_data["contract_no"], source=Source.EDA) + return TaskOrders.create(source=Source.EDA, **to_data) else: raise NotFoundError("task_order") diff --git a/atst/eda_client.py b/atst/eda_client.py index 06e3f881..b6376682 100644 --- a/atst/eda_client.py +++ b/atst/eda_client.py @@ -116,18 +116,13 @@ class MockEDAClient(EDAClientBase): def get_contract(self, contract_number, status): if contract_number == self.MOCK_CONTRACT_NUMBER and status == "y": return { - "aco_mod": "01", - "admin_dodaac": None, - "cage_code": "1U305", - "contract_no": "DCA10096D0052", - "delivery_order": "0084", - "duns_number": None, - "issue_date": "20000228", - "issue_dodaac": None, - "location": "https://docsrv1.nit.disa.mil:443/eda/enforcer/C0414345.PDF?ver=1.4&loc=Y29udHJhY3RzL29nZGVuL3ZlbmRvci8xOTk4LzA5LzE0L0MwNDE0MzQ1LlBERg==&sourceurl=aHR0cHM6Ly9lZGE0Lm5pdC5kaXNhLm1pbC9wbHMvdXNlci9uZXdfYXBwLkdldF9Eb2M_cFRhYmxlX0lEPTImcFJlY29yZF9LZXk9OEE2ODExNjM2RUY5NkU2M0UwMzQwMDYwQjBCMjgyNkM=&uid=6CFC2B2322E86FD5E054002264936E3C&qid=19344159&signed=G&qdate=20180529194407GMT&token=6xQICrrrfIMciEJSpXmfsAYrToM=", - "pay_dodaac": None, - "pco_mod": "02", - "amount": 2_000_000, + "number": "DCA10096D0052", + "clin_0001": 500, + "clin_0003": 600, + "clin_1001": 700, + "clin_1003": 800, + "clin_2001": 900, + "clin_2003": 1000, } else: return None @@ -180,7 +175,10 @@ class EDAClient(EDAClientBase): ) if response.text.startswith("No data found"): return None - return ET.fromstring(response.text) + + eda_data = {"number": contract_number} + eda_data.update(parse_eda_xml(response.text)) + return eda_data def get_clins(self, record_key, clins, cage_code="", duns_number=""): response = self._get( diff --git a/tests/test_eda_client.py b/tests/test_eda_client.py index 36619df6..3d8ee3be 100644 --- a/tests/test_eda_client.py +++ b/tests/test_eda_client.py @@ -11,8 +11,7 @@ def test_list_contracts(): def test_get_contract(): result = mock_client.get_contract("DCA10096D0052", "y") - assert result["contract_no"] == "DCA10096D0052" - assert result["amount"] == 2_000_000 + assert result["number"] == "DCA10096D0052" def test_contract_not_found(): From af81cd1da73e5eee6f7e5beb68451dbdde6ed06e Mon Sep 17 00:00:00 2001 From: dandds Date: Wed, 17 Oct 2018 16:31:44 -0400 Subject: [PATCH 3/4] small refactor for eda xml parser --- atst/eda_client.py | 14 +++++++------- tests/test_eda_client.py | 2 +- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/atst/eda_client.py b/atst/eda_client.py index b6376682..f4a2f2b6 100644 --- a/atst/eda_client.py +++ b/atst/eda_client.py @@ -28,14 +28,14 @@ class EDAXMLHandler: def _line_items(self): return self.element_tree.findall(".//LineItem[LineItemType='CLIN']/../../..") - def _parse_clin_data(self): - for line_item in self._line_items: - number = line_item.find(".//LineItemBase").text - amount_details = line_item.find(".//ItemOtherAmounts[AmountDescription='Not to Exceed Amount (Funding)']/Amount") - self.clins[number] = float(amount_details.text) - def parse(self): - self._parse_clin_data() + for line_item in self._line_items: + number_el = line_item.find(".//LineItemBase") + amount_details = line_item.find( + ".//ItemOtherAmounts[AmountDescription='Not to Exceed Amount (Funding)']/Amount" + ) + if number_el is not None and amount_details is not None: + self.clins[number_el.text] = float(amount_details.text) class EDAClientBase(object): diff --git a/tests/test_eda_client.py b/tests/test_eda_client.py index 3d8ee3be..005e3037 100644 --- a/tests/test_eda_client.py +++ b/tests/test_eda_client.py @@ -22,5 +22,5 @@ def test_contract_not_found(): def test_eda_xml_parser(): with open("tests/fixtures/eda_contract.xml") as contract: eda_data = parse_eda_xml(contract.read()) - assert eda_data["clin_0001"] == 200000.00 + assert eda_data["clin_0001"] == 200_000.00 assert not eda_data["clin_0003"] From d6ec4a5123e17642434a3b3f0613c9e8fde6caca Mon Sep 17 00:00:00 2001 From: dandds Date: Thu, 18 Oct 2018 10:43:44 -0400 Subject: [PATCH 4/4] catch type errors for non-floats in CLIN amount field --- atst/eda_client.py | 5 ++++- tests/test_eda_client.py | 31 +++++++++++++++++++++++++++++++ 2 files changed, 35 insertions(+), 1 deletion(-) diff --git a/atst/eda_client.py b/atst/eda_client.py index f4a2f2b6..fe56cd0a 100644 --- a/atst/eda_client.py +++ b/atst/eda_client.py @@ -35,7 +35,10 @@ class EDAXMLHandler: ".//ItemOtherAmounts[AmountDescription='Not to Exceed Amount (Funding)']/Amount" ) if number_el is not None and amount_details is not None: - self.clins[number_el.text] = float(amount_details.text) + try: + self.clins[number_el.text] = float(amount_details.text) + except ValueError: + continue class EDAClientBase(object): diff --git a/tests/test_eda_client.py b/tests/test_eda_client.py index 005e3037..787ba8c8 100644 --- a/tests/test_eda_client.py +++ b/tests/test_eda_client.py @@ -24,3 +24,34 @@ def test_eda_xml_parser(): eda_data = parse_eda_xml(contract.read()) assert eda_data["clin_0001"] == 200_000.00 assert not eda_data["clin_0003"] + + +_EDA_XML_NO_NUMBER = """ + + + + + + + + CLIN + 0001 + + + + + + Not to Exceed Amount (Funding) + not a number + + + + + + +""" + + +def test_eda_xml_parser_with_bad_xml(): + eda_data = parse_eda_xml(_EDA_XML_NO_NUMBER) + assert eda_data["clin_0001"] is None