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

@@ -2,9 +2,17 @@ from unittest import TestCase
2
2
  from unittest.mock import MagicMock
3
3
 
4
4
  import pytest
5
+ from requests_mock import Mocker
5
6
 
6
- from kfinance.constants import Periodicity, PeriodType
7
+ from kfinance.constants import BusinessRelationshipType, Periodicity, PeriodType
7
8
  from kfinance.fetch import KFinanceApiClient
9
+ from kfinance.kfinance import Client
10
+ from kfinance.pydantic_models import (
11
+ CompanyIdAndName,
12
+ RelationshipResponse,
13
+ RelationshipResponseNoName,
14
+ )
15
+ from kfinance.tests.conftest import SPGI_COMPANY_ID
8
16
 
9
17
 
10
18
  def build_mock_api_client() -> KFinanceApiClient:
@@ -130,6 +138,18 @@ class TestFetchItem(TestCase):
130
138
  self.kfinance_api_client.fetch_earnings_dates(company_id=company_id)
131
139
  self.kfinance_api_client.fetch.assert_called_once_with(expected_fetch_url)
132
140
 
141
+ def test_fetch_earnings(self) -> None:
142
+ company_id = 21719
143
+ expected_fetch_url = f"{self.kfinance_api_client.url_base}earnings/{company_id}"
144
+ self.kfinance_api_client.fetch_earnings(company_id=company_id)
145
+ self.kfinance_api_client.fetch.assert_called_once_with(expected_fetch_url)
146
+
147
+ def test_fetch_transcript(self) -> None:
148
+ key_dev_id = 12345
149
+ expected_fetch_url = f"{self.kfinance_api_client.url_base}transcript/{key_dev_id}"
150
+ self.kfinance_api_client.fetch_transcript(key_dev_id=key_dev_id)
151
+ self.kfinance_api_client.fetch.assert_called_once_with(expected_fetch_url)
152
+
133
153
  def test_fetch_ticker_geography_groups(self) -> None:
134
154
  country_iso_code = "USA"
