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

Potentially problematic release.


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

Files changed (134) hide show
  1. {kensho_kfinance-2.8.0.dist-info → kensho_kfinance-3.0.0.dist-info}/METADATA +2 -2
  2. kensho_kfinance-3.0.0.dist-info/RECORD +110 -0
  3. kfinance/CHANGELOG.md +6 -0
  4. kfinance/__init__.py +1 -0
  5. kfinance/client/README.md +9 -0
  6. kfinance/{batch_request_handling.py → client/batch_request_handling.py} +63 -27
  7. kfinance/{fetch.py → client/fetch.py} +23 -29
  8. kfinance/{kfinance.py → client/kfinance.py} +105 -111
  9. kfinance/{meta_classes.py → client/meta_classes.py} +26 -35
  10. kfinance/{decimal_with_unit.py → client/models/decimal_with_unit.py} +1 -1
  11. kfinance/{tests → client/models/tests}/test_decimal_with_unit.py +1 -1
  12. kfinance/client/tests/__init__.py +0 -0
  13. kfinance/{tests → client/tests}/test_batch_requests.py +8 -6
  14. kfinance/{tests → client/tests}/test_client.py +25 -19
  15. kfinance/{tests → client/tests}/test_fetch.py +11 -29
  16. kfinance/{tests → client/tests}/test_group_objects.py +1 -1
  17. kfinance/{tests → client/tests}/test_objects.py +111 -63
  18. kfinance/{tests/conftest.py → conftest.py} +14 -2
  19. kfinance/domains/README.md +14 -0
  20. kfinance/domains/__init__.py +0 -0
  21. kfinance/domains/business_relationships/__init__.py +0 -0
  22. kfinance/{models → domains/business_relationships}/business_relationship_models.py +10 -0
  23. kfinance/domains/business_relationships/business_relationship_tools.py +74 -0
  24. kfinance/domains/business_relationships/tests/__init__.py +0 -0
  25. kfinance/domains/business_relationships/tests/test_business_relationship_tools.py +55 -0
  26. kfinance/domains/capitalizations/__init__.py +0 -0
  27. kfinance/{models → domains/capitalizations}/capitalization_models.py +24 -17
  28. kfinance/domains/capitalizations/capitalization_tools.py +89 -0
  29. kfinance/domains/capitalizations/tests/__init__.py +0 -0
  30. kfinance/{tests/test_models → domains/capitalizations/tests}/test_capitalization_models.py +8 -10
  31. kfinance/domains/capitalizations/tests/test_capitalization_tools.py +85 -0
  32. kfinance/domains/companies/__init__.py +0 -0
  33. kfinance/domains/companies/company_identifiers.py +175 -0
  34. kfinance/domains/companies/company_models.py +27 -0
  35. kfinance/domains/companies/company_tools.py +66 -0
  36. kfinance/domains/companies/tests/__init__.py +0 -0
  37. kfinance/domains/companies/tests/test_company_tools.py +26 -0
  38. kfinance/domains/competitors/__init__.py +0 -0
  39. kfinance/{models → domains/competitors}/competitor_models.py +7 -0
  40. kfinance/domains/competitors/competitor_tools.py +62 -0
  41. kfinance/domains/competitors/tests/__init__.py +0 -0
  42. kfinance/domains/competitors/tests/test_competitor_tools.py +45 -0
  43. kfinance/domains/cusip_and_isin/__init__.py +0 -0
  44. kfinance/domains/cusip_and_isin/cusip_and_isin_tools.py +80 -0
  45. kfinance/domains/cusip_and_isin/tests/__init__.py +0 -0
  46. kfinance/domains/cusip_and_isin/tests/test_cusip_and_isin_tools.py +57 -0
  47. kfinance/domains/earnings/__init__.py +0 -0
  48. kfinance/domains/earnings/earning_models.py +41 -0
  49. kfinance/domains/earnings/earning_tools.py +174 -0
  50. kfinance/domains/earnings/tests/__init__.py +0 -0
  51. kfinance/domains/earnings/tests/test_earnings_tools.py +195 -0
  52. kfinance/domains/line_items/__init__.py +0 -0
  53. kfinance/domains/line_items/line_item_tools.py +114 -0
  54. kfinance/domains/line_items/tests/__init__.py +0 -0
  55. kfinance/domains/line_items/tests/test_line_item_tools.py +86 -0
  56. kfinance/domains/mergers_and_acquisitions/__init__.py +0 -0
  57. kfinance/domains/mergers_and_acquisitions/merger_and_acquisition_tools.py +176 -0
  58. kfinance/domains/mergers_and_acquisitions/tests/__init__.py +0 -0
  59. kfinance/domains/mergers_and_acquisitions/tests/test_merger_and_acquisition_tools.py +124 -0
  60. kfinance/domains/prices/__init__.py +0 -0
  61. kfinance/{models → domains/prices}/price_models.py +1 -1
  62. kfinance/domains/prices/price_tools.py +165 -0
  63. kfinance/domains/prices/tests/__init__.py +0 -0
  64. kfinance/{tests/test_models → domains/prices/tests}/test_price_models.py +2 -2
  65. kfinance/domains/prices/tests/test_price_tools.py +141 -0
  66. kfinance/domains/segments/__init__.py +0 -0
  67. kfinance/domains/segments/segment_tools.py +91 -0
  68. kfinance/domains/segments/tests/__init__.py +0 -0
  69. kfinance/domains/segments/tests/test_segment_tools.py +80 -0
  70. kfinance/domains/statements/__init__.py +0 -0
  71. kfinance/domains/statements/statement_tools.py +113 -0
  72. kfinance/domains/statements/tests/__init__.py +0 -0
  73. kfinance/domains/statements/tests/test_statement_tools.py +73 -0
  74. kfinance/integrations/README.md +8 -0
  75. kfinance/integrations/__init__.py +0 -0
  76. kfinance/integrations/mcp/__init__.py +0 -0
  77. kfinance/{mcp.py → integrations/mcp/mcp.py} +2 -2
  78. kfinance/integrations/tests/__init__.py +0 -0
  79. kfinance/{tests → integrations/tests}/test_example_notebook.py +4 -4
  80. kfinance/{tool_calling → integrations/tool_calling}/README.md +2 -2
  81. kfinance/integrations/tool_calling/__init__.py +0 -0
  82. kfinance/integrations/tool_calling/all_tools.py +55 -0
  83. kfinance/{tool_calling → integrations/tool_calling}/prompts.py +3 -2
  84. kfinance/integrations/tool_calling/static_tools/README.md +4 -0
  85. kfinance/integrations/tool_calling/static_tools/__init__.py +0 -0
  86. kfinance/{tool_calling → integrations/tool_calling/static_tools}/get_latest.py +3 -3
  87. kfinance/{tool_calling → integrations/tool_calling/static_tools}/get_n_quarters_ago.py +3 -3
  88. kfinance/integrations/tool_calling/static_tools/tests/__init__.py +0 -0
  89. kfinance/integrations/tool_calling/static_tools/tests/test_get_lastest.py +30 -0
  90. kfinance/integrations/tool_calling/static_tools/tests/test_get_n_quarters_ago.py +24 -0
  91. kfinance/integrations/tool_calling/tests/__init__.py +0 -0
  92. kfinance/integrations/tool_calling/tests/test_tool_calling_models.py +69 -0
  93. kfinance/{tool_calling/shared_models.py → integrations/tool_calling/tool_calling_models.py} +37 -7
  94. kfinance/version.py +2 -2
  95. kensho_kfinance-2.8.0.dist-info/RECORD +0 -70
  96. kfinance/models/id_models.py +0 -7
  97. kfinance/prompt.py +0 -526
  98. kfinance/pydantic_models.py +0 -33
  99. kfinance/tests/test_tools.py +0 -804
  100. kfinance/tool_calling/__init__.py +0 -53
  101. kfinance/tool_calling/get_advisors_for_company_in_transaction_from_identifier.py +0 -39
  102. kfinance/tool_calling/get_business_relationship_from_identifier.py +0 -30
  103. kfinance/tool_calling/get_capitalization_from_identifier.py +0 -35
  104. kfinance/tool_calling/get_competitors_from_identifier.py +0 -25
  105. kfinance/tool_calling/get_cusip_from_ticker.py +0 -20
  106. kfinance/tool_calling/get_earnings.py +0 -33
  107. kfinance/tool_calling/get_financial_line_item_from_identifier.py +0 -48
  108. kfinance/tool_calling/get_financial_statement_from_identifier.py +0 -44
  109. kfinance/tool_calling/get_history_metadata_from_identifier.py +0 -17
  110. kfinance/tool_calling/get_info_from_identifier.py +0 -16
  111. kfinance/tool_calling/get_isin_from_ticker.py +0 -20
  112. kfinance/tool_calling/get_latest_earnings.py +0 -30
  113. kfinance/tool_calling/get_merger_info_from_transaction_id.py +0 -68
  114. kfinance/tool_calling/get_mergers_from_identifier.py +0 -41
  115. kfinance/tool_calling/get_next_earnings.py +0 -30
  116. kfinance/tool_calling/get_prices_from_identifier.py +0 -46
  117. kfinance/tool_calling/get_segments_from_identifier.py +0 -44
  118. kfinance/tool_calling/get_transcript.py +0 -23
  119. kfinance/tool_calling/resolve_identifier.py +0 -18
  120. {kensho_kfinance-2.8.0.dist-info → kensho_kfinance-3.0.0.dist-info}/WHEEL +0 -0
  121. {kensho_kfinance-2.8.0.dist-info → kensho_kfinance-3.0.0.dist-info}/licenses/AUTHORS.md +0 -0
  122. {kensho_kfinance-2.8.0.dist-info → kensho_kfinance-3.0.0.dist-info}/licenses/LICENSE +0 -0
  123. {kensho_kfinance-2.8.0.dist-info → kensho_kfinance-3.0.0.dist-info}/top_level.txt +0 -0
  124. /kfinance/{models → client}/__init__.py +0 -0
  125. /kfinance/{models → client}/industry_models.py +0 -0
  126. /kfinance/{tests → client/models}/__init__.py +0 -0
  127. /kfinance/{models → client/models}/currency_models.py +0 -0
  128. /kfinance/{models → client/models}/date_and_period_models.py +0 -0
  129. /kfinance/{tests/test_models → client/models/tests}/__init__.py +0 -0
  130. /kfinance/{models → client}/permission_models.py +0 -0
  131. /kfinance/{server_thread.py → client/server_thread.py} +0 -0
  132. /kfinance/{models → domains/line_items}/line_item_models.py +0 -0
  133. /kfinance/{models → domains/segments}/segment_models.py +0 -0
  134. /kfinance/{models → domains/statements}/statement_models.py +0 -0
