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
@@ -18,14 +18,17 @@ class GetCapitalizationFromIdentifiersArgs(ToolArgsWithIdentifiers):
18
18
  # no description because the description for enum fields comes from the enum docstring.
19
19
  capitalization: Capitalization
20
20
  start_date: date | None = Field(
21
- description="The start date for historical capitalization retrieval", default=None
21
+ description="The start date for historical capitalization retrieval. Use null for latest values. For annual data, use January 1st of the year.",
22
+ default=None,
22
23
  )
23
24
  end_date: date | None = Field(
24
- description="The end date for historical capitalization retrieval", default=None
25
+ description="The end date for historical capitalization retrieval. Use null for latest values. For annual data, use December 31st of the year.",
26
+ default=None,
25
27
  )
26
28
 
27
29
 
28
30
  class GetCapitalizationFromIdentifiersResp(ToolRespWithErrors):
31
+ capitalization: Capitalization
29
32
  results: dict[str, Capitalizations]
30
33
 
31
34
 
@@ -35,11 +38,20 @@ class GetCapitalizationFromIdentifiers(KfinanceTool):
35
38
  Get the historical market cap, tev (Total Enterprise Value), or shares outstanding for a group of identifiers between inclusive start_date and inclusive end date.
36
39
 
37
40
  - When possible, pass multiple identifiers in a single call rather than making multiple calls.
38
- - When requesting the most recent values, leave start_date and end_date empty.
41
+ - When requesting the most recent values, leave start_date and end_date null.
42
+ - For annual data (e.g., "market cap in 2020", "FY2021 values"), use the full year range: start_date as January 1st and end_date as December 31st.
43
+ - For "latest" or "current" values, always leave dates null to get the most recent data point.
44
+ - Only specify date ranges when the user explicitly requests historical data over a specific period.
45
+
46
+ Examples:
47
+ Query: "What are the market caps of Visa and Mastercard?"
48
+ Function: get_capitalization_from_identifiers(capitalization="market_cap", identifiers=["Visa", "Mastercard"], start_date=null, end_date=null)
49
+
50
+ Query: "What was MDT's market cap in 2020?"
51
+ Function: get_capitalization_from_identifiers(capitalization="market_cap", identifiers=["MDT"], start_date="2020-01-01", end_date="2020-12-31")
39
52
 
40
- Example:
41
- Query: "What are the market caps of AAPL and WMT?"
42
- Function: get_capitalization_from_identifiers(capitalization=Capitalization.market_cap, identifiers=["AAPL", "WMT"])
53
+ Query: "Market cap trends for MSFT from Q1 2020 to Q3 2021"
54
+ Function: get_capitalization_from_identifiers(capitalization="market_cap", identifiers=["MSFT"], start_date="2020-01-01", end_date="2021-09-30")
43
55
  """).strip()
44
56
  args_schema: Type[BaseModel] = GetCapitalizationFromIdentifiersArgs
45
57
  accepted_permissions: set[Permission] | None = {Permission.PricingPermission}
@@ -50,13 +62,13 @@ class GetCapitalizationFromIdentifiers(KfinanceTool):
50
62
  capitalization: Capitalization,
51
63
  start_date: str | None = None,
52
64
  end_date: str | None = None,
53
- ) -> dict:
65
+ ) -> GetCapitalizationFromIdentifiersResp:
54
66
  """Sample response:
55
67
 
