kensho-kfinance 2.9.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.
- {kensho_kfinance-2.9.0.dist-info → kensho_kfinance-3.0.0.dist-info}/METADATA +1 -1
- kensho_kfinance-3.0.0.dist-info/RECORD +110 -0
- kfinance/CHANGELOG.md +3 -0
- kfinance/__init__.py +1 -0
- kfinance/client/README.md +9 -0
- kfinance/{batch_request_handling.py → client/batch_request_handling.py} +63 -27
- kfinance/{fetch.py → client/fetch.py} +23 -29
- kfinance/{kfinance.py → client/kfinance.py} +37 -33
- kfinance/{meta_classes.py → client/meta_classes.py} +26 -35
- kfinance/{decimal_with_unit.py → client/models/decimal_with_unit.py} +1 -1
- kfinance/{tests → client/models/tests}/test_decimal_with_unit.py +1 -1
- kfinance/client/tests/__init__.py +0 -0
- kfinance/{tests → client/tests}/test_batch_requests.py +8 -6
- kfinance/{tests → client/tests}/test_client.py +25 -19
- kfinance/{tests → client/tests}/test_fetch.py +11 -29
- kfinance/{tests → client/tests}/test_group_objects.py +1 -1
- kfinance/{tests → client/tests}/test_objects.py +33 -29
- kfinance/{tests/conftest.py → conftest.py} +14 -2
- kfinance/domains/README.md +14 -0
- kfinance/domains/__init__.py +0 -0
- kfinance/domains/business_relationships/__init__.py +0 -0
- kfinance/{models → domains/business_relationships}/business_relationship_models.py +10 -0
- kfinance/domains/business_relationships/business_relationship_tools.py +74 -0
- kfinance/domains/business_relationships/tests/__init__.py +0 -0
- kfinance/domains/business_relationships/tests/test_business_relationship_tools.py +55 -0
- kfinance/domains/capitalizations/__init__.py +0 -0
- kfinance/{models → domains/capitalizations}/capitalization_models.py +24 -17
- kfinance/domains/capitalizations/capitalization_tools.py +89 -0
- kfinance/domains/capitalizations/tests/__init__.py +0 -0
- kfinance/{tests/test_models → domains/capitalizations/tests}/test_capitalization_models.py +8 -10
- kfinance/domains/capitalizations/tests/test_capitalization_tools.py +85 -0
- kfinance/domains/companies/__init__.py +0 -0
- kfinance/domains/companies/company_identifiers.py +175 -0
- kfinance/domains/companies/company_models.py +27 -0
- kfinance/domains/companies/company_tools.py +66 -0
- kfinance/domains/companies/tests/__init__.py +0 -0
- kfinance/domains/companies/tests/test_company_tools.py +26 -0
- kfinance/domains/competitors/__init__.py +0 -0
- kfinance/{models → domains/competitors}/competitor_models.py +7 -0
- kfinance/domains/competitors/competitor_tools.py +62 -0
- kfinance/domains/competitors/tests/__init__.py +0 -0
- kfinance/domains/competitors/tests/test_competitor_tools.py +45 -0
- kfinance/domains/cusip_and_isin/__init__.py +0 -0
- kfinance/domains/cusip_and_isin/cusip_and_isin_tools.py +80 -0
- kfinance/domains/cusip_and_isin/tests/__init__.py +0 -0
- kfinance/domains/cusip_and_isin/tests/test_cusip_and_isin_tools.py +57 -0
- kfinance/domains/earnings/__init__.py +0 -0
- kfinance/domains/earnings/earning_models.py +41 -0
- kfinance/domains/earnings/earning_tools.py +174 -0
- kfinance/domains/earnings/tests/__init__.py +0 -0
- kfinance/domains/earnings/tests/test_earnings_tools.py +195 -0
- kfinance/domains/line_items/__init__.py +0 -0
- kfinance/domains/line_items/line_item_tools.py +114 -0
- kfinance/domains/line_items/tests/__init__.py +0 -0
- kfinance/domains/line_items/tests/test_line_item_tools.py +86 -0
- kfinance/domains/mergers_and_acquisitions/__init__.py +0 -0
- kfinance/domains/mergers_and_acquisitions/merger_and_acquisition_tools.py +176 -0
- kfinance/domains/mergers_and_acquisitions/tests/__init__.py +0 -0
- kfinance/domains/mergers_and_acquisitions/tests/test_merger_and_acquisition_tools.py +124 -0
- kfinance/domains/prices/__init__.py +0 -0
- kfinance/{models → domains/prices}/price_models.py +1 -1
- kfinance/domains/prices/price_tools.py +165 -0
- kfinance/domains/prices/tests/__init__.py +0 -0
- kfinance/{tests/test_models → domains/prices/tests}/test_price_models.py +2 -2
- kfinance/domains/prices/tests/test_price_tools.py +141 -0
- kfinance/domains/segments/__init__.py +0 -0
- kfinance/domains/segments/segment_tools.py +91 -0
- kfinance/domains/segments/tests/__init__.py +0 -0
- kfinance/domains/segments/tests/test_segment_tools.py +80 -0
- kfinance/domains/statements/__init__.py +0 -0
- kfinance/domains/statements/statement_tools.py +113 -0
- kfinance/domains/statements/tests/__init__.py +0 -0
- kfinance/domains/statements/tests/test_statement_tools.py +73 -0
- kfinance/integrations/README.md +8 -0
- kfinance/integrations/__init__.py +0 -0
- kfinance/integrations/mcp/__init__.py +0 -0
- kfinance/{mcp.py → integrations/mcp/mcp.py} +2 -2
- kfinance/integrations/tests/__init__.py +0 -0
- kfinance/{tests → integrations/tests}/test_example_notebook.py +4 -4
- kfinance/{tool_calling → integrations/tool_calling}/README.md +2 -2
- kfinance/integrations/tool_calling/__init__.py +0 -0
- kfinance/integrations/tool_calling/all_tools.py +55 -0
- kfinance/{tool_calling → integrations/tool_calling}/prompts.py +3 -2
- kfinance/integrations/tool_calling/static_tools/README.md +4 -0
- kfinance/integrations/tool_calling/static_tools/__init__.py +0 -0
- kfinance/{tool_calling → integrations/tool_calling/static_tools}/get_latest.py +3 -3
- kfinance/{tool_calling → integrations/tool_calling/static_tools}/get_n_quarters_ago.py +3 -3
- kfinance/integrations/tool_calling/static_tools/tests/__init__.py +0 -0
- kfinance/integrations/tool_calling/static_tools/tests/test_get_lastest.py +30 -0
- kfinance/integrations/tool_calling/static_tools/tests/test_get_n_quarters_ago.py +24 -0
- kfinance/integrations/tool_calling/tests/__init__.py +0 -0
- kfinance/integrations/tool_calling/tests/test_tool_calling_models.py +69 -0
- kfinance/{tool_calling/shared_models.py → integrations/tool_calling/tool_calling_models.py} +37 -7
- kfinance/version.py +2 -2
- kensho_kfinance-2.9.0.dist-info/RECORD +0 -70
- kfinance/models/id_models.py +0 -7
- kfinance/prompt.py +0 -526
- kfinance/pydantic_models.py +0 -33
- kfinance/tests/test_tools.py +0 -804
- kfinance/tool_calling/__init__.py +0 -53
- kfinance/tool_calling/get_advisors_for_company_in_transaction_from_identifier.py +0 -42
- kfinance/tool_calling/get_business_relationship_from_identifier.py +0 -30
- kfinance/tool_calling/get_capitalization_from_identifier.py +0 -35
- kfinance/tool_calling/get_competitors_from_identifier.py +0 -25
- kfinance/tool_calling/get_cusip_from_ticker.py +0 -20
- kfinance/tool_calling/get_earnings.py +0 -33
- kfinance/tool_calling/get_financial_line_item_from_identifier.py +0 -48
- kfinance/tool_calling/get_financial_statement_from_identifier.py +0 -44
- kfinance/tool_calling/get_history_metadata_from_identifier.py +0 -17
- kfinance/tool_calling/get_info_from_identifier.py +0 -16
- kfinance/tool_calling/get_isin_from_ticker.py +0 -20
- kfinance/tool_calling/get_latest_earnings.py +0 -30
- kfinance/tool_calling/get_merger_info_from_transaction_id.py +0 -69
- kfinance/tool_calling/get_mergers_from_identifier.py +0 -44
- kfinance/tool_calling/get_next_earnings.py +0 -30
- kfinance/tool_calling/get_prices_from_identifier.py +0 -46
- kfinance/tool_calling/get_segments_from_identifier.py +0 -44
- kfinance/tool_calling/get_transcript.py +0 -23
- kfinance/tool_calling/resolve_identifier.py +0 -18
- {kensho_kfinance-2.9.0.dist-info → kensho_kfinance-3.0.0.dist-info}/WHEEL +0 -0
- {kensho_kfinance-2.9.0.dist-info → kensho_kfinance-3.0.0.dist-info}/licenses/AUTHORS.md +0 -0
- {kensho_kfinance-2.9.0.dist-info → kensho_kfinance-3.0.0.dist-info}/licenses/LICENSE +0 -0
- {kensho_kfinance-2.9.0.dist-info → kensho_kfinance-3.0.0.dist-info}/top_level.txt +0 -0
- /kfinance/{models → client}/__init__.py +0 -0
- /kfinance/{models → client}/industry_models.py +0 -0
- /kfinance/{tests → client/models}/__init__.py +0 -0
- /kfinance/{models → client/models}/currency_models.py +0 -0
- /kfinance/{models → client/models}/date_and_period_models.py +0 -0
- /kfinance/{tests/test_models → client/models/tests}/__init__.py +0 -0
- /kfinance/{models → client}/permission_models.py +0 -0
- /kfinance/{server_thread.py → client/server_thread.py} +0 -0
- /kfinance/{models → domains/line_items}/line_item_models.py +0 -0
- /kfinance/{models → domains/segments}/segment_models.py +0 -0
- /kfinance/{models → domains/statements}/statement_models.py +0 -0
|
@@ -2,18 +2,20 @@ from unittest.mock import Mock
|
|
|
2
2
|
|
|
3
3
|
import pytest
|
|
4
4
|
|
|
5
|
-
from kfinance.kfinance import Client
|
|
6
|
-
from kfinance.
|
|
7
|
-
from kfinance.
|
|
8
|
-
|
|
9
|
-
GetEarnings,
|
|
10
|
-
GetFinancialStatementFromIdentifier,
|
|
11
|
-
GetLatest,
|
|
12
|
-
GetLatestEarnings,
|
|
13
|
-
GetNextEarnings,
|
|
14
|
-
GetTranscript,
|
|
5
|
+
from kfinance.client.kfinance import Client
|
|
6
|
+
from kfinance.client.permission_models import Permission
|
|
7
|
+
from kfinance.domains.business_relationships.business_relationship_tools import (
|
|
8
|
+
GetBusinessRelationshipFromIdentifiers,
|
|
15
9
|
)
|
|
16
|
-
from kfinance.
|
|
10
|
+
from kfinance.domains.earnings.earning_tools import (
|
|
11
|
+
GetEarningsFromIdentifiers,
|
|
12
|
+
GetLatestEarningsFromIdentifiers,
|
|
13
|
+
GetNextEarningsFromIdentifiers,
|
|
14
|
+
GetTranscriptFromKeyDevId,
|
|
15
|
+
)
|
|
16
|
+
from kfinance.domains.statements.statement_tools import GetFinancialStatementFromIdentifiers
|
|
17
|
+
from kfinance.integrations.tool_calling.static_tools.get_latest import GetLatest
|
|
18
|
+
from kfinance.integrations.tool_calling.tool_calling_models import KfinanceTool
|
|
17
19
|
|
|
18
20
|
|
|
19
21
|
class TestLangchainTools:
|
|
@@ -52,19 +54,23 @@ class TestLangchainTools:
|
|
|
52
54
|
mock_client.kfinance_api_client._user_permissions = {Permission.RelationshipPermission} # noqa: SLF001
|
|
53
55
|
tool_classes = [type(t) for t in mock_client.langchain_tools]
|
|
54
56
|
# User should have access to GetBusinessRelationshipFromIdentifier
|
|
55
|
-
assert
|
|
57
|
+
assert GetBusinessRelationshipFromIdentifiers in tool_classes
|
|
56
58
|
# User should have access to functions that don't require permissions
|
|
57
59
|
assert GetLatest in tool_classes
|
|
58
60
|
# User should not have access to functions that require statement permissions
|
|
59
|
-
assert
|
|
61
|
+
assert GetFinancialStatementFromIdentifiers not in tool_classes
|
|
60
62
|
|
|
61
63
|
@pytest.mark.parametrize(
|
|
62
64
|
"user_permission, expected_tool",
|
|
63
65
|
[
|
|
64
66
|
pytest.param(
|
|
65
|
-
Permission.TranscriptsPermission,
|
|
67
|
+
Permission.TranscriptsPermission,
|
|
68
|
+
GetTranscriptFromKeyDevId,
|
|
69
|
+
id="Transcript Permissions",
|
|
70
|
+
),
|
|
71
|
+
pytest.param(
|
|
72
|
+
Permission.EarningsPermission, GetEarningsFromIdentifiers, id="Earnings Permissions"
|
|
66
73
|
),
|
|
67
|
-
pytest.param(Permission.EarningsPermission, GetEarnings, id="Earnings Permissions"),
|
|
68
74
|
],
|
|
69
75
|
)
|
|
70
76
|
def test_permission_set_handling(
|
|
@@ -79,11 +85,11 @@ class TestLangchainTools:
|
|
|
79
85
|
mock_client.kfinance_api_client._user_permissions = {user_permission} # noqa: SLF001
|
|
80
86
|
tool_classes = [type(t) for t in mock_client.langchain_tools]
|
|
81
87
|
# User should have access to GetEarnings, GetNextEarnings, GetLatestEarnings, GetTranscript
|
|
82
|
-
assert
|
|
83
|
-
assert
|
|
84
|
-
assert
|
|
88
|
+
assert GetEarningsFromIdentifiers in tool_classes
|
|
89
|
+
assert GetNextEarningsFromIdentifiers in tool_classes
|
|
90
|
+
assert GetLatestEarningsFromIdentifiers in tool_classes
|
|
85
91
|
assert expected_tool in tool_classes
|
|
86
92
|
# User should have access to functions that don't require permissions
|
|
87
93
|
assert GetLatest in tool_classes
|
|
88
94
|
# User should not have access to functions that require statement permissions
|
|
89
|
-
assert
|
|
95
|
+
assert GetFinancialStatementFromIdentifiers not in tool_classes
|
|
@@ -5,16 +5,15 @@ from pydantic import ValidationError
|
|
|
5
5
|
import pytest
|
|
6
6
|
from requests_mock import Mocker
|
|
7
7
|
|
|
8
|
-
from kfinance.fetch import KFinanceApiClient
|
|
9
|
-
from kfinance.kfinance import Client
|
|
10
|
-
from kfinance.models.
|
|
11
|
-
from kfinance.
|
|
12
|
-
from kfinance.
|
|
13
|
-
|
|
8
|
+
from kfinance.client.fetch import KFinanceApiClient
|
|
9
|
+
from kfinance.client.kfinance import Client
|
|
10
|
+
from kfinance.client.models.date_and_period_models import Periodicity, PeriodType
|
|
11
|
+
from kfinance.conftest import SPGI_COMPANY_ID
|
|
12
|
+
from kfinance.domains.business_relationships.business_relationship_models import (
|
|
13
|
+
BusinessRelationshipType,
|
|
14
14
|
RelationshipResponse,
|
|
15
|
-
RelationshipResponseNoName,
|
|
16
15
|
)
|
|
17
|
-
from kfinance.
|
|
16
|
+
from kfinance.domains.companies.company_models import CompanyIdAndName
|
|
18
17
|
|
|
19
18
|
|
|
20
19
|
def build_mock_api_client() -> KFinanceApiClient:
|
|
@@ -150,7 +149,8 @@ class TestFetchItem(TestCase):
|
|
|
150
149
|
def test_fetch_earnings(self) -> None:
|
|
151
150
|
company_id = 21719
|
|
152
151
|
expected_fetch_url = f"{self.kfinance_api_client.url_base}earnings/{company_id}"
|
|
153
|
-
|
|
152
|
+
with pytest.raises(ValidationError):
|
|
153
|
+
self.kfinance_api_client.fetch_earnings(company_id=company_id)
|
|
154
154
|
self.kfinance_api_client.fetch.assert_called_once_with(expected_fetch_url)
|
|
155
155
|
|
|
156
156
|
def test_fetch_transcript(self) -> None:
|
|
@@ -334,28 +334,10 @@ class TestMarketCap:
|
|
|
334
334
|
|
|
335
335
|
|
|
336
336
|
class TestFetchCompaniesFromBusinessRelationship:
|
|
337
|
-
def
|
|
338
|
-
"""
|
|
339
|
-
GIVEN a business relationship request
|
|
340
|
-
WHEN the api returns a response in the old (no name) format
|
|
341
|
-
THEN the response can successfully be parsed.
|
|
342
|
-
"""
|
|
343
|
-
http_resp = {"current": [883103], "previous": [472898, 8182358]}
|
|
344
|
-
expected_result = RelationshipResponseNoName(current=[883103], previous=[472898, 8182358])
|
|
345
|
-
requests_mock.get(
|
|
346
|
-
url=f"{mock_client.kfinance_api_client.url_base}relationship/{SPGI_COMPANY_ID}/{BusinessRelationshipType.supplier}",
|
|
347
|
-
json=http_resp,
|
|
348
|
-
)
|
|
349
|
-
|
|
350
|
-
resp = mock_client.kfinance_api_client.fetch_companies_from_business_relationship(
|
|
351
|
-
company_id=SPGI_COMPANY_ID, relationship_type=BusinessRelationshipType.supplier
|
|
352
|
-
)
|
|
353
|
-
assert resp == expected_result
|
|
354
|
-
|
|
355
|
-
def test_new_response_format(self, requests_mock: Mocker, mock_client: Client) -> None:
|
|
337
|
+
def test_fetch_business_relationships(self, requests_mock: Mocker, mock_client: Client) -> None:
|
|
356
338
|
"""
|
|
357
339
|
GIVEN a business relationship request
|
|
358
|
-
WHEN the api returns a response
|
|
340
|
+
WHEN the api returns a response
|
|
359
341
|
THEN the response can successfully be parsed.
|
|
360
342
|
"""
|
|
361
343
|
|
|
@@ -10,7 +10,7 @@ import pandas as pd
|
|
|
10
10
|
from PIL.Image import open as image_open
|
|
11
11
|
import time_machine
|
|
12
12
|
|
|
13
|
-
from kfinance.kfinance import (
|
|
13
|
+
from kfinance.client.kfinance import (
|
|
14
14
|
BusinessRelationships,
|
|
15
15
|
Company,
|
|
16
16
|
Earnings,
|
|
@@ -21,9 +21,13 @@ from kfinance.kfinance import (
|
|
|
21
21
|
TradingItem,
|
|
22
22
|
Transcript,
|
|
23
23
|
)
|
|
24
|
-
from kfinance.
|
|
25
|
-
|
|
26
|
-
|
|
24
|
+
from kfinance.domains.business_relationships.business_relationship_models import (
|
|
25
|
+
BusinessRelationshipType,
|
|
26
|
+
RelationshipResponse,
|
|
27
|
+
)
|
|
28
|
+
from kfinance.domains.capitalizations.capitalization_models import Capitalizations
|
|
29
|
+
from kfinance.domains.companies.company_models import CompanyIdAndName
|
|
30
|
+
from kfinance.domains.earnings.earning_models import EarningsCallResp
|
|
27
31
|
|
|
28
32
|
|
|
29
33
|
msft_company_id = "21835"
|
|
@@ -70,25 +74,27 @@ MOCK_COMPANY_DB = {
|
|
|
70
74
|
"iso_country": "USA",
|
|
71
75
|
},
|
|
72
76
|
"earnings_call_dates": {"earnings": ["2004-07-22T21:30:00"]},
|
|
73
|
-
"earnings":
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
77
|
+
"earnings": EarningsCallResp.model_validate(
|
|
78
|
+
{
|
|
79
|
+
"earnings": [
|
|
80
|
+
{
|
|
81
|
+
"name": "Microsoft Corporation, Q4 2024 Earnings Call, Jul 25, 2024",
|
|
82
|
+
"key_dev_id": 1916266380,
|
|
83
|
+
"datetime": "2024-07-25T21:30:00Z",
|
|
84
|
+
},
|
|
85
|
+
{
|
|
86
|
+
"name": "Microsoft Corporation, Q1 2025 Earnings Call, Oct 24, 2024",
|
|
87
|
+
"key_dev_id": 1916266381,
|
|
88
|
+
"datetime": "2024-10-24T21:30:00Z",
|
|
89
|
+
},
|
|
90
|
+
{
|
|
91
|
+
"name": "Microsoft Corporation, Q2 2025 Earnings Call, Jan 25, 2025",
|
|
92
|
+
"key_dev_id": 1916266382,
|
|
93
|
+
"datetime": "2025-01-25T21:30:00Z",
|
|
94
|
+
},
|
|
95
|
+
]
|
|
96
|
+
}
|
|
97
|
+
),
|
|
92
98
|
"statements": {
|
|
93
99
|
"income_statement": {
|
|
94
100
|
"statements": {
|
|
@@ -890,12 +896,10 @@ class TestTicker(TestCase):
|
|
|
890
896
|
THEN the Ticker object can correctly extract market caps from the dict.
|
|
891
897
|
"""
|
|
892
898
|
|
|
893
|
-
expected_response =
|
|
894
|
-
"market_cap":
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
]
|
|
898
|
-
}
|
|
899
|
+
expected_response = [
|
|
900
|
+
{"date": "2025-01-01", "market_cap": {"unit": "USD", "value": "3133802247084.00"}},
|
|
901
|
+
{"date": "2025-01-02", "market_cap": {"unit": "USD", "value": "3112092395218.00"}},
|
|
902
|
+
]
|
|
899
903
|
market_caps = self.msft_ticker_from_ticker.market_cap()
|
|
900
904
|
assert market_caps == expected_response
|
|
901
905
|
|
|
@@ -3,7 +3,7 @@ from datetime import datetime
|
|
|
3
3
|
import pytest
|
|
4
4
|
from requests_mock import Mocker
|
|
5
5
|
|
|
6
|
-
from kfinance.kfinance import Client
|
|
6
|
+
from kfinance.client.kfinance import Client
|
|
7
7
|
|
|
8
8
|
|
|
9
9
|
SPGI_COMPANY_ID = 21719
|
|
@@ -18,7 +18,7 @@ def mock_client(requests_mock: Mocker) -> Client:
|
|
|
18
18
|
client = Client(refresh_token="foo")
|
|
19
19
|
# Set access token so that the client doesn't try to fetch it.
|
|
20
20
|
client.kfinance_api_client._access_token = "foo" # noqa: SLF001
|
|
21
|
-
client.kfinance_api_client._access_token_expiry = datetime(2100, 1, 1).timestamp() # noqa: SLF001
|
|
21
|
+
client.kfinance_api_client._access_token_expiry = int(datetime(2100, 1, 1).timestamp()) # noqa: SLF001
|
|
22
22
|
|
|
23
23
|
# Create a mock for the SPGI id triple.
|
|
24
24
|
requests_mock.get(
|
|
@@ -33,4 +33,16 @@ def mock_client(requests_mock: Mocker) -> Client:
|
|
|
33
33
|
url="https://kfinance.kensho.com/api/v1/id/MSFT",
|
|
34
34
|
json={"trading_item_id": 2630413, "security_id": 2630412, "company_id": 21835},
|
|
35
35
|
)
|
|
36
|
+
|
|
37
|
+
# Create mock security id and trading item id for company ids 1 and 2:
|
|
38
|
+
for company_id in [1, 2]:
|
|
39
|
+
requests_mock.get(
|
|
40
|
+
url=f"https://kfinance.kensho.com/api/v1/securities/{company_id}/primary",
|
|
41
|
+
json={"primary_security": company_id},
|
|
42
|
+
)
|
|
43
|
+
requests_mock.get(
|
|
44
|
+
url=f"https://kfinance.kensho.com/api/v1/trading_items/{company_id}/primary",
|
|
45
|
+
json={"primary_trading_item": company_id},
|
|
46
|
+
)
|
|
47
|
+
|
|
36
48
|
return client
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
# Domains
|
|
2
|
+
|
|
3
|
+
Domains loosely correspond to datasets and permission boundaries.
|
|
4
|
+
They are used to group together related tools and models,
|
|
5
|
+
for example all earnings related tools and models.
|
|
6
|
+
|
|
7
|
+
There are no hard boundaries around what constitutes a domain. If you
|
|
8
|
+
think it makes sense to group certain tools or models together, it probably does.
|
|
9
|
+
|
|
10
|
+
Each domain will usually have a tools file with `KfinanceTools` and a
|
|
11
|
+
models file with enums, pydantic models, data classes, or typed dicts
|
|
12
|
+
related to the domain. We may at some point also move ORM models related
|
|
13
|
+
to the domain into these sub folders. Each domain should have a `tests`
|
|
14
|
+
directory for tool and model tests.
|
|
File without changes
|
|
File without changes
|
|
@@ -1,5 +1,8 @@
|
|
|
1
|
+
from pydantic import BaseModel
|
|
1
2
|
from strenum import StrEnum
|
|
2
3
|
|
|
4
|
+
from kfinance.domains.companies.company_models import CompanyIdAndName
|
|
5
|
+
|
|
3
6
|
|
|
4
7
|
class BusinessRelationshipType(StrEnum):
|
|
5
8
|
"""The type of business relationship"""
|
|
@@ -24,3 +27,10 @@ class BusinessRelationshipType(StrEnum):
|
|
|
24
27
|
transfer_agent_client = "transfer_agent_client"
|
|
25
28
|
vendor = "vendor"
|
|
26
29
|
client_services = "client_services"
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
class RelationshipResponse(BaseModel):
|
|
33
|
+
"""A response from the relationship endpoint that includes both company_id and name."""
|
|
34
|
+
|
|
35
|
+
current: list[CompanyIdAndName]
|
|
36
|
+
previous: list[CompanyIdAndName]
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
from textwrap import dedent
|
|
2
|
+
from typing import Type
|
|
3
|
+
|
|
4
|
+
from pydantic import BaseModel
|
|
5
|
+
|
|
6
|
+
from kfinance.client.batch_request_handling import Task, process_tasks_in_thread_pool_executor
|
|
7
|
+
from kfinance.client.permission_models import Permission
|
|
8
|
+
from kfinance.domains.business_relationships.business_relationship_models import (
|
|
9
|
+
BusinessRelationshipType,
|
|
10
|
+
)
|
|
11
|
+
from kfinance.domains.companies.company_identifiers import (
|
|
12
|
+
fetch_company_ids_from_identifiers,
|
|
13
|
+
parse_identifiers,
|
|
14
|
+
)
|
|
15
|
+
from kfinance.integrations.tool_calling.tool_calling_models import (
|
|
16
|
+
KfinanceTool,
|
|
17
|
+
ToolArgsWithIdentifiers,
|
|
18
|
+
)
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class GetBusinessRelationshipFromIdentifiersArgs(ToolArgsWithIdentifiers):
|
|
22
|
+
# no description because the description for enum fields comes from the enum docstring.
|
|
23
|
+
business_relationship: BusinessRelationshipType
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
class GetBusinessRelationshipFromIdentifiers(KfinanceTool):
|
|
27
|
+
name: str = "get_business_relationship_from_identifiers"
|
|
28
|
+
description: str = dedent("""
|
|
29
|
+
Get the current and previous company IDs that are relationship_type for a list of identifiers.
|
|
30
|
+
|
|
31
|
+
Example:
|
|
32
|
+
Query: "What are the previous borrowers of SPGI and JPM?"
|
|
33
|
+
Function: get_business_relationship_from_identifiers(identifiers=["SPGI", "JPM"], business_relationship=BusinessRelationshipType.borrower)
|
|
34
|
+
""").strip()
|
|
35
|
+
args_schema: Type[BaseModel] = GetBusinessRelationshipFromIdentifiersArgs
|
|
36
|
+
accepted_permissions: set[Permission] | None = {Permission.RelationshipPermission}
|
|
37
|
+
|
|
38
|
+
def _run(self, identifiers: list[str], business_relationship: BusinessRelationshipType) -> dict:
|
|
39
|
+
"""Sample response:
|
|
40
|
+
|
|
41
|
+
{
|
|
42
|
+
"SPGI": {
|
|
43
|
+
"current": [{"company_id": "C_883103", "company_name": "CRISIL Limited"}],
|
|
44
|
+
"previous": [
|
|
45
|
+
{"company_id": "C_472898", "company_name": "Morgan Stanley"},
|
|
46
|
+
{"company_id": "C_8182358", "company_name": "Eloqua, Inc."},
|
|
47
|
+
],
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
"""
|
|
51
|
+
|
|
52
|
+
api_client = self.kfinance_client.kfinance_api_client
|
|
53
|
+
parsed_identifiers = parse_identifiers(identifiers=identifiers, api_client=api_client)
|
|
54
|
+
identifiers_to_company_ids = fetch_company_ids_from_identifiers(
|
|
55
|
+
identifiers=parsed_identifiers, api_client=api_client
|
|
56
|
+
)
|
|
57
|
+
|
|
58
|
+
tasks = [
|
|
59
|
+
Task(
|
|
60
|
+
func=api_client.fetch_companies_from_business_relationship,
|
|
61
|
+
kwargs=dict(
|
|
62
|
+
company_id=company_id,
|
|
63
|
+
relationship_type=business_relationship,
|
|
64
|
+
),
|
|
65
|
+
result_key=identifier,
|
|
66
|
+
)
|
|
67
|
+
for identifier, company_id in identifiers_to_company_ids.items()
|
|
68
|
+
]
|
|
69
|
+
|
|
70
|
+
relationship_responses = process_tasks_in_thread_pool_executor(
|
|
71
|
+
api_client=api_client, tasks=tasks
|
|
72
|
+
)
|
|
73
|
+
|
|
74
|
+
return {str(k): v.model_dump(mode="json") for k, v in relationship_responses.items()}
|
|
File without changes
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
from requests_mock import Mocker
|
|
2
|
+
|
|
3
|
+
from kfinance.client.kfinance import Client
|
|
4
|
+
from kfinance.conftest import SPGI_COMPANY_ID
|
|
5
|
+
from kfinance.domains.business_relationships.business_relationship_models import (
|
|
6
|
+
BusinessRelationshipType,
|
|
7
|
+
)
|
|
8
|
+
from kfinance.domains.business_relationships.business_relationship_tools import (
|
|
9
|
+
GetBusinessRelationshipFromIdentifiers,
|
|
10
|
+
GetBusinessRelationshipFromIdentifiersArgs,
|
|
11
|
+
)
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class TestGetBusinessRelationshipFromIdentifiers:
|
|
15
|
+
def test_get_business_relationship_from_identifiers(
|
|
16
|
+
self, requests_mock: Mocker, mock_client: Client
|
|
17
|
+
):
|
|
18
|
+
"""
|
|
19
|
+
GIVEN the GetBusinessRelationshipFromIdentifiers tool
|
|
20
|
+
WHEN we request SPGI suppliers
|
|
21
|
+
THEN we get back the SPGI suppliers
|
|
22
|
+
"""
|
|
23
|
+
supplier_resp = {
|
|
24
|
+
"current": [{"company_id": 883103, "company_name": "CRISIL Limited"}],
|
|
25
|
+
"previous": [
|
|
26
|
+
{"company_id": 472898, "company_name": "Morgan Stanley"},
|
|
27
|
+
{"company_id": 8182358, "company_name": "Eloqua, Inc."},
|
|
28
|
+
],
|
|
29
|
+
}
|
|
30
|
+
expected_result = {
|
|
31
|
+
"SPGI": {
|
|
32
|
+
"current": [{"company_id": "C_883103", "company_name": "CRISIL Limited"}],
|
|
33
|
+
"previous": [
|
|
34
|
+
{"company_id": "C_472898", "company_name": "Morgan Stanley"},
|
|
35
|
+
{"company_id": "C_8182358", "company_name": "Eloqua, Inc."},
|
|
36
|
+
],
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
requests_mock.get(
|
|
41
|
+
url=f"https://kfinance.kensho.com/api/v1/relationship/{SPGI_COMPANY_ID}/supplier",
|
|
42
|
+
json=supplier_resp,
|
|
43
|
+
)
|
|
44
|
+
|
|
45
|
+
tool = GetBusinessRelationshipFromIdentifiers(kfinance_client=mock_client)
|
|
46
|
+
args = GetBusinessRelationshipFromIdentifiersArgs(
|
|
47
|
+
identifiers=["SPGI"], business_relationship=BusinessRelationshipType.supplier
|
|
48
|
+
)
|
|
49
|
+
resp = tool.run(args.model_dump(mode="json"))
|
|
50
|
+
|
|
51
|
+
assert list(resp.keys()) == ["SPGI"]
|
|
52
|
+
# Sort companies by ID to make the result deterministic
|
|
53
|
+
resp["SPGI"]["current"].sort(key=lambda x: x["company_id"])
|
|
54
|
+
resp["SPGI"]["previous"].sort(key=lambda x: x["company_id"])
|
|
55
|
+
assert resp == expected_result
|
|
File without changes
|
|
@@ -5,7 +5,7 @@ from typing import Any
|
|
|
5
5
|
from pydantic import BaseModel, Field, model_validator
|
|
6
6
|
from strenum import StrEnum
|
|
7
7
|
|
|
8
|
-
from kfinance.decimal_with_unit import Money, Shares
|
|
8
|
+
from kfinance.client.models.decimal_with_unit import Money, Shares
|
|
9
9
|
|
|
10
10
|
|
|
11
11
|
class Capitalization(StrEnum):
|
|
@@ -68,23 +68,30 @@ class Capitalizations(BaseModel):
|
|
|
68
68
|
capitalization[key] = dict(unit=currency, value=capitalization[key])
|
|
69
69
|
return data
|
|
70
70
|
|
|
71
|
-
def
|
|
72
|
-
|
|
71
|
+
def model_dump_json_single_metric(
|
|
72
|
+
self, capitalization_metric: Capitalization, only_include_most_recent_value: bool = False
|
|
73
|
+
) -> dict:
|
|
74
|
+
"""Dump only a single metric (market cap, tev, or shares outstanding) to json
|
|
73
75
|
|
|
74
|
-
|
|
75
|
-
{
|
|
76
|
-
"market_cap": [
|
|
77
|
-
{'2024-06-24': {'unit': 'USD', 'value': '139231113000.00'}},
|
|
78
|
-
{'2024-06-25': {'unit': 'USD', 'value': '140423262000.00'}}
|
|
79
|
-
]
|
|
80
|
-
}
|
|
76
|
+
If only_include_most_recent_value is set to True, only the most recent value gets included.
|
|
81
77
|
|
|
78
|
+
Sample response:
|
|
79
|
+
[
|
|
80
|
+
{
|
|
81
|
+
'date': datetime.date(2024, 4, 10),
|
|
82
|
+
'market_cap': {'value': Decimal('132766738270.00'), 'unit': 'USD'}
|
|
83
|
+
}
|
|
84
|
+
]
|
|
82
85
|
"""
|
|
83
86
|
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
87
|
+
return self.model_dump(
|
|
88
|
+
mode="json",
|
|
89
|
+
include={ # type: ignore[arg-type]
|
|
90
|
+
"capitalizations": {
|
|
91
|
+
0 if only_include_most_recent_value else "__all__": {
|
|
92
|
+
"date",
|
|
93
|
+
capitalization_metric.value,
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
},
|
|
97
|
+
)["capitalizations"]
|
|
@@ -0,0 +1,89 @@
|
|
|
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.permission_models import Permission
|
|
9
|
+
from kfinance.domains.capitalizations.capitalization_models import Capitalization
|
|
10
|
+
from kfinance.domains.companies.company_identifiers import (
|
|
11
|
+
fetch_company_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 GetCapitalizationFromIdentifiersArgs(ToolArgsWithIdentifiers):
|
|
21
|
+
# no description because the description for enum fields comes from the enum docstring.
|
|
22
|
+
capitalization: Capitalization
|
|
23
|
+
start_date: date | None = Field(
|
|
24
|
+
description="The start date for historical capitalization retrieval", default=None
|
|
25
|
+
)
|
|
26
|
+
end_date: date | None = Field(
|
|
27
|
+
description="The end date for historical capitalization retrieval", default=None
|
|
28
|
+
)
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
class GetCapitalizationFromIdentifiers(KfinanceTool):
|
|
32
|
+
name: str = "get_capitalization_from_identifiers"
|
|
33
|
+
description: str = dedent("""
|
|
34
|
+
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.
|
|
35
|
+
|
|
36
|
+
- When possible, pass multiple identifiers in a single call rather than making multiple calls.
|
|
37
|
+
- When requesting the most recent values, leave start_date and end_date empty.
|
|
38
|
+
|
|
39
|
+
Example:
|
|
40
|
+
Query: "What are the market caps of AAPL and WMT?"
|
|
41
|
+
Function: get_capitalization_from_identifiers(capitalization=Capitalization.market_cap, identifiers=["AAPL", "WMT"])
|
|
42
|
+
""").strip()
|
|
43
|
+
args_schema: Type[BaseModel] = GetCapitalizationFromIdentifiersArgs
|
|
44
|
+
accepted_permissions: set[Permission] | None = {Permission.PricingPermission}
|
|
45
|
+
|
|
46
|
+
def _run(
|
|
47
|
+
self,
|
|
48
|
+
identifiers: list[str],
|
|
49
|
+
capitalization: Capitalization,
|
|
50
|
+
start_date: str | None = None,
|
|
51
|
+
end_date: str | None = None,
|
|
52
|
+
) -> dict:
|
|
53
|
+
"""Sample response:
|
|
54
|
+
|
|
55
|
+
{
|
|
56
|
+
'SPGI': [
|
|
57
|
+
{'date': '2024-04-10', 'market_cap': {'unit': 'USD', 'value': '132766738270.00'}},
|
|
58
|
+
{'date': '2024-04-11', 'market_cap': {'unit': 'USD', 'value': '132416066761.00'}}
|
|
59
|
+
]
|
|
60
|
+
}
|
|
61
|
+
"""
|
|
62
|
+
api_client = self.kfinance_client.kfinance_api_client
|
|
63
|
+
parsed_identifiers = parse_identifiers(identifiers=identifiers, api_client=api_client)
|
|
64
|
+
identifiers_to_company_ids = fetch_company_ids_from_identifiers(
|
|
65
|
+
identifiers=parsed_identifiers, api_client=api_client
|
|
66
|
+
)
|
|
67
|
+
|
|
68
|
+
tasks = [
|
|
69
|
+
Task(
|
|
70
|
+
func=api_client.fetch_market_caps_tevs_and_shares_outstanding,
|
|
71
|
+
kwargs=dict(company_id=company_id, start_date=start_date, end_date=end_date),
|
|
72
|
+
result_key=identifier,
|
|
73
|
+
)
|
|
74
|
+
for identifier, company_id in identifiers_to_company_ids.items()
|
|
75
|
+
]
|
|
76
|
+
|
|
77
|
+
capitalization_responses = process_tasks_in_thread_pool_executor(
|
|
78
|
+
api_client=api_client, tasks=tasks
|
|
79
|
+
)
|
|
80
|
+
|
|
81
|
+
return {
|
|
82
|
+
str(identifier): capitalization_response.model_dump_json_single_metric(
|
|
83
|
+
capitalization_metric=capitalization,
|
|
84
|
+
only_include_most_recent_value=True
|
|
85
|
+
if (len(identifiers) > 1 and start_date == end_date is None)
|
|
86
|
+
else False,
|
|
87
|
+
)
|
|
88
|
+
for identifier, capitalization_response in capitalization_responses.items()
|
|
89
|
+
}
|
|
File without changes
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
from datetime import date
|
|
2
2
|
from decimal import Decimal
|
|
3
3
|
|
|
4
|
-
from kfinance.decimal_with_unit import Money, Shares
|
|
5
|
-
from kfinance.
|
|
4
|
+
from kfinance.client.models.decimal_with_unit import Money, Shares
|
|
5
|
+
from kfinance.domains.capitalizations.capitalization_models import (
|
|
6
6
|
Capitalization,
|
|
7
7
|
Capitalizations,
|
|
8
8
|
DailyCapitalization,
|
|
@@ -69,15 +69,13 @@ class TestCapitalizations:
|
|
|
69
69
|
def test_single_attribute_serialization(self):
|
|
70
70
|
"""
|
|
71
71
|
GIVEN a Capitalizations object
|
|
72
|
-
WHEN we only want to
|
|
72
|
+
WHEN we only want to dump a single attribute like market_cap
|
|
73
73
|
THEN that attribute gets returned in an LLM-readable, jsonifyable dict format
|
|
74
74
|
"""
|
|
75
|
-
expected_serialization =
|
|
76
|
-
"market_cap":
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
]
|
|
80
|
-
}
|
|
75
|
+
expected_serialization = [
|
|
76
|
+
{"date": "2024-06-24", "market_cap": {"unit": "USD", "value": "139231113000.00"}},
|
|
77
|
+
{"date": "2024-06-25", "market_cap": {"unit": "USD", "value": "140423262000.00"}},
|
|
78
|
+
]
|
|
81
79
|
capitalizations = Capitalizations.model_validate(self.api_resp)
|
|
82
|
-
serialized = capitalizations.
|
|
80
|
+
serialized = capitalizations.model_dump_json_single_metric(Capitalization.market_cap)
|
|
83
81
|
assert serialized == expected_serialization
|