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.

Files changed (46) hide show
  1. {kensho_kfinance-3.0.3.dist-info → kensho_kfinance-3.1.1.dist-info}/METADATA +1 -1
  2. {kensho_kfinance-3.0.3.dist-info → kensho_kfinance-3.1.1.dist-info}/RECORD +46 -45
  3. kfinance/CHANGELOG.md +6 -0
  4. kfinance/client/fetch.py +26 -17
  5. kfinance/client/kfinance.py +17 -18
  6. kfinance/client/meta_classes.py +2 -2
  7. kfinance/client/tests/test_fetch.py +36 -24
  8. kfinance/client/tests/test_objects.py +112 -120
  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_tools.py +29 -36
  26. kfinance/domains/line_items/tests/test_line_item_tools.py +23 -5
  27. kfinance/domains/mergers_and_acquisitions/merger_and_acquisition_models.py +15 -0
  28. kfinance/domains/mergers_and_acquisitions/merger_and_acquisition_tools.py +55 -38
  29. kfinance/domains/mergers_and_acquisitions/tests/test_merger_and_acquisition_tools.py +22 -9
  30. kfinance/domains/prices/price_models.py +9 -9
  31. kfinance/domains/prices/price_tools.py +49 -38
  32. kfinance/domains/prices/tests/test_price_tools.py +52 -36
  33. kfinance/domains/segments/segment_models.py +7 -0
  34. kfinance/domains/segments/segment_tools.py +37 -20
  35. kfinance/domains/segments/tests/test_segment_tools.py +13 -6
  36. kfinance/domains/statements/statement_models.py +7 -0
  37. kfinance/domains/statements/statement_tools.py +38 -40
  38. kfinance/domains/statements/tests/test_statement_tools.py +39 -10
  39. kfinance/integrations/tool_calling/prompts.py +21 -14
  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 +16 -3
  43. {kensho_kfinance-3.0.3.dist-info → kensho_kfinance-3.1.1.dist-info}/WHEEL +0 -0
  44. {kensho_kfinance-3.0.3.dist-info → kensho_kfinance-3.1.1.dist-info}/licenses/AUTHORS.md +0 -0
  45. {kensho_kfinance-3.0.3.dist-info → kensho_kfinance-3.1.1.dist-info}/licenses/LICENSE +0 -0
  46. {kensho_kfinance-3.0.3.dist-info → kensho_kfinance-3.1.1.dist-info}/top_level.txt +0 -0
@@ -14,7 +14,9 @@ class TestGetCompetitorsFromIdentifiers:
14
14
  """
15
15
  GIVEN the GetCompetitorsFromIdentifiers tool
16
16
  WHEN we request the SPGI competitors that are named by competitors
17
+ and competitors for a non-existent company
17
18
  THEN we get back the SPGI competitors that are named by competitors
19
+ and an error for the non-existent company
18
20
  """
19
21
  competitors_response = {
20
22
  "competitors": [
@@ -23,12 +25,23 @@ class TestGetCompetitorsFromIdentifiers:
23
25
  ]
24
26
  }
25
27
  expected_response = {
26
- "SPGI": {
27
- "competitors": [
28
- {"company_id": "C_35352", "company_name": "The Descartes Systems Group Inc."},
29
- {"company_id": "C_4003514", "company_name": "London Stock Exchange Group plc"},
30
- ]
31
- }
28
+ "results": {
29
+ "SPGI": {
30
+ "competitors": [
31
+ {
32
+ "company_id": "C_35352",
33
+ "company_name": "The Descartes Systems Group Inc.",
34
+ },
35
+ {
36
+ "company_id": "C_4003514",
37
+ "company_name": "London Stock Exchange Group plc",
38
+ },
39
+ ]
40
+ }
41
+ },
42
+ "errors": [
43
+ "No identification triple found for the provided identifier: NON-EXISTENT of type: ticker"
44
+ ],
32
45
  }
33
46
 
