kensho-kfinance 3.0.2__py3-none-any.whl → 3.1.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.
Potentially problematic release.
This version of kensho-kfinance might be problematic. Click here for more details.
- {kensho_kfinance-3.0.2.dist-info → kensho_kfinance-3.1.0.dist-info}/METADATA +1 -1
- {kensho_kfinance-3.0.2.dist-info → kensho_kfinance-3.1.0.dist-info}/RECORD +46 -45
- kfinance/CHANGELOG.md +7 -1
- kfinance/client/fetch.py +29 -19
- kfinance/client/kfinance.py +17 -18
- kfinance/client/meta_classes.py +12 -13
- kfinance/client/tests/test_fetch.py +51 -34
- kfinance/client/tests/test_objects.py +130 -129
- kfinance/conftest.py +49 -5
- kfinance/domains/business_relationships/business_relationship_tools.py +30 -19
- kfinance/domains/business_relationships/tests/test_business_relationship_tools.py +18 -15
- kfinance/domains/capitalizations/capitalization_models.py +1 -1
- kfinance/domains/capitalizations/capitalization_tools.py +41 -24
- kfinance/domains/capitalizations/tests/test_capitalization_tools.py +38 -13
- kfinance/domains/companies/company_identifiers.py +0 -175
- kfinance/domains/companies/company_models.py +98 -5
- kfinance/domains/companies/company_tools.py +33 -29
- kfinance/domains/companies/tests/test_company_tools.py +11 -4
- kfinance/domains/competitors/competitor_tools.py +21 -21
- kfinance/domains/competitors/tests/test_competitor_tools.py +21 -7
- kfinance/domains/cusip_and_isin/cusip_and_isin_tools.py +38 -26
- kfinance/domains/cusip_and_isin/tests/test_cusip_and_isin_tools.py +27 -26
- kfinance/domains/earnings/earning_tools.py +54 -47
- kfinance/domains/earnings/tests/test_earnings_tools.py +58 -63
- kfinance/domains/line_items/line_item_models.py +7 -0
- kfinance/domains/line_items/line_item_tools.py +31 -30
- kfinance/domains/line_items/tests/test_line_item_tools.py +23 -5
- kfinance/domains/mergers_and_acquisitions/merger_and_acquisition_models.py +15 -0
- kfinance/domains/mergers_and_acquisitions/merger_and_acquisition_tools.py +55 -38
- kfinance/domains/mergers_and_acquisitions/tests/test_merger_and_acquisition_tools.py +22 -9
- kfinance/domains/prices/price_models.py +9 -9
- kfinance/domains/prices/price_tools.py +49 -38
- kfinance/domains/prices/tests/test_price_tools.py +52 -36
- kfinance/domains/segments/segment_models.py +7 -0
- kfinance/domains/segments/segment_tools.py +37 -20
- kfinance/domains/segments/tests/test_segment_tools.py +13 -6
- kfinance/domains/statements/statement_models.py +7 -0
- kfinance/domains/statements/statement_tools.py +38 -40
- kfinance/domains/statements/tests/test_statement_tools.py +39 -10
- kfinance/integrations/tool_calling/tests/test_tool_calling_models.py +2 -2
- kfinance/integrations/tool_calling/tool_calling_models.py +24 -5
- kfinance/version.py +2 -2
- {kensho_kfinance-3.0.2.dist-info → kensho_kfinance-3.1.0.dist-info}/WHEEL +0 -0
- {kensho_kfinance-3.0.2.dist-info → kensho_kfinance-3.1.0.dist-info}/licenses/AUTHORS.md +0 -0
- {kensho_kfinance-3.0.2.dist-info → kensho_kfinance-3.1.0.dist-info}/licenses/LICENSE +0 -0
- {kensho_kfinance-3.0.2.dist-info → kensho_kfinance-3.1.0.dist-info}/top_level.txt +0 -0
|
@@ -26,27 +26,34 @@ from kfinance.domains.business_relationships.business_relationship_models import
|
|
|
26
26
|
RelationshipResponse,
|
|
27
27
|
)
|
|
28
28
|
from kfinance.domains.capitalizations.capitalization_models import Capitalizations
|
|
29
|
-
from kfinance.domains.companies.company_models import CompanyIdAndName
|
|
29
|
+
from kfinance.domains.companies.company_models import CompanyIdAndName, IdentificationTriple
|
|
30
30
|
from kfinance.domains.earnings.earning_models import EarningsCallResp
|
|
31
|
+
from kfinance.domains.line_items.line_item_models import LineItemResponse
|
|
32
|
+
from kfinance.domains.mergers_and_acquisitions.merger_and_acquisition_models import MergersResp
|
|
33
|
+
from kfinance.domains.prices.price_models import HistoryMetadataResp
|
|
34
|
+
from kfinance.domains.segments.segment_models import SegmentsResp
|
|
35
|
+
from kfinance.domains.statements.statement_models import StatementsResp
|
|
31
36
|
|
|
32
37
|
|
|
33
|
-
msft_company_id =
|
|
34
|
-
msft_security_id =
|
|
38
|
+
msft_company_id = 21835
|
|
39
|
+
msft_security_id = 2630412
|
|
35
40
|
msft_isin = "US5949181045"
|
|
36
41
|
msft_cusip = "594918104"
|
|
37
|
-
msft_trading_item_id =
|
|
42
|
+
msft_trading_item_id = 2630413
|
|
38
43
|
msft_buys_mongo = "517414"
|
|
39
44
|
|
|
40
45
|
|
|
41
46
|
MOCK_TRADING_ITEM_DB = {
|
|
42
47
|
msft_trading_item_id: {
|
|
43
|
-
"metadata":
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
48
|
+
"metadata": HistoryMetadataResp.model_validate(
|
|
49
|
+
{
|
|
50
|
+
"currency": "USD",
|
|
51
|
+
"symbol": "MSFT",
|
|
52
|
+
"exchange_name": "NasdaqGS",
|
|
53
|
+
"instrument_type": "Equity",
|
|
54
|
+
"first_trade_date": "1986-03-13",
|
|
55
|
+
}
|
|
56
|
+
),
|
|
50
57
|
"price_chart": {
|
|
51
58
|
"2020-01-01": {
|
|
52
59
|
"2021-01-01": b"\x89PNG\r\n\x1a\n\x00\x00\x00\rIHDR\x00\x00\x00\x01\x00\x00\x00\x01\x08\x02\x00\x00\x00\x90wS\xde\x00\x00\x00\x0cIDATx\x9cc\xf8\xcf\xc0\x00\x00\x03"
|
|
@@ -95,78 +102,39 @@ MOCK_COMPANY_DB = {
|
|
|
95
102
|
]
|
|
96
103
|
}
|
|
97
104
|
),
|
|
98
|
-
"
|
|
99
|
-
"
|
|
100
|
-
|
|
101
|
-
"
|
|
102
|
-
"
|
|
103
|
-
"
|
|
105
|
+
"line_items": {
|
|
106
|
+
"revenue": LineItemResponse.model_validate(
|
|
107
|
+
{
|
|
108
|
+
"line_item": {
|
|
109
|
+
"2019": "125843000000.000000",
|
|
110
|
+
"2020": "143015000000.000000",
|
|
111
|
+
"2021": "168088000000.000000",
|
|
112
|
+
"2022": "198270000000.000000",
|
|
113
|
+
"2023": "211915000000.000000",
|
|
104
114
|
}
|
|
105
115
|
}
|
|
106
|
-
|
|
116
|
+
)
|
|
107
117
|
},
|
|
108
|
-
"
|
|
109
|
-
|
|
110
|
-
"
|
|
111
|
-
"
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
118
|
+
"segments": SegmentsResp.model_validate(
|
|
119
|
+
{
|
|
120
|
+
"segments": {
|
|
121
|
+
"2024": {
|
|
122
|
+
"Intelligent Cloud": {
|
|
123
|
+
"Operating Income": 49584000000.0,
|
|
124
|
+
"Revenue": 105362000000.0,
|
|
125
|
+
},
|
|
126
|
+
"More Personal Computing": {
|
|
127
|
+
"Operating Income": 19309000000.0,
|
|
128
|
+
"Revenue": 62032000000.0,
|
|
129
|
+
},
|
|
130
|
+
"Productivity and Business Processes": {
|
|
131
|
+
"Operating Income": 40540000000.0,
|
|
132
|
+
"Revenue": 77728000000.0,
|
|
133
|
+
},
|
|
134
|
+
}
|
|
116
135
|
}
|
|
117
136
|
}
|
|
118
|
-
|
|
119
|
-
"segments": {
|
|
120
|
-
"2024": {
|
|
121
|
-
"Intelligent Cloud": {"Operating Income": 49584000000.0, "Revenue": 105362000000.0},
|
|
122
|
-
"More Personal Computing": {
|
|
123
|
-
"Operating Income": 19309000000.0,
|
|
124
|
-
"Revenue": 62032000000.0,
|
|
125
|
-
},
|
|
126
|
-
"Productivity and Business Processes": {
|
|
127
|
-
"Operating Income": 40540000000.0,
|
|
128
|
-
"Revenue": 77728000000.0,
|
|
129
|
-
},
|
|
130
|
-
}
|
|
131
|
-
},
|
|
132
|
-
"mergers": {
|
|
133
|
-
"target": [
|
|
134
|
-
{
|
|
135
|
-
"transaction_id": 10998717,
|
|
136
|
-
"merger_title": "Closed M/A of Microsoft Corporation",
|
|
137
|
-
"closed_date": "2021-01-01",
|
|
138
|
-
},
|
|
139
|
-
{
|
|
140
|
-
"transaction_id": 28237969,
|
|
141
|
-
"merger_title": "Closed M/A of Microsoft Corporation",
|
|
142
|
-
"closed_date": "2022-01-01",
|
|
143
|
-
},
|
|
144
|
-
],
|
|
145
|
-
"buyer": [
|
|
146
|
-
{
|
|
147
|
-
"transaction_id": 517414,
|
|
148
|
-
"merger_title": "Closed M/A of MongoMusic, Inc.",
|
|
149
|
-
"closed_date": "2023-01-01",
|
|
150
|
-
},
|
|
151
|
-
{
|
|
152
|
-
"transaction_id": 596722,
|
|
153
|
-
"merger_title": "Closed M/A of Digital Anvil, Inc.",
|
|
154
|
-
"closed_date": "2023-01-01",
|
|
155
|
-
},
|
|
156
|
-
],
|
|
157
|
-
"seller": [
|
|
158
|
-
{
|
|
159
|
-
"transaction_id": 455551,
|
|
160
|
-
"merger_title": "Closed M/A of VacationSpot.com, Inc.",
|
|
161
|
-
"closed_date": "2024-01-01",
|
|
162
|
-
},
|
|
163
|
-
{
|
|
164
|
-
"transaction_id": 456045,
|
|
165
|
-
"merger_title": "Closed M/A of TransPoint, LLC",
|
|
166
|
-
"closed_date": "2025-01-01",
|
|
167
|
-
},
|
|
168
|
-
],
|
|
169
|
-
},
|
|
137
|
+
),
|
|
170
138
|
"advisors": {
|
|
171
139
|
msft_buys_mongo: {
|
|
172
140
|
"advisors": [
|
|
@@ -187,7 +155,6 @@ MOCK_COMPANY_DB = {
|
|
|
187
155
|
),
|
|
188
156
|
},
|
|
189
157
|
31696: {"info": {"name": "MongoMusic, Inc."}},
|
|
190
|
-
21835: {"info": {"name": "Microsoft Corporation"}},
|
|
191
158
|
18805: {"info": {"name": "Angel Investors L.P."}},
|
|
192
159
|
20087: {"info": {"name": "Draper Richards, L.P."}},
|
|
193
160
|
22103: {"info": {"name": "BRV Partners, LLC"}},
|
|
@@ -222,33 +189,69 @@ MOCK_TRANSCRIPT_DB = {
|
|
|
222
189
|
},
|
|
223
190
|
}
|
|
224
191
|
|
|
192
|
+
INCOME_STATEMENT = StatementsResp.model_validate(
|
|
193
|
+
{
|
|
194
|
+
"statements": {
|
|
195
|
+
"2019": {
|
|
196
|
+
"Revenues": "125843000000.000000",
|
|
197
|
+
"Total Revenues": "125843000000.000000",
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
)
|
|
202
|
+
|
|
203
|
+
MERGERS_RESP = MergersResp.model_validate(
|
|
204
|
+
{
|
|
205
|
+
"target": [
|
|
206
|
+
{
|
|
207
|
+
"transaction_id": 10998717,
|
|
208
|
+
"merger_title": "Closed M/A of Microsoft Corporation",
|
|
209
|
+
"closed_date": "2021-01-01",
|
|
210
|
+
},
|
|
211
|
+
{
|
|
212
|
+
"transaction_id": 28237969,
|
|
213
|
+
"merger_title": "Closed M/A of Microsoft Corporation",
|
|
214
|
+
"closed_date": "2022-01-01",
|
|
215
|
+
},
|
|
216
|
+
],
|
|
217
|
+
"buyer": [
|
|
218
|
+
{
|
|
219
|
+
"transaction_id": 517414,
|
|
220
|
+
"merger_title": "Closed M/A of MongoMusic, Inc.",
|
|
221
|
+
"closed_date": "2023-01-01",
|
|
222
|
+
},
|
|
223
|
+
{
|
|
224
|
+
"transaction_id": 596722,
|
|
225
|
+
"merger_title": "Closed M/A of Digital Anvil, Inc.",
|
|
226
|
+
"closed_date": "2023-01-01",
|
|
227
|
+
},
|
|
228
|
+
],
|
|
229
|
+
"seller": [
|
|
230
|
+
{
|
|
231
|
+
"transaction_id": 455551,
|
|
232
|
+
"merger_title": "Closed M/A of VacationSpot.com, Inc.",
|
|
233
|
+
"closed_date": "2024-01-01",
|
|
234
|
+
},
|
|
235
|
+
{
|
|
236
|
+
"transaction_id": 456045,
|
|
237
|
+
"merger_title": "Closed M/A of TransPoint, LLC",
|
|
238
|
+
"closed_date": "2025-01-01",
|
|
239
|
+
},
|
|
240
|
+
],
|
|
241
|
+
}
|
|
242
|
+
)
|
|
225
243
|
|
|
226
244
|
MOCK_SECURITY_DB = {msft_security_id: {"isin": msft_isin, "cusip": msft_cusip}}
|
|
227
245
|
|
|
246
|
+
msft_id_triple = IdentificationTriple(
|
|
247
|
+
company_id=msft_company_id, security_id=msft_security_id, trading_item_id=msft_trading_item_id
|
|
248
|
+
)
|
|
228
249
|
|
|
229
|
-
MOCK_TICKER_DB = {
|
|
230
|
-
"MSFT": {
|
|
231
|
-
"company_id": msft_company_id,
|
|
232
|
-
"security_id": msft_security_id,
|
|
233
|
-
"trading_item_id": msft_trading_item_id,
|
|
234
|
-
}
|
|
235
|
-
}
|
|
250
|
+
MOCK_TICKER_DB = {"MSFT": msft_id_triple.model_dump(mode="json")}
|
|
236
251
|
|
|
237
|
-
MOCK_ISIN_DB = {
|
|
238
|
-
msft_isin: {
|
|
239
|
-
"company_id": msft_company_id,
|
|
240
|
-
"security_id": msft_security_id,
|
|
241
|
-
"trading_item_id": msft_trading_item_id,
|
|
242
|
-
}
|
|
243
|
-
}
|
|
252
|
+
MOCK_ISIN_DB = {msft_isin: msft_id_triple.model_dump(mode="json")}
|
|
244
253
|
|
|
245
|
-
MOCK_CUSIP_DB = {
|
|
246
|
-
msft_cusip: {
|
|
247
|
-
"company_id": msft_company_id,
|
|
248
|
-
"security_id": msft_security_id,
|
|
249
|
-
"trading_item_id": msft_trading_item_id,
|
|
250
|
-
}
|
|
251
|
-
}
|
|
254
|
+
MOCK_CUSIP_DB = {msft_cusip: msft_id_triple.model_dump(mode="json")}
|
|
252
255
|
|
|
253
256
|
MOCK_MERGERS_DB = {
|
|
254
257
|
msft_buys_mongo: {
|
|
@@ -347,7 +350,7 @@ class MockKFinanceApiClient:
|
|
|
347
350
|
end_quarter,
|
|
348
351
|
):
|
|
349
352
|
"""Get a statement"""
|
|
350
|
-
return
|
|
353
|
+
return INCOME_STATEMENT
|
|
351
354
|
|
|
352
355
|
def fetch_line_item(
|
|
353
356
|
self, company_id, line_item, period_type, start_year, end_year, start_quarter, end_quarter
|
|
@@ -392,7 +395,7 @@ class MockKFinanceApiClient:
|
|
|
392
395
|
end_quarter,
|
|
393
396
|
):
|
|
394
397
|
"""Get a segment"""
|
|
395
|
-
return MOCK_COMPANY_DB[company_id]
|
|
398
|
+
return MOCK_COMPANY_DB[company_id]["segments"]
|
|
396
399
|
|
|
397
400
|
def fetch_companies_from_business_relationship(
|
|
398
401
|
self, company_id: int, relationship_type: BusinessRelationshipType
|
|
@@ -408,7 +411,7 @@ class MockKFinanceApiClient:
|
|
|
408
411
|
return MOCK_TRANSCRIPT_DB[key_dev_id]
|
|
409
412
|
|
|
410
413
|
def fetch_mergers_for_company(self, company_id):
|
|
411
|
-
return copy.deepcopy(
|
|
414
|
+
return copy.deepcopy(MERGERS_RESP)
|
|
412
415
|
|
|
413
416
|
def fetch_merger_info(self, transaction_id: int):
|
|
414
417
|
return copy.deepcopy(MOCK_MERGERS_DB[str(transaction_id)])
|
|
@@ -421,14 +424,16 @@ class TestTradingItem(TestCase):
|
|
|
421
424
|
def setUp(self):
|
|
422
425
|
"""setup tests"""
|
|
423
426
|
self.kfinance_api_client = MockKFinanceApiClient()
|
|
424
|
-
self.msft_trading_item_from_id = TradingItem(
|
|
427
|
+
self.msft_trading_item_from_id = TradingItem(
|
|
428
|
+
self.kfinance_api_client, int(msft_trading_item_id)
|
|
429
|
+
)
|
|
425
430
|
self.msft_trading_item_from_ticker = TradingItem.from_ticker(
|
|
426
431
|
self.kfinance_api_client, "MSFT"
|
|
427
432
|
)
|
|
428
433
|
|
|
429
434
|
def test_trading_item_id(self) -> None:
|
|
430
435
|
"""test trading item id"""
|
|
431
|
-
expected_trading_item_id =
|
|
436
|
+
expected_trading_item_id = int(msft_trading_item_id)
|
|
432
437
|
trading_item_id = self.msft_trading_item_from_id.trading_item_id
|
|
433
438
|
self.assertEqual(expected_trading_item_id, trading_item_id)
|
|
434
439
|
|
|
@@ -437,18 +442,11 @@ class TestTradingItem(TestCase):
|
|
|
437
442
|
|
|
438
443
|
def test_history_metadata(self) -> None:
|
|
439
444
|
"""test history metadata"""
|
|
440
|
-
expected_history_metadata = MOCK_TRADING_ITEM_DB[msft_trading_item_id][
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
).date()
|
|
444
|
-
expected_exchange_code = "NasdaqGS"
|
|
445
|
+
expected_history_metadata: HistoryMetadataResp = MOCK_TRADING_ITEM_DB[msft_trading_item_id][
|
|
446
|
+
"metadata"
|
|
447
|
+
].copy()
|
|
445
448
|
history_metadata = self.msft_trading_item_from_id.history_metadata
|
|
446
|
-
|
|
447
|
-
self.assertEqual(expected_exchange_code, self.msft_trading_item_from_id.exchange_code)
|
|
448
|
-
|
|
449
|
-
history_metadata = self.msft_trading_item_from_ticker.history_metadata
|
|
450
|
-
self.assertEqual(expected_history_metadata, history_metadata)
|
|
451
|
-
self.assertEqual(expected_exchange_code, self.msft_trading_item_from_ticker.exchange_code)
|
|
449
|
+
assert history_metadata == expected_history_metadata
|
|
452
450
|
|
|
453
451
|
def test_price_chart(self):
|
|
454
452
|
"""test price chart"""
|
|
@@ -522,19 +520,21 @@ class TestCompany(TestCase):
|
|
|
522
520
|
def test_income_statement(self) -> None:
|
|
523
521
|
"""test income statement"""
|
|
524
522
|
expected_income_statement = (
|
|
525
|
-
pd.DataFrame(
|
|
526
|
-
MOCK_COMPANY_DB[msft_company_id]["statements"]["income_statement"]["statements"]
|
|
527
|
-
)
|
|
523
|
+
pd.DataFrame(INCOME_STATEMENT.model_dump(mode="json")["statements"])
|
|
528
524
|
.apply(pd.to_numeric)
|
|
529
525
|
.replace(np.nan, None)
|
|
530
526
|
)
|
|
527
|
+
|
|
531
528
|
income_statement = self.msft_company.company.income_statement()
|
|
532
529
|
pd.testing.assert_frame_equal(expected_income_statement, income_statement)
|
|
533
530
|
|
|
534
531
|
def test_revenue(self) -> None:
|
|
535
532
|
"""test revenue"""
|
|
533
|
+
line_item_response: LineItemResponse = MOCK_COMPANY_DB[msft_company_id]["line_items"][
|
|
534
|
+
"revenue"
|
|
535
|
+
]
|
|
536
536
|
expected_revenue = (
|
|
537
|
-
pd.DataFrame(
|
|
537
|
+
pd.DataFrame({"line_item": line_item_response.line_item})
|
|
538
538
|
.transpose()
|
|
539
539
|
.apply(pd.to_numeric)
|
|
540
540
|
.replace(np.nan, None)
|
|
@@ -545,7 +545,9 @@ class TestCompany(TestCase):
|
|
|
545
545
|
|
|
546
546
|
def test_business_segments(self) -> None:
|
|
547
547
|
"""test business statement"""
|
|
548
|
-
expected_segments = MOCK_COMPANY_DB[msft_company_id]["segments"]
|
|
548
|
+
expected_segments = MOCK_COMPANY_DB[msft_company_id]["segments"].model_dump(mode="json")[
|
|
549
|
+
"segments"
|
|
550
|
+
]
|
|
549
551
|
|
|
550
552
|
business_segment = self.msft_company.company.business_segments()
|
|
551
553
|
self.assertEqual(expected_segments, business_segment)
|
|
@@ -577,7 +579,7 @@ class TestCompany(TestCase):
|
|
|
577
579
|
self.assertEqual(suppliers_via_property, suppliers_via_method)
|
|
578
580
|
|
|
579
581
|
def test_mergers(self) -> None:
|
|
580
|
-
expected_mergers =
|
|
582
|
+
expected_mergers = MERGERS_RESP.model_dump(mode="json")
|
|
581
583
|
mergers = self.msft_company.company.mergers_and_acquisitions
|
|
582
584
|
mergers_json = {
|
|
583
585
|
"target": [
|
|
@@ -724,9 +726,6 @@ class TestTicker(TestCase):
|
|
|
724
726
|
def test_history_metadata(self) -> None:
|
|
725
727
|
"""test history metadata"""
|
|
726
728
|
expected_history_metadata = MOCK_TRADING_ITEM_DB[msft_trading_item_id]["metadata"].copy()
|
|
727
|
-
expected_history_metadata["first_trade_date"] = datetime.strptime(
|
|
728
|
-
expected_history_metadata["first_trade_date"], "%Y-%m-%d"
|
|
729
|
-
).date()
|
|
730
729
|
history_metadata = self.msft_ticker_from_ticker.history_metadata
|
|
731
730
|
expected_exchange_code = "NasdaqGS"
|
|
732
731
|
self.assertEqual(expected_history_metadata, history_metadata)
|
|
@@ -842,12 +841,11 @@ class TestTicker(TestCase):
|
|
|
842
841
|
def test_income_statement(self) -> None:
|
|
843
842
|
"""test income statement"""
|
|
844
843
|
expected_income_statement = (
|
|
845
|
-
pd.DataFrame(
|
|
846
|
-
MOCK_COMPANY_DB[msft_company_id]["statements"]["income_statement"]["statements"]
|
|
847
|
-
)
|
|
844
|
+
pd.DataFrame(INCOME_STATEMENT.model_dump(mode="json")["statements"])
|
|
848
845
|
.apply(pd.to_numeric)
|
|
849
846
|
.replace(np.nan, None)
|
|
850
847
|
)
|
|
848
|
+
|
|
851
849
|
income_statement = self.msft_ticker_from_ticker.income_statement()
|
|
852
850
|
pd.testing.assert_frame_equal(expected_income_statement, income_statement)
|
|
853
851
|
|
|
@@ -862,8 +860,11 @@ class TestTicker(TestCase):
|
|
|
862
860
|
|
|
863
861
|
def test_revenue(self) -> None:
|
|
864
862
|
"""test revenue"""
|
|
863
|
+
line_item_response: LineItemResponse = MOCK_COMPANY_DB[msft_company_id]["line_items"][
|
|
864
|
+
"revenue"
|
|
865
|
+
]
|
|
865
866
|
expected_revenue = (
|
|
866
|
-
pd.DataFrame(
|
|
867
|
+
pd.DataFrame({"line_item": line_item_response.line_item})
|
|
867
868
|
.transpose()
|
|
868
869
|
.apply(pd.to_numeric)
|
|
869
870
|
.replace(np.nan, None)
|
kfinance/conftest.py
CHANGED
|
@@ -21,13 +21,14 @@ def mock_client(requests_mock: Mocker) -> Client:
|
|
|
21
21
|
client.kfinance_api_client._access_token_expiry = int(datetime(2100, 1, 1).timestamp()) # noqa: SLF001
|
|
22
22
|
|
|
23
23
|
# Create a mock for the SPGI id triple.
|
|
24
|
+
spgi_id_triple = {
|
|
25
|
+
"trading_item_id": SPGI_TRADING_ITEM_ID,
|
|
26
|
+
"security_id": SPGI_SECURITY_ID,
|
|
27
|
+
"company_id": SPGI_COMPANY_ID,
|
|
28
|
+
}
|
|
24
29
|
requests_mock.get(
|
|
25
30
|
url="https://kfinance.kensho.com/api/v1/id/SPGI",
|
|
26
|
-
json=
|
|
27
|
-
"trading_item_id": SPGI_TRADING_ITEM_ID,
|
|
28
|
-
"security_id": SPGI_SECURITY_ID,
|
|
29
|
-
"company_id": SPGI_COMPANY_ID,
|
|
30
|
-
},
|
|
31
|
+
json=spgi_id_triple,
|
|
31
32
|
)
|
|
32
33
|
requests_mock.get(
|
|
33
34
|
url="https://kfinance.kensho.com/api/v1/id/MSFT",
|
|
@@ -45,4 +46,47 @@ def mock_client(requests_mock: Mocker) -> Client:
|
|
|
45
46
|
json={"primary_trading_item": company_id},
|
|
46
47
|
)
|
|
47
48
|
|
|
49
|
+
# Fetch SPGI
|
|
50
|
+
requests_mock.post(
|
|
51
|
+
url="https://kfinance.kensho.com/api/v1/ids",
|
|
52
|
+
additional_matcher=lambda req: req.json().get("identifiers") == ["SPGI"],
|
|
53
|
+
json={"data": {"SPGI": spgi_id_triple}},
|
|
54
|
+
)
|
|
55
|
+
|
|
56
|
+
# Fetch SPGI and a non-existent company (which will include an error)
|
|
57
|
+
requests_mock.post(
|
|
58
|
+
url="https://kfinance.kensho.com/api/v1/ids",
|
|
59
|
+
additional_matcher=lambda req: req.json().get("identifiers") == ["SPGI", "non-existent"],
|
|
60
|
+
json={
|
|
61
|
+
"data": {
|
|
62
|
+
"SPGI": spgi_id_triple,
|
|
63
|
+
"non-existent": {
|
|
64
|
+
"error": "No identification triple found for the provided identifier: NON-EXISTENT of type: ticker"
|
|
65
|
+
},
|
|
66
|
+
}
|
|
67
|
+
},
|
|
68
|
+
)
|
|
69
|
+
# Fetch SPGI and a private company (which will only have a company_id but no security or trading item id.)
|
|
70
|
+
requests_mock.post(
|
|
71
|
+
url="https://kfinance.kensho.com/api/v1/ids",
|
|
72
|
+
additional_matcher=lambda req: req.json().get("identifiers") == ["SPGI", "private_company"],
|
|
73
|
+
json={
|
|
74
|
+
"data": {
|
|
75
|
+
"SPGI": spgi_id_triple,
|
|
76
|
+
"private_company": {"company_id": 1, "security_id": None, "trading_item_id": None},
|
|
77
|
+
}
|
|
78
|
+
},
|
|
79
|
+
)
|
|
80
|
+
|
|
81
|
+
requests_mock.post(
|
|
82
|
+
url="https://kfinance.kensho.com/api/v1/ids",
|
|
83
|
+
additional_matcher=lambda req: req.json().get("identifiers") == ["C_1", "C_2"],
|
|
84
|
+
json={
|
|
85
|
+
"data": {
|
|
86
|
+
"C_1": {"company_id": 1, "security_id": 1, "trading_item_id": 1},
|
|
87
|
+
"C_2": {"company_id": 2, "security_id": 2, "trading_item_id": 2},
|
|
88
|
+
}
|
|
89
|
+
},
|
|
90
|
+
)
|
|
91
|
+
|
|
48
92
|
return client
|
|
@@ -7,10 +7,7 @@ from kfinance.client.batch_request_handling import Task, process_tasks_in_thread
|
|
|
7
7
|
from kfinance.client.permission_models import Permission
|
|
8
8
|
from kfinance.domains.business_relationships.business_relationship_models import (
|
|
9
9
|
BusinessRelationshipType,
|
|
10
|
-
|
|
11
|
-
from kfinance.domains.companies.company_identifiers import (
|
|
12
|
-
fetch_company_ids_from_identifiers,
|
|
13
|
-
parse_identifiers,
|
|
10
|
+
RelationshipResponse,
|
|
14
11
|
)
|
|
15
12
|
from kfinance.integrations.tool_calling.tool_calling_models import (
|
|
16
13
|
KfinanceTool,
|
|
@@ -23,6 +20,12 @@ class GetBusinessRelationshipFromIdentifiersArgs(ToolArgsWithIdentifiers):
|
|
|
23
20
|
business_relationship: BusinessRelationshipType
|
|
24
21
|
|
|
25
22
|
|
|
23
|
+
class GetBusinessRelationshipFromIdentifiersResp(BaseModel):
|
|
24
|
+
business_relationship: BusinessRelationshipType
|
|
25
|
+
results: dict[str, RelationshipResponse]
|
|
26
|
+
errors: list[str]
|
|
27
|
+
|
|
28
|
+
|
|
26
29
|
class GetBusinessRelationshipFromIdentifiers(KfinanceTool):
|
|
27
30
|
name: str = "get_business_relationship_from_identifiers"
|
|
28
31
|
description: str = dedent("""
|
|
@@ -39,36 +42,44 @@ class GetBusinessRelationshipFromIdentifiers(KfinanceTool):
|
|
|
39
42
|
"""Sample response:
|
|
40
43
|
|
|
41
44
|
{
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
45
|
+
'business_relationship': 'supplier',
|
|
46
|
+
'results': {
|
|
47
|
+
'SPGI': {
|
|
48
|
+
'current': [
|
|
49
|
+
{'company_id': 'C_883103', 'company_name': 'CRISIL Limited'}
|
|
50
|
+
],
|
|
51
|
+
'previous': [
|
|
52
|
+
{'company_id': 'C_472898', 'company_name': 'Morgan Stanley'},
|
|
53
|
+
{'company_id': 'C_8182358', 'company_name': 'Eloqua, Inc.'}
|
|
54
|
+
]
|
|
55
|
+
}
|
|
56
|
+
},
|
|
57
|
+
'errors': ['No identification triple found for the provided identifier: NON-EXISTENT of type: ticker']}
|
|
50
58
|
"""
|
|
51
59
|
|
|
52
60
|
api_client = self.kfinance_client.kfinance_api_client
|
|
53
|
-
|
|
54
|
-
identifiers_to_company_ids = fetch_company_ids_from_identifiers(
|
|
55
|
-
identifiers=parsed_identifiers, api_client=api_client
|
|
56
|
-
)
|
|
61
|
+
id_triple_resp = api_client.unified_fetch_id_triples(identifiers=identifiers)
|
|
57
62
|
|
|
58
63
|
tasks = [
|
|
59
64
|
Task(
|
|
60
65
|
func=api_client.fetch_companies_from_business_relationship,
|
|
61
66
|
kwargs=dict(
|
|
62
|
-
company_id=company_id,
|
|
67
|
+
company_id=id_triple.company_id,
|
|
63
68
|
relationship_type=business_relationship,
|
|
64
69
|
),
|
|
65
70
|
result_key=identifier,
|
|
66
71
|
)
|
|
67
|
-
for identifier,
|
|
72
|
+
for identifier, id_triple in id_triple_resp.identifiers_to_id_triples.items()
|
|
68
73
|
]
|
|
69
74
|
|
|
70
75
|
relationship_responses = process_tasks_in_thread_pool_executor(
|
|
71
76
|
api_client=api_client, tasks=tasks
|
|
72
77
|
)
|
|
73
78
|
|
|
74
|
-
|
|
79
|
+
output_model = GetBusinessRelationshipFromIdentifiersResp(
|
|
80
|
+
business_relationship=business_relationship,
|
|
81
|
+
results=relationship_responses,
|
|
82
|
+
errors=list(id_triple_resp.errors.values()),
|
|
83
|
+
)
|
|
84
|
+
|
|
85
|
+
return output_model.model_dump(mode="json")
|
|
@@ -17,8 +17,8 @@ class TestGetBusinessRelationshipFromIdentifiers:
|
|
|
17
17
|
):
|
|
18
18
|
"""
|
|
19
19
|
GIVEN the GetBusinessRelationshipFromIdentifiers tool
|
|
20
|
-
WHEN we request SPGI
|
|
21
|
-
THEN we get back the SPGI suppliers
|
|
20
|
+
WHEN we request suppliers for SPGI and a non-existent company
|
|
21
|
+
THEN we get back the SPGI suppliers and an error message
|
|
22
22
|
"""
|
|
23
23
|
supplier_resp = {
|
|
24
24
|
"current": [{"company_id": 883103, "company_name": "CRISIL Limited"}],
|
|
@@ -28,13 +28,19 @@ class TestGetBusinessRelationshipFromIdentifiers:
|
|
|
28
28
|
],
|
|
29
29
|
}
|
|
30
30
|
expected_result = {
|
|
31
|
-
"
|
|
32
|
-
|
|
33
|
-
"
|
|
34
|
-
{"company_id": "
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
31
|
+
"business_relationship": "supplier",
|
|
32
|
+
"results": {
|
|
33
|
+
"SPGI": {
|
|
34
|
+
"current": [{"company_id": "C_883103", "company_name": "CRISIL Limited"}],
|
|
35
|
+
"previous": [
|
|
36
|
+
{"company_id": "C_472898", "company_name": "Morgan Stanley"},
|
|
37
|
+
{"company_id": "C_8182358", "company_name": "Eloqua, Inc."},
|
|
38
|
+
],
|
|
39
|
+
}
|
|
40
|
+
},
|
|
41
|
+
"errors": [
|
|
42
|
+
"No identification triple found for the provided identifier: NON-EXISTENT of type: ticker"
|
|
43
|
+
],
|
|
38
44
|
}
|
|
39
45
|
|
|
40
46
|
requests_mock.get(
|
|
@@ -44,12 +50,9 @@ class TestGetBusinessRelationshipFromIdentifiers:
|
|
|
44
50
|
|
|
45
51
|
tool = GetBusinessRelationshipFromIdentifiers(kfinance_client=mock_client)
|
|
46
52
|
args = GetBusinessRelationshipFromIdentifiersArgs(
|
|
47
|
-
identifiers=["SPGI"],
|
|
53
|
+
identifiers=["SPGI", "non-existent"],
|
|
54
|
+
business_relationship=BusinessRelationshipType.supplier,
|
|
48
55
|
)
|
|
49
56
|
resp = tool.run(args.model_dump(mode="json"))
|
|
50
|
-
|
|
51
|
-
assert list(resp.keys()) == ["SPGI"]
|
|
52
|
-
# Sort companies by ID to make the result deterministic
|
|
53
|
-
resp["SPGI"]["current"].sort(key=lambda x: x["company_id"])
|
|
54
|
-
resp["SPGI"]["previous"].sort(key=lambda x: x["company_id"])
|
|
57
|
+
resp["results"]["SPGI"]["previous"].sort(key=lambda x: x["company_id"])
|
|
55
58
|
assert resp == expected_result
|
|
@@ -88,7 +88,7 @@ class Capitalizations(BaseModel):
|
|
|
88
88
|
mode="json",
|
|
89
89
|
include={ # type: ignore[arg-type]
|
|
90
90
|
"capitalizations": {
|
|
91
|
-
|
|
91
|
+
-1 if only_include_most_recent_value else "__all__": {
|
|
92
92
|
"date",
|
|
93
93
|
capitalization_metric.value,
|
|
94
94
|
}
|