kensho-kfinance 3.0.3__py3-none-any.whl → 3.1.1__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.3.dist-info → kensho_kfinance-3.1.1.dist-info}/METADATA +1 -1
- {kensho_kfinance-3.0.3.dist-info → kensho_kfinance-3.1.1.dist-info}/RECORD +46 -45
- kfinance/CHANGELOG.md +6 -0
- kfinance/client/fetch.py +26 -17
- kfinance/client/kfinance.py +17 -18
- kfinance/client/meta_classes.py +2 -2
- kfinance/client/tests/test_fetch.py +36 -24
- kfinance/client/tests/test_objects.py +112 -120
- 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_tools.py +29 -36
- 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/prompts.py +21 -14
- 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 +16 -3
- {kensho_kfinance-3.0.3.dist-info → kensho_kfinance-3.1.1.dist-info}/WHEEL +0 -0
- {kensho_kfinance-3.0.3.dist-info → kensho_kfinance-3.1.1.dist-info}/licenses/AUTHORS.md +0 -0
- {kensho_kfinance-3.0.3.dist-info → kensho_kfinance-3.1.1.dist-info}/licenses/LICENSE +0 -0
- {kensho_kfinance-3.0.3.dist-info → kensho_kfinance-3.1.1.dist-info}/top_level.txt +0 -0
|
@@ -26,28 +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
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
|
|
32
36
|
|
|
33
37
|
|
|
34
|
-
msft_company_id =
|
|
35
|
-
msft_security_id =
|
|
38
|
+
msft_company_id = 21835
|
|
39
|
+
msft_security_id = 2630412
|
|
36
40
|
msft_isin = "US5949181045"
|
|
37
41
|
msft_cusip = "594918104"
|
|
38
|
-
msft_trading_item_id =
|
|
42
|
+
msft_trading_item_id = 2630413
|
|
39
43
|
msft_buys_mongo = "517414"
|
|
40
44
|
|
|
41
45
|
|
|
42
46
|
MOCK_TRADING_ITEM_DB = {
|
|
43
47
|
msft_trading_item_id: {
|
|
44
|
-
"metadata":
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
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
|
+
),
|
|
51
57
|
"price_chart": {
|
|
52
58
|
"2020-01-01": {
|
|
53
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"
|
|
@@ -96,16 +102,6 @@ MOCK_COMPANY_DB = {
|
|
|
96
102
|
]
|
|
97
103
|
}
|
|
98
104
|
),
|
|
99
|
-
"statements": {
|
|
100
|
-
"income_statement": {
|
|
101
|
-
"statements": {
|
|
102
|
-
"2019": {
|
|
103
|
-
"Revenues": "125843000000.000000",
|
|
104
|
-
"Total Revenues": "125843000000.000000",
|
|
105
|
-
}
|
|
106
|
-
}
|
|
107
|
-
}
|
|
108
|
-
},
|
|
109
105
|
"line_items": {
|
|
110
106
|
"revenue": LineItemResponse.model_validate(
|
|
111
107
|
{
|
|
@@ -119,57 +115,26 @@ MOCK_COMPANY_DB = {
|
|
|
119
115
|
}
|
|
120
116
|
)
|
|
121
117
|
},
|
|
122
|
-
"segments":
|
|
123
|
-
|
|
124
|
-
"
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
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
|
+
}
|
|
135
|
+
}
|
|
133
136
|
}
|
|
134
|
-
|
|
135
|
-
"mergers": {
|
|
136
|
-
"target": [
|
|
137
|
-
{
|
|
138
|
-
"transaction_id": 10998717,
|
|
139
|
-
"merger_title": "Closed M/A of Microsoft Corporation",
|
|
140
|
-
"closed_date": "2021-01-01",
|
|
141
|
-
},
|
|
142
|
-
{
|
|
143
|
-
"transaction_id": 28237969,
|
|
144
|
-
"merger_title": "Closed M/A of Microsoft Corporation",
|
|
145
|
-
"closed_date": "2022-01-01",
|
|
146
|
-
},
|
|
147
|
-
],
|
|
148
|
-
"buyer": [
|
|
149
|
-
{
|
|
150
|
-
"transaction_id": 517414,
|
|
151
|
-
"merger_title": "Closed M/A of MongoMusic, Inc.",
|
|
152
|
-
"closed_date": "2023-01-01",
|
|
153
|
-
},
|
|
154
|
-
{
|
|
155
|
-
"transaction_id": 596722,
|
|
156
|
-
"merger_title": "Closed M/A of Digital Anvil, Inc.",
|
|
157
|
-
"closed_date": "2023-01-01",
|
|
158
|
-
},
|
|
159
|
-
],
|
|
160
|
-
"seller": [
|
|
161
|
-
{
|
|
162
|
-
"transaction_id": 455551,
|
|
163
|
-
"merger_title": "Closed M/A of VacationSpot.com, Inc.",
|
|
164
|
-
"closed_date": "2024-01-01",
|
|
165
|
-
},
|
|
166
|
-
{
|
|
167
|
-
"transaction_id": 456045,
|
|
168
|
-
"merger_title": "Closed M/A of TransPoint, LLC",
|
|
169
|
-
"closed_date": "2025-01-01",
|
|
170
|
-
},
|
|
171
|
-
],
|
|
172
|
-
},
|
|
137
|
+
),
|
|
173
138
|
"advisors": {
|
|
174
139
|
msft_buys_mongo: {
|
|
175
140
|
"advisors": [
|
|
@@ -190,7 +155,6 @@ MOCK_COMPANY_DB = {
|
|
|
190
155
|
),
|
|
191
156
|
},
|
|
192
157
|
31696: {"info": {"name": "MongoMusic, Inc."}},
|
|
193
|
-
21835: {"info": {"name": "Microsoft Corporation"}},
|
|
194
158
|
18805: {"info": {"name": "Angel Investors L.P."}},
|
|
195
159
|
20087: {"info": {"name": "Draper Richards, L.P."}},
|
|
196
160
|
22103: {"info": {"name": "BRV Partners, LLC"}},
|
|
@@ -225,33 +189,69 @@ MOCK_TRANSCRIPT_DB = {
|
|
|
225
189
|
},
|
|
226
190
|
}
|
|
227
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
|
+
)
|
|
228
243
|
|
|
229
244
|
MOCK_SECURITY_DB = {msft_security_id: {"isin": msft_isin, "cusip": msft_cusip}}
|
|
230
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
|
+
)
|
|
231
249
|
|
|
232
|
-
MOCK_TICKER_DB = {
|
|
233
|
-
"MSFT": {
|
|
234
|
-
"company_id": msft_company_id,
|
|
235
|
-
"security_id": msft_security_id,
|
|
236
|
-
"trading_item_id": msft_trading_item_id,
|
|
237
|
-
}
|
|
238
|
-
}
|
|
250
|
+
MOCK_TICKER_DB = {"MSFT": msft_id_triple.model_dump(mode="json")}
|
|
239
251
|
|
|
240
|
-
MOCK_ISIN_DB = {
|
|
241
|
-
msft_isin: {
|
|
242
|
-
"company_id": msft_company_id,
|
|
243
|
-
"security_id": msft_security_id,
|
|
244
|
-
"trading_item_id": msft_trading_item_id,
|
|
245
|
-
}
|
|
246
|
-
}
|
|
252
|
+
MOCK_ISIN_DB = {msft_isin: msft_id_triple.model_dump(mode="json")}
|
|
247
253
|
|
|
248
|
-
MOCK_CUSIP_DB = {
|
|
249
|
-
msft_cusip: {
|
|
250
|
-
"company_id": msft_company_id,
|
|
251
|
-
"security_id": msft_security_id,
|
|
252
|
-
"trading_item_id": msft_trading_item_id,
|
|
253
|
-
}
|
|
254
|
-
}
|
|
254
|
+
MOCK_CUSIP_DB = {msft_cusip: msft_id_triple.model_dump(mode="json")}
|
|
255
255
|
|
|
256
256
|
MOCK_MERGERS_DB = {
|
|
257
257
|
msft_buys_mongo: {
|
|
@@ -350,7 +350,7 @@ class MockKFinanceApiClient:
|
|
|
350
350
|
end_quarter,
|
|
351
351
|
):
|
|
352
352
|
"""Get a statement"""
|
|
353
|
-
return
|
|
353
|
+
return INCOME_STATEMENT
|
|
354
354
|
|
|
355
355
|
def fetch_line_item(
|
|
356
356
|
self, company_id, line_item, period_type, start_year, end_year, start_quarter, end_quarter
|
|
@@ -395,7 +395,7 @@ class MockKFinanceApiClient:
|
|
|
395
395
|
end_quarter,
|
|
396
396
|
):
|
|
397
397
|
"""Get a segment"""
|
|
398
|
-
return MOCK_COMPANY_DB[company_id]
|
|
398
|
+
return MOCK_COMPANY_DB[company_id]["segments"]
|
|
399
399
|
|
|
400
400
|
def fetch_companies_from_business_relationship(
|
|
401
401
|
self, company_id: int, relationship_type: BusinessRelationshipType
|
|
@@ -411,7 +411,7 @@ class MockKFinanceApiClient:
|
|
|
411
411
|
return MOCK_TRANSCRIPT_DB[key_dev_id]
|
|
412
412
|
|
|
413
413
|
def fetch_mergers_for_company(self, company_id):
|
|
414
|
-
return copy.deepcopy(
|
|
414
|
+
return copy.deepcopy(MERGERS_RESP)
|
|
415
415
|
|
|
416
416
|
def fetch_merger_info(self, transaction_id: int):
|
|
417
417
|
return copy.deepcopy(MOCK_MERGERS_DB[str(transaction_id)])
|
|
@@ -424,14 +424,16 @@ class TestTradingItem(TestCase):
|
|
|
424
424
|
def setUp(self):
|
|
425
425
|
"""setup tests"""
|
|
426
426
|
self.kfinance_api_client = MockKFinanceApiClient()
|
|
427
|
-
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
|
+
)
|
|
428
430
|
self.msft_trading_item_from_ticker = TradingItem.from_ticker(
|
|
429
431
|
self.kfinance_api_client, "MSFT"
|
|
430
432
|
)
|
|
431
433
|
|
|
432
434
|
def test_trading_item_id(self) -> None:
|
|
433
435
|
"""test trading item id"""
|
|
434
|
-
expected_trading_item_id =
|
|
436
|
+
expected_trading_item_id = int(msft_trading_item_id)
|
|
435
437
|
trading_item_id = self.msft_trading_item_from_id.trading_item_id
|
|
436
438
|
self.assertEqual(expected_trading_item_id, trading_item_id)
|
|
437
439
|
|
|
@@ -440,18 +442,11 @@ class TestTradingItem(TestCase):
|
|
|
440
442
|
|
|
441
443
|
def test_history_metadata(self) -> None:
|
|
442
444
|
"""test history metadata"""
|
|
443
|
-
expected_history_metadata = MOCK_TRADING_ITEM_DB[msft_trading_item_id][
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
).date()
|
|
447
|
-
expected_exchange_code = "NasdaqGS"
|
|
445
|
+
expected_history_metadata: HistoryMetadataResp = MOCK_TRADING_ITEM_DB[msft_trading_item_id][
|
|
446
|
+
"metadata"
|
|
447
|
+
].copy()
|
|
448
448
|
history_metadata = self.msft_trading_item_from_id.history_metadata
|
|
449
|
-
|
|
450
|
-
self.assertEqual(expected_exchange_code, self.msft_trading_item_from_id.exchange_code)
|
|
451
|
-
|
|
452
|
-
history_metadata = self.msft_trading_item_from_ticker.history_metadata
|
|
453
|
-
self.assertEqual(expected_history_metadata, history_metadata)
|
|
454
|
-
self.assertEqual(expected_exchange_code, self.msft_trading_item_from_ticker.exchange_code)
|
|
449
|
+
assert history_metadata == expected_history_metadata
|
|
455
450
|
|
|
456
451
|
def test_price_chart(self):
|
|
457
452
|
"""test price chart"""
|
|
@@ -525,12 +520,11 @@ class TestCompany(TestCase):
|
|
|
525
520
|
def test_income_statement(self) -> None:
|
|
526
521
|
"""test income statement"""
|
|
527
522
|
expected_income_statement = (
|
|
528
|
-
pd.DataFrame(
|
|
529
|
-
MOCK_COMPANY_DB[msft_company_id]["statements"]["income_statement"]["statements"]
|
|
530
|
-
)
|
|
523
|
+
pd.DataFrame(INCOME_STATEMENT.model_dump(mode="json")["statements"])
|
|
531
524
|
.apply(pd.to_numeric)
|
|
532
525
|
.replace(np.nan, None)
|
|
533
526
|
)
|
|
527
|
+
|
|
534
528
|
income_statement = self.msft_company.company.income_statement()
|
|
535
529
|
pd.testing.assert_frame_equal(expected_income_statement, income_statement)
|
|
536
530
|
|
|
@@ -551,7 +545,9 @@ class TestCompany(TestCase):
|
|
|
551
545
|
|
|
552
546
|
def test_business_segments(self) -> None:
|
|
553
547
|
"""test business statement"""
|
|
554
|
-
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
|
+
]
|
|
555
551
|
|
|
556
552
|
business_segment = self.msft_company.company.business_segments()
|
|
557
553
|
self.assertEqual(expected_segments, business_segment)
|
|
@@ -583,7 +579,7 @@ class TestCompany(TestCase):
|
|
|
583
579
|
self.assertEqual(suppliers_via_property, suppliers_via_method)
|
|
584
580
|
|
|
585
581
|
def test_mergers(self) -> None:
|
|
586
|
-
expected_mergers =
|
|
582
|
+
expected_mergers = MERGERS_RESP.model_dump(mode="json")
|
|
587
583
|
mergers = self.msft_company.company.mergers_and_acquisitions
|
|
588
584
|
mergers_json = {
|
|
589
585
|
"target": [
|
|
@@ -730,9 +726,6 @@ class TestTicker(TestCase):
|
|
|
730
726
|
def test_history_metadata(self) -> None:
|
|
731
727
|
"""test history metadata"""
|
|
732
728
|
expected_history_metadata = MOCK_TRADING_ITEM_DB[msft_trading_item_id]["metadata"].copy()
|
|
733
|
-
expected_history_metadata["first_trade_date"] = datetime.strptime(
|
|
734
|
-
expected_history_metadata["first_trade_date"], "%Y-%m-%d"
|
|
735
|
-
).date()
|
|
736
729
|
history_metadata = self.msft_ticker_from_ticker.history_metadata
|
|
737
730
|
expected_exchange_code = "NasdaqGS"
|
|
738
731
|
self.assertEqual(expected_history_metadata, history_metadata)
|
|
@@ -848,12 +841,11 @@ class TestTicker(TestCase):
|
|
|
848
841
|
def test_income_statement(self) -> None:
|
|
849
842
|
"""test income statement"""
|
|
850
843
|
expected_income_statement = (
|
|
851
|
-
pd.DataFrame(
|
|
852
|
-
MOCK_COMPANY_DB[msft_company_id]["statements"]["income_statement"]["statements"]
|
|
853
|
-
)
|
|
844
|
+
pd.DataFrame(INCOME_STATEMENT.model_dump(mode="json")["statements"])
|
|
854
845
|
.apply(pd.to_numeric)
|
|
855
846
|
.replace(np.nan, None)
|
|
856
847
|
)
|
|
848
|
+
|
|
857
849
|
income_statement = self.msft_ticker_from_ticker.income_statement()
|
|
858
850
|
pd.testing.assert_frame_equal(expected_income_statement, income_statement)
|
|
859
851
|
|
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
|
}
|