135
155
  expected_fetch_url = (
@@ -279,3 +299,56 @@ class TestMarketCap:
279
299
  expected_fetch_url = f"{client.url_base}users/permissions"
280
300
  client.fetch_permissions()
281
301
  client.fetch.assert_called_with(expected_fetch_url)
302
+
303
+
304
+ class TestFetchCompaniesFromBusinessRelationship:
305
+ def test_old_response_format(self, requests_mock: Mocker, mock_client: Client) -> None:
306
+ """
307
+ GIVEN a business relationship request
308
+ WHEN the api returns a response in the old (no name) format
309
+ THEN the response can successfully be parsed.
310
+ """
311
+ http_resp = {"current": [883103], "previous": [472898, 8182358]}
312
+ expected_result = RelationshipResponseNoName(current=[883103], previous=[472898, 8182358])
313
+ requests_mock.get(
314
+ url=f"{mock_client.kfinance_api_client.url_base}relationship/{SPGI_COMPANY_ID}/{BusinessRelationshipType.supplier}",
315
+ json=http_resp,
316
+ )
317
+
318
+ resp = mock_client.kfinance_api_client.fetch_companies_from_business_relationship(
319
+ company_id=SPGI_COMPANY_ID, relationship_type=BusinessRelationshipType.supplier
320
+ )
321
+ assert resp == expected_result
322
+
323
+ def test_new_response_format(self, requests_mock: Mocker, mock_client: Client) -> None:
324
+ """
325
+ GIVEN a business relationship request
326
+ WHEN the api returns a response in the new (with name) format
327
+ THEN the response can successfully be parsed.
328
+ """
329
+
330
+ http_resp = {
331
+ "current": [{"company_name": "foo", "company_id": 883103}],
332
+ "previous": [
333
+ {"company_name": "bar", "company_id": 472898},
334
+ {"company_name": "baz", "company_id": 8182358},
335
+ ],
336
+ }
337
+
338
+ expected_result = RelationshipResponse(
339
+ current=[CompanyIdAndName(company_name="foo", company_id=883103)],
340
+ previous=[
341
+ CompanyIdAndName(company_name="bar", company_id=472898),
342
+ CompanyIdAndName(company_name="baz", company_id=8182358),
343
+ ],
344
+ )
345
+
346
+ requests_mock.get(
347
+ url=f"{mock_client.kfinance_api_client.url_base}relationship/{SPGI_COMPANY_ID}/{BusinessRelationshipType.supplier}",
348
+ json=http_resp,
349
+ )
350
+
351
+ resp = mock_client.kfinance_api_client.fetch_companies_from_business_relationship(
352
+ company_id=SPGI_COMPANY_ID, relationship_type=BusinessRelationshipType.supplier
353
+ )
354
+ assert resp == expected_result
@@ -1,4 +1,4 @@
1
- from datetime import datetime, timezone
1
+ from datetime import date, datetime, timezone
2
2
  from io import BytesIO
3
3
  import re
4
4
  from typing import Optional
@@ -7,8 +7,9 @@ from unittest import TestCase
7
7
  import numpy as np
8
8
  import pandas as pd
9
9
  from PIL.Image import open as image_open
10
+ import time_machine
10
11
 
11
- from kfinance.kfinance import Company, Security, Ticker, TradingItem
12
+ from kfinance.kfinance import Company, Earnings, Security, Ticker, TradingItem, Transcript
12
13
 
13
14
 
14
15
  msft_company_id = "21835"
@@ -54,6 +55,25 @@ MOCK_COMPANY_DB = {
54
55
  "iso_country": "USA",
55
56
  },
56
57
  "earnings_call_dates": {"earnings": ["2004-07-22T21:30:00"]},
58
+ "earnings": {
59
+ "earnings": [
60
+ {
61
+ "name": "Microsoft Corporation, Q4 2024 Earnings Call, Jul 25, 2024",
62
+ "key_dev_id": 1916266380,
63
+ "datetime": "2024-07-25T21:30:00",
64
+ },
65
+ {
66
+ "name": "Microsoft Corporation, Q1 2025 Earnings Call, Oct 24, 2024",
67
+ "keydevid": 1916266381,
68
+ "datetime": "2024-10-24T21:30:00",
69
+ },
70
+ {
71
+ "name": "Microsoft Corporation, Q2 2025 Earnings Call, Jan 25, 2025",
72
+ "keydevid": 1916266382,
73
+ "datetime": "2025-01-25T21:30:00",
74
+ },
75
+ ]
76
+ },
57
77
  "statements": {
58
78
  "income_statement": {
59
79
  "statements": {
@@ -91,6 +111,32 @@ MOCK_COMPANY_DB = {
91
111
  }
92
112
  }
93
113
 
114
+ MOCK_TRANSCRIPT_DB = {
115
+ 1916266380: {
116
+ "transcript": [
117
+ {
118
+ "component_type": "Presentation Operator Message",
119
+ "person_name": "Operator",
120
+ "text": "Good morning, and welcome to Microsoft's Fourth Quarter 2024 Earnings Conference Call.",
121
+ },
122
+ {
123
+ "component_type": "Presenter Speech",
124
+ "person_name": "Satya Nadella",
125
+ "text": "Thank you for joining us today. We had an exceptional quarter with strong growth across all segments.",
126
+ },
127
+ ]
128
+ },
129
+ 1916266381: {
130
+ "transcript": [
131
+ {
132
+ "component_type": "Presentation Operator Message",
133
+ "person_name": "Operator",
134
+ "text": "Good morning, and welcome to Microsoft's First Quarter 2025 Earnings Conference Call.",
135
+ }
136
+ ]
137
+ },
138
+ }
139
+
94
140
 
95
141
  MOCK_SECURITY_DB = {msft_security_id: {"isin": msft_isin, "cusip": msft_cusip}}
96
142
 
@@ -215,6 +261,14 @@ class MockKFinanceApiClient:
215
261
  """Get a segment"""
216
262
  return MOCK_COMPANY_DB[company_id]
217
263
 
264
+ def fetch_earnings(self, company_id: int) -> dict:
265
+ """Get the earnings for a company."""
266
+ return MOCK_COMPANY_DB[company_id]["earnings"]
267
+
268
+ def fetch_transcript(self, key_dev_id: int) -> dict:
269
+ """Get the transcript for an earnings item."""
270
+ return MOCK_TRANSCRIPT_DB[key_dev_id]
271
+
218
272
 
219
273
  class TestTradingItem(TestCase):
220
274
  def setUp(self):
@@ -619,3 +673,112 @@ class TestTicker(TestCase):
619
673
  expected_dataframe.index.name = "date"
620
674
  market_caps = self.msft_ticker_from_ticker.market_cap()
621
675
  pd.testing.assert_frame_equal(expected_dataframe, market_caps)
676
+
677
+
678
+ class TestTranscript(TestCase):
679
+ def setUp(self):
680
+ """setup tests"""
681
+ self.transcript_components = [
682
+ {
683
+ "component_type": "Presentation Operator Message",
684
+ "person_name": "Operator",
685
+ "text": "Good morning, and welcome to Microsoft's Fourth Quarter 2024 Earnings Conference Call.",
686
+ },
687
+ {
688
+ "component_type": "Presenter Speech",
689
+ "person_name": "Satya Nadella",
690
+ "text": "Thank you for joining us today. We had an exceptional quarter with strong growth across all segments.",
691
+ },
692
+ ]
693
+ self.transcript = Transcript(self.transcript_components)
694
+
695
+ def test_transcript_length(self):
696
+ """test transcript length"""
697
+ self.assertEqual(len(self.transcript), 2)
698
+
699
+ def test_transcript_indexing(self):
700
+ """test transcript indexing"""
701
+ self.assertEqual(
702
+ self.transcript[0].person_name, self.transcript_components[0]["person_name"]
703
+ )
704
+ self.assertEqual(self.transcript[0].text, self.transcript_components[0]["text"])
705
+ self.assertEqual(
706
+ self.transcript[0].component_type, self.transcript_components[0]["component_type"]
707
+ )
708
+ self.assertEqual(
709
+ self.transcript[1].person_name, self.transcript_components[1]["person_name"]
710
+ )
711
+ self.assertEqual(self.transcript[1].text, self.transcript_components[1]["text"])
712
+ self.assertEqual(
713
+ self.transcript[1].component_type, self.transcript_components[1]["component_type"]
714
+ )
715
+
716
+ def test_transcript_raw(self):
717
+ """test transcript raw property"""
718
+ expected_raw = "Operator: Good morning, and welcome to Microsoft's Fourth Quarter 2024 Earnings Conference Call.\n\nSatya Nadella: Thank you for joining us today. We had an exceptional quarter with strong growth across all segments."
719
+ self.assertEqual(self.transcript.raw, expected_raw)
720
+
721
+
722
+ class TestEarnings(TestCase):
723
+ def setUp(self):
724
+ """setup tests"""
725
+ self.kfinance_api_client = MockKFinanceApiClient()
726
+ self.earnings = Earnings(
727
+ kfinance_api_client=self.kfinance_api_client,
728
+ name="Microsoft Corporation, Q4 2024 Earnings Call, Jul 25, 2024",
729
+ datetime=datetime.fromisoformat("2024-07-25T21:30:00").replace(tzinfo=timezone.utc),
730
+ key_dev_id=1916266380,
731
+ )
732
+
733
+ def test_earnings_attributes(self):
734
+ """test earnings attributes"""
735
+ self.assertEqual(
736
+ self.earnings.name, "Microsoft Corporation, Q4 2024 Earnings Call, Jul 25, 2024"
737
+ )
738
+ self.assertEqual(self.earnings.key_dev_id, 1916266380)
739
+ expected_datetime = datetime.fromisoformat("2024-07-25T21:30:00").replace(
740
+ tzinfo=timezone.utc
741
+ )
742
+ self.assertEqual(self.earnings.datetime, expected_datetime)
743
+
744
+ def test_earnings_transcript(self):
745
+ """test earnings transcript property"""
746
+ transcript = self.earnings.transcript
747
+ self.assertIsInstance(transcript, Transcript)
748
+ self.assertEqual(len(transcript), 2)
749
+ self.assertEqual(transcript[0].person_name, "Operator")
750
+ self.assertEqual(transcript[1].person_name, "Satya Nadella")
751
+
752
+
753
+ class TestCompanyEarnings(TestCase):
754
+ def setUp(self):
755
+ """setup tests"""
756
+ self.kfinance_api_client = MockKFinanceApiClient()
757
+ self.msft_company = Company(self.kfinance_api_client, msft_company_id)
758
+
759
+ def test_company_earnings(self):
760
+ """test company earnings method"""
761
+ earnings_list = self.msft_company.earnings()
762
+ self.assertEqual(len(earnings_list), 3)
763
+ self.assertIsInstance(earnings_list[0], Earnings)
764
+ self.assertEqual(earnings_list[0].key_dev_id, 1916266380)
765
+
766
+ def test_company_earnings_with_date_filter(self):
767
+ """test company earnings method with date filtering"""
768
+ start_date = date(2024, 8, 1)
769
+ end_date = date(2024, 12, 31)
770
+ earnings_list = self.msft_company.earnings(start_date=start_date, end_date=end_date)
771
+ self.assertEqual(len(earnings_list), 1)
772
+ self.assertEqual(earnings_list[0].key_dev_id, 1916266381)
773
+
774
+ @time_machine.travel(datetime(2025, 2, 1, 12, tzinfo=timezone.utc))
775
+ def test_company_latest_earnings(self):
776
+ """test company latest_earnings property"""
777
+ latest_earnings = self.msft_company.latest_earnings
778
+ self.assertEqual(latest_earnings.key_dev_id, 1916266382)
779
+
780
+ @time_machine.travel(datetime(2024, 6, 1, 12, tzinfo=timezone.utc))
781
+ def test_company_next_earnings(self):
782
+ """test company next_earnings property"""
783
+ next_earnings = self.msft_company.next_earnings
784
+ self.assertEqual(next_earnings.key_dev_id, 1916266380)
@@ -1,13 +1,15 @@
1
1
  from datetime import date, datetime
2
2
 
3
3
  from langchain_core.utils.function_calling import convert_to_openai_tool
4
+ from pytest import raises
4
5
  from requests_mock import Mocker
5
6
  import time_machine
6
7
 
7
8
  from kfinance.constants import BusinessRelationshipType, Capitalization, SegmentType, StatementType
8
- from kfinance.kfinance import Client
9
+ from kfinance.kfinance import Client, NoEarningsDataError
9
10
  from kfinance.tests.conftest import SPGI_COMPANY_ID, SPGI_SECURITY_ID, SPGI_TRADING_ITEM_ID
10
11
  from kfinance.tool_calling import (
12
+ GetEarnings,
11
13
  GetEarningsCallDatetimesFromIdentifier,
12
14
  GetFinancialLineItemFromIdentifier,
13
15
  GetFinancialStatementFromIdentifier,
@@ -15,8 +17,11 @@ from kfinance.tool_calling import (
15
17
  GetInfoFromIdentifier,
16
18
  GetIsinFromTicker,
17
19
  GetLatest,
20
+ GetLatestEarnings,
21
+ GetNextEarnings,
18
22
  GetNQuartersAgo,
19
23
  GetPricesFromIdentifier,
24
+ GetTranscript,
20
25
  ResolveIdentifier,
21
26
  )
22
27
  from kfinance.tool_calling.get_business_relationship_from_identifier import (
@@ -42,6 +47,7 @@ from kfinance.tool_calling.get_segments_from_identifier import (
42
47
  GetSegmentsFromIdentifier,
43
48
  GetSegmentsFromIdentifierArgs,
44
49
  )
50
+ from kfinance.tool_calling.get_transcript import GetTranscriptArgs
45
51
  from kfinance.tool_calling.shared_models import ToolArgsWithIdentifier
46
52
 
47
53
 
@@ -422,3 +428,213 @@ class TestResolveIdentifier:
422
428
  "security_id": SPGI_SECURITY_ID,
423
429
  "trading_item_id": SPGI_TRADING_ITEM_ID,
424
430
  }
431
+
432
+
433
+ class TestGetLatestEarnings:
434
+ def test_get_latest_earnings(self, requests_mock: Mocker, mock_client: Client):
435
+ """
436
+ GIVEN the GetLatestEarnings tool
437
+ WHEN we request the latest earnings for SPGI
438
+ THEN we get back the latest SPGI earnings
439
+ """
440
+ earnings_data = {
441
+ "earnings": [
442
+ {
443
+ "name": "SPGI Q4 2024 Earnings Call",
444
+ "datetime": "2025-02-11T13:30:00Z",
445
+ "keydevid": 12345,
446
+ },
447
+ {
448
+ "name": "SPGI Q3 2024 Earnings Call",
449
+ "datetime": "2024-10-30T12:30:00Z",
450
+ "keydevid": 12344,
451
+ },
452
+ ]
453
+ }
454
+
455
+ requests_mock.get(
456
+ url=f"https://kfinance.kensho.com/api/v1/earnings/{SPGI_COMPANY_ID}",
457
+ json=earnings_data,
458
+ )
459
+
460
+ expected_response = {
461
+ "name": "SPGI Q4 2024 Earnings Call",
462
+ "key_dev_id": 12345,
463
+ "datetime": "2025-02-11T13:30:00+00:00",
464
+ }
465
+
466
+ tool = GetLatestEarnings(kfinance_client=mock_client)
467
+ response = tool.run(ToolArgsWithIdentifier(identifier="SPGI").model_dump(mode="json"))
468
+ assert response == expected_response
469
+
470
+ def test_get_latest_earnings_no_data(self, requests_mock: Mocker, mock_client: Client):
471
+ """
472
+ GIVEN the GetLatestEarnings tool
473
+ WHEN we request the latest earnings for a company with no data
474
+ THEN we get a NoEarningsDataError exception
475
+ """
476
+ earnings_data = {"earnings": []}
477
+
478
+ requests_mock.get(
479
+ url=f"https://kfinance.kensho.com/api/v1/earnings/{SPGI_COMPANY_ID}",
480
+ json=earnings_data,
481
+ )
482
+
483
+ tool = GetLatestEarnings(kfinance_client=mock_client)
484
+ with raises(NoEarningsDataError, match="Latest earnings for SPGI not found"):
485
+ tool.run(ToolArgsWithIdentifier(identifier="SPGI").model_dump(mode="json"))
486
+
487
+
488
+ class TestGetNextEarnings:
489
+ def test_get_next_earnings_(self, requests_mock: Mocker, mock_client: Client):
490
+ """
491
+ GIVEN the GetNextEarnings tool
492
+ WHEN we request the next earnings for SPGI
493
+ THEN we get back the next SPGI earnings
494
+ """
495
+ earnings_data = {
496
+ "earnings": [
497
+ {
498
+ "name": "SPGI Q1 2025 Earnings Call",
499
+ "datetime": "2025-04-29T12:30:00Z",
500
+ "keydevid": 12346,
501
+ },
502
+ {
503
+ "name": "SPGI Q4 2024 Earnings Call",
504
+ "datetime": "2025-02-11T13:30:00Z",
505
+ "keydevid": 12345,
506
+ },
507
+ ]
508
+ }
509
+
510
+ requests_mock.get(
511
+ url=f"https://kfinance.kensho.com/api/v1/earnings/{SPGI_COMPANY_ID}",
512
+ json=earnings_data,
513
+ )
514
+
515
+ expected_response = {
516
+ "name": "SPGI Q1 2025 Earnings Call",
517
+ "key_dev_id": 12346,
518
+ "datetime": "2025-04-29T12:30:00+00:00",
519
+ }
520
+
521
+ with time_machine.travel("2025-03-01T00:00:00+00:00"):
522
+ tool = GetNextEarnings(kfinance_client=mock_client)
523
+ response = tool.run(ToolArgsWithIdentifier(identifier="SPGI").model_dump(mode="json"))
524
+ assert response == expected_response
525
+
526
+ def test_get_next_earnings_no_data(self, requests_mock: Mocker, mock_client: Client):
527
+ """
528
+ GIVEN the GetNextEarnings tool
529
+ WHEN we request the next earnings for a company with no data
530
+ THEN we get a NoEarningsDataError exception
531
+ """
532
+ earnings_data = {"earnings": []}
533
+
534
+ requests_mock.get(
535
+ url=f"https://kfinance.kensho.com/api/v1/earnings/{SPGI_COMPANY_ID}",
536
+ json=earnings_data,
537
+ )
538
+
539
+ with time_machine.travel("2025-03-01T00:00:00+00:00"):
540
+ tool = GetNextEarnings(kfinance_client=mock_client)
541
+ with raises(NoEarningsDataError, match="Next earnings for SPGI not found"):
542
+ tool.run(ToolArgsWithIdentifier(identifier="SPGI").model_dump(mode="json"))
543
+
544
+
545
+ class TestGetEarnings:
546
+ def test_get_earnings(self, requests_mock: Mocker, mock_client: Client):
547
+ """
548
+ GIVEN the GetEarnings tool
549
+ WHEN we request all earnings for SPGI
550
+ THEN we get back all SPGI earnings
551
+ """
552
+ earnings_data = {
553
+ "earnings": [
554
+ {
555
+ "name": "SPGI Q1 2025 Earnings Call",
556
+ "datetime": "2025-04-29T12:30:00Z",
557
+ "keydevid": 12346,
558
+ },
559
+ {
560
+ "name": "SPGI Q4 2024 Earnings Call",
561
+ "datetime": "2025-02-11T13:30:00Z",
562
+ "keydevid": 12345,
563
+ },
564
+ ]
565
+ }
566
+
567
+ requests_mock.get(
568
+ url=f"https://kfinance.kensho.com/api/v1/earnings/{SPGI_COMPANY_ID}",
569
+ json=earnings_data,
570
+ )
571
+
572
+ expected_response = [
573
+ {
574
+ "name": "SPGI Q1 2025 Earnings Call",
575
+ "key_dev_id": 12346,
576
+ "datetime": "2025-04-29T12:30:00+00:00",
577
+ },
578
+ {
579
+ "name": "SPGI Q4 2024 Earnings Call",
580
+ "key_dev_id": 12345,
581
+ "datetime": "2025-02-11T13:30:00+00:00",
582
+ },
583
+ ]
584
+
585
+ tool = GetEarnings(kfinance_client=mock_client)
586
+ response = tool.run(ToolArgsWithIdentifier(identifier="SPGI").model_dump(mode="json"))
587
+ assert response == expected_response
588
+
589
+ def test_get_earnings_no_data(self, requests_mock: Mocker, mock_client: Client):
590
+ """
591
+ GIVEN the GetEarnings tool
592
+ WHEN we request all earnings for a company with no data
593
+ THEN we get a NoEarningslDataError exception
594
+ """
595
+ earnings_data = {"earnings": []}
596
+
597
+ requests_mock.get(
598
+ url=f"https://kfinance.kensho.com/api/v1/earnings/{SPGI_COMPANY_ID}",
599
+ json=earnings_data,
600
+ )
601
+
602
+ tool = GetEarnings(kfinance_client=mock_client)
603
+ with raises(NoEarningsDataError, match="Earnings for SPGI not found"):
604
+ tool.run(ToolArgsWithIdentifier(identifier="SPGI").model_dump(mode="json"))
605
+
606
+
607
+ class TestGetTranscript:
608
+ def test_get_transcript(self, requests_mock: Mocker, mock_client: Client):
609
+ """
610
+ GIVEN the GetTranscript tool
611
+ WHEN we request a transcript by key_dev_id
612
+ THEN we get back the transcript text
613
+ """
614
+ transcript_data = {
615
+ "transcript": [
616
+ {
617
+ "person_name": "Operator",
618
+ "text": "Good morning, everyone.",
619
+ "component_type": "speech",
620
+ },
621
+ {
622
+ "person_name": "CEO",
623
+ "text": "Thank you for joining us today.",
624
+ "component_type": "speech",
625
+ },
626
+ ]
627
+ }
628
+
629
+ requests_mock.get(
630
+ url="https://kfinance.kensho.com/api/v1/transcript/12345",
631
+ json=transcript_data,
632
+ )
633
+
634
+ expected_response = (
635
+ "Operator: Good morning, everyone.\n\nCEO: Thank you for joining us today."
636
+ )
637
+
638
+ tool = GetTranscript(kfinance_client=mock_client)
639
+ response = tool.run(GetTranscriptArgs(key_dev_id=12345).model_dump(mode="json"))
640
+ assert response == expected_response
@@ -5,6 +5,7 @@ from kfinance.tool_calling.get_business_relationship_from_identifier import (
5
5
  )
6
6
  from kfinance.tool_calling.get_capitalization_from_identifier import GetCapitalizationFromIdentifier
7
7
  from kfinance.tool_calling.get_cusip_from_ticker import GetCusipFromTicker
8
+ from kfinance.tool_calling.get_earnings import GetEarnings
8
9
  from kfinance.tool_calling.get_earnings_call_datetimes_from_identifier import (
9
10
  GetEarningsCallDatetimesFromIdentifier,
10
11
  )
@@ -20,11 +21,14 @@ from kfinance.tool_calling.get_history_metadata_from_identifier import (
20
21
  from kfinance.tool_calling.get_info_from_identifier import GetInfoFromIdentifier
21
22
  from kfinance.tool_calling.get_isin_from_ticker import GetIsinFromTicker
22
23
  from kfinance.tool_calling.get_latest import GetLatest
24
+ from kfinance.tool_calling.get_latest_earnings import GetLatestEarnings
23
25
  from kfinance.tool_calling.get_n_quarters_ago import GetNQuartersAgo
26
+ from kfinance.tool_calling.get_next_earnings import GetNextEarnings
24
27
  from kfinance.tool_calling.get_prices_from_identifier import GetPricesFromIdentifier
25
28
  from kfinance.tool_calling.get_segments_from_identifier import (
26
29
  GetSegmentsFromIdentifier,
27
30
  )
31
+ from kfinance.tool_calling.get_transcript import GetTranscript
28
32
  from kfinance.tool_calling.resolve_identifier import ResolveIdentifier
29
33
  from kfinance.tool_calling.shared_models import KfinanceTool
30
34
 
@@ -36,6 +40,10 @@ ALL_TOOLS: list[Type[KfinanceTool]] = [
36
40
  GetCusipFromTicker,
37
41
  GetInfoFromIdentifier,
38
42
  GetEarningsCallDatetimesFromIdentifier,
43
+ GetEarnings,
44
+ GetLatestEarnings,
45
+ GetNextEarnings,
46
+ GetTranscript,
39
47
  GetHistoryMetadataFromIdentifier,
40
48
  GetPricesFromIdentifier,
41
49
  GetCapitalizationFromIdentifier,
@@ -0,0 +1,30 @@
1
+ from typing import Type
2
+
3
+ from pydantic import BaseModel
4
+
5
+ from kfinance.constants import Permission
6
+ from kfinance.kfinance import NoEarningsDataError
7
+ from kfinance.tool_calling.shared_models import KfinanceTool, ToolArgsWithIdentifier
8
+
9
+
10
+ class GetEarnings(KfinanceTool):
11
+ name: str = "get_earnings"
12
+ description: str = "Get all earnings for a given identifier. Returns a list of dictionaries, each with 'name' (str), 'key_dev_id' (int), and 'datetime' (str in ISO 8601 format with UTC timezone) attributes."
13
+ args_schema: Type[BaseModel] = ToolArgsWithIdentifier
14
+ required_permission: Permission | None = Permission.EarningsPermission
15
+
16
+ def _run(self, identifier: str) -> list[dict]:
17
+ ticker = self.kfinance_client.ticker(identifier)
18
+ earnings = ticker.company.earnings()
19
+
20
+ if not earnings:
21
+ raise NoEarningsDataError(f"Earnings for {identifier} not found")
22
+
23
+ return [
24
+ {
25
+ "name": earnings_item.name,
26
+ "key_dev_id": earnings_item.key_dev_id,
27
+ "datetime": earnings_item.datetime.isoformat(),
28
+ }
29
+ for earnings_item in earnings
30
+ ]
@@ -0,0 +1,27 @@
1
+ from typing import Type
2
+
3
+ from pydantic import BaseModel
4
+
5
+ from kfinance.constants import Permission
6
+ from kfinance.kfinance import NoEarningsDataError
7
+ from kfinance.tool_calling.shared_models import KfinanceTool, ToolArgsWithIdentifier
8
+
9
+
10
+ class GetLatestEarnings(KfinanceTool):
11
+ name: str = "get_latest_earnings"
12
+ description: str = "Get the latest earnings for a given identifier. Returns a dictionary with 'name' (str), 'key_dev_id' (int), and 'datetime' (str in ISO 8601 format with UTC timezone) attributes."
13
+ args_schema: Type[BaseModel] = ToolArgsWithIdentifier
14
+ required_permission: Permission | None = Permission.EarningsPermission
15
+
16
+ def _run(self, identifier: str) -> dict:
17
+ ticker = self.kfinance_client.ticker(identifier)
18
+ latest_earnings = ticker.company.latest_earnings
19
+
20
+ if latest_earnings is None:
21
+ raise NoEarningsDataError(f"Latest earnings for {identifier} not found")
22
+
23
+ return {
24
+ "name": latest_earnings.name,
25
+ "key_dev_id": latest_earnings.key_dev_id,
26
+ "datetime": latest_earnings.datetime.isoformat(),
27
+ }
@@ -0,0 +1,27 @@
1
+ from typing import Type
2
+
3
+ from pydantic import BaseModel
4
+
5
+ from kfinance.constants import Permission
6
+ from kfinance.kfinance import NoEarningsDataError
7
+ from kfinance.tool_calling.shared_models import KfinanceTool, ToolArgsWithIdentifier
8
+
9
+
10
+ class GetNextEarnings(KfinanceTool):
11
+ name: str = "get_next_earnings"
12
+ description: str = "Get the next earnings for a given identifier. Returns a dictionary with 'name' (str), 'key_dev_id' (int), and 'datetime' (str in ISO 8601 format with UTC timezone) attributes."
13
+ args_schema: Type[BaseModel] = ToolArgsWithIdentifier
14
+ required_permission: Permission | None = Permission.EarningsPermission
15
+
16
+ def _run(self, identifier: str) -> dict:
17
+ ticker = self.kfinance_client.ticker(identifier)
18
+ next_earnings = ticker.company.next_earnings
19
+
20
+ if next_earnings is None:
21
+ raise NoEarningsDataError(f"Next earnings for {identifier} not found")
22
+
23
+ return {
24
+ "name": next_earnings.name,
25
+ "key_dev_id": next_earnings.key_dev_id,
26
+ "datetime": next_earnings.datetime.isoformat(),
27
+ }