diff --git a/edgar/company_reports.py b/edgar/company_reports.py index e7488f6..706a501 100644 --- a/edgar/company_reports.py +++ b/edgar/company_reports.py @@ -1,11 +1,13 @@ from datetime import datetime from functools import lru_cache, partial from typing import Dict, List - +from rich.table import Table +from rich import box from rich import print from rich.console import Group, Text from rich.panel import Panel - +from rich.tree import Tree +from edgar.core import datefmt from edgar._filings import Attachments, Attachment from edgar._markdown import MarkdownContent from edgar.richtools import repr_rich @@ -24,6 +26,7 @@ ] + class CompanyReport: def __init__(self, filing): @@ -58,6 +61,10 @@ def cash_flow_statement(self): def financials(self): return Financials.extract(self._filing) + @property + def period_of_report(self): + return self._filing.header.period_of_report + @property @lru_cache(maxsize=1) def chunked_document(self): @@ -231,9 +238,84 @@ def __init__(self, filing): assert filing.form in ['10-K', '10-K/A'], f"This form should be a 10-K but was {filing.form}" super().__init__(filing) + @property + def business(self): + return self['Item 1'] + + @property + def risk_factors(self): + return self['Item 1A'] + + @property + def management_discussion(self): + return self['Item 7'] + + @property + def directors_officers_and_governance(self): + return self['Item 10'] + def __str__(self): return f"""TenK('{self.company}')""" + def get_structure(self): + # Create the main tree + tree = Tree("📄 ") + + # Get the actual items from the filing + actual_items = self.items + + # Create a mapping of uppercase to actual case items + case_mapping = {item.upper(): item for item in actual_items} + + # Process each part in the structure + for part, items in self.structure.structure.items(): + # Create a branch for each part + part_tree = tree.add(f"[bold blue]{part}[/]") + + # Add items under each part + for item_key, item_data in items.items(): + # Check if this item exists in the actual filing + if item_key in case_mapping: + # Use the actual case from the filing + actual_item = case_mapping[item_key] + item_text = Text.assemble( + (f"{actual_item:<7} ", "bold green"), + (f"{item_data['Title']}", "bold"), + ) + else: + # Item doesn't exist - show in grey with original structure case + item_text = Text.assemble( + (f"{item_key}: ", "dim"), + (f"{item_data['Title']}", "dim"), + ) + + part_tree.add(item_text) + + return tree + + def __rich__(self): + title = Text.assemble( + (f"{self.company}", "bold deep_sky_blue1"), + (" ", ""), + (f"{self.form}", "bold"), + ) + periods = Text.assemble( + (f"Period ending ", ""), + (f"{datefmt(self.period_of_report, '%B %d, %Y')}", "bold"), + (f" filed on ", ""), + (f"{datefmt(self.filing_date, '%B %d, %Y')}", "bold"), + + ) + panel = Panel( + Group( + periods, + self.get_structure() + ), + title=title, + box=box.ROUNDED, + ) + return panel + class TenQ(CompanyReport): structure = FilingStructure({ diff --git a/edgar/core.py b/edgar/core.py index e995391..2f5a94d 100644 --- a/edgar/core.py +++ b/edgar/core.py @@ -635,6 +635,8 @@ def datefmt(value: Union[datetime.datetime, str], fmt: str = "%Y-%m-%d") -> str: # If value matches %Y%m%d%H%M%s, then parse it elif re.match(r"^\d{14}$", value): value = datetime.datetime.strptime(value, "%Y%m%d%H%M%S") + elif re.match(r"^\d{4}-\d{2}-\d{2}$", value): + value = datetime.datetime.strptime(value, "%Y-%m-%d") return value.strftime(fmt) else: return value.strftime(fmt) @@ -770,3 +772,15 @@ def is_start_of_quarter(): return True return False + +def format_date(date: Union[str, datetime.datetime], fmt: str = "%Y-%m-%d") -> str: + """ + Format a date as a string + :param date: The date to format + :param fmt: The format to use + :return: The formatted date + """ + if isinstance(date, str): + return date + else: + return date.strftime(fmt) diff --git a/tests/test_company_reports.py b/tests/test_company_reports.py index e094f3a..c42e051 100644 --- a/tests/test_company_reports.py +++ b/tests/test_company_reports.py @@ -72,7 +72,8 @@ def test_is_valid_item_for_filing(): def test_chunk_items_for_company_reports(): - filing = find("0001193125-23-086073") + filing = Filing(form='10-K', filing_date='2023-03-31', company='7GC & Co. Holdings Inc.', +cik=1826011, accession_no='0001193125-23-086073') html = filing.html() chunked_document = ChunkedDocument(html) print() @@ -100,3 +101,20 @@ def test_items_for_10k_filing(): assert tenk['ITEM 15'] == item_15 print(item_15) +def test_tenk_item_structure(): + filing = Filing(company='Apple Inc.', cik=320193, form='10-K', filing_date='2024-11-01', accession_no='0000320193-24-000123') + tenk = filing.obj() + tenk_repr = repr(tenk) + print() + print(tenk_repr) + assert "Item 1" in tenk_repr + + +def test_tenk_section_properties(): + filing = Filing(company='Apple Inc.', cik=320193, form='10-K', filing_date='2024-11-01', accession_no='0000320193-24-000123') + tenk:TenK = filing.obj() + assert tenk.management_discussion + assert tenk.business + assert tenk.risk_factors + assert tenk.directors_officers_and_governance +