kensho-kfinance 3.2.4__py3-none-any.whl → 4.0.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {kensho_kfinance-3.2.4.dist-info → kensho_kfinance-4.0.0.dist-info}/METADATA +3 -3
- {kensho_kfinance-3.2.4.dist-info → kensho_kfinance-4.0.0.dist-info}/RECORD +57 -56
- kfinance/CHANGELOG.md +51 -0
- kfinance/client/batch_request_handling.py +3 -1
- kfinance/client/fetch.py +127 -54
- kfinance/client/kfinance.py +38 -39
- kfinance/client/meta_classes.py +50 -20
- kfinance/client/models/date_and_period_models.py +32 -7
- kfinance/client/models/decimal_with_unit.py +14 -2
- kfinance/client/models/response_models.py +33 -0
- kfinance/client/models/tests/test_decimal_with_unit.py +9 -0
- kfinance/client/tests/test_batch_requests.py +5 -4
- kfinance/client/tests/test_fetch.py +134 -58
- kfinance/client/tests/test_objects.py +207 -145
- kfinance/conftest.py +10 -0
- kfinance/domains/business_relationships/business_relationship_tools.py +17 -8
- kfinance/domains/business_relationships/tests/test_business_relationship_tools.py +18 -16
- kfinance/domains/capitalizations/capitalization_models.py +7 -5
- kfinance/domains/capitalizations/capitalization_tools.py +38 -20
- kfinance/domains/capitalizations/tests/test_capitalization_tools.py +66 -36
- kfinance/domains/companies/company_models.py +22 -2
- kfinance/domains/companies/company_tools.py +49 -16
- kfinance/domains/companies/tests/test_company_tools.py +27 -9
- kfinance/domains/competitors/competitor_tools.py +19 -5
- kfinance/domains/competitors/tests/test_competitor_tools.py +22 -19
- kfinance/domains/cusip_and_isin/cusip_and_isin_tools.py +29 -8
- kfinance/domains/cusip_and_isin/tests/test_cusip_and_isin_tools.py +13 -8
- kfinance/domains/earnings/earning_tools.py +73 -29
- kfinance/domains/earnings/tests/test_earnings_tools.py +52 -43
- kfinance/domains/line_items/line_item_models.py +372 -16
- kfinance/domains/line_items/line_item_tools.py +198 -46
- kfinance/domains/line_items/tests/test_line_item_tools.py +305 -39
- kfinance/domains/mergers_and_acquisitions/merger_and_acquisition_models.py +46 -2
- kfinance/domains/mergers_and_acquisitions/merger_and_acquisition_tools.py +55 -74
- kfinance/domains/mergers_and_acquisitions/tests/test_merger_and_acquisition_tools.py +61 -59
- kfinance/domains/prices/price_models.py +7 -6
- kfinance/domains/prices/price_tools.py +24 -16
- kfinance/domains/prices/tests/test_price_tools.py +47 -39
- kfinance/domains/segments/segment_models.py +17 -3
- kfinance/domains/segments/segment_tools.py +102 -42
- kfinance/domains/segments/tests/test_segment_tools.py +166 -37
- kfinance/domains/statements/statement_models.py +17 -3
- kfinance/domains/statements/statement_tools.py +130 -46
- kfinance/domains/statements/tests/test_statement_tools.py +251 -49
- kfinance/integrations/local_mcp/kfinance_mcp.py +1 -1
- kfinance/integrations/tests/test_example_notebook.py +57 -16
- kfinance/integrations/tool_calling/all_tools.py +5 -1
- kfinance/integrations/tool_calling/static_tools/get_n_quarters_ago.py +5 -0
- kfinance/integrations/tool_calling/static_tools/tests/test_get_lastest.py +13 -10
- kfinance/integrations/tool_calling/static_tools/tests/test_get_n_quarters_ago.py +2 -1
- kfinance/integrations/tool_calling/tests/test_tool_calling_models.py +15 -4
- kfinance/integrations/tool_calling/tool_calling_models.py +18 -6
- kfinance/version.py +2 -2
- {kensho_kfinance-3.2.4.dist-info → kensho_kfinance-4.0.0.dist-info}/WHEEL +0 -0
- {kensho_kfinance-3.2.4.dist-info → kensho_kfinance-4.0.0.dist-info}/licenses/AUTHORS.md +0 -0
- {kensho_kfinance-3.2.4.dist-info → kensho_kfinance-4.0.0.dist-info}/licenses/LICENSE +0 -0
- {kensho_kfinance-3.2.4.dist-info → kensho_kfinance-4.0.0.dist-info}/top_level.txt +0 -0
|
@@ -14,13 +14,13 @@ from kfinance.client.kfinance import (
|
|
|
14
14
|
BusinessRelationships,
|
|
15
15
|
Company,
|
|
16
16
|
Earnings,
|
|
17
|
-
MergerOrAcquisition,
|
|
18
17
|
ParticipantInMerger,
|
|
19
18
|
Security,
|
|
20
19
|
Ticker,
|
|
21
20
|
TradingItem,
|
|
22
21
|
Transcript,
|
|
23
22
|
)
|
|
23
|
+
from kfinance.client.models.response_models import PostResponse
|
|
24
24
|
from kfinance.domains.business_relationships.business_relationship_models import (
|
|
25
25
|
BusinessRelationshipType,
|
|
26
26
|
RelationshipResponse,
|
|
@@ -28,8 +28,11 @@ from kfinance.domains.business_relationships.business_relationship_models import
|
|
|
28
28
|
from kfinance.domains.capitalizations.capitalization_models import Capitalizations
|
|
29
29
|
from kfinance.domains.companies.company_models import CompanyIdAndName, IdentificationTriple
|
|
30
30
|
from kfinance.domains.earnings.earning_models import EarningsCallResp
|
|
31
|
-
from kfinance.domains.line_items.line_item_models import
|
|
32
|
-
from kfinance.domains.mergers_and_acquisitions.merger_and_acquisition_models import
|
|
31
|
+
from kfinance.domains.line_items.line_item_models import LineItemResp
|
|
32
|
+
from kfinance.domains.mergers_and_acquisitions.merger_and_acquisition_models import (
|
|
33
|
+
MergerInfo,
|
|
34
|
+
MergersResp,
|
|
35
|
+
)
|
|
33
36
|
from kfinance.domains.prices.price_models import HistoryMetadataResp
|
|
34
37
|
from kfinance.domains.segments.segment_models import SegmentsResp
|
|
35
38
|
from kfinance.domains.statements.statement_models import StatementsResp
|
|
@@ -103,36 +106,103 @@ MOCK_COMPANY_DB = {
|
|
|
103
106
|
}
|
|
104
107
|
),
|
|
105
108
|
"line_items": {
|
|
106
|
-
"revenue":
|
|
109
|
+
"revenue": LineItemResp.model_validate(
|
|
107
110
|
{
|
|
108
|
-
"
|
|
109
|
-
|
|
110
|
-
"
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
111
|
+
"currency": "USD",
|
|
112
|
+
"periods": {
|
|
113
|
+
"CY2019": {
|
|
114
|
+
"period_end_date": "2019-12-31",
|
|
115
|
+
"num_months": 12,
|
|
116
|
+
"line_item": {
|
|
117
|
+
"name": "Revenue",
|
|
118
|
+
"value": "125843000000.000000",
|
|
119
|
+
"sources": [],
|
|
120
|
+
},
|
|
121
|
+
},
|
|
122
|
+
"CY2020": {
|
|
123
|
+
"period_end_date": "2020-12-31",
|
|
124
|
+
"num_months": 12,
|
|
125
|
+
"line_item": {
|
|
126
|
+
"name": "Revenue",
|
|
127
|
+
"value": "143015000000.000000",
|
|
128
|
+
"sources": [],
|
|
129
|
+
},
|
|
130
|
+
},
|
|
131
|
+
"CY2021": {
|
|
132
|
+
"period_end_date": "2021-12-31",
|
|
133
|
+
"num_months": 12,
|
|
134
|
+
"line_item": {
|
|
135
|
+
"name": "Revenue",
|
|
136
|
+
"value": "168088000000.000000",
|
|
137
|
+
"sources": [],
|
|
138
|
+
},
|
|
139
|
+
},
|
|
140
|
+
"CY2022": {
|
|
141
|
+
"period_end_date": "2022-12-31",
|
|
142
|
+
"num_months": 12,
|
|
143
|
+
"line_item": {
|
|
144
|
+
"name": "Revenue",
|
|
145
|
+
"value": "198270000000.000000",
|
|
146
|
+
"sources": [],
|
|
147
|
+
},
|
|
148
|
+
},
|
|
149
|
+
"CY2023": {
|
|
150
|
+
"period_end_date": "2023-12-31",
|
|
151
|
+
"num_months": 12,
|
|
152
|
+
"line_item": {
|
|
153
|
+
"name": "Revenue",
|
|
154
|
+
"value": "211915000000.000000",
|
|
155
|
+
"sources": [],
|
|
156
|
+
},
|
|
157
|
+
},
|
|
158
|
+
},
|
|
115
159
|
}
|
|
116
160
|
)
|
|
117
161
|
},
|
|
118
162
|
"segments": SegmentsResp.model_validate(
|
|
119
163
|
{
|
|
120
|
-
"
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
164
|
+
"currency": "USD",
|
|
165
|
+
"periods": {
|
|
166
|
+
"CY2024": {
|
|
167
|
+
"period_end_date": "2024-12-31",
|
|
168
|
+
"num_months": 12,
|
|
169
|
+
"segments": [
|
|
170
|
+
{
|
|
171
|
+
"name": "Intelligent Cloud",
|
|
172
|
+
"line_items": [
|
|
173
|
+
{
|
|
174
|
+
"name": "Operating Income",
|
|
175
|
+
"value": 49584000000.0,
|
|
176
|
+
"sources": [],
|
|
177
|
+
},
|
|
178
|
+
{"name": "Revenue", "value": 105362000000.0, "sources": []},
|
|
179
|
+
],
|
|
180
|
+
},
|
|
181
|
+
{
|
|
182
|
+
"name": "More Personal Computing",
|
|
183
|
+
"line_items": [
|
|
184
|
+
{
|
|
185
|
+
"name": "Operating Income",
|
|
186
|
+
"value": 19309000000.0,
|
|
187
|
+
"sources": [],
|
|
188
|
+
},
|
|
189
|
+
{"name": "Revenue", "value": 62032000000.0, "sources": []},
|
|
190
|
+
],
|
|
191
|
+
},
|
|
192
|
+
{
|
|
193
|
+
"name": "Productivity and Business Processes",
|
|
194
|
+
"line_items": [
|
|
195
|
+
{
|
|
196
|
+
"name": "Operating Income",
|
|
197
|
+
"value": 40540000000.0,
|
|
198
|
+
"sources": [],
|
|
199
|
+
},
|
|
200
|
+
{"name": "Revenue", "value": 77728000000.0, "sources": []},
|
|
201
|
+
],
|
|
202
|
+
},
|
|
203
|
+
],
|
|
134
204
|
}
|
|
135
|
-
}
|
|
205
|
+
},
|
|
136
206
|
}
|
|
137
207
|
),
|
|
138
208
|
"advisors": {
|
|
@@ -191,12 +261,26 @@ MOCK_TRANSCRIPT_DB = {
|
|
|
191
261
|
|
|
192
262
|
INCOME_STATEMENT = StatementsResp.model_validate(
|
|
193
263
|
{
|
|
194
|
-
"
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
"
|
|
264
|
+
"currency": "USD",
|
|
265
|
+
"periods": {
|
|
266
|
+
"CY2019": {
|
|
267
|
+
"period_end_date": "2019-12-31",
|
|
268
|
+
"num_months": 12,
|
|
269
|
+
"statements": [
|
|
270
|
+
{
|
|
271
|
+
"name": "Income Statement",
|
|
272
|
+
"line_items": [
|
|
273
|
+
{"name": "Revenues", "value": "125843000000.000000", "sources": []},
|
|
274
|
+
{
|
|
275
|
+
"name": "Total Revenues",
|
|
276
|
+
"value": "125843000000.000000",
|
|
277
|
+
"sources": [],
|
|
278
|
+
},
|
|
279
|
+
],
|
|
280
|
+
}
|
|
281
|
+
],
|
|
198
282
|
}
|
|
199
|
-
}
|
|
283
|
+
},
|
|
200
284
|
}
|
|
201
285
|
)
|
|
202
286
|
|
|
@@ -254,39 +338,41 @@ MOCK_ISIN_DB = {msft_isin: msft_id_triple.model_dump(mode="json")}
|
|
|
254
338
|
MOCK_CUSIP_DB = {msft_cusip: msft_id_triple.model_dump(mode="json")}
|
|
255
339
|
|
|
256
340
|
MOCK_MERGERS_DB = {
|
|
257
|
-
msft_buys_mongo:
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
"participants": {
|
|
263
|
-
"target": {"company_id": 31696, "company_name": "MongoMusic, Inc."},
|
|
264
|
-
"buyers": [{"company_id": 21835, "company_name": "Microsoft Corporation"}],
|
|
265
|
-
"sellers": [
|
|
266
|
-
{"company_id": 18805, "company_name": "Angel Investors L.P."},
|
|
267
|
-
{"company_id": 20087, "company_name": "Draper Richards, L.P."},
|
|
268
|
-
{"company_id": 22103, "company_name": "BRV Partners, LLC"},
|
|
269
|
-
{"company_id": 23745, "company_name": "Venture Frogs, LLC"},
|
|
270
|
-
{"company_id": 105902, "company_name": "ARGUS Capital International Limited"},
|
|
271
|
-
{"company_id": 880300, "company_name": "Sony Music Entertainment, Inc."},
|
|
272
|
-
],
|
|
273
|
-
},
|
|
274
|
-
"consideration": {
|
|
275
|
-
"currency_name": "US Dollar",
|
|
276
|
-
"current_calculated_gross_total_transaction_value": "51609375.000000",
|
|
277
|
-
"current_calculated_implied_equity_value": "51609375.000000",
|
|
278
|
-
"current_calculated_implied_enterprise_value": "51609375.000000",
|
|
279
|
-
"details": [
|
|
280
|
-
{
|
|
281
|
-
"scenario": "Stock Lump Sum",
|
|
282
|
-
"subtype": "Common Equity",
|
|
283
|
-
"cash_or_cash_equivalent_per_target_share_unit": None,
|
|
284
|
-
"number_of_target_shares_sought": "1000000.000000",
|
|
285
|
-
"current_calculated_gross_value_of_consideration": "51609375.000000",
|
|
286
|
-
}
|
|
341
|
+
msft_buys_mongo: MergerInfo.model_validate(
|
|
342
|
+
{
|
|
343
|
+
"timeline": [
|
|
344
|
+
{"status": "Announced", "date": "2000-09-12"},
|
|
345
|
+
{"status": "Closed", "date": "2000-09-12"},
|
|
287
346
|
],
|
|
288
|
-
|
|
289
|
-
|
|
347
|
+
"participants": {
|
|
348
|
+
"target": {"company_id": 31696, "company_name": "MongoMusic, Inc."},
|
|
349
|
+
"buyers": [{"company_id": 21835, "company_name": "Microsoft Corporation"}],
|
|
350
|
+
"sellers": [
|
|
351
|
+
{"company_id": 18805, "company_name": "Angel Investors L.P."},
|
|
352
|
+
{"company_id": 20087, "company_name": "Draper Richards, L.P."},
|
|
353
|
+
{"company_id": 22103, "company_name": "BRV Partners, LLC"},
|
|
354
|
+
{"company_id": 23745, "company_name": "Venture Frogs, LLC"},
|
|
355
|
+
{"company_id": 105902, "company_name": "ARGUS Capital International Limited"},
|
|
356
|
+
{"company_id": 880300, "company_name": "Sony Music Entertainment, Inc."},
|
|
357
|
+
],
|
|
358
|
+
},
|
|
359
|
+
"consideration": {
|
|
360
|
+
"currency_name": "US Dollar",
|
|
361
|
+
"current_calculated_gross_total_transaction_value": "51609375.000000",
|
|
362
|
+
"current_calculated_implied_equity_value": "51609375.000000",
|
|
363
|
+
"current_calculated_implied_enterprise_value": "51609375.000000",
|
|
364
|
+
"details": [
|
|
365
|
+
{
|
|
366
|
+
"scenario": "Stock Lump Sum",
|
|
367
|
+
"subtype": "Common Equity",
|
|
368
|
+
"cash_or_cash_equivalent_per_target_share_unit": None,
|
|
369
|
+
"number_of_target_shares_sought": "1000000.000000",
|
|
370
|
+
"current_calculated_gross_value_of_consideration": "51609375.000000",
|
|
371
|
+
}
|
|
372
|
+
],
|
|
373
|
+
},
|
|
374
|
+
}
|
|
375
|
+
)
|
|
290
376
|
}
|
|
291
377
|
|
|
292
378
|
|
|
@@ -341,7 +427,7 @@ class MockKFinanceApiClient:
|
|
|
341
427
|
|
|
342
428
|
def fetch_statement(
|
|
343
429
|
self,
|
|
344
|
-
|
|
430
|
+
company_ids,
|
|
345
431
|
statement_type,
|
|
346
432
|
period_type,
|
|
347
433
|
start_year,
|
|
@@ -350,13 +436,26 @@ class MockKFinanceApiClient:
|
|
|
350
436
|
end_quarter,
|
|
351
437
|
):
|
|
352
438
|
"""Get a statement"""
|
|
353
|
-
return
|
|
439
|
+
return PostResponse[StatementsResp](
|
|
440
|
+
results={str(company_ids[0]): INCOME_STATEMENT}, errors={}
|
|
441
|
+
)
|
|
354
442
|
|
|
355
443
|
def fetch_line_item(
|
|
356
|
-
self,
|
|
444
|
+
self,
|
|
445
|
+
company_ids,
|
|
446
|
+
line_item,
|
|
447
|
+
period_type,
|
|
448
|
+
start_year,
|
|
449
|
+
end_year,
|
|
450
|
+
start_quarter,
|
|
451
|
+
end_quarter,
|
|
452
|
+
calendar_type=None,
|
|
453
|
+
num_periods=None,
|
|
454
|
+
num_periods_back=None,
|
|
357
455
|
):
|
|
358
456
|
"""Get a statement"""
|
|
359
|
-
|
|
457
|
+
line_item_resp = MOCK_COMPANY_DB[company_ids[0]]["line_items"][line_item]
|
|
458
|
+
return PostResponse[LineItemResp](results={str(company_ids[0]): line_item_resp}, errors={})
|
|
360
459
|
|
|
361
460
|
def fetch_market_caps_tevs_and_shares_outstanding(
|
|
362
461
|
self,
|
|
@@ -386,7 +485,7 @@ class MockKFinanceApiClient:
|
|
|
386
485
|
|
|
387
486
|
def fetch_segments(
|
|
388
487
|
self,
|
|
389
|
-
|
|
488
|
+
company_ids,
|
|
390
489
|
segment_type,
|
|
391
490
|
period_type,
|
|
392
491
|
start_year,
|
|
@@ -395,7 +494,9 @@ class MockKFinanceApiClient:
|
|
|
395
494
|
end_quarter,
|
|
396
495
|
):
|
|
397
496
|
"""Get a segment"""
|
|
398
|
-
return
|
|
497
|
+
return PostResponse[SegmentsResp](
|
|
498
|
+
results={str(company_ids[0]): MOCK_COMPANY_DB[company_ids[0]]["segments"]}, errors={}
|
|
499
|
+
)
|
|
399
500
|
|
|
400
501
|
def fetch_companies_from_business_relationship(
|
|
401
502
|
self, company_id: int, relationship_type: BusinessRelationshipType
|
|
@@ -519,10 +620,18 @@ class TestCompany(TestCase):
|
|
|
519
620
|
|
|
520
621
|
def test_income_statement(self) -> None:
|
|
521
622
|
"""test income statement"""
|
|
623
|
+
# Extract statements data from the periods structure
|
|
624
|
+
periods_data = INCOME_STATEMENT.model_dump(mode="json")["periods"]
|
|
625
|
+
statements_data = {}
|
|
626
|
+
for period_key, period_data in periods_data.items():
|
|
627
|
+
period_statements = {}
|
|
628
|
+
for statement in period_data["statements"]:
|
|
629
|
+
for line_item in statement["line_items"]:
|
|
630
|
+
period_statements[line_item["name"]] = line_item["value"]
|
|
631
|
+
statements_data[period_key] = period_statements
|
|
632
|
+
|
|
522
633
|
expected_income_statement = (
|
|
523
|
-
pd.DataFrame(
|
|
524
|
-
.apply(pd.to_numeric)
|
|
525
|
-
.replace(np.nan, None)
|
|
634
|
+
pd.DataFrame(statements_data).apply(pd.to_numeric).replace(np.nan, None)
|
|
526
635
|
)
|
|
527
636
|
|
|
528
637
|
income_statement = self.msft_company.company.income_statement()
|
|
@@ -530,11 +639,14 @@ class TestCompany(TestCase):
|
|
|
530
639
|
|
|
531
640
|
def test_revenue(self) -> None:
|
|
532
641
|
"""test revenue"""
|
|
533
|
-
line_item_response:
|
|
534
|
-
|
|
535
|
-
|
|
642
|
+
line_item_response: LineItemResp = MOCK_COMPANY_DB[msft_company_id]["line_items"]["revenue"]
|
|
643
|
+
|
|
644
|
+
line_item_data = {}
|
|
645
|
+
for period_key, period_data in line_item_response.periods.items():
|
|
646
|
+
line_item_data[period_key] = period_data.line_item.value
|
|
647
|
+
|
|
536
648
|
expected_revenue = (
|
|
537
|
-
pd.DataFrame({"line_item":
|
|
649
|
+
pd.DataFrame({"line_item": line_item_data})
|
|
538
650
|
.transpose()
|
|
539
651
|
.apply(pd.to_numeric)
|
|
540
652
|
.replace(np.nan, None)
|
|
@@ -546,7 +658,7 @@ class TestCompany(TestCase):
|
|
|
546
658
|
def test_business_segments(self) -> None:
|
|
547
659
|
"""test business statement"""
|
|
548
660
|
expected_segments = MOCK_COMPANY_DB[msft_company_id]["segments"].model_dump(mode="json")[
|
|
549
|
-
"
|
|
661
|
+
"periods"
|
|
550
662
|
]
|
|
551
663
|
|
|
552
664
|
business_segment = self.msft_company.company.business_segments()
|
|
@@ -840,10 +952,18 @@ class TestTicker(TestCase):
|
|
|
840
952
|
|
|
841
953
|
def test_income_statement(self) -> None:
|
|
842
954
|
"""test income statement"""
|
|
955
|
+
# Extract statements data from the periods structure
|
|
956
|
+
periods_data = INCOME_STATEMENT.model_dump(mode="json")["periods"]
|
|
957
|
+
statements_data = {}
|
|
958
|
+
for period_key, period_data in periods_data.items():
|
|
959
|
+
period_statements = {}
|
|
960
|
+
for statement in period_data["statements"]:
|
|
961
|
+
for line_item in statement["line_items"]:
|
|
962
|
+
period_statements[line_item["name"]] = line_item["value"]
|
|
963
|
+
statements_data[period_key] = period_statements
|
|
964
|
+
|
|
843
965
|
expected_income_statement = (
|
|
844
|
-
pd.DataFrame(
|
|
845
|
-
.apply(pd.to_numeric)
|
|
846
|
-
.replace(np.nan, None)
|
|
966
|
+
pd.DataFrame(statements_data).apply(pd.to_numeric).replace(np.nan, None)
|
|
847
967
|
)
|
|
848
968
|
|
|
849
969
|
income_statement = self.msft_ticker_from_ticker.income_statement()
|
|
@@ -860,11 +980,14 @@ class TestTicker(TestCase):
|
|
|
860
980
|
|
|
861
981
|
def test_revenue(self) -> None:
|
|
862
982
|
"""test revenue"""
|
|
863
|
-
line_item_response:
|
|
864
|
-
|
|
865
|
-
|
|
983
|
+
line_item_response: LineItemResp = MOCK_COMPANY_DB[msft_company_id]["line_items"]["revenue"]
|
|
984
|
+
|
|
985
|
+
line_item_data = {}
|
|
986
|
+
for period_key, period_data in line_item_response.periods.items():
|
|
987
|
+
line_item_data[period_key] = period_data.line_item.value
|
|
988
|
+
|
|
866
989
|
expected_revenue = (
|
|
867
|
-
pd.DataFrame({"line_item":
|
|
990
|
+
pd.DataFrame({"line_item": line_item_data})
|
|
868
991
|
.transpose()
|
|
869
992
|
.apply(pd.to_numeric)
|
|
870
993
|
.replace(np.nan, None)
|
|
@@ -1012,64 +1135,3 @@ class TestCompanyEarnings(TestCase):
|
|
|
1012
1135
|
"""test company next_earnings property"""
|
|
1013
1136
|
next_earnings = self.msft_company.next_earnings
|
|
1014
1137
|
self.assertEqual(next_earnings.key_dev_id, 1916266380)
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
class TestMerger(TestCase):
|
|
1018
|
-
def setUp(self):
|
|
1019
|
-
self.kfinance_api_client = MockKFinanceApiClient()
|
|
1020
|
-
self.merger = MergerOrAcquisition(
|
|
1021
|
-
kfinance_api_client=self.kfinance_api_client,
|
|
1022
|
-
transaction_id=int(msft_buys_mongo),
|
|
1023
|
-
merger_title="Closed M/A of MongoMusic, Inc.",
|
|
1024
|
-
closed_date=date(2021, 1, 1),
|
|
1025
|
-
)
|
|
1026
|
-
|
|
1027
|
-
def test_merger_info(self) -> None:
|
|
1028
|
-
expected_merger_info = MOCK_MERGERS_DB[msft_buys_mongo]
|
|
1029
|
-
merger_info = {
|
|
1030
|
-
"timeline": [
|
|
1031
|
-
{"status": timeline["status"], "date": timeline["date"].strftime("%Y-%m-%d")}
|
|
1032
|
-
for timeline in self.merger.get_timeline.to_dict(orient="records")
|
|
1033
|
-
],
|
|
1034
|
-
"participants": {
|
|
1035
|
-
"target": {
|
|
1036
|
-
"company_id": self.merger.get_participants["target"].company.company_id,
|
|
1037
|
-
"company_name": self.merger.get_participants["target"].company.name,
|
|
1038
|
-
},
|
|
1039
|
-
"buyers": [
|
|
1040
|
-
{"company_id": buyer.company.company_id, "company_name": buyer.company.name}
|
|
1041
|
-
for buyer in self.merger.get_participants["buyers"]
|
|
1042
|
-
],
|
|
1043
|
-
"sellers": [
|
|
1044
|
-
{"company_id": seller.company.company_id, "company_name": seller.company.name}
|
|
1045
|
-
for seller in self.merger.get_participants["sellers"]
|
|
1046
|
-
],
|
|
1047
|
-
},
|
|
1048
|
-
"consideration": {
|
|
1049
|
-
"currency_name": self.merger.get_consideration["currency_name"],
|
|
1050
|
-
"current_calculated_gross_total_transaction_value": self.merger.get_consideration[
|
|
1051
|
-
"current_calculated_gross_total_transaction_value"
|
|
1052
|
-
],
|
|
1053
|
-
"current_calculated_implied_equity_value": self.merger.get_consideration[
|
|
1054
|
-
"current_calculated_implied_equity_value"
|
|
1055
|
-
],
|
|
1056
|
-
"current_calculated_implied_enterprise_value": self.merger.get_consideration[
|
|
1057
|
-
"current_calculated_implied_enterprise_value"
|
|
1058
|
-
],
|
|
1059
|
-
"details": [
|
|
1060
|
-
{
|
|
1061
|
-
"scenario": detail["scenario"],
|
|
1062
|
-
"subtype": detail["subtype"],
|
|
1063
|
-
"cash_or_cash_equivalent_per_target_share_unit": detail[
|
|
1064
|
-
"cash_or_cash_equivalent_per_target_share_unit"
|
|
1065
|
-
],
|
|
1066
|
-
"number_of_target_shares_sought": detail["number_of_target_shares_sought"],
|
|
1067
|
-
"current_calculated_gross_value_of_consideration": detail[
|
|
1068
|
-
"current_calculated_gross_value_of_consideration"
|
|
1069
|
-
],
|
|
1070
|
-
}
|
|
1071
|
-
for detail in self.merger.get_consideration["details"].to_dict(orient="records")
|
|
1072
|
-
],
|
|
1073
|
-
},
|
|
1074
|
-
}
|
|
1075
|
-
self.assertEqual(ordered(expected_merger_info), ordered(merger_info))
|
kfinance/conftest.py
CHANGED
|
@@ -64,6 +64,16 @@ def mock_client(requests_mock: Mocker) -> Client:
|
|
|
64
64
|
}
|
|
65
65
|
},
|
|
66
66
|
)
|
|
67
|
+
# Fetch a fake company
|
|
68
|
+
requests_mock.post(
|
|
69
|
+
url="https://kfinance.kensho.com/api/v1/ids",
|
|
70
|
+
additional_matcher=lambda req: req.json().get("identifiers") == ["C_1"],
|
|
71
|
+
json={
|
|
72
|
+
"data": {
|
|
73
|
+
"C_1": {"company_id": 1, "security_id": 1, "trading_item_id": 1},
|
|
74
|
+
},
|
|
75
|
+
},
|
|
76
|
+
)
|
|
67
77
|
# Fetch SPGI and a non-existent company (which will include an error)
|
|
68
78
|
requests_mock.post(
|
|
69
79
|
url="https://kfinance.kensho.com/api/v1/ids",
|
|
@@ -29,16 +29,27 @@ class GetBusinessRelationshipFromIdentifiersResp(BaseModel):
|
|
|
29
29
|
class GetBusinessRelationshipFromIdentifiers(KfinanceTool):
|
|
30
30
|
name: str = "get_business_relationship_from_identifiers"
|
|
31
31
|
description: str = dedent("""
|
|
32
|
-
Get the current and previous
|
|
32
|
+
Get the current and previous companies that have a specified business relationship with each of the provided identifiers.
|
|
33
33
|
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
34
|
+
- When possible, pass multiple identifiers in a single call rather than making multiple calls.
|
|
35
|
+
- Results include both "current" (active) and "previous" (historical) relationships.
|
|
36
|
+
|
|
37
|
+
Examples:
|
|
38
|
+
Query: "Who are the current and previous suppliers of Intel?"
|
|
39
|
+
Function: get_business_relationship_from_identifiers(identifiers=["Intel"], business_relationship="supplier")
|
|
40
|
+
|
|
41
|
+
Query: "What are the borrowers of SPGI and JPM?"
|
|
42
|
+
Function: get_business_relationship_from_identifiers(identifiers=["SPGI", "JPM"], business_relationship="borrower")
|
|
43
|
+
|
|
44
|
+
Query: "Who are Dell's customers?"
|
|
45
|
+
Function: get_business_relationship_from_identifiers(identifiers=["Dell"], business_relationship="customer")
|
|
37
46
|
""").strip()
|
|
38
47
|
args_schema: Type[BaseModel] = GetBusinessRelationshipFromIdentifiersArgs
|
|
39
48
|
accepted_permissions: set[Permission] | None = {Permission.RelationshipPermission}
|
|
40
49
|
|
|
41
|
-
def _run(
|
|
50
|
+
def _run(
|
|
51
|
+
self, identifiers: list[str], business_relationship: BusinessRelationshipType
|
|
52
|
+
) -> GetBusinessRelationshipFromIdentifiersResp:
|
|
42
53
|
"""Sample response:
|
|
43
54
|
|
|
44
55
|
{
|
|
@@ -76,10 +87,8 @@ class GetBusinessRelationshipFromIdentifiers(KfinanceTool):
|
|
|
76
87
|
api_client=api_client, tasks=tasks
|
|
77
88
|
)
|
|
78
89
|
|
|
79
|
-
|
|
90
|
+
return GetBusinessRelationshipFromIdentifiersResp(
|
|
80
91
|
business_relationship=business_relationship,
|
|
81
92
|
results=relationship_responses,
|
|
82
93
|
errors=list(id_triple_resp.errors.values()),
|
|
83
94
|
)
|
|
84
|
-
|
|
85
|
-
return output_model.model_dump(mode="json")
|
|
@@ -8,6 +8,7 @@ from kfinance.domains.business_relationships.business_relationship_models import
|
|
|
8
8
|
from kfinance.domains.business_relationships.business_relationship_tools import (
|
|
9
9
|
GetBusinessRelationshipFromIdentifiers,
|
|
10
10
|
GetBusinessRelationshipFromIdentifiersArgs,
|
|
11
|
+
GetBusinessRelationshipFromIdentifiersResp,
|
|
11
12
|
)
|
|
12
13
|
|
|
13
14
|
|
|
@@ -27,21 +28,23 @@ class TestGetBusinessRelationshipFromIdentifiers:
|
|
|
27
28
|
{"company_id": 8182358, "company_name": "Eloqua, Inc."},
|
|
28
29
|
],
|
|
29
30
|
}
|
|
30
|
-
expected_result =
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
"
|
|
34
|
-
"
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
"
|
|
43
|
-
|
|
44
|
-
|
|
31
|
+
expected_result = GetBusinessRelationshipFromIdentifiersResp.model_validate(
|
|
32
|
+
{
|
|
33
|
+
"business_relationship": "supplier",
|
|
34
|
+
"results": {
|
|
35
|
+
"SPGI": {
|
|
36
|
+
"current": [{"company_id": 883103, "company_name": "CRISIL Limited"}],
|
|
37
|
+
"previous": [
|
|
38
|
+
{"company_id": 472898, "company_name": "Morgan Stanley"},
|
|
39
|
+
{"company_id": 8182358, "company_name": "Eloqua, Inc."},
|
|
40
|
+
],
|
|
41
|
+
}
|
|
42
|
+
},
|
|
43
|
+
"errors": [
|
|
44
|
+
"No identification triple found for the provided identifier: NON-EXISTENT of type: ticker"
|
|
45
|
+
],
|
|
46
|
+
}
|
|
47
|
+
)
|
|
45
48
|
|
|
46
49
|
requests_mock.get(
|
|
47
50
|
url=f"https://kfinance.kensho.com/api/v1/relationship/{SPGI_COMPANY_ID}/supplier",
|
|
@@ -54,5 +57,4 @@ class TestGetBusinessRelationshipFromIdentifiers:
|
|
|
54
57
|
business_relationship=BusinessRelationshipType.supplier,
|
|
55
58
|
)
|
|
56
59
|
resp = tool.run(args.model_dump(mode="json"))
|
|
57
|
-
resp["results"]["SPGI"]["previous"].sort(key=lambda x: x["company_id"])
|
|
58
60
|
assert resp == expected_result
|
|
@@ -2,7 +2,7 @@ from copy import deepcopy
|
|
|
2
2
|
from datetime import date
|
|
3
3
|
from typing import Any
|
|
4
4
|
|
|
5
|
-
from pydantic import BaseModel, Field, model_validator
|
|
5
|
+
from pydantic import BaseModel, ConfigDict, Field, model_validator
|
|
6
6
|
from strenum import StrEnum
|
|
7
7
|
|
|
8
8
|
from kfinance.client.models.decimal_with_unit import Money, Shares
|
|
@@ -20,14 +20,15 @@ class DailyCapitalization(BaseModel):
|
|
|
20
20
|
"""DailyCapitalization represents market cap, TEV, and shares outstanding for a day"""
|
|
21
21
|
|
|
22
22
|
date: date
|
|
23
|
-
market_cap: Money
|
|
24
|
-
tev: Money
|
|
25
|
-
shares_outstanding: Shares
|
|
23
|
+
market_cap: Money | None
|
|
24
|
+
tev: Money | None
|
|
25
|
+
shares_outstanding: Shares | None
|
|
26
26
|
|
|
27
27
|
|
|
28
28
|
class Capitalizations(BaseModel):
|
|
29
29
|
"""Capitalizations represents market cap, TEV, and shares outstanding for a date range"""
|
|
30
30
|
|
|
31
|
+
model_config = ConfigDict(validate_by_name=True)
|
|
31
32
|
capitalizations: list[DailyCapitalization] = Field(validation_alias="market_caps")
|
|
32
33
|
|
|
33
34
|
@model_validator(mode="before")
|
|
@@ -65,7 +66,8 @@ class Capitalizations(BaseModel):
|
|
|
65
66
|
currency = data["currency"]
|
|
66
67
|
for capitalization in data["market_caps"]:
|
|
67
68
|
for key in ["market_cap", "tev"]:
|
|
68
|
-
capitalization[key]
|
|
69
|
+
if capitalization[key] is not None:
|
|
70
|
+
capitalization[key] = dict(unit=currency, value=capitalization[key])
|
|
69
71
|
return data
|
|
70
72
|
|
|
71
73
|
def model_dump_json_single_metric(
|