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.

Files changed (46) hide show
  1. {kensho_kfinance-3.0.2.dist-info → kensho_kfinance-3.1.0.dist-info}/METADATA +1 -1
  2. {kensho_kfinance-3.0.2.dist-info → kensho_kfinance-3.1.0.dist-info}/RECORD +46 -45
  3. kfinance/CHANGELOG.md +7 -1
  4. kfinance/client/fetch.py +29 -19
  5. kfinance/client/kfinance.py +17 -18
  6. kfinance/client/meta_classes.py +12 -13
  7. kfinance/client/tests/test_fetch.py +51 -34
  8. kfinance/client/tests/test_objects.py +130 -129
  9. kfinance/conftest.py +49 -5
  10. kfinance/domains/business_relationships/business_relationship_tools.py +30 -19
  11. kfinance/domains/business_relationships/tests/test_business_relationship_tools.py +18 -15
  12. kfinance/domains/capitalizations/capitalization_models.py +1 -1
  13. kfinance/domains/capitalizations/capitalization_tools.py +41 -24
  14. kfinance/domains/capitalizations/tests/test_capitalization_tools.py +38 -13
  15. kfinance/domains/companies/company_identifiers.py +0 -175
  16. kfinance/domains/companies/company_models.py +98 -5
  17. kfinance/domains/companies/company_tools.py +33 -29
  18. kfinance/domains/companies/tests/test_company_tools.py +11 -4
  19. kfinance/domains/competitors/competitor_tools.py +21 -21
  20. kfinance/domains/competitors/tests/test_competitor_tools.py +21 -7
  21. kfinance/domains/cusip_and_isin/cusip_and_isin_tools.py +38 -26
  22. kfinance/domains/cusip_and_isin/tests/test_cusip_and_isin_tools.py +27 -26
  23. kfinance/domains/earnings/earning_tools.py +54 -47
  24. kfinance/domains/earnings/tests/test_earnings_tools.py +58 -63
  25. kfinance/domains/line_items/line_item_models.py +7 -0
  26. kfinance/domains/line_items/line_item_tools.py +31 -30
  27. kfinance/domains/line_items/tests/test_line_item_tools.py +23 -5
  28. kfinance/domains/mergers_and_acquisitions/merger_and_acquisition_models.py +15 -0
  29. kfinance/domains/mergers_and_acquisitions/merger_and_acquisition_tools.py +55 -38
  30. kfinance/domains/mergers_and_acquisitions/tests/test_merger_and_acquisition_tools.py +22 -9
  31. kfinance/domains/prices/price_models.py +9 -9
  32. kfinance/domains/prices/price_tools.py +49 -38
  33. kfinance/domains/prices/tests/test_price_tools.py +52 -36
  34. kfinance/domains/segments/segment_models.py +7 -0
  35. kfinance/domains/segments/segment_tools.py +37 -20
  36. kfinance/domains/segments/tests/test_segment_tools.py +13 -6
  37. kfinance/domains/statements/statement_models.py +7 -0
  38. kfinance/domains/statements/statement_tools.py +38 -40
  39. kfinance/domains/statements/tests/test_statement_tools.py +39 -10
  40. kfinance/integrations/tool_calling/tests/test_tool_calling_models.py +2 -2
  41. kfinance/integrations/tool_calling/tool_calling_models.py +24 -5
  42. kfinance/version.py +2 -2
  43. {kensho_kfinance-3.0.2.dist-info → kensho_kfinance-3.1.0.dist-info}/WHEEL +0 -0
  44. {kensho_kfinance-3.0.2.dist-info → kensho_kfinance-3.1.0.dist-info}/licenses/AUTHORS.md +0 -0
  45. {kensho_kfinance-3.0.2.dist-info → kensho_kfinance-3.1.0.dist-info}/licenses/LICENSE +0 -0
  46. {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 = "21835"
34
- msft_security_id = "2630412"
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 = "2630413"
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
- "currency": "USD",
45
- "symbol": "MSFT",
46
- "exchange_name": "NasdaqGS",
47
- "instrument_type": "Equity",
48
- "first_trade_date": "1986-03-13",
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
- "statements": {
99
- "income_statement": {
100
- "statements": {
101
- "2019": {
102
- "Revenues": "125843000000.000000",
103
- "Total Revenues": "125843000000.000000",
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
- "line_items": {
109
- "revenue": {
110
- "line_item": {
111
- "2019": "125843000000.000000",
112
- "2020": "143015000000.000000",
113
- "2021": "168088000000.000000",
114
- "2022": "198270000000.000000",
115
- "2023": "211915000000.000000",
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 MOCK_COMPANY_DB[company_id]["statements"][statement_type]
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(MOCK_COMPANY_DB[company_id]["mergers"])
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(self.kfinance_api_client, msft_trading_item_id)
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 = MOCK_TICKER_DB["MSFT"]["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]["metadata"].copy()
441
- expected_history_metadata["first_trade_date"] = datetime.strptime(
442
- expected_history_metadata["first_trade_date"], "%Y-%m-%d"
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
- self.assertEqual(expected_history_metadata, history_metadata)
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(MOCK_COMPANY_DB[msft_company_id]["line_items"]["revenue"])
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 = MOCK_COMPANY_DB[msft_company_id]["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(MOCK_COMPANY_DB[msft_company_id]["line_items"]["revenue"])
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
- "SPGI": {
43
- "current": [{"company_id": "C_883103", "company_name": "CRISIL Limited"}],
44
- "previous": [
45
- {"company_id": "C_472898", "company_name": "Morgan Stanley"},
46
- {"company_id": "C_8182358", "company_name": "Eloqua, Inc."},
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
- parsed_identifiers = parse_identifiers(identifiers=identifiers, api_client=api_client)
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, company_id in identifiers_to_company_ids.items()
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
- return {str(k): v.model_dump(mode="json") for k, v in relationship_responses.items()}
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 suppliers
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
- "SPGI": {
32
- "current": [{"company_id": "C_883103", "company_name": "CRISIL Limited"}],
33
- "previous": [
34
- {"company_id": "C_472898", "company_name": "Morgan Stanley"},
35
- {"company_id": "C_8182358", "company_name": "Eloqua, Inc."},
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"], business_relationship=BusinessRelationshipType.supplier
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
- 0 if only_include_most_recent_value else "__all__": {
91
+ -1 if only_include_most_recent_value else "__all__": {
92
92
  "date",
93
93
  capitalization_metric.value,
94
94
  }