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.
- {kensho_kfinance-2.8.0.dist-info → kensho_kfinance-3.0.0.dist-info}/METADATA +2 -2
- kensho_kfinance-3.0.0.dist-info/RECORD +110 -0
- kfinance/CHANGELOG.md +6 -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} +105 -111
- 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 +111 -63
- 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.8.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 -39
- 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 -68
- kfinance/tool_calling/get_mergers_from_identifier.py +0 -41
- 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.8.0.dist-info → kensho_kfinance-3.0.0.dist-info}/WHEEL +0 -0
- {kensho_kfinance-2.8.0.dist-info → kensho_kfinance-3.0.0.dist-info}/licenses/AUTHORS.md +0 -0
- {kensho_kfinance-2.8.0.dist-info → kensho_kfinance-3.0.0.dist-info}/licenses/LICENSE +0 -0
- {kensho_kfinance-2.8.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
|
@@ -10,20 +10,24 @@ 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 (
|
|
14
|
-
AdvisedCompany,
|
|
13
|
+
from kfinance.client.kfinance import (
|
|
15
14
|
BusinessRelationships,
|
|
16
15
|
Company,
|
|
17
16
|
Earnings,
|
|
18
17
|
MergerOrAcquisition,
|
|
18
|
+
ParticipantInMerger,
|
|
19
19
|
Security,
|
|
20
20
|
Ticker,
|
|
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": {
|
|
@@ -125,16 +131,40 @@ MOCK_COMPANY_DB = {
|
|
|
125
131
|
},
|
|
126
132
|
"mergers": {
|
|
127
133
|
"target": [
|
|
128
|
-
{
|
|
129
|
-
|
|
134
|
+
{
|
|
135
|
+
"transaction_id": 10998717,
|
|
136
|
+
"merger_title": "Closed M/A of Microsoft Corporation",
|
|
137
|
+
"closed_date": "2021-01-01",
|
|
138
|
+
},
|
|
139
|
+
{
|
|
140
|
+
"transaction_id": 28237969,
|
|
141
|
+
"merger_title": "Closed M/A of Microsoft Corporation",
|
|
142
|
+
"closed_date": "2022-01-01",
|
|
143
|
+
},
|
|
130
144
|
],
|
|
131
145
|
"buyer": [
|
|
132
|
-
{
|
|
133
|
-
|
|
146
|
+
{
|
|
147
|
+
"transaction_id": 517414,
|
|
148
|
+
"merger_title": "Closed M/A of MongoMusic, Inc.",
|
|
149
|
+
"closed_date": "2023-01-01",
|
|
150
|
+
},
|
|
151
|
+
{
|
|
152
|
+
"transaction_id": 596722,
|
|
153
|
+
"merger_title": "Closed M/A of Digital Anvil, Inc.",
|
|
154
|
+
"closed_date": "2023-01-01",
|
|
155
|
+
},
|
|
134
156
|
],
|
|
135
157
|
"seller": [
|
|
136
|
-
{
|
|
137
|
-
|
|
158
|
+
{
|
|
159
|
+
"transaction_id": 455551,
|
|
160
|
+
"merger_title": "Closed M/A of VacationSpot.com, Inc.",
|
|
161
|
+
"closed_date": "2024-01-01",
|
|
162
|
+
},
|
|
163
|
+
{
|
|
164
|
+
"transaction_id": 456045,
|
|
165
|
+
"merger_title": "Closed M/A of TransPoint, LLC",
|
|
166
|
+
"closed_date": "2025-01-01",
|
|
167
|
+
},
|
|
138
168
|
],
|
|
139
169
|
},
|
|
140
170
|
"advisors": {
|
|
@@ -380,8 +410,8 @@ class MockKFinanceApiClient:
|
|
|
380
410
|
def fetch_mergers_for_company(self, company_id):
|
|
381
411
|
return copy.deepcopy(MOCK_COMPANY_DB[company_id]["mergers"])
|
|
382
412
|
|
|
383
|
-
def fetch_merger_info(self, transaction_id):
|
|
384
|
-
return copy.deepcopy(MOCK_MERGERS_DB[transaction_id])
|
|
413
|
+
def fetch_merger_info(self, transaction_id: int):
|
|
414
|
+
return copy.deepcopy(MOCK_MERGERS_DB[str(transaction_id)])
|
|
385
415
|
|
|
386
416
|
def fetch_advisors_for_company_in_merger(self, transaction_id, advised_company_id):
|
|
387
417
|
return copy.deepcopy(MOCK_COMPANY_DB[advised_company_id]["advisors"][transaction_id])
|
|
@@ -444,26 +474,31 @@ class TestCompany(TestCase):
|
|
|
444
474
|
def setUp(self):
|
|
445
475
|
"""setup tests"""
|
|
446
476
|
self.kfinance_api_client = MockKFinanceApiClient()
|
|
447
|
-
self.msft_company =
|
|
448
|
-
self.kfinance_api_client,
|
|
477
|
+
self.msft_company = ParticipantInMerger(
|
|
478
|
+
kfinance_api_client=self.kfinance_api_client,
|
|
479
|
+
transaction_id=msft_buys_mongo,
|
|
480
|
+
company=Company(
|
|
481
|
+
kfinance_api_client=self.kfinance_api_client,
|
|
482
|
+
company_id=msft_company_id,
|
|
483
|
+
),
|
|
449
484
|
)
|
|
450
485
|
|
|
451
486
|
def test_company_id(self) -> None:
|
|
452
487
|
"""test company id"""
|
|
453
488
|
expected_company_id = msft_company_id
|
|
454
|
-
company_id = self.msft_company.company_id
|
|
489
|
+
company_id = self.msft_company.company.company_id
|
|
455
490
|
self.assertEqual(expected_company_id, company_id)
|
|
456
491
|
|
|
457
492
|
def test_info(self) -> None:
|
|
458
493
|
"""test info"""
|
|
459
494
|
expected_info = MOCK_COMPANY_DB[msft_company_id]["info"]
|
|
460
|
-
info = self.msft_company.info
|
|
495
|
+
info = self.msft_company.company.info
|
|
461
496
|
self.assertEqual(expected_info, info)
|
|
462
497
|
|
|
463
498
|
def test_name(self) -> None:
|
|
464
499
|
"""test name"""
|
|
465
500
|
expected_name = MOCK_COMPANY_DB[msft_company_id]["info"]["name"]
|
|
466
|
-
name = self.msft_company.name
|
|
501
|
+
name = self.msft_company.company.name
|
|
467
502
|
self.assertEqual(expected_name, name)
|
|
468
503
|
|
|
469
504
|
def test_founding_date(self) -> None:
|
|
@@ -471,7 +506,7 @@ class TestCompany(TestCase):
|
|
|
471
506
|
expected_founding_date = datetime.strptime(
|
|
472
507
|
MOCK_COMPANY_DB[msft_company_id]["info"]["founding_date"], "%Y-%m-%d"
|
|
473
508
|
).date()
|
|
474
|
-
founding_date = self.msft_company.founding_date
|
|
509
|
+
founding_date = self.msft_company.company.founding_date
|
|
475
510
|
self.assertEqual(expected_founding_date, founding_date)
|
|
476
511
|
|
|
477
512
|
def test_earnings_call_datetimes(self) -> None:
|
|
@@ -481,7 +516,7 @@ class TestCompany(TestCase):
|
|
|
481
516
|
MOCK_COMPANY_DB[msft_company_id]["earnings_call_dates"]["earnings"][0]
|
|
482
517
|
).replace(tzinfo=timezone.utc)
|
|
483
518
|
]
|
|
484
|
-
earnings_call_datetimes = self.msft_company.earnings_call_datetimes
|
|
519
|
+
earnings_call_datetimes = self.msft_company.company.earnings_call_datetimes
|
|
485
520
|
self.assertEqual(expected_earnings_call_datetimes, earnings_call_datetimes)
|
|
486
521
|
|
|
487
522
|
def test_income_statement(self) -> None:
|
|
@@ -493,7 +528,7 @@ class TestCompany(TestCase):
|
|
|
493
528
|
.apply(pd.to_numeric)
|
|
494
529
|
.replace(np.nan, None)
|
|
495
530
|
)
|
|
496
|
-
income_statement = self.msft_company.income_statement()
|
|
531
|
+
income_statement = self.msft_company.company.income_statement()
|
|
497
532
|
pd.testing.assert_frame_equal(expected_income_statement, income_statement)
|
|
498
533
|
|
|
499
534
|
def test_revenue(self) -> None:
|
|
@@ -505,14 +540,14 @@ class TestCompany(TestCase):
|
|
|
505
540
|
.replace(np.nan, None)
|
|
506
541
|
.set_index(pd.Index(["revenue"]))
|
|
507
542
|
)
|
|
508
|
-
revenue = self.msft_company.revenue()
|
|
543
|
+
revenue = self.msft_company.company.revenue()
|
|
509
544
|
pd.testing.assert_frame_equal(expected_revenue, revenue)
|
|
510
545
|
|
|
511
546
|
def test_business_segments(self) -> None:
|
|
512
547
|
"""test business statement"""
|
|
513
548
|
expected_segments = MOCK_COMPANY_DB[msft_company_id]["segments"]
|
|
514
549
|
|
|
515
|
-
business_segment = self.msft_company.business_segments()
|
|
550
|
+
business_segment = self.msft_company.company.business_segments()
|
|
516
551
|
self.assertEqual(expected_segments, business_segment)
|
|
517
552
|
|
|
518
553
|
def test_relationships(self) -> None:
|
|
@@ -523,7 +558,9 @@ class TestCompany(TestCase):
|
|
|
523
558
|
|
|
524
559
|
expected_suppliers = MOCK_COMPANY_DB[msft_company_id][BusinessRelationshipType.supplier]
|
|
525
560
|
|
|
526
|
-
suppliers_via_method = self.msft_company.relationships(
|
|
561
|
+
suppliers_via_method = self.msft_company.company.relationships(
|
|
562
|
+
BusinessRelationshipType.supplier
|
|
563
|
+
)
|
|
527
564
|
self.assertIsInstance(suppliers_via_method, BusinessRelationships)
|
|
528
565
|
# Company ids should match
|
|
529
566
|
self.assertEqual(
|
|
@@ -536,23 +573,35 @@ class TestCompany(TestCase):
|
|
|
536
573
|
)
|
|
537
574
|
|
|
538
575
|
# Fetching via property should return the same result
|
|
539
|
-
suppliers_via_property = self.msft_company.supplier
|
|
576
|
+
suppliers_via_property = self.msft_company.company.supplier
|
|
540
577
|
self.assertEqual(suppliers_via_property, suppliers_via_method)
|
|
541
578
|
|
|
542
579
|
def test_mergers(self) -> None:
|
|
543
580
|
expected_mergers = MOCK_COMPANY_DB[msft_company_id]["mergers"]
|
|
544
|
-
mergers = self.msft_company.mergers_and_acquisitions
|
|
581
|
+
mergers = self.msft_company.company.mergers_and_acquisitions
|
|
545
582
|
mergers_json = {
|
|
546
583
|
"target": [
|
|
547
|
-
{
|
|
584
|
+
{
|
|
585
|
+
"transaction_id": merger.transaction_id,
|
|
586
|
+
"merger_title": merger.merger_title,
|
|
587
|
+
"closed_date": merger.closed_date,
|
|
588
|
+
}
|
|
548
589
|
for merger in mergers["target"]
|
|
549
590
|
],
|
|
550
591
|
"buyer": [
|
|
551
|
-
{
|
|
592
|
+
{
|
|
593
|
+
"transaction_id": merger.transaction_id,
|
|
594
|
+
"merger_title": merger.merger_title,
|
|
595
|
+
"closed_date": merger.closed_date,
|
|
596
|
+
}
|
|
552
597
|
for merger in mergers["buyer"]
|
|
553
598
|
],
|
|
554
599
|
"seller": [
|
|
555
|
-
{
|
|
600
|
+
{
|
|
601
|
+
"transaction_id": merger.transaction_id,
|
|
602
|
+
"merger_title": merger.merger_title,
|
|
603
|
+
"closed_date": merger.closed_date,
|
|
604
|
+
}
|
|
556
605
|
for merger in mergers["seller"]
|
|
557
606
|
],
|
|
558
607
|
}
|
|
@@ -571,7 +620,7 @@ class TestCompany(TestCase):
|
|
|
571
620
|
company_ids: list[int] = []
|
|
572
621
|
advisor_type_names: list[str] = []
|
|
573
622
|
for advisor in advisors:
|
|
574
|
-
company_ids.append(advisor.company_id)
|
|
623
|
+
company_ids.append(advisor.company.company_id)
|
|
575
624
|
advisor_type_names.append(advisor.advisor_type_name)
|
|
576
625
|
self.assertListEqual(expected_company_ids, company_ids)
|
|
577
626
|
self.assertListEqual(expected_advisor_type_names, advisor_type_names)
|
|
@@ -847,12 +896,10 @@ class TestTicker(TestCase):
|
|
|
847
896
|
THEN the Ticker object can correctly extract market caps from the dict.
|
|
848
897
|
"""
|
|
849
898
|
|
|
850
|
-
expected_response =
|
|
851
|
-
"market_cap":
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
]
|
|
855
|
-
}
|
|
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
|
+
]
|
|
856
903
|
market_caps = self.msft_ticker_from_ticker.market_cap()
|
|
857
904
|
assert market_caps == expected_response
|
|
858
905
|
|
|
@@ -970,9 +1017,10 @@ class TestMerger(TestCase):
|
|
|
970
1017
|
def setUp(self):
|
|
971
1018
|
self.kfinance_api_client = MockKFinanceApiClient()
|
|
972
1019
|
self.merger = MergerOrAcquisition(
|
|
973
|
-
self.kfinance_api_client,
|
|
974
|
-
transaction_id=msft_buys_mongo,
|
|
1020
|
+
kfinance_api_client=self.kfinance_api_client,
|
|
1021
|
+
transaction_id=int(msft_buys_mongo),
|
|
975
1022
|
merger_title="Closed M/A of MongoMusic, Inc.",
|
|
1023
|
+
closed_date=date(2021, 1, 1),
|
|
976
1024
|
)
|
|
977
1025
|
|
|
978
1026
|
def test_merger_info(self) -> None:
|
|
@@ -984,16 +1032,16 @@ class TestMerger(TestCase):
|
|
|
984
1032
|
],
|
|
985
1033
|
"participants": {
|
|
986
1034
|
"target": {
|
|
987
|
-
"company_id": self.merger.get_participants["target"].company_id,
|
|
988
|
-
"company_name": self.merger.get_participants["target"].name,
|
|
1035
|
+
"company_id": self.merger.get_participants["target"].company.company_id,
|
|
1036
|
+
"company_name": self.merger.get_participants["target"].company.name,
|
|
989
1037
|
},
|
|
990
1038
|
"buyers": [
|
|
991
|
-
{"company_id": company.company_id, "company_name": company.name}
|
|
992
|
-
for
|
|
1039
|
+
{"company_id": buyer.company.company_id, "company_name": buyer.company.name}
|
|
1040
|
+
for buyer in self.merger.get_participants["buyers"]
|
|
993
1041
|
],
|
|
994
1042
|
"sellers": [
|
|
995
|
-
{"company_id": company.company_id, "company_name": company.name}
|
|
996
|
-
for
|
|
1043
|
+
{"company_id": seller.company.company_id, "company_name": seller.company.name}
|
|
1044
|
+
for seller in self.merger.get_participants["sellers"]
|
|
997
1045
|
],
|
|
998
1046
|
},
|
|
999
1047
|
"consideration": {
|
|
@@ -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"]
|