@@ -0,0 +1,86 @@
1
+ from langchain_core.utils.function_calling import convert_to_openai_tool
2
+ from requests_mock import Mocker
3
+
4
+ from kfinance.client.kfinance import Client
5
+ from kfinance.conftest import SPGI_COMPANY_ID
6
+ from kfinance.domains.companies.company_models import COMPANY_ID_PREFIX
7
+ from kfinance.domains.line_items.line_item_tools import (
8
+ GetFinancialLineItemFromIdentifiers,
9
+ GetFinancialLineItemFromIdentifiersArgs,
10
+ )
11
+
12
+
13
+ class TestGetFinancialLineItemFromCompanyIds:
14
+ line_item_resp = {
15
+ "line_item": {
16
+ "2022": "11181000000.000000",
17
+ "2023": "12497000000.000000",
18
+ "2024": "14208000000.000000",
19
+ }
20
+ }
21
+
22
+ def test_get_financial_line_item_from_identifiers(
23
+ self, mock_client: Client, requests_mock: Mocker
24
+ ):
25
+ """
26
+ GIVEN the GetFinancialLineItemFromCompanyId tool
27
+ WHEN we request SPGI revenue
28
+ THEN we get back the SPGI revenue
29
+ """
30
+
31
+ expected_response = {
32
+ "SPGI": {
33
+ "2022": {"revenue": 11181000000.0},
34
+ "2023": {"revenue": 12497000000.0},
35
+ "2024": {"revenue": 14208000000.0},
36
+ }
37
+ }
38
+
39
+ requests_mock.get(
40
+ url=f"https://kfinance.kensho.com/api/v1/line_item/{SPGI_COMPANY_ID}/revenue/none/none/none/none/none",
41
+ json=self.line_item_resp,
42
+ )
43
+
44
+ tool = GetFinancialLineItemFromIdentifiers(kfinance_client=mock_client)
45
+ args = GetFinancialLineItemFromIdentifiersArgs(identifiers=["SPGI"], line_item="revenue")
46
+ response = tool.run(args.model_dump(mode="json"))
47
+ assert response == expected_response
48
+
49
+ def test_most_recent_request(self, requests_mock: Mocker, mock_client: Client) -> None:
50
+ """
51
+ GIVEN the GetFinancialLineItemFromIdentifiers tool
52
+ WHEN we request most recent line items for multiple companies
53
+ THEN we only get back the most recent line item for each company
54
+ """
55
+
56
+ company_ids = [1, 2]
57
+ expected_response = {
58
+ "C_1": {"2024": {"revenue": 14208000000.0}},
59
+ "C_2": {"2024": {"revenue": 14208000000.0}},
60
+ }
61
+ for company_id in company_ids:
62
+ requests_mock.get(
63
+ url=f"https://kfinance.kensho.com/api/v1/line_item/{company_id}/revenue/none/none/none/none/none",
64
+ json=self.line_item_resp,
65
+ )
66
+ tool = GetFinancialLineItemFromIdentifiers(kfinance_client=mock_client)
67
+ args = GetFinancialLineItemFromIdentifiersArgs(
68
+ identifiers=[f"{COMPANY_ID_PREFIX}{company_id}" for company_id in company_ids],
69
+ line_item="revenue",
70
+ )
71
+ response = tool.run(args.model_dump(mode="json"))
72
+ assert response == expected_response
73
+
74
+ def test_line_items_and_aliases_included_in_schema(self, mock_client: Client):
75
+ """
76
+ GIVEN a GetFinancialLineItemFromCompanyIds tool
77
+ WHEN we generate an openai schema from the tool
78
+ THEN all line items and aliases are included in the line item enum
79
+ """
80
+ tool = GetFinancialLineItemFromIdentifiers(kfinance_client=mock_client)
81
+ oai_schema = convert_to_openai_tool(tool)
82
+ line_items = oai_schema["function"]["parameters"]["properties"]["line_item"]["enum"]
83
+ # revenue is a line item
84
+ assert "revenue" in line_items
85
+ # normal_revenue is an alias for revenue
86
+ assert "normal_revenue" in line_items
File without changes
@@ -0,0 +1,176 @@
1
+ from textwrap import dedent
2
+ from typing import Type
3
+
4
+ from pydantic import BaseModel, Field
5
+
6
+ from kfinance.client.batch_request_handling import Task, process_tasks_in_thread_pool_executor
7
+ from kfinance.client.kfinance import Company, MergerOrAcquisition, ParticipantInMerger
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
+ )
14
+ from kfinance.integrations.tool_calling.tool_calling_models import (
15
+ KfinanceTool,
16
+ ToolArgsWithIdentifier,
17
+ ToolArgsWithIdentifiers,
18
+ )
19
+
20
+
21
+ class GetMergersFromIdentifier(KfinanceTool):
22
+ name: str = "get_mergers_from_identifiers"
23
+ description: str = dedent("""
24
+ Get the transaction IDs that involve the given identifiers.
25
+
26
+ For example, "Which companies did Microsoft purchase?" or "Which company bought Ben & Jerrys?"
27
+ """).strip()
28
+ args_schema: Type[BaseModel] = ToolArgsWithIdentifiers
29
+ accepted_permissions: set[Permission] | None = {Permission.MergersPermission}
30
+
31
+ def _run(self, identifiers: list[str]) -> dict:
32
+ 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
+ )
37
+
38
+ tasks = [
39
+ Task(
40
+ func=api_client.fetch_mergers_for_company,
41
+ kwargs=dict(company_id=company_id),
42
+ result_key=identifier,
43
+ )
44
+ for identifier, company_id in identifiers_to_company_ids.items()
45
+ ]
46
+
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()}
50
+
51
+
52
+ class GetMergerInfoFromTransactionIdArgs(BaseModel):
53
+ transaction_id: int | None = Field(description="The ID of the transaction.", default=None)
54
+
55
+
56
+ class GetMergerInfoFromTransactionId(KfinanceTool):
57
+ name: str = "get_merger_info_from_transaction_id"
58
+ description: str = dedent("""
59
+ Get the timeline, the participants, and the consideration of the merger or acquisition from the given transaction ID.
60
+
61
+ For example, "How much was Ben & Jerrys purchased for?" or "What was the price per share for LinkedIn?" or "When did S&P purchase Kensho?"
62
+ """).strip()
63
+ args_schema: Type[BaseModel] = GetMergerInfoFromTransactionIdArgs
64
+ accepted_permissions: set[Permission] | None = {Permission.MergersPermission}
65
+
66
+ def _run(self, transaction_id: int) -> dict:
67
+ merger_or_acquisition = MergerOrAcquisition(
68
+ kfinance_api_client=self.kfinance_client.kfinance_api_client,
69
+ transaction_id=transaction_id,
70
+ merger_title=None,
71
+ closed_date=None,
72
+ )
73
+ merger_timeline = merger_or_acquisition.get_timeline
74
+ merger_participants = merger_or_acquisition.get_participants
75
+ merger_consideration = merger_or_acquisition.get_consideration
76
+
77
+ return {
78
+ "timeline": [
79
+ {"status": timeline["status"], "date": timeline["date"].strftime("%Y-%m-%d")}
80
+ for timeline in merger_timeline.to_dict(orient="records")
81
+ ]
82
+ if merger_timeline is not None
83
+ else None,
84
+ "participants": {
85
+ "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
+ )
91
+ ),
92
+ "company_name": merger_participants["target"].company.name,
93
+ },
94
+ "buyers": [
95
+ {
96
+ "company_id": str(
97
+ CompanyId(
98
+ buyer.company.company_id,
99
+ api_client=self.kfinance_client.kfinance_api_client,
100
+ )
101
+ ),
102
+ "company_name": buyer.company.name,
103
+ }
104
+ for buyer in merger_participants["buyers"]
105
+ ],
106
+ "sellers": [
107
+ {
108
+ "company_id": str(
109
+ CompanyId(
110
+ seller.company.company_id,
111
+ api_client=self.kfinance_client.kfinance_api_client,
112
+ )
113
+ ),
114
+ "company_name": seller.company.name,
115
+ }
116
+ for seller in merger_participants["sellers"]
117
+ ],
118
+ }
119
+ if merger_participants is not None
120
+ else None,
121
+ "consideration": {
122
+ "currency_name": merger_consideration["currency_name"],
123
+ "current_calculated_gross_total_transaction_value": merger_consideration[
124
+ "current_calculated_gross_total_transaction_value"
125
+ ],
126
+ "current_calculated_implied_equity_value": merger_consideration[
127
+ "current_calculated_implied_equity_value"
128
+ ],
129
+ "current_calculated_implied_enterprise_value": merger_consideration[
130
+ "current_calculated_implied_enterprise_value"
131
+ ],
132
+ "details": merger_consideration["details"].to_dict(orient="records"),
133
+ }
134
+ if merger_consideration is not None
135
+ else None,
136
+ }
137
+
138
+
139
+ class GetAdvisorsForCompanyInTransactionFromIdentifierArgs(ToolArgsWithIdentifier):
140
+ transaction_id: int | None = Field(description="The ID of the merger.", default=None)
141
+
142
+
143
+ class GetAdvisorsForCompanyInTransactionFromIdentifier(KfinanceTool):
144
+ name: str = "get_advisors_for_company_in_transaction_from_identifier"
145
+ description: str = 'Get the companies advising a company in a given transaction. For example, "Who advised S&P Global during their purchase of Kensho?"'
146
+ args_schema: Type[BaseModel] = GetAdvisorsForCompanyInTransactionFromIdentifierArgs
147
+ accepted_permissions: set[Permission] | None = {Permission.MergersPermission}
148
+
149
+ def _run(self, identifier: str, transaction_id: int) -> list:
150
+ ticker = self.kfinance_client.ticker(identifier)
151
+ participant_in_merger = ParticipantInMerger(
152
+ kfinance_api_client=ticker.kfinance_api_client,
153
+ transaction_id=transaction_id,
154
+ company=Company(
155
+ kfinance_api_client=ticker.kfinance_api_client,
156
+ company_id=ticker.company.company_id,
157
+ ),
158
+ )
159
+ advisors = participant_in_merger.advisors
160
+
161
+ if advisors:
162
+ return [
163
+ {
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
+ ),
170
+ "advisor_company_name": advisor.company.name,
171
+ "advisor_type_name": advisor.advisor_type_name,
172
+ }
173
+ for advisor in advisors
174
+ ]
175
+ else:
176
+ return []
@@ -0,0 +1,124 @@
1
+ from copy import deepcopy
2
+
3
+ from requests_mock import Mocker
4
+
5
+ from kfinance.client.kfinance import Client
6
+ from kfinance.client.tests.test_objects import MOCK_COMPANY_DB, ordered
7
+ from kfinance.domains.companies.company_models import COMPANY_ID_PREFIX
8
+ from kfinance.domains.mergers_and_acquisitions.merger_and_acquisition_tools import (
9
+ GetAdvisorsForCompanyInTransactionFromIdentifier,
10
+ GetAdvisorsForCompanyInTransactionFromIdentifierArgs,
11
+ GetMergerInfoFromTransactionId,
12
+ GetMergerInfoFromTransactionIdArgs,
13
+ GetMergersFromIdentifier,
14
+ )
15
+ from kfinance.integrations.tool_calling.tool_calling_models import ToolArgsWithIdentifiers
16
+
17
+
18
+ class TestGetMergersFromIdentifiers:
19
+ 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
23
+ requests_mock.get(
24
+ url=f"https://kfinance.kensho.com/api/v1/mergers/{company_id}", json=msft_mergers
25
+ )
26
+ tool = GetMergersFromIdentifier(kfinance_client=mock_client)
27
+ args = ToolArgsWithIdentifiers(identifiers=["MSFT"])
28
+ response = tool.run(args.model_dump(mode="json"))
29
+ assert ordered(response) == ordered(expected_response)
30
+
31
+
32
+ class TestGetCompaniesAdvisingCompanyInTransactionFromIdentifier:
33
+ def test_get_companies_advising_company_in_transaction_from_identifier(
34
+ self, requests_mock: Mocker, mock_client: Client
35
+ ):
36
+ api_response = {
37
+ "advisors": [
38
+ {
39
+ "advisor_company_id": 251994106,
40
+ "advisor_company_name": "Kensho Technologies, Inc.",
41
+ "advisor_type_name": "Professional Mongo Enjoyer",
42
+ }
43
+ ]
44
+ }
45
+ expected_response = deepcopy(api_response)
46
+ expected_response["advisors"][0]["advisor_company_id"] = f"{COMPANY_ID_PREFIX}251994106"
47
+ transaction_id = 517414
48
+ requests_mock.get(
49
+ url=f"https://kfinance.kensho.com/api/v1/merger/info/{transaction_id}/advisors/21835",
50
+ json=api_response,
51
+ )
52
+ tool = GetAdvisorsForCompanyInTransactionFromIdentifier(kfinance_client=mock_client)
53
+ args = GetAdvisorsForCompanyInTransactionFromIdentifierArgs(
54
+ identifier="MSFT", transaction_id=transaction_id
55
+ )
56
+ response = tool.run(args.model_dump(mode="json"))
57
+ assert response == expected_response["advisors"]
58
+
59
+
60
+ class TestGetMergerInfoFromTransactionId:
61
+ def test_get_merger_info_from_transaction_id(self, requests_mock: Mocker, mock_client: Client):
62
+ response_base = {
63
+ "timeline": [
64
+ {"status": "Announced", "date": "2000-09-12"},
65
+ {"status": "Closed", "date": "2000-09-12"},
66
+ ],
67
+ "participants": {},
68
+ "consideration": {
69
+ "currency_name": "US Dollar",
70
+ "current_calculated_gross_total_transaction_value": "51609375.000000",
71
+ "current_calculated_implied_equity_value": "51609375.000000",
72
+ "current_calculated_implied_enterprise_value": "51609375.000000",
73
+ "details": [
74
+ {
75
+ "scenario": "Stock Lump Sum",
76
+ "subtype": "Common Equity",
77
+ "cash_or_cash_equivalent_per_target_share_unit": None,
78
+ "number_of_target_shares_sought": "1000000.000000",
79
+ "current_calculated_gross_value_of_consideration": "51609375.000000",
80
+ }
81
+ ],
82
+ },
83
+ }
84
+
85
+ participants_api_response = {
86
+ "target": {"company_id": 31696, "company_name": "MongoMusic, Inc."},
87
+ "buyers": [{"company_id": 21835, "company_name": "Microsoft Corporation"}],
88
+ "sellers": [
89
+ {"company_id": 18805, "company_name": "Angel Investors L.P."},
90
+ {"company_id": 20087, "company_name": "Draper Richards, L.P."},
91
+ ],
92
+ }
93
+ api_response = deepcopy(response_base)
94
+ api_response["participants"] = participants_api_response
95
+
96
+ # Returned company IDs should be prefixed
97
+ expected_participants_tool_response = {
98
+ "target": {
99
+ "company_id": f"{COMPANY_ID_PREFIX}31696",
100
+ "company_name": "MongoMusic, Inc.",
101
+ },
102
+ "buyers": [
103
+ {"company_id": f"{COMPANY_ID_PREFIX}21835", "company_name": "Microsoft Corporation"}
104
+ ],
105
+ "sellers": [
106
+ {"company_id": f"{COMPANY_ID_PREFIX}18805", "company_name": "Angel Investors L.P."},
107
+ {
108
+ "company_id": f"{COMPANY_ID_PREFIX}20087",
109
+ "company_name": "Draper Richards, L.P.",
110
+ },
111
+ ],
112
+ }
113
+ expected_response = deepcopy(response_base)
114
+ expected_response["participants"] = expected_participants_tool_response
115
+
116
+ transaction_id = 517414
117
+ requests_mock.get(
118
+ url=f"https://kfinance.kensho.com/api/v1/merger/info/{transaction_id}",
119
+ json=api_response,
120
+ )
121
+ tool = GetMergerInfoFromTransactionId(kfinance_client=mock_client)
122
+ args = GetMergerInfoFromTransactionIdArgs(transaction_id=transaction_id)
123
+ response = tool.run(args.model_dump(mode="json"))
124
+ assert ordered(response) == ordered(expected_response)
File without changes
@@ -4,7 +4,7 @@ from typing import Any, TypedDict
4
4
 
