kensho-kfinance 3.0.3__py3-none-any.whl → 3.1.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.

Potentially problematic release.


This version of kensho-kfinance might be problematic. Click here for more details.

Files changed (45) hide show
  1. {kensho_kfinance-3.0.3.dist-info → kensho_kfinance-3.1.0.dist-info}/METADATA +1 -1
  2. {kensho_kfinance-3.0.3.dist-info → kensho_kfinance-3.1.0.dist-info}/RECORD +45 -44
  3. kfinance/CHANGELOG.md +3 -0
  4. kfinance/client/fetch.py +26 -17
  5. kfinance/client/kfinance.py +17 -18
  6. kfinance/client/meta_classes.py +2 -2
  7. kfinance/client/tests/test_fetch.py +36 -24
  8. kfinance/client/tests/test_objects.py +112 -120
  9. kfinance/conftest.py +49 -5
  10. kfinance/domains/business_relationships/business_relationship_tools.py +30 -19
  11. kfinance/domains/business_relationships/tests/test_business_relationship_tools.py +18 -15
  12. kfinance/domains/capitalizations/capitalization_models.py +1 -1
  13. kfinance/domains/capitalizations/capitalization_tools.py +41 -24
  14. kfinance/domains/capitalizations/tests/test_capitalization_tools.py +38 -13
  15. kfinance/domains/companies/company_identifiers.py +0 -175
  16. kfinance/domains/companies/company_models.py +98 -5
  17. kfinance/domains/companies/company_tools.py +33 -29
  18. kfinance/domains/companies/tests/test_company_tools.py +11 -4
  19. kfinance/domains/competitors/competitor_tools.py +21 -21
  20. kfinance/domains/competitors/tests/test_competitor_tools.py +21 -7
  21. kfinance/domains/cusip_and_isin/cusip_and_isin_tools.py +38 -26
  22. kfinance/domains/cusip_and_isin/tests/test_cusip_and_isin_tools.py +27 -26
  23. kfinance/domains/earnings/earning_tools.py +54 -47
  24. kfinance/domains/earnings/tests/test_earnings_tools.py +58 -63
  25. kfinance/domains/line_items/line_item_tools.py +29 -36
  26. kfinance/domains/line_items/tests/test_line_item_tools.py +23 -5
  27. kfinance/domains/mergers_and_acquisitions/merger_and_acquisition_models.py +15 -0
  28. kfinance/domains/mergers_and_acquisitions/merger_and_acquisition_tools.py +55 -38
  29. kfinance/domains/mergers_and_acquisitions/tests/test_merger_and_acquisition_tools.py +22 -9
  30. kfinance/domains/prices/price_models.py +9 -9
  31. kfinance/domains/prices/price_tools.py +49 -38
  32. kfinance/domains/prices/tests/test_price_tools.py +52 -36
  33. kfinance/domains/segments/segment_models.py +7 -0
  34. kfinance/domains/segments/segment_tools.py +37 -20
  35. kfinance/domains/segments/tests/test_segment_tools.py +13 -6
  36. kfinance/domains/statements/statement_models.py +7 -0
  37. kfinance/domains/statements/statement_tools.py +38 -40
  38. kfinance/domains/statements/tests/test_statement_tools.py +39 -10
  39. kfinance/integrations/tool_calling/tests/test_tool_calling_models.py +2 -2
  40. kfinance/integrations/tool_calling/tool_calling_models.py +24 -5
  41. kfinance/version.py +2 -2
  42. {kensho_kfinance-3.0.3.dist-info → kensho_kfinance-3.1.0.dist-info}/WHEEL +0 -0
  43. {kensho_kfinance-3.0.3.dist-info → kensho_kfinance-3.1.0.dist-info}/licenses/AUTHORS.md +0 -0
  44. {kensho_kfinance-3.0.3.dist-info → kensho_kfinance-3.1.0.dist-info}/licenses/LICENSE +0 -0
  45. {kensho_kfinance-3.0.3.dist-info → kensho_kfinance-3.1.0.dist-info}/top_level.txt +0 -0
@@ -1,18 +1,11 @@
1
1
  from textwrap import dedent
2
2
  from typing import Literal, Type
3
3
 
