File size: 4,467 Bytes
c6cfb1f
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
"""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)
    # If we'd kept the TOC entry, Item 1A's text would start with '. Risk Factors'
    # from the TOC. Real section starts with actual body.
    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
    # The cover ends where the FIRST Item heading starts β€” so the actual
    # "Item 1. Business" section body must NOT be in it.
    assert "Apple Inc. designs, manufactures" not in c.cover


def test_section_bodies_do_not_leak_into_neighbors():
    c = chunk_filing(SYNTHETIC_10K)
    # Item 1A's chunk should have risk-factor content, not Item 7 MD&A content.
    body_1a = c.get_text("1A")
    assert "Supply chain" in body_1a
    assert "Net sales grew 3%" not in body_1a
    # Item 8's chunk holds financials, not MD&A.
    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   # lowercase input
    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():
    # 10-Ks in the wild use "Item 1A -", "Item 1A β€”", "Item 1A:"
    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():
    # ITEM 1A. and item 1a. are both valid in real filings.
    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}"