datamule 1.2.6__py3-none-any.whl → 1.2.7__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- datamule/document/document.py +2 -2
- datamule/document/mappings/ex102_abs.py +63 -0
- datamule/document/processing.py +36 -9
- datamule/document/table.py +44 -6
- {datamule-1.2.6.dist-info → datamule-1.2.7.dist-info}/METADATA +1 -1
- {datamule-1.2.6.dist-info → datamule-1.2.7.dist-info}/RECORD +8 -7
- {datamule-1.2.6.dist-info → datamule-1.2.7.dist-info}/WHEEL +0 -0
- {datamule-1.2.6.dist-info → datamule-1.2.7.dist-info}/top_level.txt +0 -0
datamule/document/document.py
CHANGED
@@ -141,7 +141,7 @@ class Document:
|
|
141
141
|
with open(output_filename, 'w',encoding='utf-8') as f:
|
142
142
|
json.dump(self.data, f, indent=2)
|
143
143
|
|
144
|
-
def
|
144
|
+
def tables(self):
|
145
145
|
if self.type == 'submission_metadata':
|
146
146
|
return process_tabular_data(self)
|
147
147
|
elif self.extension != '.xml':
|
@@ -155,7 +155,7 @@ class Document:
|
|
155
155
|
output_folder = Path(output_folder)
|
156
156
|
output_folder.mkdir(exist_ok=True)
|
157
157
|
|
158
|
-
tables = self.
|
158
|
+
tables = self.tables()
|
159
159
|
|
160
160
|
if not tables:
|
161
161
|
return
|
@@ -0,0 +1,63 @@
|
|
1
|
+
# Assets dictionary mapping
|
2
|
+
assets_dict_ex102_abs = {
|
3
|
+
'assetNumber': 'assetNumber',
|
4
|
+
'DefeasedStatusCode': 'DefeasedStatusCode',
|
5
|
+
'defeasanceOptionStartDate': 'defeasanceOptionStartDate',
|
6
|
+
'mostRecentDebtServiceCoverageNetOperatingIncomePercentage': 'mostRecentDebtServiceCoverageNetOperatingIncomePercentage',
|
7
|
+
'mostRecentDebtServiceAmount': 'mostRecentDebtServiceAmount',
|
8
|
+
'debtServiceCoverageSecuritizationCode': 'debtServiceCoverageSecuritizationCode',
|
9
|
+
'debtServiceCoverageNetOperatingIncomeSecuritizationPercentage': 'debtServiceCoverageNetOperatingIncomeSecuritizationPercentage',
|
10
|
+
'valuationSecuritizationDate': 'valuationSecuritizationDate',
|
11
|
+
'physicalOccupancySecuritizationPercentage': 'physicalOccupancySecuritizationPercentage',
|
12
|
+
'revenueSecuritizationAmount': 'revenueSecuritizationAmount',
|
13
|
+
'valuationSourceSecuritizationCode': 'valuationSourceSecuritizationCode',
|
14
|
+
'financialsSecuritizationDate': 'financialsSecuritizationDate',
|
15
|
+
'mostRecentNetCashFlowAmount': 'mostRecentNetCashFlowAmount',
|
16
|
+
'operatingExpensesAmount': 'operatingExpensesAmount',
|
17
|
+
'operatingExpensesSecuritizationAmount': 'operatingExpensesSecuritizationAmount',
|
18
|
+
'netOperatingIncomeNetCashFlowSecuritizationCode': 'netOperatingIncomeNetCashFlowSecuritizationCode',
|
19
|
+
'mostRecentValuationSourceCode': 'mostRecentValuationSourceCode',
|
20
|
+
'mostRecentDebtServiceCoverageNetCashFlowpercentage': 'mostRecentDebtServiceCoverageNetCashFlowpercentage',
|
21
|
+
'debtServiceCoverageNetCashFlowSecuritizationPercentage': 'debtServiceCoverageNetCashFlowSecuritizationPercentage',
|
22
|
+
'mostRecentAnnualLeaseRolloverReviewDate': 'mostRecentAnnualLeaseRolloverReviewDate',
|
23
|
+
'mostRecentRevenueAmount': 'mostRecentRevenueAmount',
|
24
|
+
'mostRecentPhysicalOccupancyPercentage': 'mostRecentPhysicalOccupancyPercentage',
|
25
|
+
'mostRecentNetOperatingIncomeAmount': 'mostRecentNetOperatingIncomeAmount',
|
26
|
+
'netOperatingIncomeSecuritizationAmount': 'netOperatingIncomeSecuritizationAmount',
|
27
|
+
'netOperatingIncomeNetCashFlowCode': 'netOperatingIncomeNetCashFlowCode',
|
28
|
+
'mostRecentFinancialsStartDate': 'mostRecentFinancialsStartDate',
|
29
|
+
'mostRecentFinancialsEndDate': 'mostRecentFinancialsEndDate',
|
30
|
+
'accession': 'accession',
|
31
|
+
'valuationSecuritizationAmount': 'valuationSecuritizationAmount',
|
32
|
+
'mostRecentValuationDate': 'mostRecentValuationDate',
|
33
|
+
'mostRecentValuationAmount': 'mostRecentValuationAmount',
|
34
|
+
'mostRecentDebtServiceCoverageCode': 'mostRecentDebtServiceCoverageCode',
|
35
|
+
'netCashFlowFlowSecuritizationAmount': 'netCashFlowFlowSecuritizationAmount'
|
36
|
+
}
|
37
|
+
|
38
|
+
# Properties dictionary mapping
|
39
|
+
properties_dict_ex102_abs = {
|
40
|
+
'unitsBedsRoomsNumber': 'unitsBedsRoomsNumber',
|
41
|
+
'propertyCounty': 'propertyCounty',
|
42
|
+
'squareFeetLargestTenantNumber': 'squareFeetLargestTenantNumber',
|
43
|
+
'netRentableSquareFeetNumber': 'netRentableSquareFeetNumber',
|
44
|
+
'leaseExpirationThirdLargestTenantDate': 'leaseExpirationThirdLargestTenantDate',
|
45
|
+
'leaseExpirationLargestTenantDate': 'leaseExpirationLargestTenantDate',
|
46
|
+
'propertyZip': 'propertyZip',
|
47
|
+
'squareFeetThirdLargestTenantNumber': 'squareFeetThirdLargestTenantNumber',
|
48
|
+
'propertyStatusCode': 'propertyStatusCode',
|
49
|
+
'propertyState': 'propertyState',
|
50
|
+
'yearBuiltNumber': 'yearBuiltNumber',
|
51
|
+
'propertyCity': 'propertyCity',
|
52
|
+
'propertyName': 'propertyName',
|
53
|
+
'propertyAddress': 'propertyAddress',
|
54
|
+
'yearLastRenovated': 'yearLastRenovated',
|
55
|
+
'leaseExpirationSecondLargestTenantDate': 'leaseExpirationSecondLargestTenantDate',
|
56
|
+
'thirdLargestTenant': 'thirdLargestTenant',
|
57
|
+
'unitsBedsRoomsSecuritizationNumber': 'unitsBedsRoomsSecuritizationNumber',
|
58
|
+
'propertyTypeCode': 'propertyTypeCode',
|
59
|
+
'largestTenant': 'largestTenant',
|
60
|
+
'squareFeetSecondLargestTenantNumber': 'squareFeetSecondLargestTenantNumber',
|
61
|
+
'netRentableSquareFeetSecuritizationNumber': 'netRentableSquareFeetSecuritizationNumber',
|
62
|
+
'secondLargestTenant': 'secondLargestTenant'
|
63
|
+
}
|
datamule/document/processing.py
CHANGED
@@ -22,6 +22,8 @@ def process_tabular_data(self):
|
|
22
22
|
# complete mark:
|
23
23
|
elif self.type in ["N-PX","N-PX/A"]:
|
24
24
|
tables = process_npx(self.data, self.accession)
|
25
|
+
elif self.type in ["EX-102"]:
|
26
|
+
tables = process_ex102_abs(self.data, self.accession)
|
25
27
|
|
26
28
|
elif self.type in ["SBSEF","SBSEF/A","SBSEF-V","SBSEF-W"]:
|
27
29
|
tables = process_sbsef(self.data, self.accession)
|
@@ -70,8 +72,7 @@ def process_tabular_data(self):
|
|
70
72
|
tables = process_reg_a(self.data, self.accession)
|
71
73
|
# elif self.type in ["SBSE","SBSE/A","SBSE-A","SBSE-A/A","SBSE-BD","SBSE-BD/A","SBSE-C","SBSE-W","SBSE-CCO-RPT","SBSE-CCO-RPT/A"]:
|
72
74
|
# tables = process_sbs(self.data, self.accession)
|
73
|
-
|
74
|
-
# tables = process_ex102_abs(self.data, self.accession)
|
75
|
+
|
75
76
|
elif self.type == "PROXY VOTING RECORD":
|
76
77
|
tables = process_proxy_voting_record(self.data, self.accession)
|
77
78
|
elif self.type == 'submission_metadata':
|
@@ -589,13 +590,39 @@ def process_reg_a(data, accession):
|
|
589
590
|
|
590
591
|
# return tables
|
591
592
|
|
592
|
-
|
593
|
-
|
594
|
-
|
595
|
-
|
596
|
-
#
|
597
|
-
|
598
|
-
|
593
|
+
def process_ex102_abs(data, accession):
|
594
|
+
tables = []
|
595
|
+
data = safe_get(data, ['assetData', 'assets'])
|
596
|
+
|
597
|
+
# Create assets list: all items without their 'property' field
|
598
|
+
assets = [{k: v for k, v in item.items() if k != 'property'} for item in data]
|
599
|
+
|
600
|
+
# Create properties list in a more vectorized way
|
601
|
+
properties = []
|
602
|
+
|
603
|
+
# Handle dictionary properties
|
604
|
+
properties.extend([
|
605
|
+
item['property'] | {'assetNumber': item['assetNumber']}
|
606
|
+
for item in data
|
607
|
+
if 'property' in item and isinstance(item['property'], dict)
|
608
|
+
])
|
609
|
+
|
610
|
+
# Handle list properties - flatten in one operation
|
611
|
+
properties.extend([
|
612
|
+
prop | {'assetNumber': item['assetNumber']}
|
613
|
+
for item in data
|
614
|
+
if 'property' in item and isinstance(item['property'], list)
|
615
|
+
for prop in item['property']
|
616
|
+
if isinstance(prop, dict)
|
617
|
+
])
|
618
|
+
|
619
|
+
if assets:
|
620
|
+
tables.append(Table(_flatten_dict(assets), 'assets_ex102_absee', accession))
|
621
|
+
|
622
|
+
if properties:
|
623
|
+
tables.append(Table(_flatten_dict(properties), 'properties_ex102_absee', accession))
|
624
|
+
|
625
|
+
return tables
|
599
626
|
|
600
627
|
# def process_ma(data, accession):
|
601
628
|
# tables = []
|
datamule/document/table.py
CHANGED
@@ -19,6 +19,10 @@ from .mappings.twentyfivense import *
|
|
19
19
|
from .mappings.twentyfourf2nt import *
|
20
20
|
from .mappings.information_table import *
|
21
21
|
from .mappings.submission_metadata import *
|
22
|
+
from .mappings.ex102_abs import *
|
23
|
+
|
24
|
+
from pathlib import Path
|
25
|
+
import csv
|
22
26
|
# need to check if mappings correctly create new columns
|
23
27
|
class Table():
|
24
28
|
def __init__(self, data, type,accession):
|
@@ -27,11 +31,18 @@ class Table():
|
|
27
31
|
self.type = type
|
28
32
|
self.data = data
|
29
33
|
self.accession = accession
|
30
|
-
self.columns = self.
|
34
|
+
self.columns = self.determine_columns_complete()
|
35
|
+
|
36
|
+
def determine_columns_complete(self):
|
37
|
+
if not self.data:
|
38
|
+
return []
|
39
|
+
return list(set().union(*(row.keys() for row in self.data)))
|
40
|
+
|
31
41
|
|
32
42
|
def determine_columns(self):
|
33
43
|
if len(self.data) == 0:
|
34
44
|
return []
|
45
|
+
|
35
46
|
return self.data[0].keys()
|
36
47
|
|
37
48
|
def add_column(self,column_name,value):
|
@@ -227,7 +238,11 @@ class Table():
|
|
227
238
|
mapping_dict = item_9_24f2nt_dict
|
228
239
|
elif self.type == 'signature_info_schedule_a':
|
229
240
|
mapping_dict = signature_24f2nt_dict
|
230
|
-
|
241
|
+
# ABS
|
242
|
+
elif self.type == 'assets_ex102_absee':
|
243
|
+
mapping_dict = assets_dict_ex102_abs
|
244
|
+
elif self.type =='properties_ex102_absee':
|
245
|
+
mapping_dict = properties_dict_ex102_abs
|
231
246
|
# submission metadata
|
232
247
|
elif self.type == 'document_submission_metadata':
|
233
248
|
mapping_dict = document_submission_metadata_dict
|
@@ -250,9 +265,6 @@ class Table():
|
|
250
265
|
for old_key, new_key in mapping_dict.items():
|
251
266
|
if old_key in row:
|
252
267
|
ordered_row[new_key] = row.pop(old_key)
|
253
|
-
else:
|
254
|
-
# if the old key is not present, set the new key to None
|
255
|
-
ordered_row[new_key] = None
|
256
268
|
|
257
269
|
# Then add any remaining keys that weren't in the mapping
|
258
270
|
for key, value in row.items():
|
@@ -262,4 +274,30 @@ class Table():
|
|
262
274
|
row.clear()
|
263
275
|
row.update(ordered_row)
|
264
276
|
|
265
|
-
|
277
|
+
# Update the columns after mapping
|
278
|
+
columns = set(self.columns)
|
279
|
+
# remove the old columns that are now in the mapping
|
280
|
+
columns.difference_update(mapping_dict.keys())
|
281
|
+
# add the new columns from the mapping
|
282
|
+
columns.update(mapping_dict.values())
|
283
|
+
# add the accession column to the columns
|
284
|
+
columns.add('accession')
|
285
|
+
|
286
|
+
self.columns = list(columns)
|
287
|
+
|
288
|
+
def write_csv(self, output_file):
|
289
|
+
output_file = Path(output_file)
|
290
|
+
fieldnames = self.columns
|
291
|
+
|
292
|
+
# Check if the file already exists
|
293
|
+
if output_file.exists():
|
294
|
+
# Append to existing file without writing header
|
295
|
+
with open(output_file, 'a', newline='') as csvfile:
|
296
|
+
writer = csv.DictWriter(csvfile, fieldnames=fieldnames, quoting=csv.QUOTE_ALL)
|
297
|
+
writer.writerows(self.data)
|
298
|
+
else:
|
299
|
+
# Create new file with header
|
300
|
+
with open(output_file, 'w', newline='') as csvfile:
|
301
|
+
writer = csv.DictWriter(csvfile, fieldnames=fieldnames, quoting=csv.QUOTE_ALL)
|
302
|
+
writer.writeheader()
|
303
|
+
writer.writerows(self.data)
|
@@ -7,12 +7,13 @@ datamule/portfolio.py,sha256=8fiK-vfZM5-NJSvOEsDR2YDb-2njjzFk6l7BiRyrzOM,7168
|
|
7
7
|
datamule/sheet.py,sha256=TvFqK9eAYuVoJ2uWdAlx5EN6vS9lke-aZf7FqtUiDBc,22304
|
8
8
|
datamule/submission.py,sha256=Yh5nG3ioumhl6z30wJdIEmKjDDNSuo0r2xycZSIaeIg,11035
|
9
9
|
datamule/document/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
10
|
-
datamule/document/document.py,sha256=
|
11
|
-
datamule/document/processing.py,sha256=
|
12
|
-
datamule/document/table.py,sha256=
|
10
|
+
datamule/document/document.py,sha256=menUFoeWwiY0rJnBkQiqY4NWnO0J17-qs8jFvO_1jiY,9969
|
11
|
+
datamule/document/processing.py,sha256=MLgOtVNmsUotfxj5XvQqw5Q3idhvK6FdHcQz3U4ud7s,29333
|
12
|
+
datamule/document/table.py,sha256=NdDQh7EWOf2qSx5ZxydCHpaNPd7J7wtVFoooiVzbmgk,12443
|
13
13
|
datamule/document/mappings/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
14
14
|
datamule/document/mappings/atsn.py,sha256=qkZGNIhyPC3VTTOjQ8-FSCQIhUy4XeSycUGLShxNVCo,17743
|
15
15
|
datamule/document/mappings/cfportal.py,sha256=bR9d6DDY0kJ_HGx_hND2y1PNNkZjemYZ2KdyFAcv760,25257
|
16
|
+
datamule/document/mappings/ex102_abs.py,sha256=FdGKvteRh_HsYgILF-8o4R6aSsjYwcaLpJxzdru4FTE,3976
|
16
17
|
datamule/document/mappings/ex99a_sdr.py,sha256=PNdj9I0ZhNicPObLelNmjp33EgTwzvukqkBDnwxarE0,19
|
17
18
|
datamule/document/mappings/ex99c_sdr.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
18
19
|
datamule/document/mappings/ex99g_sdr.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
@@ -54,7 +55,7 @@ datamule/seclibrary/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSu
|
|
54
55
|
datamule/seclibrary/bq.py,sha256=C8sb_rpXTvchprrFLcbRar4Qi0XWW25tnv1YsHSS5o4,18025
|
55
56
|
datamule/seclibrary/downloader.py,sha256=PIgz_7ASUTZOHcUZGcD1SmLaGSbq7xe7EiJT0Z7HU4M,13653
|
56
57
|
datamule/seclibrary/query.py,sha256=qGuursTERRbOGfoDcYcpo4oWkW3PCBW6x1Qf1Puiak4,7352
|
57
|
-
datamule-1.2.
|
58
|
-
datamule-1.2.
|
59
|
-
datamule-1.2.
|
60
|
-
datamule-1.2.
|
58
|
+
datamule-1.2.7.dist-info/METADATA,sha256=EvHa-0eCP6v0GRF6tq4QqnIDQpqM0m9yIXc68BO8Wck,490
|
59
|
+
datamule-1.2.7.dist-info/WHEEL,sha256=oiQVh_5PnQM0E3gPdiz09WCNmwiHDMaGer_elqB3coM,92
|
60
|
+
datamule-1.2.7.dist-info/top_level.txt,sha256=iOfgmtSMFVyr7JGl_bYSTDry79JbmsG4p8zKq89ktKk,9
|
61
|
+
datamule-1.2.7.dist-info/RECORD,,
|
File without changes
|
File without changes
|