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
|
@@ -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
|
-
"
|
|
27
|
-
"
|
|
28
|
-
|
|
29
|
-
|
|
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"],
|
|
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
|
-
{
|
|
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
|
-
|
|
30
|
-
|
|
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,
|
|
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
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
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
|
-
{
|
|
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
|
-
|
|
65
|
-
|
|
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,
|
|
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
|
-
|
|
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.
|
|
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
|
|
17
|
-
THEN we get back the
|
|
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
|
-
|
|
21
|
-
expected_response = {
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
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
|
|
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
|
|
41
|
-
THEN we get back the
|
|
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
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
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.
|
|
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
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
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
|
-
|
|
75
|
-
'
|
|
76
|
-
|
|
77
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
95
|
+
output_model.results[identifier] = most_recent_earnings
|
|
90
96
|
else:
|
|
91
|
-
|
|
92
|
-
|
|
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
|
-
|
|
114
|
-
'
|
|
115
|
-
|
|
116
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
135
|
+
output_model.results[identifier] = next_earnings
|
|
129
136
|
else:
|
|
130
|
-
|
|
131
|
-
|
|
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
|
-
) ->
|
|
143
|
+
) -> GetEarningsFromIdentifiersResp:
|
|
138
144
|
"""Return the earnings call response for all passed identifiers."""
|
|
139
145
|
|
|
140
|
-
|
|
141
|
-
|
|
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,
|
|
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
|
-
|
|
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
|
-
"
|
|
48
|
-
{
|
|
49
|
-
"
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
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(
|
|
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
|
-
"
|
|
86
|
-
"
|
|
87
|
-
|
|
88
|
-
|
|
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(
|
|
94
|
-
|
|
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
|
-
"
|
|
133
|
-
"
|
|
134
|
-
|
|
135
|
-
|
|
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(
|
|
141
|
-
|
|
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
|
|