5
5
  from pydantic import BaseModel, model_validator
6
6
 
7
- from kfinance.decimal_with_unit import Money, Shares
7
+ from kfinance.client.models.decimal_with_unit import Money, Shares
8
8
 
9
9
 
10
10
  class HistoryMetadata(TypedDict):
@@ -0,0 +1,165 @@
1
+ from datetime import date
2
+ from textwrap import dedent
3
+ from typing import Type
4
+
5
+ from pydantic import BaseModel, Field
6
+
7
+ from kfinance.client.batch_request_handling import Task, process_tasks_in_thread_pool_executor
8
+ from kfinance.client.models.date_and_period_models import Periodicity
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
+ )
14
+ from kfinance.integrations.tool_calling.tool_calling_models import (
15
+ KfinanceTool,
16
+ ToolArgsWithIdentifiers,
17
+ )
18
+
19
+
20
+ class GetPricesFromIdentifiersArgs(ToolArgsWithIdentifiers):
21
+ start_date: date | None = Field(
22
+ description="The start date for historical price retrieval", default=None
23
+ )
24
+ end_date: date | None = Field(
25
+ description="The end date for historical price retrieval", default=None
26
+ )
27
+ # no description because the description for enum fields comes from the enum docstring.
28
+ periodicity: Periodicity = Field(default=Periodicity.day)
29
+ adjusted: bool = Field(
30
+ description="Whether to retrieve adjusted prices that account for corporate actions such as dividends and splits.",
31
+ default=True,
32
+ )
33
+
34
+
35
+ class GetPricesFromIdentifiers(KfinanceTool):
36
+ name: str = "get_prices_from_identifiers"
37
+ description: str = dedent("""
38
+ Get the historical open, high, low, and close prices, and volume of a group of identifiers between inclusive start_date and inclusive end date.
39
+
40
+ - When possible, pass multiple identifiers in a single call rather than making multiple calls.
41
+ - When requesting the most recent values, leave start_date and end_date empty.
42
+
43
+ Example:
44
+ Query: "What are the prices of Facebook and Google?"
45
+ Do:
46
+ get_prices_from_identifiers(identifiers=["META", "GOOGL"])
47
+ Don't:
48
+ get_prices_from_identifiers(trading_item_ids=["META"])
49
+ get_prices_from_identifiers(trading_item_ids=["GOOGL"])
50
+ """).strip()
51
+ args_schema: Type[BaseModel] = GetPricesFromIdentifiersArgs
52
+ accepted_permissions: set[Permission] | None = {Permission.PricingPermission}
53
+
54
+ def _run(
55
+ self,
56
+ identifiers: list[str],
57
+ start_date: date | None = None,
58
+ end_date: date | None = None,
59
+ periodicity: Periodicity = Periodicity.day,
60
+ adjusted: bool = True,
61
+ ) -> dict:
62
+ """Sample Response:
63
+
64
+ {
65
+ "SPGI": {
66
+ 'prices': [
67
+ {
68
+ 'date': '2024-04-11',
69
+ 'open': {'value': '424.26', 'unit': 'USD'},
70
+ 'high': {'value': '425.99', 'unit': 'USD'},
71
+ 'low': {'value': '422.04', 'unit': 'USD'},
72
+ 'close': {'value': '422.92', 'unit': 'USD'},
73
+ 'volume': {'value': '1129158', 'unit': 'Shares'}
74
+ },
75
+ {
76
+ 'date': '2024-04-12',
77
+ 'open': {'value': '419.23', 'unit': 'USD'},
78
+ 'high': {'value': '421.94', 'unit': 'USD'},
79
+ 'low': {'value': '416.45', 'unit': 'USD'},
80
+ 'close': {'value': '417.81', 'unit': 'USD'},
81
+ 'volume': {'value': '1182229', 'unit': 'Shares'}
82
+ }
83
+ ]
84
+ }
85
+ }
86
+ """
87
+ 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
+
93
+ tasks = [
94
+ Task(
95
+ func=api_client.fetch_history,
96
+ kwargs=dict(
97
+ trading_item_id=trading_item_id,
98
+ start_date=start_date,
99
+ end_date=end_date,
100
+ periodicity=periodicity,
101
+ is_adjusted=adjusted,
102
+ ),
103
+ result_key=identifier,
104
+ )
105
+ for identifier, trading_item_id in identifiers_to_trading_item_ids.items()
106
+ ]
107
+
108
+ price_responses = process_tasks_in_thread_pool_executor(api_client=api_client, tasks=tasks)
109
+
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
+
115
+ return {
116
+ str(identifier): prices.model_dump(mode="json", include=dump_include_filter)
117
+ for identifier, prices in price_responses.items()
118
+ }
119
+
120
+
121
+ class GetHistoryMetadataFromIdentifiers(KfinanceTool):
122
+ name: str = "get_history_metadata_from_identifiers"
123
+ description: str = dedent("""
124
+ Get the history metadata associated with a list of identifiers. History metadata includes currency, symbol, exchange name, instrument type, and first trade date.
125
+
126
+ - When possible, pass multiple identifiers in a single call rather than making multiple calls.
127
+ """).strip()
128
+ args_schema: Type[BaseModel] = ToolArgsWithIdentifiers
129
+ accepted_permissions: set[Permission] | None = None
130
+
131
+ def _run(self, identifiers: list[str]) -> dict:
132
+ """Sample response:
133
+
134
+ {
135
+ 'SPGI': {
136
+ 'currency': 'USD',
137
+ 'exchange_name': 'NYSE',
138
+ 'first_trade_date': '1968-01-02',
139
+ 'instrument_type': 'Equity',
140
+ 'symbol': 'SPGI'
141
+ }
142
+ }
143
+ """
144
+ 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
+ )
149
+
150
+ tasks = [
151
+ Task(
152
+ func=api_client.fetch_history_metadata,
153
+ kwargs=dict(trading_item_id=trading_item_id),
154
+ result_key=identifier,
155
+ )
156
+ for identifier, trading_item_id in identifiers_to_trading_item_ids.items()
157
+ ]
158
+
159
+ history_metadata_responses = process_tasks_in_thread_pool_executor(
160
+ api_client=api_client, tasks=tasks
161
+ )
162
+
163
+ return {
164
+ str(identifier): result for identifier, result in history_metadata_responses.items()
165
+ }
File without changes
@@ -1,7 +1,7 @@
1
1
  from decimal import Decimal
2
2
 
3
- from kfinance.decimal_with_unit import Money, Shares
4
- from kfinance.models.price_models import PriceHistory, Prices
3
+ from kfinance.client.models.decimal_with_unit import Money, Shares
4
+ from kfinance.domains.prices.price_models import PriceHistory, Prices
5
5
 
6
6
 
7
7
  class TestPriceHistory: