Viney Claude Opus 4.8 commited on
Commit
8dbbf97
·
1 Parent(s): 559c2ff

fix: chronological sort of periods in sections_db (Q12027 before Q32026)

Browse files
Files changed (2) hide show
  1. storage/sections_db.py +29 -12
  2. tests/test_sections_db.py +13 -0
storage/sections_db.py CHANGED
@@ -77,8 +77,31 @@ def get_section(ticker: str, period: str, section: str) -> Optional[str]:
77
  return row[0] if row else None
78
 
79
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
80
  def get_periods_for_ticker(ticker: str, form_type: Optional[str] = None) -> list[str]:
81
- """Return all period strings stored for a ticker, sorted newest first.
82
 
83
  Optionally filtered by form_type (e.g. '10-Q').
84
  """
@@ -87,20 +110,14 @@ def get_periods_for_ticker(ticker: str, form_type: Optional[str] = None) -> list
87
  with sqlite3.connect(SECTIONS_DB_PATH) as conn:
88
  if form_type:
89
  rows = conn.execute(
90
- """
91
- SELECT DISTINCT period FROM sections
92
- WHERE ticker=? AND form_type=?
93
- ORDER BY period DESC
94
- """,
95
  (ticker.upper(), form_type),
96
  ).fetchall()
97
  else:
98
  rows = conn.execute(
99
- """
100
- SELECT DISTINCT period FROM sections
101
- WHERE ticker=?
102
- ORDER BY period DESC
103
- """,
104
  (ticker.upper(),),
105
  ).fetchall()
106
- return [r[0] for r in rows]
 
 
 
77
  return row[0] if row else None
78
 
79
 
80
+ def _period_sort_key(period: str) -> tuple[int, int]:
81
+ """Parse 'Q12027' → (2027, 1) for correct chronological sort (newest first).
82
+
83
+ Falls back to (0, 0) for unparseable strings (e.g. 'FY2024').
84
+ """
85
+ if not period:
86
+ return (0, 0)
87
+ if period.startswith("Q") and len(period) >= 6:
88
+ try:
89
+ body = period[1:] # "12027"
90
+ year = int(body[-4:]) # 2027
91
+ quarter = int(body[:-4]) # 1
92
+ return (year, quarter)
93
+ except (ValueError, IndexError):
94
+ pass
95
+ if period.startswith("FY") and len(period) == 6:
96
+ try:
97
+ return (int(period[2:]), 0)
98
+ except ValueError:
99
+ pass
100
+ return (0, 0)
101
+
102
+
103
  def get_periods_for_ticker(ticker: str, form_type: Optional[str] = None) -> list[str]:
104
+ """Return all period strings stored for a ticker, sorted newest first (chronologically).
105
 
106
  Optionally filtered by form_type (e.g. '10-Q').
107
  """
 
110
  with sqlite3.connect(SECTIONS_DB_PATH) as conn:
111
  if form_type:
112
  rows = conn.execute(
113
+ "SELECT DISTINCT period FROM sections WHERE ticker=? AND form_type=?",
 
 
 
 
114
  (ticker.upper(), form_type),
115
  ).fetchall()
116
  else:
117
  rows = conn.execute(
118
+ "SELECT DISTINCT period FROM sections WHERE ticker=?",
 
 
 
 
119
  (ticker.upper(),),
120
  ).fetchall()
121
+ periods = [r[0] for r in rows]
122
+ periods.sort(key=_period_sort_key, reverse=True)
123
+ return periods
tests/test_sections_db.py CHANGED
@@ -61,6 +61,19 @@ def test_get_periods_returns_empty_when_no_db(tmp_path):
61
  assert get_periods_for_ticker("AAPL") == []
62
 
63
 
 
 
 
 
 
 
 
 
 
 
 
 
 
64
  def test_ticker_is_case_insensitive(tmp_path):
65
  db_path = _tmp_db(tmp_path)
66
  with patch("storage.sections_db.SECTIONS_DB_PATH", db_path):
 
61
  assert get_periods_for_ticker("AAPL") == []
62
 
63
 
64
+ def test_get_periods_chronological_sort(tmp_path):
65
+ """Q12027 must come before Q32026 — chronological not alphabetical."""
66
+ db_path = _tmp_db(tmp_path)
67
+ with patch("storage.sections_db.SECTIONS_DB_PATH", db_path):
68
+ from storage.sections_db import init_sections_db, upsert_section, get_periods_for_ticker
69
+ init_sections_db()
70
+ for period in ["Q32026", "Q22026", "Q12027", "Q12026"]:
71
+ upsert_section("NVDA", period, "10-Q", "mda", f"text {period}")
72
+ periods = get_periods_for_ticker("NVDA", form_type="10-Q")
73
+ assert periods[0] == "Q12027", f"Expected Q12027 first, got {periods}"
74
+ assert periods[1] == "Q32026", f"Expected Q32026 second, got {periods}"
75
+
76
+
77
  def test_ticker_is_case_insensitive(tmp_path):
78
  db_path = _tmp_db(tmp_path)
79
  with patch("storage.sections_db.SECTIONS_DB_PATH", db_path):