56
68
  {
69
+ 'capitalization': 'market_cap'
57
70
  'results': {
58
71
  'SPGI': {
59
- 'capitalizations': [
60
72
  {'date': '2024-04-10', 'market_cap': {'value': '132766738270.00', 'unit': 'USD'}},
61
73
  {'date': '2024-04-11', 'market_cap': {'value': '132416066761.00', 'unit': 'USD'}}
62
74
  ]
@@ -83,7 +95,11 @@ class GetCapitalizationFromIdentifiers(KfinanceTool):
83
95
  api_client=api_client, tasks=tasks
84
96
  )
85
97
 
86
- for capitalization_response in capitalization_responses.values():
98
+ for identifier, capitalization_response in capitalization_responses.items():
99
+ # If we get an empty response for a company, assign an empty object
100
+ if not capitalization_response:
101
+ capitalization_responses[identifier] = Capitalizations(capitalizations=list())
102
+ capitalization_response = capitalization_responses[identifier]
87
103
  # If we return results for more than one company and the start and end dates are unset,
88
104
  # truncate data to only return the most recent datapoint.
89
105
  if len(capitalization_responses) > 1 and start_date is None and end_date is None:
@@ -92,15 +108,17 @@ class GetCapitalizationFromIdentifiers(KfinanceTool):
92
108
  ]
93
109
  # Set capitalizations that were not requested to None.
94
110
  # That way, they can be skipped for serialization via `exclude_none=True`
95
- for daily_capitalization in capitalization_response.capitalizations:
96
- if capitalization is not Capitalization.market_cap:
97
- daily_capitalization.market_cap = None
98
- if capitalization is not Capitalization.tev:
99
- daily_capitalization.tev = None
100
- if capitalization is not Capitalization.shares_outstanding:
101
- daily_capitalization.shares_outstanding = None
102
-
103
- resp_model = GetCapitalizationFromIdentifiersResp(
104
- results=capitalization_responses, errors=list(id_triple_resp.errors.values())
111
+ if capitalization_response.capitalizations:
112
+ for daily_capitalization in capitalization_response.capitalizations:
113
+ if capitalization is not Capitalization.market_cap:
114
+ daily_capitalization.market_cap = None
115
+ if capitalization is not Capitalization.tev:
116
+ daily_capitalization.tev = None
117
+ if capitalization is not Capitalization.shares_outstanding:
118
+ daily_capitalization.shares_outstanding = None
119
+
120
+ return GetCapitalizationFromIdentifiersResp(
121
+ capitalization=capitalization,
122
+ results=capitalization_responses,
123
+ errors=list(id_triple_resp.errors.values()),
105
124
  )
106
- return resp_model.model_dump(mode="json", exclude_none=True)
@@ -1,11 +1,20 @@
1
+ from datetime import date
2
+ from decimal import Decimal
3
+
1
4
  from requests_mock import Mocker
2
5
 
3
6
  from kfinance.client.kfinance import Client
7
+ from kfinance.client.models.decimal_with_unit import Money
4
8
  from kfinance.conftest import SPGI_COMPANY_ID
5
- from kfinance.domains.capitalizations.capitalization_models import Capitalization
9
+ from kfinance.domains.capitalizations.capitalization_models import (
10
+ Capitalization,
11
+ Capitalizations,
12
+ DailyCapitalization,
13
+ )
6
14
  from kfinance.domains.capitalizations.capitalization_tools import (
7
15
  GetCapitalizationFromIdentifiers,
8
16
  GetCapitalizationFromIdentifiersArgs,
17
+ GetCapitalizationFromIdentifiersResp,
9
18
  )
10
19
  from kfinance.domains.companies.company_models import COMPANY_ID_PREFIX
11
20
 
@@ -40,25 +49,30 @@ class TestGetCapitalizationFromCompanyIds:
40
49
  json=self.market_caps_resp,
41
50
  )
42
51
 
43
- expected_response = {
44
- "results": {
45
- "SPGI": {
46
- "capitalizations": [
47
- {
48
- "date": "2024-04-10",
49
- "market_cap": {"value": "132766738270.00", "unit": "USD"},
50
- },
51
- {
52
- "date": "2024-04-11",
53
- "market_cap": {"value": "132416066761.00", "unit": "USD"},
54
- },
52
+ expected_response = GetCapitalizationFromIdentifiersResp(
53
+ capitalization=Capitalization.market_cap,
54
+ results={
55
+ "SPGI": Capitalizations(
56
+ market_caps=[
57
+ DailyCapitalization(
58
+ date=date(2024, 4, 10),
59
+ market_cap=Money(value=Decimal(132766738270), unit="USD"),
60
+ tev=None,
61
+ shares_outstanding=None,
62
+ ),
63
+ DailyCapitalization(
64
+ date=date(2024, 4, 11),
65
+ market_cap=Money(value=Decimal(132416066761), unit="USD"),
66
+ tev=None,
67
+ shares_outstanding=None,
68
+ ),
55
69
  ]
56
- }
70
+ ),
57
71
  },
58
- "errors": [
72
+ errors=[
59
73
  "No identification triple found for the provided identifier: NON-EXISTENT of type: ticker"
60
74
  ],
61
- }
75
+ )
62
76
 
63
77
  tool = GetCapitalizationFromIdentifiers(kfinance_client=mock_client)
64
78
  args = GetCapitalizationFromIdentifiersArgs(
@@ -67,32 +81,48 @@ class TestGetCapitalizationFromCompanyIds:
67
81
  response = tool.run(args.model_dump(mode="json"))
68
82
  assert response == expected_response
69
83
 
84
+ def test_get_capitalization_from_identifiers_property_400(
85
+ self, requests_mock: Mocker, mock_client: Client
86
+ ) -> None:
87
+ requests_mock.get(
88
+ url=f"https://kfinance.kensho.com/api/v1/market_cap/1/none/none",
89
+ status_code=400,
90
+ )
91
+
92
+ expected_response = GetCapitalizationFromIdentifiersResp(
93
+ capitalization=Capitalization.market_cap,
94
+ results={"C_1": Capitalizations(market_caps=list())},
95
+ )
96
+
97
+ tool = GetCapitalizationFromIdentifiers(kfinance_client=mock_client)
98
+ args = GetCapitalizationFromIdentifiersArgs(
99
+ identifiers=["C_1"], capitalization=Capitalization.market_cap
100
+ )
101
+ response = tool.run(args.model_dump(mode="json"))
102
+ assert response == expected_response
103
+
70
104
  def test_most_recent_request(self, requests_mock: Mocker, mock_client: Client) -> None:
71
105
  """
72
106
  GIVEN the GetCapitalizationFromIdentifiers tool
73
107
  WHEN we request most recent market caps for multiple companies
74
108
  THEN we only get back the most recent market cap for each company
75
109
  """
76
- expected_response = {
77
- "results": {
78
- "C_1": {
79
- "capitalizations": [
80
- {
81
- "date": "2024-04-11",
82
- "market_cap": {"unit": "USD", "value": "132416066761.00"},
83
- }
84
- ]
85
- },
86
- "C_2": {
87
- "capitalizations": [
88
- {
89
- "date": "2024-04-11",
90
- "market_cap": {"unit": "USD", "value": "132416066761.00"},
91
- }
92
- ]
93
- },
94
- }
95
- }
110
+
111
+ capitalization = Capitalizations(
112
+ market_caps=[
113
+ DailyCapitalization(
114
+ date=date(2024, 4, 11),
115
+ market_cap=Money(value=Decimal(132416066761), unit="USD"),
116
+ tev=None,
117
+ shares_outstanding=None,
118
+ )
119
+ ]
120
+ )
121
+
122
+ expected_response = GetCapitalizationFromIdentifiersResp(
123
+ capitalization=Capitalization.market_cap,
124
+ results={"C_1": capitalization, "C_2": capitalization},
125
+ )
96
126
 
97
127
  company_ids = [1, 2]
98
128
  for company_id in company_ids:
@@ -84,14 +84,20 @@ class UnifiedIdTripleResponse(BaseModel):
84
84
 
85
85
 
86
86
  """
87
- output: dict[str, dict] = dict(identifiers_to_id_triples=dict(), errors=dict())
87
+ # Separate successful and failed resolutions for kfinance api responses
88
88
  if isinstance(data, dict) and "data" in data:
89
+ output: dict[str, dict] = dict(identifiers_to_id_triples=dict(), errors=dict())
90
+
89
91
  for key, val in data["data"].items():
90
92
  if "error" in val:
91
93
  output["errors"][key] = val["error"]
92
94
  else:
93
95
  output["identifiers_to_id_triples"][key] = val
94
- return output
96
+ return output
97
+ # In all other cases (e.g. UnifiedIdTripleResponse directly initialized),
98
+ # just return the data.
99
+ else:
100
+ return data
95
101
 
96
102
  def filter_out_companies_without_security_ids(self) -> None:
97
103
  """Filter out companies that don't have a security_id and add an error for them."""
@@ -119,6 +125,20 @@ class UnifiedIdTripleResponse(BaseModel):
119
125
  )
120
126
  self.identifiers_to_id_triples.pop(identifier)
121
127
 
128
+ def get_identifier_from_company_id(self, company_id: int) -> str:
129
+ """Return the (originally passed) identifier from a company id."""
130
+ if not hasattr(self, "_company_id_to_identifier"):
131
+ self._company_id_to_identifier = {
132
+ id_triple.company_id: identifier
133
+ for identifier, id_triple in self.identifiers_to_id_triples.items()
134
+ }
135
+ return self._company_id_to_identifier[company_id]
136
+
137
+ @property
138
+ def company_ids(self) -> list[int]:
139
+ """Returns a list of all company ids in the response."""
140
+ return [id_triple.company_id for id_triple in self.identifiers_to_id_triples.values()]
141
+
122
142
 
123
143
  class CompanyDescriptions(BaseModel):
124
144
  """A company summary and description"""
@@ -5,7 +5,11 @@ from pydantic import BaseModel
5
5
 
6
6
  from kfinance.client.batch_request_handling import Task, process_tasks_in_thread_pool_executor
7
7
  from kfinance.client.permission_models import Permission
8
- from kfinance.domains.companies.company_models import CompanyDescriptions, CompanyOtherNames
8
+ from kfinance.domains.companies.company_models import (
9
+ COMPANY_ID_PREFIX,
10
+ CompanyDescriptions,
11
+ CompanyOtherNames,
12
+ )
9
13
  from kfinance.integrations.tool_calling.tool_calling_models import (
10
14
  KfinanceTool,
11
15
  ToolArgsWithIdentifiers,
@@ -20,14 +24,21 @@ class GetInfoFromIdentifiersResp(ToolRespWithErrors):
20
24
  class GetInfoFromIdentifiers(KfinanceTool):
21
25
  name: str = "get_info_from_identifiers"
22
26
  description: str = dedent("""
23
- Get the information associated with a list of identifiers. Info includes company name, status, type, simple industry, number of employees (if available), founding date, webpage, HQ address, HQ city, HQ zip code, HQ state, HQ country, and HQ country iso code.
27
+ Get the information associated with a list of identifiers. Info includes company name, status, type, simple industry, number of employees (if available), founding date, webpage, HQ address, HQ city, HQ zip code, HQ state, HQ country, HQ country iso code, and CIQ company_id.
24
28
 
25
29
  - When possible, pass multiple identifiers in a single call rather than making multiple calls.
30
+
31
+ Examples:
32
+ Query: "What's the company information for Northrop Grumman and Lockheed Martin?"
33
+ Function: get_info_from_identifiers(identifiers=["Northrop Grumman", "Lockheed Martin"])
34
+
35
+ Query: "Get company info for UBER and LYFT"
36
+ Function: get_info_from_identifiers(identifiers=["UBER", "LYFT"])
26
37
  """).strip()
27
38
  args_schema: Type[BaseModel] = ToolArgsWithIdentifiers
28
39
  accepted_permissions: set[Permission] | None = None
29
40
 
30
- def _run(self, identifiers: list[str]) -> dict:
41
+ def _run(self, identifiers: list[str]) -> GetInfoFromIdentifiersResp:
31
42
  """Sample response:
32
43
 
33
44
  { "results": {
@@ -44,7 +55,8 @@ class GetInfoFromIdentifiers(KfinanceTool):
44
55
  "zip_code": "10041-0001",
45
56
  "state": "New York",
46
57
  "country": "United States",
47
- "iso_country": "USA"
58
+ "iso_country": "USA",
59
+ "company_id": "C_21719"
48
60
  }
49
61
  },
50
62
  "errors": [['No identification triple found for the provided identifier: NON-EXISTENT of type: ticker']
@@ -65,10 +77,13 @@ class GetInfoFromIdentifiers(KfinanceTool):
65
77
  info_responses: dict[str, dict] = process_tasks_in_thread_pool_executor(
66
78
  api_client=api_client, tasks=tasks
67
79
  )
68
- resp_model = GetInfoFromIdentifiersResp(
80
+
81
+ for identifier, id_triple in id_triple_resp.identifiers_to_id_triples.items():
82
+ info_responses[identifier]["company_id"] = f"{COMPANY_ID_PREFIX}{id_triple.company_id}"
83
+
84
+ return GetInfoFromIdentifiersResp(
69
85
  results=info_responses, errors=list(id_triple_resp.errors.values())
70
86
  )
71
- return resp_model.model_dump(mode="json")
72
87
 
73
88
 
74
89
  class GetCompanyOtherNamesFromIdentifiersResp(ToolRespWithErrors):
@@ -81,6 +96,13 @@ class GetCompanyOtherNamesFromIdentifiers(KfinanceTool):
81
96
  Given a list of identifiers, fetch the alternate, historical, and native names associated with each identifier. Alternate names are additional names a company might go by (for example, Hewlett-Packard Company also goes by the name HP). Historical names are previous names for the company if it has changed over time. Native names are primary non-Latin character native names for global companies, including languages such as Arabic, Russian, Greek, Japanese, etc. This also includes limited history on native name changes.
82
97
 
83
98
  - When possible, pass multiple identifiers in a single call rather than making multiple calls.
99
+
100
+ Examples:
101
+ Query: "What are the alternate names for Meta and Alphabet?"
102
+ Function: get_company_other_names_from_identifiers(identifiers=["Meta", "Alphabet"])
103
+
104
+ Query: "Get other names for NSRGY"
105
+ Function: get_company_other_names_from_identifiers(identifiers=["NSRGY"])
84
106
  """).strip()
85
107
  args_schema: Type[BaseModel] = ToolArgsWithIdentifiers
86
108
  accepted_permissions: set[Permission] | None = {Permission.CompanyIntelligencePermission}
@@ -88,7 +110,7 @@ class GetCompanyOtherNamesFromIdentifiers(KfinanceTool):
88
110
  def _run(
89
111
  self,
90
112
  identifiers: list[str],
91
- ) -> dict:
113
+ ) -> GetCompanyOtherNamesFromIdentifiersResp:
92
114
  api_client = self.kfinance_client.kfinance_api_client
93
115
  id_triple_resp = api_client.unified_fetch_id_triples(identifiers=identifiers)
94
116
  tasks = [
@@ -102,10 +124,9 @@ class GetCompanyOtherNamesFromIdentifiers(KfinanceTool):
102
124
  info_responses: dict[str, CompanyOtherNames] = process_tasks_in_thread_pool_executor(
103
125
  api_client=api_client, tasks=tasks
104
126
  )
105
- resp_model = GetCompanyOtherNamesFromIdentifiersResp(
127
+ return GetCompanyOtherNamesFromIdentifiersResp(
106
128
  results=info_responses, errors=list(id_triple_resp.errors.values())
107
129
  )
108
- return resp_model.model_dump(mode="json")
109
130
 
110
131
 
111
132
  class GetCompanySummaryFromIdentifiersResp(ToolRespWithErrors):
@@ -115,9 +136,16 @@ class GetCompanySummaryFromIdentifiersResp(ToolRespWithErrors):
115
136
  class GetCompanySummaryFromIdentifiers(KfinanceTool):
116
137
  name: str = "get_company_summary_from_identifiers"
117
138
  description: str = dedent("""
118
- Get one paragraph summary/short descriptions of companies, including information about the company's primary business, products and services offered and their applications, business segment details, client/customer groups served, geographic markets served, distribution channels, strategic alliances/partnerships, founded/incorporated year, latest former name, and headquarters and additional offices."
139
+ Get one paragraph summary/short descriptions of companies, including information about the company's primary business, products and services offered and their applications, business segment details, client/customer groups served, geographic markets served, distribution channels, strategic alliances/partnerships, founded/incorporated year, latest former name, and headquarters and additional offices.
119
140
 
120
141
  - When possible, pass multiple identifiers in a single call rather than making multiple calls.
142
+
143
+ Examples:
144
+ Query: "Give me summaries of Tesla and General Motors"
145
+ Function: get_company_summary_from_identifiers(identifiers=["Tesla", "General Motors"])
146
+
147
+ Query: "What are the summaries for F and STLA?"
148
+ Function: get_company_summary_from_identifiers(identifiers=["F", "STLA"])
121
149
  """).strip()
122
150
  args_schema: Type[BaseModel] = ToolArgsWithIdentifiers
123
151
  accepted_permissions: set[Permission] | None = {Permission.CompanyIntelligencePermission}
@@ -125,7 +153,7 @@ class GetCompanySummaryFromIdentifiers(KfinanceTool):
125
153
  def _run(
126
154
  self,
127
155
  identifiers: list[str],
128
- ) -> dict:
156
+ ) -> GetCompanySummaryFromIdentifiersResp:
129
157
  api_client = self.kfinance_client.kfinance_api_client
130
158
  id_triple_resp = api_client.unified_fetch_id_triples(identifiers=identifiers)
131
159
 
@@ -147,10 +175,9 @@ class GetCompanySummaryFromIdentifiers(KfinanceTool):
147
175
  for identifier, descriptions in company_description_responses.items()
148
176
  }
149
177
 
150
- resp_model = GetCompanySummaryFromIdentifiersResp(
178
+ return GetCompanySummaryFromIdentifiersResp(
151
179
  results=summary_results, errors=list(id_triple_resp.errors.values())
152
180
  )
153
- return resp_model.model_dump(mode="json")
154
181
 
155
182
 
156
183
  class GetCompanyDescriptionFromIdentifiersResp(ToolRespWithErrors):
@@ -163,6 +190,13 @@ class GetCompanyDescriptionFromIdentifiers(KfinanceTool):
163
190
  Get detailed descriptions of companies, broken down into sections, which may include information about the company's Primary business, Segments (including Products and Services for each), Competition, Significant events, and History. Within the text, four spaces represent a new paragraph. Note that the description is divided into sections with headers, where each section has a new paragraph (four spaces) before and after the section header.
164
191
 
165
192
  - When possible, pass multiple identifiers in a single call rather than making multiple calls.
193
+
194
+ Examples:
195
+ Query: "Get detailed descriptions for Netflix and Disney"
196
+ Function: get_company_description_from_identifiers(identifiers=["Netflix", "Disney"])
197
+
198
+ Query: "What are the detailed company descriptions for KO and PEP?"
199
+ Function: get_company_description_from_identifiers(identifiers=["KO", "PEP"])
166
200
  """).strip()
167
201
  args_schema: Type[BaseModel] = ToolArgsWithIdentifiers
168
202
  accepted_permissions: set[Permission] | None = {Permission.CompanyIntelligencePermission}
@@ -170,7 +204,7 @@ class GetCompanyDescriptionFromIdentifiers(KfinanceTool):
170
204
  def _run(
171
205
  self,
172
206
  identifiers: list[str],
173
- ) -> dict:
207
+ ) -> GetCompanyDescriptionFromIdentifiersResp:
174
208
  api_client = self.kfinance_client.kfinance_api_client
175
209
  id_triple_resp = api_client.unified_fetch_id_triples(identifiers=identifiers)
176
210
 
@@ -192,7 +226,6 @@ class GetCompanyDescriptionFromIdentifiers(KfinanceTool):
192
226
  for identifier, descriptions in company_description_responses.items()
193
227
  }
194
228
 
195
- resp_model = GetCompanyDescriptionFromIdentifiersResp(
229
+ return GetCompanyDescriptionFromIdentifiersResp(
196
230
  results=description_results, errors=list(id_triple_resp.errors.values())
197
231
  )
198
- return resp_model.model_dump(mode="json")
@@ -2,11 +2,16 @@ from requests_mock import Mocker
2
2
 
3
3
  from kfinance.client.kfinance import Client
4
4
  from kfinance.conftest import SPGI_COMPANY_ID
5
+ from kfinance.domains.companies.company_models import COMPANY_ID_PREFIX
5
6
  from kfinance.domains.companies.company_tools import (
6
7
  GetCompanyDescriptionFromIdentifiers,
8
+ GetCompanyDescriptionFromIdentifiersResp,
7
9
  GetCompanyOtherNamesFromIdentifiers,
10
+ GetCompanyOtherNamesFromIdentifiersResp,
8
11
  GetCompanySummaryFromIdentifiers,
12
+ GetCompanySummaryFromIdentifiersResp,
9
13
  GetInfoFromIdentifiers,
14
+ GetInfoFromIdentifiersResp,
10
15
  )
11
16
  from kfinance.integrations.tool_calling.tool_calling_models import ToolArgsWithIdentifiers
12
17
 
@@ -19,13 +24,20 @@ class TestGetInfoFromIdentifiers:
19
24
  THEN we get back info for SPGI and an error for the non-existent company
20
25
  """
21
26
 
22
- info_resp = {"name": "S&P Global Inc.", "status": "Operating"}
23
- expected_response = {
24
- "results": {"SPGI": info_resp},
25
- "errors": [
26
- "No identification triple found for the provided identifier: NON-EXISTENT of type: ticker"
27
- ],
27
+ info_resp = {
28
+ "name": "S&P Global Inc.",
29
+ "status": "Operating",
30
+ "company_id": f"{COMPANY_ID_PREFIX}{SPGI_COMPANY_ID}",
28
31
  }
32
+ expected_response = GetInfoFromIdentifiersResp.model_validate(
33
+ {
34
+ "results": {"SPGI": info_resp},
35
+ "errors": [
36
+ "No identification triple found for the provided identifier: NON-EXISTENT of type: ticker"
37
+ ],
38
+ }
39
+ )
40
+ del info_resp["company_id"]
29
41
  requests_mock.get(
30
42
  url=f"https://kfinance.kensho.com/api/v1/info/{SPGI_COMPANY_ID}",
31
43
  json=info_resp,
@@ -61,7 +73,9 @@ class TestGetCompanyDescriptions:
61
73
  tool = GetCompanySummaryFromIdentifiers(kfinance_client=mock_client)
62
74
  args = ToolArgsWithIdentifiers(identifiers=["SPGI"])
63
75
  response = tool.run(args.model_dump(mode="json"))
64
- expected_response = {"results": {"SPGI": self.summary}}
76
+ expected_response = GetCompanySummaryFromIdentifiersResp.model_validate(
77
+ {"results": {"SPGI": self.summary}}
78
+ )
65
79
  assert response == expected_response
66
80
 
67
81
  def test_get_company_description_from_identifier(
@@ -81,7 +95,9 @@ class TestGetCompanyDescriptions:
81
95
  tool = GetCompanyDescriptionFromIdentifiers(kfinance_client=mock_client)
82
96
  args = ToolArgsWithIdentifiers(identifiers=["SPGI"])
83
97
  response = tool.run(args.model_dump(mode="json"))
84
- expected_response = {"results": {"SPGI": self.description}}
98
+ expected_response = GetCompanyDescriptionFromIdentifiersResp.model_validate(
99
+ {"results": {"SPGI": self.description}}
100
+ )
85
101
  assert response == expected_response
86
102
 
87
103
 
@@ -121,5 +137,7 @@ class TestGetCompanyOtherNames:
121
137
  tool = GetCompanyOtherNamesFromIdentifiers(kfinance_client=mock_client)
122
138
  args = ToolArgsWithIdentifiers(identifiers=["SPGI"])
123
139
  response = tool.run(args.model_dump(mode="json"))
124
- expected_response = {"results": {"SPGI": self.company_other_names_info}}
140
+ expected_response = GetCompanyOtherNamesFromIdentifiersResp.model_validate(
141
+ {"results": {"SPGI": self.company_other_names_info}}
142
+ )
125
143
  assert response == expected_response
@@ -1,3 +1,6 @@
1
+ from textwrap import dedent
2
+ from typing import Type
3
+
1
4
  from kfinance.client.batch_request_handling import Task, process_tasks_in_thread_pool_executor
2
5
  from kfinance.client.permission_models import Permission
3
6
  from kfinance.domains.competitors.competitor_models import CompetitorResponse, CompetitorSource
@@ -19,15 +22,27 @@ class GetCompetitorsFromIdentifiersResp(ToolRespWithErrors):
19
22
 
20
23
  class GetCompetitorsFromIdentifiers(KfinanceTool):
21
24
  name: str = "get_competitors_from_identifiers"
22
- description: str = "Retrieves a list of company_id and company_name that are competitors for a list of companies, optionally filtered by the source of the competitor information."
23
- args_schema = GetCompetitorsFromIdentifiersArgs
25
+ description: str = dedent("""
26
+ Retrieves a list of company_id and company_name that are competitors for a list of companies, filtered by the source of the competitor information.
27
+
28
+ - When possible, pass multiple identifiers in a single call rather than making multiple calls.
29
+ - Available competitor sources: all, filing (from SEC filings), key_dev (from key developments), contact (from contact relationships), third_party (from third-party sources), self_identified (self-identified), named_by_competitor (from competitor's perspective)
30
+
31
+ Examples:
32
+ Query: "Who are Microsoft's competitors from SEC filings?"
33
+ Function: get_competitors_from_identifiers(identifiers=["Microsoft"], competitor_source="filing")
34
+
35
+ Query: "Get all competitors of AAPL and GOOGL"
36
+ Function: get_competitors_from_identifiers(identifiers=["AAPL", "GOOGL"], competitor_source="all")
37
+ """).strip()
38
+ args_schema: Type[GetCompetitorsFromIdentifiersArgs] = GetCompetitorsFromIdentifiersArgs
24
39
  accepted_permissions: set[Permission] | None = {Permission.CompetitorsPermission}
25
40
 
26
41
  def _run(
27
42
  self,
28
43
  identifiers: list[str],
29
44
  competitor_source: CompetitorSource,
30
- ) -> dict:
45
+ ) -> GetCompetitorsFromIdentifiersResp:
31
46
  """Sample response:
32
47
 
33
48
  {
@@ -56,7 +71,6 @@ class GetCompetitorsFromIdentifiers(KfinanceTool):
56
71
  competitor_responses: dict[str, CompetitorResponse] = process_tasks_in_thread_pool_executor(
57
72
  api_client=api_client, tasks=tasks
58
73
  )
59
- resp_model = GetCompetitorsFromIdentifiersResp(
74
+ return GetCompetitorsFromIdentifiersResp(
60
75
  results=competitor_responses, errors=list(id_triple_resp.errors.values())
61
76
  )
62
- return resp_model.model_dump(mode="json")
@@ -6,6 +6,7 @@ from kfinance.domains.competitors.competitor_models import CompetitorSource
6
6
  from kfinance.domains.competitors.competitor_tools import (
7
7
  GetCompetitorsFromIdentifiers,
8
8
  GetCompetitorsFromIdentifiersArgs,
9
+ GetCompetitorsFromIdentifiersResp,
9
10
  )
10
11
 
11
12
 
@@ -24,25 +25,27 @@ class TestGetCompetitorsFromIdentifiers:
24
25
  {"company_id": 4003514, "company_name": "London Stock Exchange Group plc"},
25
26
  ]
26
27
  }
27
- expected_response = {
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
- ],
45
- }
28
+ expected_response = GetCompetitorsFromIdentifiersResp.model_validate(
29
+ {
30
+ "results": {
31
+ "SPGI": {
32
+ "competitors": [
33
+ {
34
+ "company_id": 35352,
35
+ "company_name": "The Descartes Systems Group Inc.",
36
+ },
37
+ {
38
+ "company_id": 4003514,
39
+ "company_name": "London Stock Exchange Group plc",
40
+ },
41
+ ]
42
+ }
43
+ },
44
+ "errors": [
45
+ "No identification triple found for the provided identifier: NON-EXISTENT of type: ticker"
46
+ ],
47
+ }
48
+ )
46
49
 
47
50
  requests_mock.get(
48
51
  url=f"https://kfinance.kensho.com/api/v1/competitors/{SPGI_COMPANY_ID}/named_by_competitor",