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.
Files changed (57) hide show
  1. {kensho_kfinance-3.2.4.dist-info → kensho_kfinance-4.0.0.dist-info}/METADATA +3 -3
  2. {kensho_kfinance-3.2.4.dist-info → kensho_kfinance-4.0.0.dist-info}/RECORD +57 -56
  3. kfinance/CHANGELOG.md +51 -0
  4. kfinance/client/batch_request_handling.py +3 -1
  5. kfinance/client/fetch.py +127 -54
  6. kfinance/client/kfinance.py +38 -39
  7. kfinance/client/meta_classes.py +50 -20
  8. kfinance/client/models/date_and_period_models.py +32 -7
  9. kfinance/client/models/decimal_with_unit.py +14 -2
  10. kfinance/client/models/response_models.py +33 -0
  11. kfinance/client/models/tests/test_decimal_with_unit.py +9 -0
  12. kfinance/client/tests/test_batch_requests.py +5 -4
  13. kfinance/client/tests/test_fetch.py +134 -58
  14. kfinance/client/tests/test_objects.py +207 -145
  15. kfinance/conftest.py +10 -0
  16. kfinance/domains/business_relationships/business_relationship_tools.py +17 -8
  17. kfinance/domains/business_relationships/tests/test_business_relationship_tools.py +18 -16
  18. kfinance/domains/capitalizations/capitalization_models.py +7 -5
  19. kfinance/domains/capitalizations/capitalization_tools.py +38 -20
  20. kfinance/domains/capitalizations/tests/test_capitalization_tools.py +66 -36
  21. kfinance/domains/companies/company_models.py +22 -2
  22. kfinance/domains/companies/company_tools.py +49 -16
  23. kfinance/domains/companies/tests/test_company_tools.py +27 -9
  24. kfinance/domains/competitors/competitor_tools.py +19 -5
  25. kfinance/domains/competitors/tests/test_competitor_tools.py +22 -19
  26. kfinance/domains/cusip_and_isin/cusip_and_isin_tools.py +29 -8
  27. kfinance/domains/cusip_and_isin/tests/test_cusip_and_isin_tools.py +13 -8
  28. kfinance/domains/earnings/earning_tools.py +73 -29
  29. kfinance/domains/earnings/tests/test_earnings_tools.py +52 -43
  30. kfinance/domains/line_items/line_item_models.py +372 -16
  31. kfinance/domains/line_items/line_item_tools.py +198 -46
  32. kfinance/domains/line_items/tests/test_line_item_tools.py +305 -39
  33. kfinance/domains/mergers_and_acquisitions/merger_and_acquisition_models.py +46 -2
  34. kfinance/domains/mergers_and_acquisitions/merger_and_acquisition_tools.py +55 -74
  35. kfinance/domains/mergers_and_acquisitions/tests/test_merger_and_acquisition_tools.py +61 -59
  36. kfinance/domains/prices/price_models.py +7 -6
  37. kfinance/domains/prices/price_tools.py +24 -16
  38. kfinance/domains/prices/tests/test_price_tools.py +47 -39
  39. kfinance/domains/segments/segment_models.py +17 -3
  40. kfinance/domains/segments/segment_tools.py +102 -42
  41. kfinance/domains/segments/tests/test_segment_tools.py +166 -37
  42. kfinance/domains/statements/statement_models.py +17 -3
  43. kfinance/domains/statements/statement_tools.py +130 -46
  44. kfinance/domains/statements/tests/test_statement_tools.py +251 -49
  45. kfinance/integrations/local_mcp/kfinance_mcp.py +1 -1
  46. kfinance/integrations/tests/test_example_notebook.py +57 -16
  47. kfinance/integrations/tool_calling/all_tools.py +5 -1
  48. kfinance/integrations/tool_calling/static_tools/get_n_quarters_ago.py +5 -0
  49. kfinance/integrations/tool_calling/static_tools/tests/test_get_lastest.py +13 -10
  50. kfinance/integrations/tool_calling/static_tools/tests/test_get_n_quarters_ago.py +2 -1
  51. kfinance/integrations/tool_calling/tests/test_tool_calling_models.py +15 -4
  52. kfinance/integrations/tool_calling/tool_calling_models.py +18 -6
  53. kfinance/version.py +2 -2
  54. {kensho_kfinance-3.2.4.dist-info → kensho_kfinance-4.0.0.dist-info}/WHEEL +0 -0
  55. {kensho_kfinance-3.2.4.dist-info → kensho_kfinance-4.0.0.dist-info}/licenses/AUTHORS.md +0 -0
  56. {kensho_kfinance-3.2.4.dist-info → kensho_kfinance-4.0.0.dist-info}/licenses/LICENSE +0 -0
  57. {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.companies.company_models import COMPANY_ID_PREFIX
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
- "results": {"SPGI": merger_data},
32
- "errors": [
33
- "No identification triple found for the provided identifier: NON-EXISTENT of type: ticker"
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
- api_response = {"advisors": [deepcopy(advisor_data)]}
55
- expected_response = {"results": [deepcopy(advisor_data)]}
56
- expected_response["results"][0]["advisor_company_id"] = f"{COMPANY_ID_PREFIX}251994106"
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=api_response,
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, requests_mock: Mocker, mock_client: Client
85
+ self, mock_client: Client
71
86
  ):
72
- expected_response = {
73
- "results": [],
74
- "errors": [
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
- response_base = {
90
- "timeline": [
91
- {"status": "Announced", "date": "2000-09-12"},
92
- {"status": "Closed", "date": "2000-09-12"},
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
- # Returned company IDs should be prefixed
124
- expected_participants_tool_response = {
125
- "target": {
126
- "company_id": f"{COMPANY_ID_PREFIX}31696",
127
- "company_name": "MongoMusic, Inc.",
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
- "company_id": f"{COMPANY_ID_PREFIX}20087",
136
- "company_name": "Draper Richards, L.P.",
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
- expected_response = deepcopy(response_base)
141
- expected_response["participants"] = expected_participants_tool_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=api_response,
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] = dict(unit=currency, value=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", default=None
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", default=None
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 empty.
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
- Example:
49
+ Examples:
46
50
  Query: "What are the prices of Facebook and Google?"
47
- Do:
48
- get_prices_from_identifiers(identifiers=["META", "GOOGL"])
49
- Don't:
50
- get_prices_from_identifiers(trading_item_ids=["META"])
51
- get_prices_from_identifiers(trading_item_ids=["GOOGL"])
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
- ) -> dict:
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
- output_model = GetPricesFromIdentifiersResp(
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]) -> dict:
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
- output_model = GetHistoryMetadataFromIdentifiersResp(
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
- "results": {"SPGI": metadata_resp},
32
- "errors": [
33
- "No identification triple found for the provided identifier: NON-EXISTENT of type: ticker"
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
- "results": {
85
- "SPGI": {
86
- "prices": [
87
- {
88
- "date": "2024-04-11",
89
- "open": {"value": "424.26", "unit": "USD"},
90
- "high": {"value": "425.99", "unit": "USD"},
91
- "low": {"value": "422.04", "unit": "USD"},
92
- "close": {"value": "422.92", "unit": "USD"},
93
- "volume": {"value": "1129158", "unit": "Shares"},
94
- },
95
- {
96
- "date": "2024-04-12",
97
- "open": {"value": "419.23", "unit": "USD"},
98
- "high": {"value": "421.94", "unit": "USD"},
99
- "low": {"value": "416.45", "unit": "USD"},
100
- "close": {"value": "417.81", "unit": "USD"},
101
- "volume": {"value": "1182229", "unit": "Shares"},
102
- },
103
- ]
104
- }
105
- },
106
- "errors": [
107
- "No identification triple found for the provided identifier: NON-EXISTENT of type: ticker"
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
- "results": {
147
- "C_1": expected_single_company_response,
148
- "C_2": expected_single_company_response,
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 typing import Any
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 SegmentsResp(BaseModel):
15
- segments: dict[str, Any]
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.batch_request_handling import Task, process_tasks_in_thread_pool_executor
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 = "Get the templated segments associated with a list of identifiers."
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
- ) -> dict:
47
- """Sample Response:
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
- 'segments': {
53
- '2021': {
54
- 'Commodity Insights': {'CAPEX': -2000000.0, 'D&A': 12000000.0},
55
- 'Unallocated Assets Held for Sale': {'Total Assets': 321000000.0}
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
- id_triple_resp = api_client.unified_fetch_id_triples(identifiers=identifiers)
68
-
69
- tasks = [
70
- Task(
71
- func=api_client.fetch_segments,
72
- kwargs=dict(
73
- company_id=id_triple.company_id,
74
- segment_type=segment_type,
75
- period_type=period_type,
76
- start_year=start_year,
77
- end_year=end_year,
78
- start_quarter=start_quarter,
79
- end_quarter=end_quarter,
80
- ),
81
- result_key=identifier,
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 len(segments_responses) > 1
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 segments_responses.values():
101
- most_recent_year = max(segments_response.segments.keys())
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
- output_model = GetSegmentsFromIdentifiersResp(
106
- results=segments_responses, errors=list(id_triple_resp.errors.values())
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)