4
- import numpy as np
5
- import pandas as pd
6
4
  from pydantic import BaseModel, Field
7
5
 
8
6
  from kfinance.client.batch_request_handling import Task, process_tasks_in_thread_pool_executor
9
7
  from kfinance.client.models.date_and_period_models import PeriodType
10
8
  from kfinance.client.permission_models import Permission
11
- from kfinance.domains.companies.company_identifiers import (
12
- Identifier,
13
- fetch_company_ids_from_identifiers,
14
- parse_identifiers,
15
- )
16
9
  from kfinance.domains.line_items.line_item_models import (
17
10
  LINE_ITEM_NAMES_AND_ALIASES,
18
11
  LineItemResponse,
@@ -20,6 +13,7 @@ from kfinance.domains.line_items.line_item_models import (
20
13
  from kfinance.integrations.tool_calling.tool_calling_models import (
21
14
  KfinanceTool,
22
15
  ToolArgsWithIdentifiers,
16
+ ToolRespWithErrors,
23
17
  )
24
18
 
25
19
 
@@ -37,6 +31,10 @@ class GetFinancialLineItemFromIdentifiersArgs(ToolArgsWithIdentifiers):
37
31
  end_quarter: Literal[1, 2, 3, 4] | None = Field(default=None, description="Ending quarter")
38
32
 
39
33
 
34
+ class GetFinancialLineItemFromIdentifiersResp(ToolRespWithErrors):
35
+ results: dict[str, LineItemResponse]
36
+
37
+
40
38
  class GetFinancialLineItemFromIdentifiers(KfinanceTool):
41
39
  name: str = "get_financial_line_item_from_identifiers"
42
40
  description: str = dedent("""
@@ -76,16 +74,13 @@ class GetFinancialLineItemFromIdentifiers(KfinanceTool):
76
74
  }
77
75
  """
78
76
  api_client = self.kfinance_client.kfinance_api_client
79
- parsed_identifiers = parse_identifiers(identifiers=identifiers, api_client=api_client)
80
- identifiers_to_company_ids = fetch_company_ids_from_identifiers(
81
- identifiers=parsed_identifiers, api_client=api_client
82
- )
77
+ id_triple_resp = api_client.unified_fetch_id_triples(identifiers=identifiers)
83
78
 
84
79
  tasks = [
85
80
  Task(
86
81
  func=api_client.fetch_line_item,
87
82
  kwargs=dict(
88
- company_id=company_id,
83
+ company_id=id_triple.company_id,
89
84
  line_item=line_item,
90
85
  period_type=period_type,
91
86
  start_year=start_year,
@@ -95,31 +90,29 @@ class GetFinancialLineItemFromIdentifiers(KfinanceTool):
95
90
  ),
96
91
  result_key=identifier,
97
92
  )
98
- for identifier, company_id in identifiers_to_company_ids.items()
93
+ for identifier, id_triple in id_triple_resp.identifiers_to_id_triples.items()
99
94
  ]
100
95
 
101
- line_item_responses: dict[Identifier, LineItemResponse] = (
102
- process_tasks_in_thread_pool_executor(api_client=api_client, tasks=tasks)
96
+ line_item_responses: dict[str, LineItemResponse] = process_tasks_in_thread_pool_executor(
97
+ api_client=api_client, tasks=tasks
103
98
  )
104
99
 
105
- output = dict()
106
- for identifier, result in line_item_responses.items():
107
- df = (
108
- pd.DataFrame({"line_item": result.line_item})
109
- .apply(pd.to_numeric)
110
- .replace(np.nan, None)
111
- )
112
- # If no date and multiple companies, only return the most recent value.
113
- # By default, we return 5 years of data, which can be too much when
114
- # returning data for many companies.
115
- if (
116
- start_year is None
117
- and end_year is None
118
- and start_quarter is None
119
- and end_quarter is None
120
- and len(identifiers) > 1
121
- ):
122
- df = df.tail(1)
123
- output[str(identifier)] = df.transpose().set_index(pd.Index([line_item])).to_dict()
124
-
125
- return output
100
+ # If no date and multiple companies, only return the most recent value.
101
+ # By default, we return 5 years of data, which can be too much when
102
+ # returning data for many companies.
103
+ if (
104
+ start_year is None
105
+ and end_year is None
106
+ and start_quarter is None
107
+ and end_quarter is None
108
+ and len(line_item_responses) > 1
109
+ ):
110
+ for line_item_response in line_item_responses.values():
111
+ most_recent_year = max(line_item_response.line_item.keys())
112
+ most_recent_year_data = line_item_response.line_item[most_recent_year]
113
+ line_item_response.line_item = {most_recent_year: most_recent_year_data}
114
+
115
+ output_model = GetFinancialLineItemFromIdentifiersResp(
116
+ results=line_item_responses, errors=list(id_triple_resp.errors.values())
117
+ )
118
+ return output_model.model_dump(mode="json")
@@ -24,8 +24,8 @@ class TestGetFinancialLineItemFromCompanyIds:
24
24
  ):
25
25
  """
26
26
  GIVEN the GetFinancialLineItemFromCompanyId tool
27
- WHEN we request SPGI revenue
28
- THEN we get back the SPGI revenue
27
+ WHEN we request revenue for SPGI and a non-existent company
28
+ THEN we get back the SPGI revenue and an error for the non-existent company
29
29
  """
30
30
 
31
31
  expected_response = {
@@ -35,6 +35,20 @@ class TestGetFinancialLineItemFromCompanyIds:
35
35
  "2024": {"revenue": 14208000000.0},
36
36
  }
37
37
  }
38
+ expected_response = {
39
+ "results": {
40
+ "SPGI": {
41
+ "line_item": {
42
+ "2022": "11181000000.000000",
43
+ "2023": "12497000000.000000",
44
+ "2024": "14208000000.000000",
45
+ }
46
+ }
47
+ },
48
+ "errors": [
49
+ "No identification triple found for the provided identifier: NON-EXISTENT of type: ticker"
50
+ ],
51
+ }
38
52
 
39
53
  requests_mock.get(
40
54
  url=f"https://kfinance.kensho.com/api/v1/line_item/{SPGI_COMPANY_ID}/revenue/none/none/none/none/none",
@@ -42,7 +56,9 @@ class TestGetFinancialLineItemFromCompanyIds:
42
56
  )
43
57
 
44
58
  tool = GetFinancialLineItemFromIdentifiers(kfinance_client=mock_client)
45
- args = GetFinancialLineItemFromIdentifiersArgs(identifiers=["SPGI"], line_item="revenue")
59
+ args = GetFinancialLineItemFromIdentifiersArgs(
60
+ identifiers=["SPGI", "non-existent"], line_item="revenue"
61
+ )
46
62
  response = tool.run(args.model_dump(mode="json"))
47
63
  assert response == expected_response
48
64
 
@@ -55,8 +71,10 @@ class TestGetFinancialLineItemFromCompanyIds:
55
71
 
56
72
  company_ids = [1, 2]
57
73
  expected_response = {
58
- "C_1": {"2024": {"revenue": 14208000000.0}},
59
- "C_2": {"2024": {"revenue": 14208000000.0}},
74
+ "results": {
75
+ "C_1": {"line_item": {"2024": "14208000000.000000"}},
76
+ "C_2": {"line_item": {"2024": "14208000000.000000"}},
77
+ }
60
78
  }
61
79
  for company_id in company_ids:
62
80
  requests_mock.get(
@@ -0,0 +1,15 @@
1
+ from datetime import date
2
+
3
+ from pydantic import BaseModel
4
+
5
+
6
+ class MergerSummary(BaseModel):
7
+ transaction_id: int
8
+ merger_title: str
9
+ closed_date: date
10
+
11
+
12
+ class MergersResp(BaseModel):
13
+ target: list[MergerSummary]
14
+ buyer: list[MergerSummary]
15
+ seller: list[MergerSummary]
@@ -6,19 +6,21 @@ from pydantic import BaseModel, Field
6
6
  from kfinance.client.batch_request_handling import Task, process_tasks_in_thread_pool_executor
7
7
  from kfinance.client.kfinance import Company, MergerOrAcquisition, ParticipantInMerger
8
8
  from kfinance.client.permission_models import Permission
9
- from kfinance.domains.companies.company_identifiers import (
10
- CompanyId,
11
- fetch_company_ids_from_identifiers,
12
- parse_identifiers,
13
- )
9
+ from kfinance.domains.companies.company_models import prefix_company_id
10
+ from kfinance.domains.mergers_and_acquisitions.merger_and_acquisition_models import MergersResp
14
11
  from kfinance.integrations.tool_calling.tool_calling_models import (
15
12
  KfinanceTool,
16
13
  ToolArgsWithIdentifier,
17
14
  ToolArgsWithIdentifiers,
15
+ ToolRespWithErrors,
18
16
  )
19
17
 
20
18
 
21
- class GetMergersFromIdentifier(KfinanceTool):
19
+ class GetMergersFromIdentifiersResp(ToolRespWithErrors):
20
+ results: dict[str, MergersResp]
21
+
22
+
23
+ class GetMergersFromIdentifiers(KfinanceTool):
22
24
  name: str = "get_mergers_from_identifiers"
23
25
  description: str = dedent("""
24
26
  Get the transaction IDs that involve the given identifiers.
@@ -29,24 +31,57 @@ class GetMergersFromIdentifier(KfinanceTool):
29
31
  accepted_permissions: set[Permission] | None = {Permission.MergersPermission}
30
32
 
31
33
  def _run(self, identifiers: list[str]) -> dict:
34
+ """Sample Response:
35
+
36
+ {
37
+ 'results': {
38
+ 'SPGI': {
39
+ 'target': [
40
+ {
41
+ 'transaction_id': 10998717,
42
+ 'merger_title': 'Closed M/A of Microsoft Corporation',
43
+ 'closed_date': '2021-01-01'
44
+ }
45
+ ],
46
+ 'buyer': [
47
+ {
48
+ 'transaction_id': 517414,
49
+ 'merger_title': 'Closed M/A of MongoMusic, Inc.',
50
+ 'closed_date': '2023-01-01'
51
+ },
52
+ 'seller': [
53
+ {
54
+ 'transaction_id': 455551,
55
+ 'merger_title': 'Closed M/A of VacationSpot.com, Inc.',
56
+ 'closed_date': '2024-01-01'
57
+ },
58
+ ]
59
+ }
60
+ },
61
+ 'errors': ['No identification triple found for the provided identifier: NON-EXISTENT of type: ticker']
62
+ }
63
+
64
+ """
65
+
32
66
  api_client = self.kfinance_client.kfinance_api_client
33
- parsed_identifiers = parse_identifiers(identifiers=identifiers, api_client=api_client)
34
- identifiers_to_company_ids = fetch_company_ids_from_identifiers(
35
- identifiers=parsed_identifiers, api_client=api_client
36
- )
67
+ id_triple_resp = api_client.unified_fetch_id_triples(identifiers=identifiers)
37
68
 
38
69
  tasks = [
39
70
  Task(
40
71
  func=api_client.fetch_mergers_for_company,
41
- kwargs=dict(company_id=company_id),
72
+ kwargs=dict(company_id=id_triple.company_id),
42
73
  result_key=identifier,
43
74
  )
44
- for identifier, company_id in identifiers_to_company_ids.items()
75
+ for identifier, id_triple in id_triple_resp.identifiers_to_id_triples.items()
45
76
  ]
46
77
 
47
- merger_responses = process_tasks_in_thread_pool_executor(api_client=api_client, tasks=tasks)
48
-
49
- return {str(identifier): mergers for identifier, mergers in merger_responses.items()}
78
+ merger_responses: dict[str, MergersResp] = process_tasks_in_thread_pool_executor(
79
+ api_client=api_client, tasks=tasks
80
+ )
81
+ output_model = GetMergersFromIdentifiersResp(
82
+ results=merger_responses, errors=list(id_triple_resp.errors.values())
83
+ )
84
+ return output_model.model_dump(mode="json")
50
85
 
51
86
 
52
87
  class GetMergerInfoFromTransactionIdArgs(BaseModel):
@@ -83,34 +118,21 @@ class GetMergerInfoFromTransactionId(KfinanceTool):
83
118
  else None,
84
119
  "participants": {
85
120
  "target": {
86
- "company_id": str(
87
- CompanyId(
88
- company_id=merger_participants["target"].company.company_id,
89
- api_client=self.kfinance_client.kfinance_api_client,
90
- )
121
+ "company_id": prefix_company_id(
122
+ merger_participants["target"].company.company_id
91
123
  ),
92
124
  "company_name": merger_participants["target"].company.name,
93
125
  },
94
126
  "buyers": [
95
127
  {
96
- "company_id": str(
97
- CompanyId(
98
- buyer.company.company_id,
99
- api_client=self.kfinance_client.kfinance_api_client,
100
- )
101
- ),
128
+ "company_id": prefix_company_id(buyer.company.company_id),
102
129
  "company_name": buyer.company.name,
103
130
  }
104
131
  for buyer in merger_participants["buyers"]
105
132
  ],
106
133
  "sellers": [
107
134
  {
108
- "company_id": str(
109
- CompanyId(
110
- seller.company.company_id,
111
- api_client=self.kfinance_client.kfinance_api_client,
112
- )
113
- ),
135
+ "company_id": prefix_company_id(seller.company.company_id),
114
136
  "company_name": seller.company.name,
115
137
  }
116
138
  for seller in merger_participants["sellers"]
@@ -161,12 +183,7 @@ class GetAdvisorsForCompanyInTransactionFromIdentifier(KfinanceTool):
161
183
  if advisors:
162
184
  return [
163
185
  {
164
- "advisor_company_id": str(
165
- CompanyId(
166
- company_id=advisor.company.company_id,
167
- api_client=self.kfinance_client.kfinance_api_client,
168
- )
169
- ),
186
+ "advisor_company_id": prefix_company_id(advisor.company.company_id),
170
187
  "advisor_company_name": advisor.company.name,
171
188
  "advisor_type_name": advisor.advisor_type_name,
172
189
  }
@@ -3,30 +3,43 @@ from copy import deepcopy
3
3
  from requests_mock import Mocker
4
4
 
5
5
  from kfinance.client.kfinance import Client
6
- from kfinance.client.tests.test_objects import MOCK_COMPANY_DB, ordered
6
+ from kfinance.client.tests.test_objects import (
7
+ MERGERS_RESP,
8
+ ordered,
9
+ )
10
+ from kfinance.conftest import SPGI_COMPANY_ID
7
11
  from kfinance.domains.companies.company_models import COMPANY_ID_PREFIX
8
12
  from kfinance.domains.mergers_and_acquisitions.merger_and_acquisition_tools import (
9
13
  GetAdvisorsForCompanyInTransactionFromIdentifier,
10
14
  GetAdvisorsForCompanyInTransactionFromIdentifierArgs,
11
15
  GetMergerInfoFromTransactionId,
12
16
  GetMergerInfoFromTransactionIdArgs,
13
- GetMergersFromIdentifier,
17
+ GetMergersFromIdentifiers,
14
18
  )
15
19
  from kfinance.integrations.tool_calling.tool_calling_models import ToolArgsWithIdentifiers
16
20
 
17
21
 
18
22
  class TestGetMergersFromIdentifiers:
19
23
  def test_get_mergers_from_identifiers(self, requests_mock: Mocker, mock_client: Client):
20
- msft_mergers = MOCK_COMPANY_DB["21835"]["mergers"]
21
- expected_response = {"MSFT": msft_mergers}
22
- company_id = 21835
24
+ """
25
+ GIVEN the GetMergersFromIdentifiers tool
26
+ WHEN we request mergers for SPGI and a non-existent company
27
+ THEN we get back the SPGI mergers and an error for the non-existent company"""
28
+
29
+ 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
+ }
23
36
  requests_mock.get(
24
- url=f"https://kfinance.kensho.com/api/v1/mergers/{company_id}", json=msft_mergers
37
+ url=f"https://kfinance.kensho.com/api/v1/mergers/{SPGI_COMPANY_ID}", json=merger_data
25
38
  )
26
- tool = GetMergersFromIdentifier(kfinance_client=mock_client)
27
- args = ToolArgsWithIdentifiers(identifiers=["MSFT"])
39
+ tool = GetMergersFromIdentifiers(kfinance_client=mock_client)
40
+ args = ToolArgsWithIdentifiers(identifiers=["SPGI", "non-existent"])
28
41
  response = tool.run(args.model_dump(mode="json"))
29
- assert ordered(response) == ordered(expected_response)
42
+ assert response == expected_response
30
43
 
31
44
 
32
45
  class TestGetCompaniesAdvisingCompanyInTransactionFromIdentifier:
@@ -1,20 +1,12 @@
1
1
  from copy import deepcopy
2
2
  from datetime import date
3
- from typing import Any, TypedDict
3
+ from typing import Any
4
4
 
5
5
  from pydantic import BaseModel, model_validator
6
6
 
7
7
  from kfinance.client.models.decimal_with_unit import Money, Shares
8
8
 
9
9
 
10
- class HistoryMetadata(TypedDict):
11
- currency: str
12
- symbol: str
13
- exchange_name: str
14
- instrument_type: str
15
- first_trade_date: date
16
-
17
-
18
10
  class Prices(BaseModel):
19
11
  """Prices represents prices for a stock for a specific "date".
20
12
 
@@ -68,3 +60,11 @@ class PriceHistory(BaseModel):
68
60
  for key in ["open", "high", "low", "close"]:
69
61
  capitalization[key] = dict(unit=currency, value=capitalization[key])
70
62
  return data
63
+
64
+
65
+ class HistoryMetadataResp(BaseModel):
66
+ currency: str
67
+ symbol: str
68
+ exchange_name: str
69
+ instrument_type: str
70
+ first_trade_date: date
@@ -7,13 +7,11 @@ from pydantic import BaseModel, Field
7
7
  from kfinance.client.batch_request_handling import Task, process_tasks_in_thread_pool_executor
8
8
  from kfinance.client.models.date_and_period_models import Periodicity
9
9
  from kfinance.client.permission_models import Permission
10
- from kfinance.domains.companies.company_identifiers import (
11
- fetch_trading_item_ids_from_identifiers,
12
- parse_identifiers,
13
- )
10
+ from kfinance.domains.prices.price_models import HistoryMetadataResp, PriceHistory
14
11
  from kfinance.integrations.tool_calling.tool_calling_models import (
15
12
  KfinanceTool,
16
13
  ToolArgsWithIdentifiers,
14
+ ToolRespWithErrors,
17
15
  )
18
16
 
19
17
 
@@ -32,6 +30,10 @@ class GetPricesFromIdentifiersArgs(ToolArgsWithIdentifiers):
32
30
  )
33
31
 
34
32
 
33
+ class GetPricesFromIdentifiersResp(ToolRespWithErrors):
34
+ results: dict[str, PriceHistory]
35
+
36
+
35
37
  class GetPricesFromIdentifiers(KfinanceTool):
36
38
  name: str = "get_prices_from_identifiers"
37
39
  description: str = dedent("""
@@ -81,20 +83,20 @@ class GetPricesFromIdentifiers(KfinanceTool):
81
83
  'volume': {'value': '1182229', 'unit': 'Shares'}
82
84
  }
83
85
  ]
84
- }
86
+ },
87
+ 'errors': ['No identification triple found for the provided identifier: NON-EXISTENT of type: ticker']
85
88
  }
86
89
  """
90
+
87
91
  api_client = self.kfinance_client.kfinance_api_client
88
- parsed_identifiers = parse_identifiers(identifiers=identifiers, api_client=api_client)
89
- identifiers_to_trading_item_ids = fetch_trading_item_ids_from_identifiers(
90
- identifiers=parsed_identifiers, api_client=api_client
91
- )
92
+ id_triple_resp = api_client.unified_fetch_id_triples(identifiers=identifiers)
93
+ id_triple_resp.filter_out_companies_without_trading_item_ids()
92
94
 
93
95
  tasks = [
94
96
  Task(
95
97
  func=api_client.fetch_history,
96
98
  kwargs=dict(
97
- trading_item_id=trading_item_id,
99
+ trading_item_id=id_triple.trading_item_id,
98
100
  start_date=start_date,
99
101
  end_date=end_date,
100
102
  periodicity=periodicity,
@@ -102,20 +104,26 @@ class GetPricesFromIdentifiers(KfinanceTool):
102
104
  ),
103
105
  result_key=identifier,
104
106
  )
105
- for identifier, trading_item_id in identifiers_to_trading_item_ids.items()
107
+ for identifier, id_triple in id_triple_resp.identifiers_to_id_triples.items()
106
108
  ]
107
109
 
108
- price_responses = process_tasks_in_thread_pool_executor(api_client=api_client, tasks=tasks)
110
+ price_responses: dict[str, PriceHistory] = process_tasks_in_thread_pool_executor(
111
+ api_client=api_client, tasks=tasks
112
+ )
113
+ # If we return results for more than one company and the start and end dates are unset,
114
+ # truncate data to only return the most recent datapoint.
115
+ if len(price_responses) > 1 and start_date is None and end_date is None:
116
+ for price_response in price_responses.values():
117
+ price_response.prices = price_response.prices[-1:]
118
+
119
+ output_model = GetPricesFromIdentifiersResp(
120
+ results=price_responses, errors=list(id_triple_resp.errors.values())
121
+ )
122
+ return output_model.model_dump(mode="json")
109
123
 
110
- # Only include most recent price if more than one identifier passed and start_date == end_date == None
111
- dump_include_filter = None
112
- if len(identifiers) > 1 and start_date == end_date is None:
113
- dump_include_filter = {"prices": {0: True}}
114
124
 
115
- return {
116
- str(identifier): prices.model_dump(mode="json", include=dump_include_filter)
117
- for identifier, prices in price_responses.items()
118
- }
125
+ class GetHistoryMetadataFromIdentifiersResp(ToolRespWithErrors):
126
+ results: dict[str, HistoryMetadataResp]
119
127
 
120
128
 
121
129
  class GetHistoryMetadataFromIdentifiers(KfinanceTool):
@@ -132,34 +140,37 @@ class GetHistoryMetadataFromIdentifiers(KfinanceTool):
132
140
  """Sample response:
133
141
 
134
142
  {
135
- 'SPGI': {
136
- 'currency': 'USD',
137
- 'exchange_name': 'NYSE',
138
- 'first_trade_date': '1968-01-02',
139
- 'instrument_type': 'Equity',
140
- 'symbol': 'SPGI'
141
- }
143
+ 'results': {
144
+ 'SPGI': {
145
+ 'currency': 'USD',
146
+ 'exchange_name': 'NYSE',
147
+ 'first_trade_date': '1968-01-02',
148
+ 'instrument_type': 'Equity',
149
+ 'symbol': 'SPGI'
150
+ }
151
+ },
152
+ 'errors': ['No identification triple found for the provided identifier: NON-EXISTENT of type: ticker']
142
153
  }
143
154
  """
155
+
144
156
  api_client = self.kfinance_client.kfinance_api_client
145
- parsed_identifiers = parse_identifiers(identifiers=identifiers, api_client=api_client)
146
- identifiers_to_trading_item_ids = fetch_trading_item_ids_from_identifiers(
147
- identifiers=parsed_identifiers, api_client=api_client
148
- )
157
+ id_triple_resp = api_client.unified_fetch_id_triples(identifiers=identifiers)
158
+ id_triple_resp.filter_out_companies_without_trading_item_ids()
149
159
 
150
160
  tasks = [
151
161
  Task(
152
162
  func=api_client.fetch_history_metadata,
153
- kwargs=dict(trading_item_id=trading_item_id),
163
+ kwargs=dict(trading_item_id=id_triple.trading_item_id),
154
164
  result_key=identifier,
155
165
  )
156
- for identifier, trading_item_id in identifiers_to_trading_item_ids.items()
166
+ for identifier, id_triple in id_triple_resp.identifiers_to_id_triples.items()
157
167
  ]
158
168
 
159
- history_metadata_responses = process_tasks_in_thread_pool_executor(
160
- api_client=api_client, tasks=tasks
169
+ history_metadata_responses: dict[str, HistoryMetadataResp] = (
170
+ process_tasks_in_thread_pool_executor(api_client=api_client, tasks=tasks)
171
+ )
172
+ output_model = GetHistoryMetadataFromIdentifiersResp(
173
+ results=history_metadata_responses, errors=list(id_triple_resp.errors.values())
161
174
  )
162
175
 
163
- return {
164
- str(identifier): result for identifier, result in history_metadata_responses.items()
165
- }
176
+ return output_model.model_dump(mode="json")