| """Tests for the 10-K section chunker.""" |
| from __future__ import annotations |
|
|
| import textwrap |
|
|
| from src.extractors.section_chunker import chunk_filing |
|
|
| SYNTHETIC_10K = textwrap.dedent("""\ |
| APPLE INC. |
| FORM 10-K |
| Annual Report Pursuant to Section 13 or 15(d) of the Securities Exchange Act of 1934 |
| |
| Common Stock, $0.00001 par value per share |
| NASDAQ Global Select Market |
| Central Index Key: 0000320193 |
| State of Incorporation: California |
| |
| TABLE OF CONTENTS |
| Item 1. Business ..................... 3 |
| Item 1A. Risk Factors ................. 12 |
| Item 7. MD&A ......................... 34 |
| Item 8. Financial Statements ......... 42 |
| |
| PART I |
| |
| Item 1. Business |
| |
| Apple Inc. designs, manufactures, and markets smartphones, personal |
| computers, tablets, wearables, and accessories. |
| |
| Item 1A. Risk Factors |
| |
| The company faces intense competition and the following risks could |
| materially affect the business. |
| |
| Supply chain β heavy dependence on manufacturing in China. |
| Foreign exchange β a substantial portion of net sales from non-US markets. |
| |
| PART II |
| |
| Item 7. Management's Discussion and Analysis of Financial Condition |
| |
| Net sales grew 3% year over year, driven by Services growth. |
| |
| Item 8. Financial Statements and Supplementary Data |
| |
| Revenue: $391,035 million |
| Net income: $93,736 million |
| Diluted EPS: $6.11 |
| Cash and cash equivalents: $29,943 million |
| Total assets: $364,980 million |
| """) |
|
|
|
|
| def test_finds_the_four_headline_items_in_order(): |
| c = chunk_filing(SYNTHETIC_10K) |
| assert c.item_ids == ["1", "1A", "7", "8"] |
|
|
|
|
| def test_toc_entries_deduped_last_wins(): |
| """A synthetic 10-K has each Item mentioned twice (TOC + real section). |
| We keep the LAST occurrence β the real section, not the TOC entry. |
| """ |
| c = chunk_filing(SYNTHETIC_10K) |
| |
| |
| body = c.get_text("1A").lstrip() |
| assert body.startswith("The company faces intense competition"), body[:80] |
|
|
|
|
| def test_cover_captured_before_first_item(): |
| c = chunk_filing(SYNTHETIC_10K) |
| assert "APPLE INC." in c.cover |
| assert "Central Index Key: 0000320193" in c.cover |
| |
| |
| assert "Apple Inc. designs, manufactures" not in c.cover |
|
|
|
|
| def test_section_bodies_do_not_leak_into_neighbors(): |
| c = chunk_filing(SYNTHETIC_10K) |
| |
| body_1a = c.get_text("1A") |
| assert "Supply chain" in body_1a |
| assert "Net sales grew 3%" not in body_1a |
| |
| body_8 = c.get_text("8") |
| assert "Revenue: $391,035" in body_8 |
| assert "Net sales grew 3%" not in body_8 |
|
|
|
|
| def test_has_and_get_are_case_insensitive(): |
| c = chunk_filing(SYNTHETIC_10K) |
| assert c.has("1a") is True |
| assert c.has("1A") is True |
| assert c.get("1a") is not None |
|
|
|
|
| def test_missing_item_returns_none(): |
| c = chunk_filing(SYNTHETIC_10K) |
| assert c.get("15") is None |
| assert c.get_text("15", default="β") == "β" |
|
|
|
|
| def test_empty_input_returns_empty_result(): |
| c = chunk_filing("") |
| assert c.cover == "" |
| assert c.sections == [] |
| c2 = chunk_filing(" \n\n ") |
| assert c2.sections == [] |
|
|
|
|
| def test_no_headings_returns_single_blob(): |
| """A plain document with no Item headings shouldn't crash β degrades gracefully.""" |
| text = "This document does not follow SEC 10-K structure." |
| c = chunk_filing(text) |
| assert len(c.sections) == 1 |
| assert c.sections[0].item == "0" |
| assert c.sections[0].text == text |
|
|
|
|
| def test_variant_heading_punctuation_still_matches(): |
| |
| for sep in [". ", " - ", " β ", ": "]: |
| text = f"cover\n\nItem 1A{sep}Risk Factors\n\nThe risks are as follows." |
| c = chunk_filing(text) |
| assert c.has("1A"), f"failed on separator {sep!r}" |
|
|
|
|
| def test_case_variants_in_source_still_match(): |
| |
| for form in ["Item 1A.", "ITEM 1A.", "item 1a."]: |
| text = f"cover\n\n{form} Risk Factors\n\nRisks." |
| c = chunk_filing(text) |
| assert c.has("1A"), f"failed on form {form!r}" |
|
|