datapungibea 2.0.0__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.
- datapungibea/__init__.py +19 -0
- datapungibea/api.py +616 -0
- datapungibea/codegen.py +93 -0
- datapungibea/config/CFGindentations.py +182 -0
- datapungibea/config/CFGnipaSummary.py +316 -0
- datapungibea/config/__init__.py +0 -0
- datapungibea/config/datasetlist.yaml +273 -0
- datapungibea/config/pkgConfig.yaml +1 -0
- datapungibea/core.py +238 -0
- datapungibea/factory.py +52 -0
- datapungibea/generalSettings.py +214 -0
- datapungibea/tests/__init__.py +1 -0
- datapungibea/tests/conftest.py +13 -0
- datapungibea/tests/main.py +33 -0
- datapungibea/tests/test_drivers.py +228 -0
- datapungibea/transform.py +342 -0
- datapungibea/utils.py +180 -0
- datapungibea-2.0.0.dist-info/METADATA +328 -0
- datapungibea-2.0.0.dist-info/RECORD +21 -0
- datapungibea-2.0.0.dist-info/WHEEL +4 -0
- datapungibea-2.0.0.dist-info/licenses/LICENSE +21 -0
datapungibea/__init__.py
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
"""Connect to Bureau of Economic Analysis (BEA) API and enrich data output.
|
|
2
|
+
|
|
3
|
+
Modernized version 2.0 with YAML-driven architecture.
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
import logging
|
|
7
|
+
from typing import Any, Dict, Optional
|
|
8
|
+
|
|
9
|
+
import pandas
|
|
10
|
+
import requests
|
|
11
|
+
|
|
12
|
+
from datapungibea.api import data
|
|
13
|
+
import datapungibea.tests as tests
|
|
14
|
+
|
|
15
|
+
__version__ = '2.0.0'
|
|
16
|
+
__all__ = ['data', 'tests', 'utils']
|
|
17
|
+
|
|
18
|
+
# Set up logging
|
|
19
|
+
logging.getLogger(__name__).addHandler(logging.NullHandler())
|
datapungibea/api.py
ADDED
|
@@ -0,0 +1,616 @@
|
|
|
1
|
+
r'''
|
|
2
|
+
Main user-facing API for datapungibea.
|
|
3
|
+
Uses factory pattern from datapungi_fed architecture.
|
|
4
|
+
'''
|
|
5
|
+
|
|
6
|
+
import logging
|
|
7
|
+
from typing import Any, Dict, Optional
|
|
8
|
+
|
|
9
|
+
import pandas as pd
|
|
10
|
+
|
|
11
|
+
from datapungibea import generalSettings
|
|
12
|
+
from datapungibea.core import driverCore
|
|
13
|
+
|
|
14
|
+
logger = logging.getLogger(__name__)
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class data:
|
|
18
|
+
r'''
|
|
19
|
+
Main API class for accessing BEA data.
|
|
20
|
+
|
|
21
|
+
Provides access to all BEA datasets through a unified interface.
|
|
22
|
+
Uses factory pattern to create query functions for each dataset group.
|
|
23
|
+
|
|
24
|
+
Example:
|
|
25
|
+
import datapungibea as dpb
|
|
26
|
+
|
|
27
|
+
# Initialize with API key
|
|
28
|
+
data = dpb.data("YOUR_API_KEY")
|
|
29
|
+
|
|
30
|
+
# Or use environment variable
|
|
31
|
+
data = dpb.data()
|
|
32
|
+
|
|
33
|
+
# Query datasets
|
|
34
|
+
data.nipa('T10101')
|
|
35
|
+
data.metadata() # list all datasets
|
|
36
|
+
'''
|
|
37
|
+
|
|
38
|
+
def __init__(
|
|
39
|
+
self,
|
|
40
|
+
key: Optional[str] = None,
|
|
41
|
+
keyfile: Optional[str] = None,
|
|
42
|
+
connectionParameters: Optional[Dict[str, Any]] = None,
|
|
43
|
+
):
|
|
44
|
+
'''
|
|
45
|
+
Initialize the BEA data API.
|
|
46
|
+
|
|
47
|
+
Three ways to provide an API key:
|
|
48
|
+
1. Environment variable API_KEY_BEA (default):
|
|
49
|
+
export API_KEY_BEA=your_key_here
|
|
50
|
+
data = dpb.data()
|
|
51
|
+
|
|
52
|
+
2. Pass key directly:
|
|
53
|
+
data = dpb.data("YOUR_API_KEY")
|
|
54
|
+
|
|
55
|
+
3. Point to a keys file (YAML or JSON):
|
|
56
|
+
data = dpb.data(keyfile="/path/to/keys.yaml")
|
|
57
|
+
|
|
58
|
+
Args:
|
|
59
|
+
key: API key string passed directly
|
|
60
|
+
keyfile: path to a YAML or JSON file containing API keys
|
|
61
|
+
connectionParameters: explicit dict with 'key' and 'url' (advanced)
|
|
62
|
+
'''
|
|
63
|
+
# Load general settings
|
|
64
|
+
self.__connectInfo = generalSettings.getGeneralSettings(
|
|
65
|
+
key=key,
|
|
66
|
+
keyfile=keyfile,
|
|
67
|
+
connectionParameters=connectionParameters,
|
|
68
|
+
)
|
|
69
|
+
|
|
70
|
+
self._metadata = self.__connectInfo.packageMetadata
|
|
71
|
+
self._help = self.__connectInfo.datasourceOverview
|
|
72
|
+
|
|
73
|
+
# Load parameters for driver creation
|
|
74
|
+
loadInfo = {
|
|
75
|
+
'baseRequest': self.__connectInfo.baseRequest,
|
|
76
|
+
'connectionParameters': self.__connectInfo.connectionParameters
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
# Initialize dataset groups using factory pattern
|
|
80
|
+
self._metadata_driver = driverCore('Metadata', **loadInfo)
|
|
81
|
+
self._nipa_driver = driverCore('NIPA', **loadInfo)
|
|
82
|
+
self._national_accounts_driver = driverCore('National Accounts', **loadInfo)
|
|
83
|
+
self._international_transactions_driver = driverCore('International Transactions', **loadInfo)
|
|
84
|
+
self._international_investment_driver = driverCore('International Investment Position', **loadInfo)
|
|
85
|
+
self._industry_driver = driverCore('Industry', **loadInfo)
|
|
86
|
+
self._regional_driver = driverCore('Regional', **loadInfo)
|
|
87
|
+
self._input_output_driver = driverCore('Input-Output', **loadInfo)
|
|
88
|
+
self._international_services_driver = driverCore('International Services Trade', **loadInfo)
|
|
89
|
+
self._multinational_driver = driverCore('Multinational Enterprises', **loadInfo)
|
|
90
|
+
|
|
91
|
+
# Store last loaded driver for clipcode
|
|
92
|
+
self._lastCalledDriver = None
|
|
93
|
+
|
|
94
|
+
def __str__(self) -> str:
|
|
95
|
+
'''List all available dataset groups.'''
|
|
96
|
+
groups = [
|
|
97
|
+
'Metadata',
|
|
98
|
+
'NIPA',
|
|
99
|
+
'National Accounts',
|
|
100
|
+
'International Transactions',
|
|
101
|
+
'International Investment Position',
|
|
102
|
+
'Industry',
|
|
103
|
+
'Regional',
|
|
104
|
+
'Input-Output',
|
|
105
|
+
'International Services Trade',
|
|
106
|
+
'Multinational Enterprises',
|
|
107
|
+
'NIPA Vintage (not API)',
|
|
108
|
+
'NIPA Summary (not API)'
|
|
109
|
+
]
|
|
110
|
+
|
|
111
|
+
output = "BEA Dataset Groups:\n"
|
|
112
|
+
output += "=" * 50 + "\n"
|
|
113
|
+
for group in groups:
|
|
114
|
+
output += f" - {group}\n"
|
|
115
|
+
output += "\n"
|
|
116
|
+
output += "Usage: data.<group_name>() or data.<group_name>['<dataset>']()\n"
|
|
117
|
+
output += "Example: data.nipa('T10101')\n"
|
|
118
|
+
output += "\nRun data._help for more information.\n"
|
|
119
|
+
|
|
120
|
+
return output
|
|
121
|
+
|
|
122
|
+
# =========================================================================
|
|
123
|
+
# Metadata queries
|
|
124
|
+
# =========================================================================
|
|
125
|
+
|
|
126
|
+
def datasetlist(self, verbose: bool = False, **kwargs: Any) -> Any:
|
|
127
|
+
'''Get list of available BEA datasets.'''
|
|
128
|
+
self._lastCalledDriver = self._metadata_driver
|
|
129
|
+
return self._metadata_driver['datasetlist'](verbose=verbose, **kwargs)
|
|
130
|
+
|
|
131
|
+
def getParameterList(self, datasetname: str, verbose: bool = False, **kwargs: Any) -> Any:
|
|
132
|
+
'''Get parameters for a specific dataset.'''
|
|
133
|
+
self._lastCalledDriver = self._metadata_driver
|
|
134
|
+
return self._metadata_driver['parameterlist'](datasetname=datasetname, verbose=verbose, **kwargs)
|
|
135
|
+
|
|
136
|
+
def getParameterValues(self, datasetName: str, parameterName: str, verbose: bool = False, **kwargs: Any) -> Any:
|
|
137
|
+
'''Get available values for a dataset parameter.'''
|
|
138
|
+
self._lastCalledDriver = self._metadata_driver
|
|
139
|
+
return self._metadata_driver['parametervalues'](
|
|
140
|
+
datasetname=datasetName,
|
|
141
|
+
parametername=parameterName,
|
|
142
|
+
verbose=verbose,
|
|
143
|
+
**kwargs
|
|
144
|
+
)
|
|
145
|
+
|
|
146
|
+
# =========================================================================
|
|
147
|
+
# NIPA queries
|
|
148
|
+
# =========================================================================
|
|
149
|
+
|
|
150
|
+
def NIPA(
|
|
151
|
+
self,
|
|
152
|
+
TableName: str,
|
|
153
|
+
Frequency: str = 'Q',
|
|
154
|
+
Year: str = 'X',
|
|
155
|
+
verbose: bool = False,
|
|
156
|
+
includeIndentations: bool = True,
|
|
157
|
+
**kwargs: Any
|
|
158
|
+
) -> Any:
|
|
159
|
+
'''
|
|
160
|
+
Query NIPA (National Income and Product Accounts) data.
|
|
161
|
+
|
|
162
|
+
Args:
|
|
163
|
+
TableName: NIPA table name (e.g., 'T10101')
|
|
164
|
+
Frequency: 'Q' (quarterly), 'A' (annual), or 'M' (monthly)
|
|
165
|
+
Year: specific year or 'X' for all years
|
|
166
|
+
verbose: if True, return dict with dataFrame, request, and code
|
|
167
|
+
includeIndentations: if True, add indentation markers to LineDescription
|
|
168
|
+
|
|
169
|
+
Returns:
|
|
170
|
+
pandas DataFrame or dict (if verbose=True)
|
|
171
|
+
|
|
172
|
+
Example:
|
|
173
|
+
data.NIPA('T10101')
|
|
174
|
+
data.NIPA('T10101', Frequency='A', Year='X', verbose=True)
|
|
175
|
+
'''
|
|
176
|
+
self._lastCalledDriver = self._nipa_driver
|
|
177
|
+
return self._nipa_driver(
|
|
178
|
+
TableName=TableName,
|
|
179
|
+
Frequency=Frequency,
|
|
180
|
+
Year=Year,
|
|
181
|
+
verbose=verbose,
|
|
182
|
+
includeIndentations=includeIndentations,
|
|
183
|
+
**kwargs
|
|
184
|
+
)
|
|
185
|
+
|
|
186
|
+
def NIUnderlyingDetail(
|
|
187
|
+
self,
|
|
188
|
+
TableName: str,
|
|
189
|
+
Frequency: str = 'Q',
|
|
190
|
+
Year: str = 'X',
|
|
191
|
+
verbose: bool = False,
|
|
192
|
+
**kwargs: Any
|
|
193
|
+
) -> Any:
|
|
194
|
+
'''
|
|
195
|
+
Query National Income underlying detail tables.
|
|
196
|
+
|
|
197
|
+
Args:
|
|
198
|
+
TableName: table name
|
|
199
|
+
Frequency: 'Q', 'A', or 'M'
|
|
200
|
+
Year: specific year or 'X' for all years
|
|
201
|
+
verbose: if True, return dict with dataFrame, request, and code
|
|
202
|
+
|
|
203
|
+
Returns:
|
|
204
|
+
pandas DataFrame or dict (if verbose=True)
|
|
205
|
+
'''
|
|
206
|
+
self._lastCalledDriver = self._nipa_driver
|
|
207
|
+
return self._nipa_driver['NIUnderlyingDetail'](
|
|
208
|
+
TableName=TableName,
|
|
209
|
+
Frequency=Frequency,
|
|
210
|
+
Year=Year,
|
|
211
|
+
verbose=verbose,
|
|
212
|
+
**kwargs
|
|
213
|
+
)
|
|
214
|
+
|
|
215
|
+
# =========================================================================
|
|
216
|
+
# National Accounts queries
|
|
217
|
+
# =========================================================================
|
|
218
|
+
|
|
219
|
+
def fixedAssets(self, TableName: str, Year: str, verbose: bool = False, **kwargs: Any) -> Any:
|
|
220
|
+
'''Query Fixed Assets data.'''
|
|
221
|
+
self._lastCalledDriver = self._national_accounts_driver
|
|
222
|
+
return self._national_accounts_driver(
|
|
223
|
+
TableName=TableName,
|
|
224
|
+
Year=Year,
|
|
225
|
+
verbose=verbose,
|
|
226
|
+
**kwargs
|
|
227
|
+
)
|
|
228
|
+
|
|
229
|
+
# =========================================================================
|
|
230
|
+
# International Transactions queries
|
|
231
|
+
# =========================================================================
|
|
232
|
+
|
|
233
|
+
def ITA(
|
|
234
|
+
self,
|
|
235
|
+
Indicator: str,
|
|
236
|
+
AreaOrCountry: str,
|
|
237
|
+
Frequency: str,
|
|
238
|
+
Year: str,
|
|
239
|
+
verbose: bool = False,
|
|
240
|
+
**kwargs: Any
|
|
241
|
+
) -> Any:
|
|
242
|
+
'''Query International Transactions Accounts.'''
|
|
243
|
+
self._lastCalledDriver = self._international_transactions_driver
|
|
244
|
+
return self._international_transactions_driver(
|
|
245
|
+
Indicator=Indicator,
|
|
246
|
+
AreaOrCountry=AreaOrCountry,
|
|
247
|
+
Frequency=Frequency,
|
|
248
|
+
Year=Year,
|
|
249
|
+
verbose=verbose,
|
|
250
|
+
**kwargs
|
|
251
|
+
)
|
|
252
|
+
|
|
253
|
+
# =========================================================================
|
|
254
|
+
# International Investment Position queries
|
|
255
|
+
# =========================================================================
|
|
256
|
+
|
|
257
|
+
def IIP(
|
|
258
|
+
self,
|
|
259
|
+
TypeOfInvestment: str,
|
|
260
|
+
Component: str,
|
|
261
|
+
Frequency: str,
|
|
262
|
+
Year: str,
|
|
263
|
+
verbose: bool = False,
|
|
264
|
+
**kwargs: Any
|
|
265
|
+
) -> Any:
|
|
266
|
+
'''Query International Investment Position.'''
|
|
267
|
+
self._lastCalledDriver = self._international_investment_driver
|
|
268
|
+
return self._international_investment_driver(
|
|
269
|
+
TypeOfInvestment=TypeOfInvestment,
|
|
270
|
+
Component=Component,
|
|
271
|
+
Frequency=Frequency,
|
|
272
|
+
Year=Year,
|
|
273
|
+
verbose=verbose,
|
|
274
|
+
**kwargs
|
|
275
|
+
)
|
|
276
|
+
|
|
277
|
+
# =========================================================================
|
|
278
|
+
# Industry queries
|
|
279
|
+
# =========================================================================
|
|
280
|
+
|
|
281
|
+
def GDPbyIndustry(
|
|
282
|
+
self,
|
|
283
|
+
Industry: str,
|
|
284
|
+
TableID: str,
|
|
285
|
+
Frequency: str,
|
|
286
|
+
Year: str,
|
|
287
|
+
verbose: bool = False,
|
|
288
|
+
**kwargs: Any
|
|
289
|
+
) -> Any:
|
|
290
|
+
'''Query GDP by Industry.'''
|
|
291
|
+
self._lastCalledDriver = self._industry_driver
|
|
292
|
+
return self._industry_driver['GDPbyIndustry'](
|
|
293
|
+
Industry=Industry,
|
|
294
|
+
TableID=TableID,
|
|
295
|
+
Frequency=Frequency,
|
|
296
|
+
Year=Year,
|
|
297
|
+
verbose=verbose,
|
|
298
|
+
**kwargs
|
|
299
|
+
)
|
|
300
|
+
|
|
301
|
+
def UnderlyingGDPbyIndustry(
|
|
302
|
+
self,
|
|
303
|
+
Industry: str,
|
|
304
|
+
TableID: str,
|
|
305
|
+
Frequency: str,
|
|
306
|
+
Year: str,
|
|
307
|
+
verbose: bool = False,
|
|
308
|
+
**kwargs: Any
|
|
309
|
+
) -> Any:
|
|
310
|
+
'''Query Underlying GDP by Industry.'''
|
|
311
|
+
self._lastCalledDriver = self._industry_driver
|
|
312
|
+
return self._industry_driver['UnderlyingGDPbyIndustry'](
|
|
313
|
+
Industry=Industry,
|
|
314
|
+
TableID=TableID,
|
|
315
|
+
Frequency=Frequency,
|
|
316
|
+
Year=Year,
|
|
317
|
+
verbose=verbose,
|
|
318
|
+
**kwargs
|
|
319
|
+
)
|
|
320
|
+
|
|
321
|
+
# =========================================================================
|
|
322
|
+
# Regional queries
|
|
323
|
+
# =========================================================================
|
|
324
|
+
|
|
325
|
+
def Regional(
|
|
326
|
+
self,
|
|
327
|
+
GeoFips: str,
|
|
328
|
+
LineCode: str,
|
|
329
|
+
TableName: str,
|
|
330
|
+
Year: str,
|
|
331
|
+
verbose: bool = False,
|
|
332
|
+
**kwargs: Any
|
|
333
|
+
) -> Any:
|
|
334
|
+
'''Query Regional economic data.'''
|
|
335
|
+
self._lastCalledDriver = self._regional_driver
|
|
336
|
+
return self._regional_driver(
|
|
337
|
+
GeoFips=GeoFips,
|
|
338
|
+
LineCode=LineCode,
|
|
339
|
+
TableName=TableName,
|
|
340
|
+
Year=Year,
|
|
341
|
+
verbose=verbose,
|
|
342
|
+
**kwargs
|
|
343
|
+
)
|
|
344
|
+
|
|
345
|
+
# =========================================================================
|
|
346
|
+
# Input-Output queries
|
|
347
|
+
# =========================================================================
|
|
348
|
+
|
|
349
|
+
def InputOutput(self, TableID: str, Year: str, verbose: bool = False, **kwargs: Any) -> Any:
|
|
350
|
+
'''Query Input-Output tables.'''
|
|
351
|
+
self._lastCalledDriver = self._input_output_driver
|
|
352
|
+
return self._input_output_driver(
|
|
353
|
+
TableID=TableID,
|
|
354
|
+
Year=Year,
|
|
355
|
+
verbose=verbose,
|
|
356
|
+
**kwargs
|
|
357
|
+
)
|
|
358
|
+
|
|
359
|
+
# =========================================================================
|
|
360
|
+
# International Services Trade queries
|
|
361
|
+
# =========================================================================
|
|
362
|
+
|
|
363
|
+
def IntlServTrade(
|
|
364
|
+
self,
|
|
365
|
+
TypeOfService: str,
|
|
366
|
+
TradeDirection: str,
|
|
367
|
+
Affiliation: str,
|
|
368
|
+
AreaOrCountry: str,
|
|
369
|
+
Year: str,
|
|
370
|
+
verbose: bool = False,
|
|
371
|
+
**kwargs: Any
|
|
372
|
+
) -> Any:
|
|
373
|
+
'''Query International Services Trade.'''
|
|
374
|
+
self._lastCalledDriver = self._international_services_driver
|
|
375
|
+
return self._international_services_driver(
|
|
376
|
+
TypeOfService=TypeOfService,
|
|
377
|
+
TradeDirection=TradeDirection,
|
|
378
|
+
Affiliation=Affiliation,
|
|
379
|
+
AreaOrCountry=AreaOrCountry,
|
|
380
|
+
Year=Year,
|
|
381
|
+
verbose=verbose,
|
|
382
|
+
**kwargs
|
|
383
|
+
)
|
|
384
|
+
|
|
385
|
+
def IntlServSTA(
|
|
386
|
+
self,
|
|
387
|
+
Channel: str = 'ALL',
|
|
388
|
+
Destination: str = 'ALL',
|
|
389
|
+
Industry: str = 'ALL',
|
|
390
|
+
AreaOrCountry: str = 'AllCountries',
|
|
391
|
+
Year: str = 'ALL',
|
|
392
|
+
verbose: bool = False,
|
|
393
|
+
**kwargs: Any
|
|
394
|
+
) -> Any:
|
|
395
|
+
'''
|
|
396
|
+
Query International Services Supplied Through Affiliates.
|
|
397
|
+
|
|
398
|
+
Args:
|
|
399
|
+
Channel: channel requested (default 'ALL')
|
|
400
|
+
Destination: destination requested (default 'ALL')
|
|
401
|
+
Industry: industry requested (default 'ALL')
|
|
402
|
+
AreaOrCountry: area or country requested (default 'AllCountries')
|
|
403
|
+
Year: year requested (default 'ALL')
|
|
404
|
+
verbose: if True, return dict with dataFrame, request, and code
|
|
405
|
+
|
|
406
|
+
Returns:
|
|
407
|
+
pandas DataFrame or dict (if verbose=True)
|
|
408
|
+
'''
|
|
409
|
+
self._lastCalledDriver = self._international_services_driver
|
|
410
|
+
return self._international_services_driver['IntlServSTA'](
|
|
411
|
+
Channel=Channel,
|
|
412
|
+
Destination=Destination,
|
|
413
|
+
Industry=Industry,
|
|
414
|
+
AreaOrCountry=AreaOrCountry,
|
|
415
|
+
Year=Year,
|
|
416
|
+
verbose=verbose,
|
|
417
|
+
**kwargs
|
|
418
|
+
)
|
|
419
|
+
|
|
420
|
+
# =========================================================================
|
|
421
|
+
# Multinational Enterprises queries
|
|
422
|
+
# =========================================================================
|
|
423
|
+
|
|
424
|
+
def MNE(
|
|
425
|
+
self,
|
|
426
|
+
Frequency: str,
|
|
427
|
+
TableID: str,
|
|
428
|
+
DirectionOfInvestment: str,
|
|
429
|
+
OwnershipLevel: str,
|
|
430
|
+
NonbankAffiliatesOnly: str,
|
|
431
|
+
Classification: str,
|
|
432
|
+
Country: str,
|
|
433
|
+
Industry: str,
|
|
434
|
+
Year: str,
|
|
435
|
+
State: str,
|
|
436
|
+
SeriesID: str,
|
|
437
|
+
GetFootnotes: str,
|
|
438
|
+
Investment: str,
|
|
439
|
+
ParentInvestment: str,
|
|
440
|
+
verbose: bool = False,
|
|
441
|
+
**kwargs: Any
|
|
442
|
+
) -> Any:
|
|
443
|
+
'''Query Activities of U.S. Multinational Enterprises.'''
|
|
444
|
+
self._lastCalledDriver = self._multinational_driver
|
|
445
|
+
return self._multinational_driver(
|
|
446
|
+
Frequency=Frequency,
|
|
447
|
+
TableID=TableID,
|
|
448
|
+
DirectionOfInvestment=DirectionOfInvestment,
|
|
449
|
+
OwnershipLevel=OwnershipLevel,
|
|
450
|
+
NonbankAffiliatesOnly=NonbankAffiliatesOnly,
|
|
451
|
+
Classification=Classification,
|
|
452
|
+
Country=Country,
|
|
453
|
+
Industry=Industry,
|
|
454
|
+
Year=Year,
|
|
455
|
+
State=State,
|
|
456
|
+
SeriesID=SeriesID,
|
|
457
|
+
GetFootnotes=GetFootnotes,
|
|
458
|
+
Investment=Investment,
|
|
459
|
+
ParentInvestment=ParentInvestment,
|
|
460
|
+
verbose=verbose,
|
|
461
|
+
**kwargs
|
|
462
|
+
)
|
|
463
|
+
|
|
464
|
+
# =========================================================================
|
|
465
|
+
|
|
466
|
+
# =========================================================================
|
|
467
|
+
# NIPA Summary (not API - custom computation)
|
|
468
|
+
# =========================================================================
|
|
469
|
+
|
|
470
|
+
def NIPASummary(self, year: str, frequency: str, verbose: bool = False, **kwargs: Any) -> Any:
|
|
471
|
+
'''
|
|
472
|
+
Get NIPA Summary tables - overall view of NIPA accounts.
|
|
473
|
+
|
|
474
|
+
Queries 6 NIPA accounts (Domestic Income, Private Enterprise, Personal Income,
|
|
475
|
+
Government, Foreign Transactions, Domestic Capital) and returns source/uses tables.
|
|
476
|
+
|
|
477
|
+
Args:
|
|
478
|
+
year: year for summary (e.g., '2010')
|
|
479
|
+
frequency: 'Q' or 'A'
|
|
480
|
+
verbose: if True, return dict with dataFrame and request metadata
|
|
481
|
+
|
|
482
|
+
Returns:
|
|
483
|
+
List of DataFrames (one per account source/use) or dict (if verbose)
|
|
484
|
+
|
|
485
|
+
Example:
|
|
486
|
+
data.NIPASummary('2010', 'A')
|
|
487
|
+
'''
|
|
488
|
+
from datapungibea.config import CFGnipaSummary
|
|
489
|
+
import pandas as pd
|
|
490
|
+
|
|
491
|
+
logger.info(f"Generating NIPA Summary for year={year}, frequency={frequency}")
|
|
492
|
+
|
|
493
|
+
output = {'dataFrame': [], 'request': []}
|
|
494
|
+
|
|
495
|
+
for account_name, account_config in CFGnipaSummary.tabparams.items():
|
|
496
|
+
logger.debug(f"Processing {account_name}")
|
|
497
|
+
|
|
498
|
+
# Get source data
|
|
499
|
+
source_table = account_config['source']
|
|
500
|
+
source_df = self._getAccountUseOrSource(
|
|
501
|
+
tableName=source_table['tableName'],
|
|
502
|
+
year=year,
|
|
503
|
+
frequency=frequency if account_name != 'Account 2' else 'A', # Account 2 is annual only
|
|
504
|
+
tableEntries=source_table['tableEntries']
|
|
505
|
+
)
|
|
506
|
+
source_df.insert(0, 'Account', f"{account_name} sources")
|
|
507
|
+
source_df.insert(1, 'Heading', account_config['meta']['heading'])
|
|
508
|
+
source_df.attrs['meta'] = {'caption': account_config['meta']['caption'], 'type': 'sources'}
|
|
509
|
+
|
|
510
|
+
# Get uses data
|
|
511
|
+
uses_table = account_config['uses']
|
|
512
|
+
uses_df = self._getAccountUseOrSource(
|
|
513
|
+
tableName=uses_table['tableName'],
|
|
514
|
+
year=year,
|
|
515
|
+
frequency=frequency if account_name != 'Account 2' else 'A',
|
|
516
|
+
tableEntries=uses_table['tableEntries']
|
|
517
|
+
)
|
|
518
|
+
uses_df.insert(0, 'Account', f"{account_name} uses")
|
|
519
|
+
uses_df.insert(1, 'Heading', account_config['meta']['heading'])
|
|
520
|
+
uses_df.attrs['meta'] = {'caption': account_config['meta']['caption'], 'type': 'uses'}
|
|
521
|
+
|
|
522
|
+
output['dataFrame'].append(source_df)
|
|
523
|
+
output['dataFrame'].append(uses_df)
|
|
524
|
+
|
|
525
|
+
if verbose:
|
|
526
|
+
return output
|
|
527
|
+
else:
|
|
528
|
+
return output['dataFrame']
|
|
529
|
+
|
|
530
|
+
def _getAccountUseOrSource(
|
|
531
|
+
self,
|
|
532
|
+
tableName: str,
|
|
533
|
+
year: str,
|
|
534
|
+
frequency: str,
|
|
535
|
+
tableEntries: list
|
|
536
|
+
) -> pd.DataFrame:
|
|
537
|
+
'''Helper method to get and filter NIPA table data for summary.'''
|
|
538
|
+
import pandas as pd
|
|
539
|
+
|
|
540
|
+
# Query the NIPA table without indentation (get clean LineDescription)
|
|
541
|
+
readTable = self.NIPA(TableName=tableName, Frequency=frequency, Year=year, includeIndentations=False)
|
|
542
|
+
readTable = readTable.reset_index()
|
|
543
|
+
|
|
544
|
+
# Create restriction DataFrame
|
|
545
|
+
restrict = pd.DataFrame(tableEntries)
|
|
546
|
+
|
|
547
|
+
# Merge to get only the rows we need
|
|
548
|
+
output = pd.merge(restrict, readTable, on='SeriesCode', how='left')
|
|
549
|
+
|
|
550
|
+
# Sometimes there are two lines with the same SeriesCode on readTable
|
|
551
|
+
# (Account 5 - Foreeign Transactions Current Account
|
|
552
|
+
# Balance on Current account: A124RC)
|
|
553
|
+
# this leads to sequential duplication of rows - removing them here
|
|
554
|
+
output = output.loc[output['SeriesCode'].shift() != output['SeriesCode']]
|
|
555
|
+
|
|
556
|
+
# Apply indentation from config by adding dashes to LineDescription
|
|
557
|
+
output['LineDescription'] = output.apply(
|
|
558
|
+
lambda x: '-' * int(x['indentation']) + str(x.get('LineDescription', '')),
|
|
559
|
+
axis=1
|
|
560
|
+
)
|
|
561
|
+
|
|
562
|
+
# Clean up columns
|
|
563
|
+
output = output.drop(['LineNumber'], axis=1, errors='ignore')
|
|
564
|
+
|
|
565
|
+
# Set index for sorting
|
|
566
|
+
output = output.set_index(['LineDescription', 'SeriesCode'])
|
|
567
|
+
output = output.reset_index()
|
|
568
|
+
|
|
569
|
+
return output
|
|
570
|
+
|
|
571
|
+
# =========================================================================
|
|
572
|
+
# Utility methods
|
|
573
|
+
# =========================================================================
|
|
574
|
+
|
|
575
|
+
def _clipcode(self) -> None:
|
|
576
|
+
'''Copy the last query's code to clipboard.'''
|
|
577
|
+
if self._lastCalledDriver and hasattr(self._lastCalledDriver, '_ETDB'):
|
|
578
|
+
from datapungibea.codegen import transformIncludeCodeSnippet
|
|
579
|
+
codegen = transformIncludeCodeSnippet()
|
|
580
|
+
codegen.clipcode(self._lastCalledDriver._lastLoad)
|
|
581
|
+
else:
|
|
582
|
+
logger.warning('Get data using a driver first, e.g., data.NIPA("T10101", verbose=True)')
|
|
583
|
+
|
|
584
|
+
def _docDriver(self, driverName: str) -> str:
|
|
585
|
+
'''Get documentation for a specific driver.'''
|
|
586
|
+
driver_map = {
|
|
587
|
+
'NIPA': self._nipa_driver,
|
|
588
|
+
'fixedAssets': self._national_accounts_driver,
|
|
589
|
+
'ITA': self._international_transactions_driver,
|
|
590
|
+
'IIP': self._international_investment_driver,
|
|
591
|
+
'GDPbyIndustry': self._industry_driver,
|
|
592
|
+
'Regional': self._regional_driver,
|
|
593
|
+
'InputOutput': self._input_output_driver,
|
|
594
|
+
'IntlServTrade': self._international_services_driver,
|
|
595
|
+
'MNE': self._multinational_driver,
|
|
596
|
+
}
|
|
597
|
+
|
|
598
|
+
if driverName in driver_map:
|
|
599
|
+
return str(driver_map[driverName])
|
|
600
|
+
else:
|
|
601
|
+
return f"Driver '{driverName}' not found. Available: {list(driver_map.keys())}"
|
|
602
|
+
|
|
603
|
+
|
|
604
|
+
if __name__ == '__main__':
|
|
605
|
+
# Test basic functionality
|
|
606
|
+
import sys
|
|
607
|
+
logging.basicConfig(level=logging.INFO)
|
|
608
|
+
|
|
609
|
+
try:
|
|
610
|
+
d = data()
|
|
611
|
+
print(d)
|
|
612
|
+
print("\nTrying datasetlist...")
|
|
613
|
+
print(d.datasetlist())
|
|
614
|
+
except Exception as e:
|
|
615
|
+
print(f"Error: {e}")
|
|
616
|
+
sys.exit(1)
|