34
47
  requests_mock.get(
@@ -39,7 +52,8 @@ class TestGetCompetitorsFromIdentifiers:
39
52
 
40
53
  tool = GetCompetitorsFromIdentifiers(kfinance_client=mock_client)
41
54
  args = GetCompetitorsFromIdentifiersArgs(
42
- identifiers=["SPGI"], competitor_source=CompetitorSource.named_by_competitor
55
+ identifiers=["SPGI", "non-existent"],
56
+ competitor_source=CompetitorSource.named_by_competitor,
43
57
  )
44
58
  response = tool.run(args.model_dump(mode="json"))
45
59
  assert response == expected_response
@@ -4,16 +4,19 @@ from pydantic import BaseModel
4
4
 
5
5
  from kfinance.client.batch_request_handling import Task, process_tasks_in_thread_pool_executor
6
6
  from kfinance.client.permission_models import Permission
7
- from kfinance.domains.companies.company_identifiers import (
8
- fetch_security_ids_from_identifiers,
9
- parse_identifiers,
10
- )
11
7
  from kfinance.integrations.tool_calling.tool_calling_models import (
12
8
  KfinanceTool,
13
9
  ToolArgsWithIdentifiers,
10
+ ToolRespWithErrors,
14
11
  )
15
12
 
16
13
 
14
+ class GetCusipOrIsinFromIdentifiersResp(ToolRespWithErrors):
15
+ """Both cusip and isin return a mapping from identifier to str (isin or cusip)."""
16
+
17
+ results: dict[str, str]
18
+
19
+
17
20
  class GetCusipFromIdentifiers(KfinanceTool):
18
21
  name: str = "get_cusip_from_identifiers"
19
22
  description: str = "Get the CUSIPs for a group of identifiers."
@@ -23,30 +26,33 @@ class GetCusipFromIdentifiers(KfinanceTool):
23
26
  def _run(self, identifiers: list[str]) -> dict[str, str]:
24
27
  """Sample response:
25
28
 
26
- {"SPGI": "78409V104"}
29
+ {
30
+ 'results': {'SPGI': '78409V104'},
31
+ 'errors': ['Kensho is a private company without a security_id.']
32
+ }
27
33
  """
28
34
  api_client = self.kfinance_client.kfinance_api_client
29
- parsed_identifiers = parse_identifiers(identifiers=identifiers, api_client=api_client)
30
- identifiers_to_security_ids = fetch_security_ids_from_identifiers(
31
- identifiers=parsed_identifiers, api_client=api_client
32
- )
35
+ id_triple_resp = api_client.unified_fetch_id_triples(identifiers=identifiers)
36
+ id_triple_resp.filter_out_companies_without_security_ids()
33
37
 
34
38
  tasks = [
35
39
  Task(
36
40
  func=api_client.fetch_cusip,
37
- kwargs=dict(security_id=security_id),
41
+ kwargs=dict(security_id=id_triple.security_id),
38
42
  result_key=identifier,
39
43
  )
40
- for identifier, security_id in identifiers_to_security_ids.items()
44
+ for identifier, id_triple in id_triple_resp.identifiers_to_id_triples.items()
41
45
  ]
42
46
 
43
47
  cusip_responses = process_tasks_in_thread_pool_executor(api_client=api_client, tasks=tasks)
44
-
45
- return {str(identifier): resp["cusip"] for identifier, resp in cusip_responses.items()}
46
-
47
-
48
- class GetIsinFromIdentifiersArgs(BaseModel):
49
- security_ids: list[int]
48
+ resp_model = GetCusipOrIsinFromIdentifiersResp(
49
+ results={
50
+ identifier: cusip_resp["cusip"]
51
+ for identifier, cusip_resp in cusip_responses.items()
52
+ },
53
+ errors=list(id_triple_resp.errors.values()),
54
+ )
55
+ return resp_model.model_dump(mode="json")
50
56
 
51
57
 
52
58
  class GetIsinFromIdentifiers(KfinanceTool):
@@ -58,23 +64,29 @@ class GetIsinFromIdentifiers(KfinanceTool):
58
64
  def _run(self, identifiers: list[str]) -> dict:
59
65
  """Sample response:
60
66
 
61
- {"SPGI": "US78409V1044"}
67
+ {
68
+ 'results': {'SPGI': 'US78409V104'},
69
+ 'errors': ['Kensho is a private company without a security_id.']
70
+ }
62
71
  """
63
72
  api_client = self.kfinance_client.kfinance_api_client
64
- parsed_identifiers = parse_identifiers(identifiers=identifiers, api_client=api_client)
65
- identifiers_to_security_ids = fetch_security_ids_from_identifiers(
66
- identifiers=parsed_identifiers, api_client=api_client
67
- )
73
+ id_triple_resp = api_client.unified_fetch_id_triples(identifiers=identifiers)
74
+ id_triple_resp.filter_out_companies_without_security_ids()
68
75
 
69
76
  tasks = [
70
77
  Task(
71
78
  func=api_client.fetch_isin,
72
- kwargs=dict(security_id=security_id),
79
+ kwargs=dict(security_id=id_triple.security_id),
73
80
  result_key=identifier,
74
81
  )
75
- for identifier, security_id in identifiers_to_security_ids.items()
82
+ for identifier, id_triple in id_triple_resp.identifiers_to_id_triples.items()
76
83
  ]
77
84
 
78
85
  isin_responses = process_tasks_in_thread_pool_executor(api_client=api_client, tasks=tasks)
79
-
80
- return {str(identifier): resp["isin"] for identifier, resp in isin_responses.items()}
86
+ resp_model = GetCusipOrIsinFromIdentifiersResp(
87
+ results={
88
+ identifier: isin_resp["isin"] for identifier, isin_resp in isin_responses.items()
89
+ },
90
+ errors=list(id_triple_resp.errors.values()),
91
+ )
92
+ return resp_model.model_dump(mode="json")
@@ -1,7 +1,7 @@
1
1
  from requests_mock import Mocker
2
2
 
3
3
  from kfinance.client.kfinance import Client
4
- from kfinance.domains.companies.company_models import COMPANY_ID_PREFIX
4
+ from kfinance.conftest import SPGI_SECURITY_ID
5
5
  from kfinance.domains.cusip_and_isin.cusip_and_isin_tools import (
6
6
  GetCusipFromIdentifiers,
7
7
  GetIsinFromIdentifiers,
@@ -13,45 +13,46 @@ class TestGetCusipFromIdentifiers:
13
13
  def test_get_cusip_from_identifiers(self, requests_mock: Mocker, mock_client: Client):
14
14
  """
15
15
  GIVEN the GetCusipFromIdentifiers tool
16
- WHEN we request the CUSIPs for multiple companies
17
- THEN we get back the corresponding CUSIPs
16
+ WHEN we request the CUSIPs for SPGI including a private one
17
+ THEN we get back the CUSIP of SPGI and an error for the private company
18
18
  """
19
19
 
20
- company_ids = [1, 2]
21
- expected_response = {"C_1": "CU1", "C_2": "CU2"}
22
- for security_id in company_ids:
23
- requests_mock.get(
24
- url=f"https://kfinance.kensho.com/api/v1/cusip/{security_id}",
25
- json={"cusip": f"CU{security_id}"},
26
- )
20
+ spgi_cusip = "78409V104"
21
+ expected_response = {
22
+ "results": {"SPGI": "78409V104"},
23
+ "errors": ["private_company is a private company without a security_id."],
24
+ }
25
+ requests_mock.get(
26
+ url=f"https://kfinance.kensho.com/api/v1/cusip/{SPGI_SECURITY_ID}",
27
+ json={"cusip": spgi_cusip},
28
+ )
27
29
  tool = GetCusipFromIdentifiers(kfinance_client=mock_client)
28
30
  resp = tool.run(
29
- ToolArgsWithIdentifiers(
30
- identifiers=[f"{COMPANY_ID_PREFIX}{company_id}" for company_id in company_ids]
31
- ).model_dump(mode="json")
31
+ ToolArgsWithIdentifiers(identifiers=["SPGI", "private_company"]).model_dump(mode="json")
32
32
  )
33
33
  assert resp == expected_response
34
34
 
35
35
 
36
- class TestGetIsinFromSecurityIds:
36
+ class TestGetIsinFromIdentifiers:
37
37
  def test_get_isin_from_security_ids(self, requests_mock: Mocker, mock_client: Client):
38
38
  """
39
39
  GIVEN the GetIsinFromSecurityIds tool
40
- WHEN we request the ISINs for multiple security ids
41
- THEN we get back the corresponding ISINs
40
+ WHEN we request the ISINs for SPGI including a private one
41
+ THEN we get back the ISIN of SPGI and an error for the private company
42
42
  """
43
43
 
44
- company_ids = [1, 2]
45
- expected_response = {"C_1": "IS1", "C_2": "IS2"}
46
- for security_id in company_ids:
47
- requests_mock.get(
48
- url=f"https://kfinance.kensho.com/api/v1/isin/{security_id}",
49
- json={"isin": f"IS{security_id}"},
50
- )
44
+ spgi_isin = "US78409V1044"
45
+
46
+ expected_response = {
47
+ "results": {"SPGI": "US78409V1044"},
48
+ "errors": ["private_company is a private company without a security_id."],
49
+ }
50
+ requests_mock.get(
51
+ url=f"https://kfinance.kensho.com/api/v1/isin/{SPGI_SECURITY_ID}",
52
+ json={"isin": spgi_isin},
53
+ )
51
54
  tool = GetIsinFromIdentifiers(kfinance_client=mock_client)
52
55
  resp = tool.run(
53
- ToolArgsWithIdentifiers(
54
- identifiers=[f"{COMPANY_ID_PREFIX}{company_id}" for company_id in company_ids]
55
- ).model_dump(mode="json")
56
+ ToolArgsWithIdentifiers(identifiers=["SPGI", "private_company"]).model_dump(mode="json")
56
57
  )
57
58
  assert resp == expected_response
@@ -6,18 +6,22 @@ from pydantic import BaseModel, Field
6
6
  from kfinance.client.batch_request_handling import Task, process_tasks_in_thread_pool_executor
7
7
  from kfinance.client.fetch import KFinanceApiClient
8
8
  from kfinance.client.permission_models import Permission
9
- from kfinance.domains.companies.company_identifiers import (
10
- CompanyIdentifier,
11
- fetch_company_ids_from_identifiers,
12
- parse_identifiers,
13
- )
14
- from kfinance.domains.earnings.earning_models import EarningsCallResp
9
+ from kfinance.domains.earnings.earning_models import EarningsCall, EarningsCallResp
15
10
  from kfinance.integrations.tool_calling.tool_calling_models import (
16
11
  KfinanceTool,
17
12
  ToolArgsWithIdentifiers,
13
+ ToolRespWithErrors,
18
14
  )
19
15
 
20
16
 
17
+ class GetEarningsFromIdentifiersResp(ToolRespWithErrors):
18
+ results: dict[str, EarningsCallResp]
19
+
20
+
21
+ class GetNextOrLatestEarningsFromIdentifiersResp(ToolRespWithErrors):
22
+ results: dict[str, EarningsCall]
23
+
24
+
21
25
  class GetEarningsFromIdentifiers(KfinanceTool):
22
26
  name: str = "get_earnings_from_identifiers"
23
27
  description: str = dedent("""
@@ -35,23 +39,23 @@ class GetEarningsFromIdentifiers(KfinanceTool):
35
39
  """Sample response:
36
40
 
37
41
  {
38
- 'SPGI': [
39
- {
40
- 'datetime': '2025-04-29T12:30:00Z',
41
- 'key_dev_id': 12346,
42
- 'name': 'SPGI Q1 2025 Earnings Call'
43
- }
44
- ]
42
+ "results": {
43
+ 'SPGI': [
44
+ {
45
+ 'datetime': '2025-04-29T12:30:00Z',
46
+ 'key_dev_id': 12346,
47
+ 'name': 'SPGI Q1 2025 Earnings Call'
48
+ }
49
+ ]
50
+ },
51
+ "errors": ['No identification triple found for the provided identifier: NON-EXISTENT of type: ticker']
45
52
  }
46
53
 
47
54
  """
48
55
  earnings_responses = get_earnings_from_identifiers(
49
56
  identifiers=identifiers, kfinance_api_client=self.kfinance_client.kfinance_api_client
50
57
  )
51
- return {
52
- str(identifier): earnings.model_dump(mode="json")["earnings_calls"]
53
- for identifier, earnings in earnings_responses.items()
54
- }
58
+ return earnings_responses.model_dump(mode="json")
55
59
 
56
60
 
57
61
  class GetLatestEarningsFromIdentifiers(KfinanceTool):
@@ -71,26 +75,27 @@ class GetLatestEarningsFromIdentifiers(KfinanceTool):
71
75
  """Sample response:
72
76
 
73
77
  {
74
- 'JPM': {
75
- 'datetime': '2025-04-29T12:30:00Z',
76
- 'key_dev_id': 12346,
77
- 'name': 'SPGI Q1 2025 Earnings Call'
78
+ "results": {
79
+ 'JPM': {
80
+ 'datetime': '2025-04-29T12:30:00Z',
81
+ 'key_dev_id': 12346,
82
+ 'name': 'SPGI Q1 2025 Earnings Call'
83
+ },
78
84
  },
79
- 'SPGI': 'No latest earnings available.'
85
+ "errors": ["No latest earnings available for Kensho."]
80
86
  }
81
87
  """
82
88
  earnings_responses = get_earnings_from_identifiers(
83
89
  identifiers=identifiers, kfinance_api_client=self.kfinance_client.kfinance_api_client
84
90
  )
85
- output = {}
86
- for identifier, earnings in earnings_responses.items():
91
+ output_model = GetNextOrLatestEarningsFromIdentifiersResp(results=dict(), errors=list())
92
+ for identifier, earnings in earnings_responses.results.items():
87
93
  most_recent_earnings = earnings.most_recent_earnings
88
94
  if most_recent_earnings:
89
- identifier_output: str | dict = most_recent_earnings.model_dump(mode="json")
95
+ output_model.results[identifier] = most_recent_earnings
90
96
  else:
91
- identifier_output = f"No latest earnings available."
92
- output[str(identifier)] = identifier_output
93
- return output
97
+ output_model.errors.append(f"No latest earnings available for {identifier}.")
98
+ return output_model.model_dump(mode="json")
94
99
 
95
100
 
96
101
  class GetNextEarningsFromIdentifiers(KfinanceTool):
@@ -110,51 +115,53 @@ class GetNextEarningsFromIdentifiers(KfinanceTool):
110
115
  """Sample response:
111
116
 
112
117
  {
113
- 'JPM': {
114
- 'datetime': '2025-04-29T12:30:00Z',
115
- 'key_dev_id': 12346,
116
- 'name': 'SPGI Q1 2025 Earnings Call'
118
+ "results": {
119
+ 'JPM': {
120
+ 'datetime': '2025-04-29T12:30:00Z',
121
+ 'key_dev_id': 12346,
122
+ 'name': 'SPGI Q1 2025 Earnings Call'
123
+ },
117
124
  },
118
- 'SPGI': 'No next earnings available.'
125
+ "errors": ["No next earnings available for Kensho."]
119
126
  }
120
127
  """
121
128
  earnings_responses = get_earnings_from_identifiers(
122
129
  identifiers=identifiers, kfinance_api_client=self.kfinance_client.kfinance_api_client
123
130
  )
124
- output = {}
125
- for identifier, earnings in earnings_responses.items():
131
+ output_model = GetNextOrLatestEarningsFromIdentifiersResp(results=dict(), errors=list())
132
+ for identifier, earnings in earnings_responses.results.items():
126
133
  next_earnings = earnings.next_earnings
127
134
  if next_earnings:
128
- identifier_output: str | dict = next_earnings.model_dump(mode="json")
135
+ output_model.results[identifier] = next_earnings
129
136
  else:
130
- identifier_output = f"No next earnings available."
131
- output[str(identifier)] = identifier_output
132
- return output
137
+ output_model.errors.append(f"No next earnings available for {identifier}.")
138
+ return output_model.model_dump(mode="json")
133
139
 
134
140
 
135
141
  def get_earnings_from_identifiers(
136
142
  identifiers: list[str], kfinance_api_client: KFinanceApiClient
137
- ) -> dict[CompanyIdentifier, EarningsCallResp]:
143
+ ) -> GetEarningsFromIdentifiersResp:
138
144
  """Return the earnings call response for all passed identifiers."""
139
145
 
140
- parsed_identifiers = parse_identifiers(identifiers=identifiers, api_client=kfinance_api_client)
141
- identifiers_to_company_ids = fetch_company_ids_from_identifiers(
142
- identifiers=parsed_identifiers, api_client=kfinance_api_client
143
- )
146
+ api_client = kfinance_api_client
147
+ id_triple_resp = api_client.unified_fetch_id_triples(identifiers=identifiers)
144
148
 
145
149
  tasks = [
146
150
  Task(
147
151
  func=kfinance_api_client.fetch_earnings,
148
- kwargs=dict(company_id=company_id),
152
+ kwargs=dict(company_id=id_triple.company_id),
149
153
  result_key=identifier,
150
154
  )
151
- for identifier, company_id in identifiers_to_company_ids.items()
155
+ for identifier, id_triple in id_triple_resp.identifiers_to_id_triples.items()
152
156
  ]
153
157
 
154
158
  earnings_responses = process_tasks_in_thread_pool_executor(
155
159
  api_client=kfinance_api_client, tasks=tasks
156
160
  )
157
- return earnings_responses
161
+ resp_model = GetEarningsFromIdentifiersResp(
162
+ results=earnings_responses, errors=list(id_triple_resp.errors.values())
163
+ )
164
+ return resp_model
158
165
 
159
166
 
160
167
  class GetTranscriptFromKeyDevIdArgs(BaseModel):
@@ -34,8 +34,8 @@ class TestGetEarnings:
34
34
  def test_get_earnings_from_identifiers(self, requests_mock: Mocker, mock_client: Client):
35
35
  """
36
36
  GIVEN the GetEarnings tool
37
- WHEN we request all earnings for SPGI
38
- THEN we get back all SPGI earnings
37
+ WHEN we request all earnings for SPGI and a non-existent company
38
+ THEN we get back all SPGI earnings and an error for the non-existent company
39
39
  """
40
40
 
41
41
  requests_mock.get(
@@ -44,22 +44,31 @@ class TestGetEarnings:
44
44
  )
45
45
 
46
46
  expected_response = {
47
- "SPGI": [
48
- {
49
- "datetime": "2025-04-29T12:30:00Z",
50
- "key_dev_id": 12346,
51
- "name": "SPGI Q1 2025 Earnings Call",
52
- },
53
- {
54
- "datetime": "2025-02-11T13:30:00Z",
55
- "key_dev_id": 12345,
56
- "name": "SPGI Q4 2024 Earnings Call",
57
- },
58
- ]
47
+ "results": {
48
+ "SPGI": {
49
+ "earnings_calls": [
50
+ {
51
+ "name": "SPGI Q1 2025 Earnings Call",
52
+ "key_dev_id": 12346,
53
+ "datetime": "2025-04-29T12:30:00Z",
54
+ },
55
+ {
56
+ "name": "SPGI Q4 2024 Earnings Call",
57
+ "key_dev_id": 12345,
58
+ "datetime": "2025-02-11T13:30:00Z",
59
+ },
60
+ ]
61
+ }
62
+ },
63
+ "errors": [
64
+ "No identification triple found for the provided identifier: NON-EXISTENT of type: ticker"
65
+ ],
59
66
  }
60
67
 
61
68
  tool = GetEarningsFromIdentifiers(kfinance_client=mock_client)
62
- response = tool.run(ToolArgsWithIdentifiers(identifiers=["SPGI"]).model_dump(mode="json"))
69
+ response = tool.run(
70
+ ToolArgsWithIdentifiers(identifiers=["SPGI", "non-existent"]).model_dump(mode="json")
71
+ )
63
72
  assert response == expected_response
64
73
 
65
74
  @time_machine.travel(
@@ -72,41 +81,35 @@ class TestGetEarnings:
72
81
  def test_get_latest_earnings_from_identifiers(self, requests_mock: Mocker, mock_client: Client):
73
82
  """
74
83
  GIVEN the GetLatestEarnings tool
75
- WHEN we request the latest earnings for SPGI
76
- THEN we get back the latest SPGI earnings
84
+ WHEN we request the latest earnings for SPGI and a private company without earnings
85
+ THEN we get back the latest SPGI earnings and an error for the private company.
77
86
  """
78
87
 
79
88
  requests_mock.get(
80
89
  url=f"https://kfinance.kensho.com/api/v1/earnings/{SPGI_COMPANY_ID}",
81
90
  json=self.earnings_response,
82
91
  )
92
+ # private company without earnings
93
+ requests_mock.get(
94
+ url=f"https://kfinance.kensho.com/api/v1/earnings/1",
95
+ json={"earnings": []},
96
+ )
83
97
 
84
98
  expected_response = {
85
- "SPGI": {
86
- "datetime": "2025-04-29T12:30:00Z",
87
- "key_dev_id": 12346,
88
- "name": "SPGI Q1 2025 Earnings Call",
89
- }
99
+ "results": {
100
+ "SPGI": {
101
+ "name": "SPGI Q1 2025 Earnings Call",
102
+ "key_dev_id": 12346,
103
+ "datetime": "2025-04-29T12:30:00Z",
104
+ }
105
+ },
106
+ "errors": ["No latest earnings available for private_company."],
90
107
  }
91
108
 
92
109
  tool = GetLatestEarningsFromIdentifiers(kfinance_client=mock_client)
93
- response = tool.run(ToolArgsWithIdentifiers(identifiers=["SPGI"]).model_dump(mode="json"))
94
- assert response == expected_response
95
-
96
- def test_get_latest_earnings_no_data(self, requests_mock: Mocker, mock_client: Client):
97
- """
98
- GIVEN the GetLatestEarnings tool
99
- WHEN we request the latest earnings for a company with no data
100
- THEN we get a `No latest earnings available` message.
101
- """
102
- requests_mock.get(
103
- url=f"https://kfinance.kensho.com/api/v1/earnings/{SPGI_COMPANY_ID}",
104
- json={"earnings": []},
110
+ response = tool.run(
111
+ ToolArgsWithIdentifiers(identifiers=["SPGI", "private_company"]).model_dump(mode="json")
105
112
  )
106
- expected_response = {"SPGI": "No latest earnings available."}
107
-
108
- tool = GetLatestEarningsFromIdentifiers(kfinance_client=mock_client)
109
- response = tool.run(ToolArgsWithIdentifiers(identifiers=["SPGI"]).model_dump(mode="json"))
110
113
  assert response == expected_response
111
114
 
112
115
  @time_machine.travel(
@@ -119,43 +122,35 @@ class TestGetEarnings:
119
122
  def test_get_next_earnings(self, requests_mock: Mocker, mock_client: Client):
120
123
  """
121
124
  GIVEN the GetNextEarningsFromIdentifiers tool
122
- WHEN we request the next earnings for SPGI
123
- THEN we get back the next SPGI earnings
125
+ WHEN we request the next earnings for SPGI and a private company
126
+ THEN we get back the next SPGI earnings and an error for the private company
124
127
  """
125
128
 
126
129
  requests_mock.get(
127
130
  url=f"https://kfinance.kensho.com/api/v1/earnings/{SPGI_COMPANY_ID}",
128
131
  json=self.earnings_response,
129
132
  )
133
+ # private company without earnings
134
+ requests_mock.get(
135
+ url=f"https://kfinance.kensho.com/api/v1/earnings/1",
136
+ json={"earnings": []},
137
+ )
130
138
 
131
139
  expected_response = {
132
- "SPGI": {
133
- "datetime": "2025-04-29T12:30:00Z",
134
- "key_dev_id": 12346,
135
- "name": "SPGI Q1 2025 Earnings Call",
136
- }
140
+ "results": {
141
+ "SPGI": {
142
+ "datetime": "2025-04-29T12:30:00Z",
143
+ "key_dev_id": 12346,
144
+ "name": "SPGI Q1 2025 Earnings Call",
145
+ }
146
+ },
147
+ "errors": ["No next earnings available for private_company."],
137
148
  }
138
149
 
139
150
  tool = GetNextEarningsFromIdentifiers(kfinance_client=mock_client)
140
- response = tool.run(ToolArgsWithIdentifiers(identifiers=["SPGI"]).model_dump(mode="json"))
141
- assert response == expected_response
142
-
143
- def test_get_next_earnings_no_data(self, requests_mock: Mocker, mock_client: Client):
144
- """
145
- GIVEN the GetNextEarnings tool
146
- WHEN we request the next earnings for a company with no data
147
- THEN we get a `No next earnings available` message.
148
- """
149
- earnings_data = {"earnings": []}
150
-
151
- requests_mock.get(
152
- url=f"https://kfinance.kensho.com/api/v1/earnings/{SPGI_COMPANY_ID}",
153
- json=earnings_data,
151
+ response = tool.run(
152
+ ToolArgsWithIdentifiers(identifiers=["SPGI", "private_company"]).model_dump(mode="json")
154
153
  )
155
- expected_response = {"SPGI": "No next earnings available."}
156
-
157
- tool = GetNextEarningsFromIdentifiers(kfinance_client=mock_client)
158
- response = tool.run(ToolArgsWithIdentifiers(identifiers=["SPGI"]).model_dump(mode="json"))
159
154
  assert response == expected_response
160
155
 
161
156