kensho-kfinance 3.2.4__py3-none-any.whl → 4.0.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.
- {kensho_kfinance-3.2.4.dist-info → kensho_kfinance-4.0.0.dist-info}/METADATA +3 -3
- {kensho_kfinance-3.2.4.dist-info → kensho_kfinance-4.0.0.dist-info}/RECORD +57 -56
- kfinance/CHANGELOG.md +51 -0
- kfinance/client/batch_request_handling.py +3 -1
- kfinance/client/fetch.py +127 -54
- kfinance/client/kfinance.py +38 -39
- kfinance/client/meta_classes.py +50 -20
- kfinance/client/models/date_and_period_models.py +32 -7
- kfinance/client/models/decimal_with_unit.py +14 -2
- kfinance/client/models/response_models.py +33 -0
- kfinance/client/models/tests/test_decimal_with_unit.py +9 -0
- kfinance/client/tests/test_batch_requests.py +5 -4
- kfinance/client/tests/test_fetch.py +134 -58
- kfinance/client/tests/test_objects.py +207 -145
- kfinance/conftest.py +10 -0
- kfinance/domains/business_relationships/business_relationship_tools.py +17 -8
- kfinance/domains/business_relationships/tests/test_business_relationship_tools.py +18 -16
- kfinance/domains/capitalizations/capitalization_models.py +7 -5
- kfinance/domains/capitalizations/capitalization_tools.py +38 -20
- kfinance/domains/capitalizations/tests/test_capitalization_tools.py +66 -36
- kfinance/domains/companies/company_models.py +22 -2
- kfinance/domains/companies/company_tools.py +49 -16
- kfinance/domains/companies/tests/test_company_tools.py +27 -9
- kfinance/domains/competitors/competitor_tools.py +19 -5
- kfinance/domains/competitors/tests/test_competitor_tools.py +22 -19
- kfinance/domains/cusip_and_isin/cusip_and_isin_tools.py +29 -8
- kfinance/domains/cusip_and_isin/tests/test_cusip_and_isin_tools.py +13 -8
- kfinance/domains/earnings/earning_tools.py +73 -29
- kfinance/domains/earnings/tests/test_earnings_tools.py +52 -43
- kfinance/domains/line_items/line_item_models.py +372 -16
- kfinance/domains/line_items/line_item_tools.py +198 -46
- kfinance/domains/line_items/tests/test_line_item_tools.py +305 -39
- kfinance/domains/mergers_and_acquisitions/merger_and_acquisition_models.py +46 -2
- kfinance/domains/mergers_and_acquisitions/merger_and_acquisition_tools.py +55 -74
- kfinance/domains/mergers_and_acquisitions/tests/test_merger_and_acquisition_tools.py +61 -59
- kfinance/domains/prices/price_models.py +7 -6
- kfinance/domains/prices/price_tools.py +24 -16
- kfinance/domains/prices/tests/test_price_tools.py +47 -39
- kfinance/domains/segments/segment_models.py +17 -3
- kfinance/domains/segments/segment_tools.py +102 -42
- kfinance/domains/segments/tests/test_segment_tools.py +166 -37
- kfinance/domains/statements/statement_models.py +17 -3
- kfinance/domains/statements/statement_tools.py +130 -46
- kfinance/domains/statements/tests/test_statement_tools.py +251 -49
- kfinance/integrations/local_mcp/kfinance_mcp.py +1 -1
- kfinance/integrations/tests/test_example_notebook.py +57 -16
- kfinance/integrations/tool_calling/all_tools.py +5 -1
- kfinance/integrations/tool_calling/static_tools/get_n_quarters_ago.py +5 -0
- kfinance/integrations/tool_calling/static_tools/tests/test_get_lastest.py +13 -10
- kfinance/integrations/tool_calling/static_tools/tests/test_get_n_quarters_ago.py +2 -1
- kfinance/integrations/tool_calling/tests/test_tool_calling_models.py +15 -4
- kfinance/integrations/tool_calling/tool_calling_models.py +18 -6
- kfinance/version.py +2 -2
- {kensho_kfinance-3.2.4.dist-info → kensho_kfinance-4.0.0.dist-info}/WHEEL +0 -0
- {kensho_kfinance-3.2.4.dist-info → kensho_kfinance-4.0.0.dist-info}/licenses/AUTHORS.md +0 -0
- {kensho_kfinance-3.2.4.dist-info → kensho_kfinance-4.0.0.dist-info}/licenses/LICENSE +0 -0
- {kensho_kfinance-3.2.4.dist-info → kensho_kfinance-4.0.0.dist-info}/top_level.txt +0 -0
|
@@ -8,13 +8,18 @@ from kfinance.client.tests.test_objects import (
|
|
|
8
8
|
ordered,
|
|
9
9
|
)
|
|
10
10
|
from kfinance.conftest import SPGI_COMPANY_ID
|
|
11
|
-
from kfinance.domains.
|
|
11
|
+
from kfinance.domains.mergers_and_acquisitions.merger_and_acquisition_models import (
|
|
12
|
+
AdvisorResp,
|
|
13
|
+
MergerInfo,
|
|
14
|
+
)
|
|
12
15
|
from kfinance.domains.mergers_and_acquisitions.merger_and_acquisition_tools import (
|
|
13
16
|
GetAdvisorsForCompanyInTransactionFromIdentifier,
|
|
14
17
|
GetAdvisorsForCompanyInTransactionFromIdentifierArgs,
|
|
18
|
+
GetAdvisorsForCompanyInTransactionFromIdentifierResp,
|
|
15
19
|
GetMergerInfoFromTransactionId,
|
|
16
20
|
GetMergerInfoFromTransactionIdArgs,
|
|
17
21
|
GetMergersFromIdentifiers,
|
|
22
|
+
GetMergersFromIdentifiersResp,
|
|
18
23
|
)
|
|
19
24
|
from kfinance.integrations.tool_calling.tool_calling_models import ToolArgsWithIdentifiers
|
|
20
25
|
|
|
@@ -27,12 +32,14 @@ class TestGetMergersFromIdentifiers:
|
|
|
27
32
|
THEN we get back the SPGI mergers and an error for the non-existent company"""
|
|
28
33
|
|
|
29
34
|
merger_data = MERGERS_RESP.model_dump(mode="json")
|
|
30
|
-
expected_response =
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
"
|
|
34
|
-
|
|
35
|
-
|
|
35
|
+
expected_response = GetMergersFromIdentifiersResp.model_validate(
|
|
36
|
+
{
|
|
37
|
+
"results": {"SPGI": merger_data},
|
|
38
|
+
"errors": [
|
|
39
|
+
"No identification triple found for the provided identifier: NON-EXISTENT of type: ticker"
|
|
40
|
+
],
|
|
41
|
+
}
|
|
42
|
+
)
|
|
36
43
|
requests_mock.get(
|
|
37
44
|
url=f"https://kfinance.kensho.com/api/v1/mergers/{SPGI_COMPANY_ID}", json=merger_data
|
|
38
45
|
)
|
|
@@ -51,13 +58,21 @@ class TestGetCompaniesAdvisingCompanyInTransactionFromIdentifier:
|
|
|
51
58
|
"advisor_company_name": "Kensho Technologies, Inc.",
|
|
52
59
|
"advisor_type_name": "Professional Mongo Enjoyer",
|
|
53
60
|
}
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
61
|
+
expected_response = GetAdvisorsForCompanyInTransactionFromIdentifierResp(
|
|
62
|
+
results=[
|
|
63
|
+
AdvisorResp(
|
|
64
|
+
advisor_company_id=251994106,
|
|
65
|
+
advisor_company_name="Kensho Technologies, Inc.",
|
|
66
|
+
advisor_type_name="Professional Mongo Enjoyer",
|
|
67
|
+
)
|
|
68
|
+
],
|
|
69
|
+
errors=[],
|
|
70
|
+
)
|
|
71
|
+
|
|
57
72
|
transaction_id = 554979212
|
|
58
73
|
requests_mock.get(
|
|
59
74
|
url=f"https://kfinance.kensho.com/api/v1/merger/info/{transaction_id}/advisors/{SPGI_COMPANY_ID}",
|
|
60
|
-
json=
|
|
75
|
+
json={"advisors": [deepcopy(advisor_data)]},
|
|
61
76
|
)
|
|
62
77
|
tool = GetAdvisorsForCompanyInTransactionFromIdentifier(kfinance_client=mock_client)
|
|
63
78
|
args = GetAdvisorsForCompanyInTransactionFromIdentifierArgs(
|
|
@@ -67,14 +82,14 @@ class TestGetCompaniesAdvisingCompanyInTransactionFromIdentifier:
|
|
|
67
82
|
assert response == expected_response
|
|
68
83
|
|
|
69
84
|
def test_get_companies_advising_company_in_transaction_from_bad_identifier(
|
|
70
|
-
self,
|
|
85
|
+
self, mock_client: Client
|
|
71
86
|
):
|
|
72
|
-
expected_response =
|
|
73
|
-
|
|
74
|
-
|
|
87
|
+
expected_response = GetAdvisorsForCompanyInTransactionFromIdentifierResp(
|
|
88
|
+
results=[],
|
|
89
|
+
errors=[
|
|
75
90
|
"No identification triple found for the provided identifier: NON-EXISTENT of type: ticker"
|
|
76
91
|
],
|
|
77
|
-
|
|
92
|
+
)
|
|
78
93
|
transaction_id = 554979212
|
|
79
94
|
tool = GetAdvisorsForCompanyInTransactionFromIdentifier(kfinance_client=mock_client)
|
|
80
95
|
args = GetAdvisorsForCompanyInTransactionFromIdentifierArgs(
|
|
@@ -86,30 +101,11 @@ class TestGetCompaniesAdvisingCompanyInTransactionFromIdentifier:
|
|
|
86
101
|
|
|
87
102
|
class TestGetMergerInfoFromTransactionId:
|
|
88
103
|
def test_get_merger_info_from_transaction_id(self, requests_mock: Mocker, mock_client: Client):
|
|
89
|
-
|
|
90
|
-
"
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
"participants": {},
|
|
95
|
-
"consideration": {
|
|
96
|
-
"currency_name": "US Dollar",
|
|
97
|
-
"current_calculated_gross_total_transaction_value": "51609375.000000",
|
|
98
|
-
"current_calculated_implied_equity_value": "51609375.000000",
|
|
99
|
-
"current_calculated_implied_enterprise_value": "51609375.000000",
|
|
100
|
-
"details": [
|
|
101
|
-
{
|
|
102
|
-
"scenario": "Stock Lump Sum",
|
|
103
|
-
"subtype": "Common Equity",
|
|
104
|
-
"cash_or_cash_equivalent_per_target_share_unit": None,
|
|
105
|
-
"number_of_target_shares_sought": "1000000.000000",
|
|
106
|
-
"current_calculated_gross_value_of_consideration": "51609375.000000",
|
|
107
|
-
}
|
|
108
|
-
],
|
|
109
|
-
},
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
participants_api_response = {
|
|
104
|
+
timeline_resp = [
|
|
105
|
+
{"status": "Announced", "date": "2000-09-12"},
|
|
106
|
+
{"status": "Closed", "date": "2000-09-12"},
|
|
107
|
+
]
|
|
108
|
+
participants_resp = {
|
|
113
109
|
"target": {"company_id": 31696, "company_name": "MongoMusic, Inc."},
|
|
114
110
|
"buyers": [{"company_id": 21835, "company_name": "Microsoft Corporation"}],
|
|
115
111
|
"sellers": [
|
|
@@ -117,33 +113,39 @@ class TestGetMergerInfoFromTransactionId:
|
|
|
117
113
|
{"company_id": 20087, "company_name": "Draper Richards, L.P."},
|
|
118
114
|
],
|
|
119
115
|
}
|
|
120
|
-
api_response = deepcopy(response_base)
|
|
121
|
-
api_response["participants"] = participants_api_response
|
|
122
116
|
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
"
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
"buyers": [
|
|
130
|
-
{"company_id": f"{COMPANY_ID_PREFIX}21835", "company_name": "Microsoft Corporation"}
|
|
131
|
-
],
|
|
132
|
-
"sellers": [
|
|
133
|
-
{"company_id": f"{COMPANY_ID_PREFIX}18805", "company_name": "Angel Investors L.P."},
|
|
117
|
+
consideration_resp = {
|
|
118
|
+
"currency_name": "US Dollar",
|
|
119
|
+
"current_calculated_gross_total_transaction_value": "51609375.000000",
|
|
120
|
+
"current_calculated_implied_equity_value": "51609375.000000",
|
|
121
|
+
"current_calculated_implied_enterprise_value": "51609375.000000",
|
|
122
|
+
"details": [
|
|
134
123
|
{
|
|
135
|
-
"
|
|
136
|
-
"
|
|
137
|
-
|
|
124
|
+
"scenario": "Stock Lump Sum",
|
|
125
|
+
"subtype": "Common Equity",
|
|
126
|
+
"cash_or_cash_equivalent_per_target_share_unit": None,
|
|
127
|
+
"number_of_target_shares_sought": "1000000.000000",
|
|
128
|
+
"current_calculated_gross_value_of_consideration": "51609375.000000",
|
|
129
|
+
}
|
|
138
130
|
],
|
|
139
131
|
}
|
|
140
|
-
|
|
141
|
-
expected_response
|
|
132
|
+
|
|
133
|
+
expected_response = MergerInfo.model_validate(
|
|
134
|
+
{
|
|
135
|
+
"timeline": timeline_resp,
|
|
136
|
+
"participants": participants_resp,
|
|
137
|
+
"consideration": consideration_resp,
|
|
138
|
+
}
|
|
139
|
+
)
|
|
142
140
|
|
|
143
141
|
transaction_id = 517414
|
|
144
142
|
requests_mock.get(
|
|
145
143
|
url=f"https://kfinance.kensho.com/api/v1/merger/info/{transaction_id}",
|
|
146
|
-
json=
|
|
144
|
+
json={
|
|
145
|
+
"timeline": timeline_resp,
|
|
146
|
+
"participants": participants_resp,
|
|
147
|
+
"consideration": consideration_resp,
|
|
148
|
+
},
|
|
147
149
|
)
|
|
148
150
|
tool = GetMergerInfoFromTransactionId(kfinance_client=mock_client)
|
|
149
151
|
args = GetMergerInfoFromTransactionIdArgs(transaction_id=transaction_id)
|
|
@@ -15,11 +15,11 @@ class Prices(BaseModel):
|
|
|
15
15
|
"""
|
|
16
16
|
|
|
17
17
|
date: str
|
|
18
|
-
open: Money
|
|
19
|
-
high: Money
|
|
20
|
-
low: Money
|
|
21
|
-
close: Money
|
|
22
|
-
volume: Shares
|
|
18
|
+
open: Money | None
|
|
19
|
+
high: Money | None
|
|
20
|
+
low: Money | None
|
|
21
|
+
close: Money | None # For Consistency with other OHLC dtype
|
|
22
|
+
volume: Shares | None
|
|
23
23
|
|
|
24
24
|
|
|
25
25
|
class PriceHistory(BaseModel):
|
|
@@ -58,7 +58,8 @@ class PriceHistory(BaseModel):
|
|
|
58
58
|
currency = data["currency"]
|
|
59
59
|
for capitalization in data["prices"]:
|
|
60
60
|
for key in ["open", "high", "low", "close"]:
|
|
61
|
-
capitalization[key]
|
|
61
|
+
if capitalization[key] is not None:
|
|
62
|
+
capitalization[key] = dict(unit=currency, value=capitalization[key])
|
|
62
63
|
return data
|
|
63
64
|
|
|
64
65
|
|
|
@@ -17,10 +17,12 @@ from kfinance.integrations.tool_calling.tool_calling_models import (
|
|
|
17
17
|
|
|
18
18
|
class GetPricesFromIdentifiersArgs(ToolArgsWithIdentifiers):
|
|
19
19
|
start_date: date | None = Field(
|
|
20
|
-
description="The start date for historical price retrieval",
|
|
20
|
+
description="The start date for historical price retrieval. Use null for latest values. For annual queries (e.g., 'prices in 2020'), use January 1st of the year.",
|
|
21
|
+
default=None,
|
|
21
22
|
)
|
|
22
23
|
end_date: date | None = Field(
|
|
23
|
-
description="The end date for historical price retrieval",
|
|
24
|
+
description="The end date for historical price retrieval. Use null for latest values. For annual queries (e.g., 'prices in 2020'), use December 31st of the year.",
|
|
25
|
+
default=None,
|
|
24
26
|
)
|
|
25
27
|
# no description because the description for enum fields comes from the enum docstring.
|
|
26
28
|
periodicity: Periodicity = Field(default=Periodicity.day)
|
|
@@ -40,15 +42,19 @@ class GetPricesFromIdentifiers(KfinanceTool):
|
|
|
40
42
|
Get the historical open, high, low, and close prices, and volume of a group of identifiers between inclusive start_date and inclusive end date.
|
|
41
43
|
|
|
42
44
|
- When possible, pass multiple identifiers in a single call rather than making multiple calls.
|
|
43
|
-
- When requesting the most recent values, leave start_date and end_date
|
|
45
|
+
- When requesting the most recent values, leave start_date and end_date null.
|
|
46
|
+
- For annual queries (e.g., "prices in 2020"), use the full year range from January 1st to December 31st.
|
|
47
|
+
- If requesting prices for long periods of time (e.g., multiple years), consider using a coarser periodicity (e.g., weekly or monthly) to reduce the amount of data returned.
|
|
44
48
|
|
|
45
|
-
|
|
49
|
+
Examples:
|
|
46
50
|
Query: "What are the prices of Facebook and Google?"
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
51
|
+
Function: get_prices_from_identifiers(identifiers=["Facebook", "Google"], start_date=null, end_date=null)
|
|
52
|
+
|
|
53
|
+
Query: "Get prices for META and GOOGL"
|
|
54
|
+
Function: get_prices_from_identifiers(identifiers=["META", "GOOGL"], start_date=null, end_date=null)
|
|
55
|
+
|
|
56
|
+
Query: "How did Meta's stock perform in 2020?"
|
|
57
|
+
Function: get_prices_from_identifiers(identifiers=["Meta"], start_date="2020-01-01", end_date="2020-12-31", periodicity="day")
|
|
52
58
|
""").strip()
|
|
53
59
|
args_schema: Type[BaseModel] = GetPricesFromIdentifiersArgs
|
|
54
60
|
accepted_permissions: set[Permission] | None = {Permission.PricingPermission}
|
|
@@ -60,7 +66,7 @@ class GetPricesFromIdentifiers(KfinanceTool):
|
|
|
60
66
|
end_date: date | None = None,
|
|
61
67
|
periodicity: Periodicity = Periodicity.day,
|
|
62
68
|
adjusted: bool = True,
|
|
63
|
-
) ->
|
|
69
|
+
) -> GetPricesFromIdentifiersResp:
|
|
64
70
|
"""Sample Response:
|
|
65
71
|
|
|
66
72
|
{
|
|
@@ -116,10 +122,9 @@ class GetPricesFromIdentifiers(KfinanceTool):
|
|
|
116
122
|
for price_response in price_responses.values():
|
|
117
123
|
price_response.prices = price_response.prices[-1:]
|
|
118
124
|
|
|
119
|
-
|
|
125
|
+
return GetPricesFromIdentifiersResp(
|
|
120
126
|
results=price_responses, errors=list(id_triple_resp.errors.values())
|
|
121
127
|
)
|
|
122
|
-
return output_model.model_dump(mode="json")
|
|
123
128
|
|
|
124
129
|
|
|
125
130
|
class GetHistoryMetadataFromIdentifiersResp(ToolRespWithErrors):
|
|
@@ -132,11 +137,16 @@ class GetHistoryMetadataFromIdentifiers(KfinanceTool):
|
|
|
132
137
|
Get the history metadata associated with a list of identifiers. History metadata includes currency, symbol, exchange name, instrument type, and first trade date.
|
|
133
138
|
|
|
134
139
|
- When possible, pass multiple identifiers in a single call rather than making multiple calls.
|
|
140
|
+
|
|
141
|
+
Examples:
|
|
142
|
+
Query: "What exchange does Starbucks trade on?"
|
|
143
|
+
Function: get_history_metadata_from_identifiers(identifiers=["Starbucks"])
|
|
144
|
+
|
|
135
145
|
""").strip()
|
|
136
146
|
args_schema: Type[BaseModel] = ToolArgsWithIdentifiers
|
|
137
147
|
accepted_permissions: set[Permission] | None = None
|
|
138
148
|
|
|
139
|
-
def _run(self, identifiers: list[str]) ->
|
|
149
|
+
def _run(self, identifiers: list[str]) -> GetHistoryMetadataFromIdentifiersResp:
|
|
140
150
|
"""Sample response:
|
|
141
151
|
|
|
142
152
|
{
|
|
@@ -169,8 +179,6 @@ class GetHistoryMetadataFromIdentifiers(KfinanceTool):
|
|
|
169
179
|
history_metadata_responses: dict[str, HistoryMetadataResp] = (
|
|
170
180
|
process_tasks_in_thread_pool_executor(api_client=api_client, tasks=tasks)
|
|
171
181
|
)
|
|
172
|
-
|
|
182
|
+
return GetHistoryMetadataFromIdentifiersResp(
|
|
173
183
|
results=history_metadata_responses, errors=list(id_triple_resp.errors.values())
|
|
174
184
|
)
|
|
175
|
-
|
|
176
|
-
return output_model.model_dump(mode="json")
|
|
@@ -5,8 +5,10 @@ from kfinance.conftest import SPGI_TRADING_ITEM_ID
|
|
|
5
5
|
from kfinance.domains.companies.company_models import COMPANY_ID_PREFIX
|
|
6
6
|
from kfinance.domains.prices.price_tools import (
|
|
7
7
|
GetHistoryMetadataFromIdentifiers,
|
|
8
|
+
GetHistoryMetadataFromIdentifiersResp,
|
|
8
9
|
GetPricesFromIdentifiers,
|
|
9
10
|
GetPricesFromIdentifiersArgs,
|
|
11
|
+
GetPricesFromIdentifiersResp,
|
|
10
12
|
)
|
|
11
13
|
from kfinance.integrations.tool_calling.tool_calling_models import ToolArgsWithIdentifiers
|
|
12
14
|
|
|
@@ -27,12 +29,14 @@ class TestGetHistoryMetadataFromIdentifiers:
|
|
|
27
29
|
"instrument_type": "Equity",
|
|
28
30
|
"symbol": "SPGI",
|
|
29
31
|
}
|
|
30
|
-
expected_resp =
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
"
|
|
34
|
-
|
|
35
|
-
|
|
32
|
+
expected_resp = GetHistoryMetadataFromIdentifiersResp.model_validate(
|
|
33
|
+
{
|
|
34
|
+
"results": {"SPGI": metadata_resp},
|
|
35
|
+
"errors": [
|
|
36
|
+
"No identification triple found for the provided identifier: NON-EXISTENT of type: ticker"
|
|
37
|
+
],
|
|
38
|
+
}
|
|
39
|
+
)
|
|
36
40
|
|
|
37
41
|
requests_mock.get(
|
|
38
42
|
url=f"https://kfinance.kensho.com/api/v1/pricing/{SPGI_TRADING_ITEM_ID}/metadata",
|
|
@@ -80,33 +84,35 @@ class TestGetPricesFromIdentifiers:
|
|
|
80
84
|
url=f"https://kfinance.kensho.com/api/v1/pricing/{SPGI_TRADING_ITEM_ID}/none/none/day/adjusted",
|
|
81
85
|
json=self.prices_resp,
|
|
82
86
|
)
|
|
83
|
-
expected_response =
|
|
84
|
-
|
|
85
|
-
"
|
|
86
|
-
"
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
"
|
|
108
|
-
|
|
109
|
-
|
|
87
|
+
expected_response = GetPricesFromIdentifiersResp.model_validate(
|
|
88
|
+
{
|
|
89
|
+
"results": {
|
|
90
|
+
"SPGI": {
|
|
91
|
+
"prices": [
|
|
92
|
+
{
|
|
93
|
+
"date": "2024-04-11",
|
|
94
|
+
"open": {"value": "424.26", "unit": "USD"},
|
|
95
|
+
"high": {"value": "425.99", "unit": "USD"},
|
|
96
|
+
"low": {"value": "422.04", "unit": "USD"},
|
|
97
|
+
"close": {"value": "422.92", "unit": "USD"},
|
|
98
|
+
"volume": {"value": "1129158", "unit": "Shares"},
|
|
99
|
+
},
|
|
100
|
+
{
|
|
101
|
+
"date": "2024-04-12",
|
|
102
|
+
"open": {"value": "419.23", "unit": "USD"},
|
|
103
|
+
"high": {"value": "421.94", "unit": "USD"},
|
|
104
|
+
"low": {"value": "416.45", "unit": "USD"},
|
|
105
|
+
"close": {"value": "417.81", "unit": "USD"},
|
|
106
|
+
"volume": {"value": "1182229", "unit": "Shares"},
|
|
107
|
+
},
|
|
108
|
+
]
|
|
109
|
+
}
|
|
110
|
+
},
|
|
111
|
+
"errors": [
|
|
112
|
+
"No identification triple found for the provided identifier: NON-EXISTENT of type: ticker"
|
|
113
|
+
],
|
|
114
|
+
}
|
|
115
|
+
)
|
|
110
116
|
|
|
111
117
|
tool = GetPricesFromIdentifiers(kfinance_client=mock_client)
|
|
112
118
|
response = tool.run(
|
|
@@ -142,12 +148,14 @@ class TestGetPricesFromIdentifiers:
|
|
|
142
148
|
}
|
|
143
149
|
]
|
|
144
150
|
}
|
|
145
|
-
expected_response =
|
|
146
|
-
|
|
147
|
-
"
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
+
expected_response = GetPricesFromIdentifiersResp.model_validate(
|
|
152
|
+
{
|
|
153
|
+
"results": {
|
|
154
|
+
"C_1": expected_single_company_response,
|
|
155
|
+
"C_2": expected_single_company_response,
|
|
156
|
+
},
|
|
157
|
+
}
|
|
158
|
+
)
|
|
151
159
|
tool = GetPricesFromIdentifiers(kfinance_client=mock_client)
|
|
152
160
|
response = tool.run(
|
|
153
161
|
GetPricesFromIdentifiersArgs(
|
|
@@ -1,8 +1,10 @@
|
|
|
1
|
-
from
|
|
1
|
+
from datetime import date
|
|
2
2
|
|
|
3
3
|
from pydantic import BaseModel
|
|
4
4
|
from strenum import StrEnum
|
|
5
5
|
|
|
6
|
+
from kfinance.domains.line_items.line_item_models import BasePeriodsResp, LineItem
|
|
7
|
+
|
|
6
8
|
|
|
7
9
|
class SegmentType(StrEnum):
|
|
8
10
|
"""The type of segment"""
|
|
@@ -11,5 +13,17 @@ class SegmentType(StrEnum):
|
|
|
11
13
|
geographic = "geographic"
|
|
12
14
|
|
|
13
15
|
|
|
14
|
-
class
|
|
15
|
-
|
|
16
|
+
class Segment(BaseModel):
|
|
17
|
+
name: str
|
|
18
|
+
line_items: list[LineItem]
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class SegmentPeriodData(BaseModel):
|
|
22
|
+
period_end_date: date
|
|
23
|
+
num_months: int
|
|
24
|
+
segments: list[Segment]
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
class SegmentsResp(BasePeriodsResp):
|
|
28
|
+
currency: str | None
|
|
29
|
+
periods: dict[str, SegmentPeriodData] # period -> segment and period data
|
|
@@ -1,10 +1,11 @@
|
|
|
1
|
+
from textwrap import dedent
|
|
1
2
|
from typing import Literal, Type
|
|
2
3
|
|
|
3
4
|
from pydantic import BaseModel, Field
|
|
4
5
|
|
|
5
|
-
from kfinance.client.
|
|
6
|
-
from kfinance.client.models.date_and_period_models import PeriodType
|
|
6
|
+
from kfinance.client.models.date_and_period_models import NumPeriods, NumPeriodsBack, PeriodType
|
|
7
7
|
from kfinance.client.permission_models import Permission
|
|
8
|
+
from kfinance.domains.line_items.line_item_models import CalendarType
|
|
8
9
|
from kfinance.domains.segments.segment_models import SegmentsResp, SegmentType
|
|
9
10
|
from kfinance.integrations.tool_calling.tool_calling_models import (
|
|
10
11
|
KfinanceTool,
|
|
@@ -22,15 +23,42 @@ class GetSegmentsFromIdentifiersArgs(ToolArgsWithIdentifiers):
|
|
|
22
23
|
end_year: int | None = Field(default=None, description="The ending year for the data range")
|
|
23
24
|
start_quarter: ValidQuarter | None = Field(default=None, description="Starting quarter")
|
|
24
25
|
end_quarter: ValidQuarter | None = Field(default=None, description="Ending quarter")
|
|
26
|
+
calendar_type: CalendarType | None = Field(
|
|
27
|
+
default=None, description="Fiscal year or calendar year"
|
|
28
|
+
)
|
|
29
|
+
num_periods: NumPeriods | None = Field(
|
|
30
|
+
default=None, description="The number of periods to retrieve data for (1-99)"
|
|
31
|
+
)
|
|
32
|
+
num_periods_back: NumPeriodsBack | None = Field(
|
|
33
|
+
default=None,
|
|
34
|
+
description="The end period of the data range expressed as number of periods back relative to the present period (0-99)",
|
|
35
|
+
)
|
|
25
36
|
|
|
26
37
|
|
|
27
38
|
class GetSegmentsFromIdentifiersResp(ToolRespWithErrors):
|
|
28
|
-
results: dict[str, SegmentsResp]
|
|
39
|
+
results: dict[str, SegmentsResp] # identifier -> response
|
|
29
40
|
|
|
30
41
|
|
|
31
42
|
class GetSegmentsFromIdentifiers(KfinanceTool):
|
|
32
43
|
name: str = "get_segments_from_identifiers"
|
|
33
|
-
description: str = "
|
|
44
|
+
description: str = dedent("""
|
|
45
|
+
Get the templated business or geographic segments associated with a list of identifiers.
|
|
46
|
+
|
|
47
|
+
- When possible, pass multiple identifiers in a single call rather than making multiple calls.
|
|
48
|
+
- The tool accepts an optional calendar_type argument, which can either be 'calendar' or 'fiscal'. If 'calendar' is chosen, then start_year and end_year will filter on calendar year, and the output returned will be in calendar years. If 'fiscal' is chosen (which is the default), then start_year and end_year will filter on fiscal year, and the output returned will be in fiscal years.
|
|
49
|
+
- To fetch the most recent segment data, leave start_year, start_quarter, end_year, end_quarter, num_periods, and num_periods_back as None.
|
|
50
|
+
- To filter by time, use either absolute (start_year, end_year, start_quarter, end_quarter) for specific dates like "in 2023" or "Q2 2021", OR relative (num_periods, num_periods_back) for phrases like "last 3 quarters" or "past five years"—but not both.
|
|
51
|
+
|
|
52
|
+
Examples:
|
|
53
|
+
Query: "What are the business segments for AT&T?"
|
|
54
|
+
Function: get_segments_from_identifiers(identifiers=["AT&T"], segment_type="business")
|
|
55
|
+
|
|
56
|
+
Query: "Get geographic segments for PFE and JNJ"
|
|
57
|
+
Function: get_segments_from_identifiers(identifiers=["PFE", "JNJ"], segment_type="geographic")
|
|
58
|
+
|
|
59
|
+
Query: "What are the ltm business segments for S&P for the last three calendar quarters but one?"
|
|
60
|
+
Function: get_segments_from_identifiers(segment_type="business", period_type="ltm", calendar_type="calendar", num_periods=3, num_periods_back=1, identifiers=["SPGI"])
|
|
61
|
+
""").strip()
|
|
34
62
|
args_schema: Type[BaseModel] = GetSegmentsFromIdentifiersArgs
|
|
35
63
|
accepted_permissions: set[Permission] | None = {Permission.SegmentsPermission}
|
|
36
64
|
|
|
@@ -43,50 +71,83 @@ class GetSegmentsFromIdentifiers(KfinanceTool):
|
|
|
43
71
|
end_year: int | None = None,
|
|
44
72
|
start_quarter: Literal[1, 2, 3, 4] | None = None,
|
|
45
73
|
end_quarter: Literal[1, 2, 3, 4] | None = None,
|
|
46
|
-
|
|
47
|
-
|
|
74
|
+
calendar_type: CalendarType | None = None,
|
|
75
|
+
num_periods: int | None = None,
|
|
76
|
+
num_periods_back: int | None = None,
|
|
77
|
+
) -> GetSegmentsFromIdentifiersResp:
|
|
78
|
+
"""Sample response:
|
|
48
79
|
|
|
49
80
|
{
|
|
50
81
|
'results': {
|
|
51
82
|
'SPGI': {
|
|
52
|
-
'
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
'
|
|
83
|
+
'currency': 'USD',
|
|
84
|
+
'periods': {
|
|
85
|
+
'CY2021': {
|
|
86
|
+
'period_end_date': '2021-12-31',
|
|
87
|
+
'num_months': 12,
|
|
88
|
+
'segments': [
|
|
89
|
+
{
|
|
90
|
+
'name': 'Commodity Insights',
|
|
91
|
+
'line_items': [
|
|
92
|
+
{
|
|
93
|
+
'name': 'CAPEX',
|
|
94
|
+
'value': -2000000.0,
|
|
95
|
+
'sources': [
|
|
96
|
+
{
|
|
97
|
+
'type': 'doc-viewer segment',
|
|
98
|
+
'url': 'https://www.capitaliq.spglobal.com/...'
|
|
99
|
+
}
|
|
100
|
+
]
|
|
101
|
+
},
|
|
102
|
+
{
|
|
103
|
+
'name': 'D&A',
|
|
104
|
+
'value': 12000000.0
|
|
105
|
+
}
|
|
106
|
+
]
|
|
107
|
+
},
|
|
108
|
+
{
|
|
109
|
+
'name': 'Unallocated Assets Held for Sale',
|
|
110
|
+
'line_items': [
|
|
111
|
+
{
|
|
112
|
+
'name': 'Total Assets',
|
|
113
|
+
'value': 321000000.0
|
|
114
|
+
}
|
|
115
|
+
]
|
|
116
|
+
}
|
|
117
|
+
]
|
|
56
118
|
}
|
|
57
119
|
}
|
|
58
120
|
}
|
|
59
121
|
},
|
|
60
122
|
'errors': ['No identification triple found for the provided identifier: NON-EXISTENT of type: ticker']
|
|
61
123
|
}
|
|
62
|
-
|
|
63
|
-
|
|
64
124
|
"""
|
|
65
125
|
|
|
66
126
|
api_client = self.kfinance_client.kfinance_api_client
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
for identifier, id_triple in id_triple_resp.identifiers_to_id_triples.items()
|
|
84
|
-
]
|
|
85
|
-
|
|
86
|
-
segments_responses: dict[str, SegmentsResp] = process_tasks_in_thread_pool_executor(
|
|
87
|
-
api_client=api_client, tasks=tasks
|
|
127
|
+
|
|
128
|
+
# First resolve identifiers to company IDs
|
|
129
|
+
ids_response = api_client.unified_fetch_id_triples(identifiers)
|
|
130
|
+
|
|
131
|
+
# Call the simplified fetch_segments API with company IDs
|
|
132
|
+
response = api_client.fetch_segments(
|
|
133
|
+
company_ids=ids_response.company_ids,
|
|
134
|
+
segment_type=segment_type,
|
|
135
|
+
period_type=period_type,
|
|
136
|
+
start_year=start_year,
|
|
137
|
+
end_year=end_year,
|
|
138
|
+
start_quarter=start_quarter,
|
|
139
|
+
end_quarter=end_quarter,
|
|
140
|
+
calendar_type=calendar_type,
|
|
141
|
+
num_periods=num_periods,
|
|
142
|
+
num_periods_back=num_periods_back,
|
|
88
143
|
)
|
|
89
144
|
|
|
145
|
+
identifier_to_results = {}
|
|
146
|
+
for company_id_str, segments_resp in response.results.items():
|
|
147
|
+
company_id = int(company_id_str)
|
|
148
|
+
original_identifier = ids_response.get_identifier_from_company_id(company_id)
|
|
149
|
+
identifier_to_results[original_identifier] = segments_resp
|
|
150
|
+
|
|
90
151
|
# If no date and multiple companies, only return the most recent value.
|
|
91
152
|
# By default, we return 5 years of data, which can be too much when
|
|
92
153
|
# returning data for many companies.
|
|
@@ -95,14 +156,13 @@ class GetSegmentsFromIdentifiers(KfinanceTool):
|
|
|
95
156
|
and end_year is None
|
|
96
157
|
and start_quarter is None
|
|
97
158
|
and end_quarter is None
|
|
98
|
-
and
|
|
159
|
+
and num_periods is None
|
|
160
|
+
and num_periods_back is None
|
|
161
|
+
and len(identifier_to_results) > 1
|
|
99
162
|
):
|
|
100
|
-
for segments_response in
|
|
101
|
-
|
|
102
|
-
most_recent_year_data = segments_response.segments[most_recent_year]
|
|
103
|
-
segments_response.segments = {most_recent_year: most_recent_year_data}
|
|
163
|
+
for segments_response in identifier_to_results.values():
|
|
164
|
+
segments_response.remove_all_periods_other_than_the_most_recent_one()
|
|
104
165
|
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
)
|
|
108
|
-
return output_model.model_dump(mode="json")
|
|
166
|
+
all_errors = list(ids_response.errors.values()) + list(response.errors.values())
|
|
167
|
+
|
|
168
|
+
return GetSegmentsFromIdentifiersResp(results=identifier_to_results, errors=all_errors)
|