Add DB indexes and extract shared query methods to Database class

Add missing indexes on ratings(false_positive), drafts(source), and
draft_authors(person_id) for faster filtering. Extract 12 shared query
methods (false_positive_drafts_raw, non_false_positive_ratings_raw,
false_positive_names, rated_count, gap_count, search_gaps, search_authors,
draft_affiliation_pairs, all_persons_info, category_counts,
draft_author_count_map, source_counts) to eliminate duplicated SQL across
cli.py, data.py, and reports.py.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-03-09 03:33:18 +01:00
parent 4710668419
commit c066b04d74
4 changed files with 141 additions and 168 deletions

View File

@@ -253,12 +253,7 @@ def get_overview_stats(db: Database) -> OverviewStats:
def get_category_counts(db: Database) -> dict[str, int]:
"""Return {category: draft_count} for all categories."""
pairs = db.drafts_with_ratings(limit=1000)
counts: dict[str, int] = Counter()
for _, rating in pairs:
for cat in rating.categories:
counts[cat] += 1
return dict(counts.most_common())
return db.category_counts()
def get_category_summary(db: Database, category: str) -> dict | None:
@@ -1002,8 +997,7 @@ def _compute_idea_clusters(db: Database) -> dict:
return {"clusters": [], "scatter": [], "stats": {"total": 0, "clustered": 0, "num_clusters": 0}, "empty": True}
# Exclude ideas from false-positive drafts
fp_names = {r[0] for r in db.conn.execute(
"SELECT draft_name FROM ratings WHERE false_positive = 1").fetchall()}
fp_names = db.false_positive_names()
# Fetch ideas with IDs for metadata lookup
rows = db.conn.execute("SELECT id, title, description, idea_type, draft_name FROM ideas").fetchall()
@@ -1512,34 +1506,10 @@ def global_search(db: Database, query: str) -> SearchResults:
})
# 3. Authors via LIKE
rows = db.conn.execute(
"""SELECT person_id, name, affiliation FROM authors
WHERE name LIKE ? OR affiliation LIKE ?
ORDER BY name LIMIT 50""",
(like, like),
).fetchall()
for r in rows:
results["authors"].append({
"person_id": r["person_id"],
"name": r["name"],
"affiliation": r["affiliation"] or "",
})
results["authors"] = db.search_authors(q, limit=50)
# 4. Gaps via LIKE
rows = db.conn.execute(
"""SELECT id, topic, description, category, severity FROM gaps
WHERE topic LIKE ? OR description LIKE ?
ORDER BY id LIMIT 50""",
(like, like),
).fetchall()
for r in rows:
results["gaps"].append({
"id": r["id"],
"topic": r["topic"],
"description": (r["description"] or "")[:200],
"category": r["category"],
"severity": r["severity"],
})
results["gaps"] = db.search_gaps(q, limit=50)
return results
@@ -2258,24 +2228,12 @@ def get_source_comparison(db: Database) -> dict:
def get_false_positive_profile(db: Database) -> dict:
"""Profile drafts flagged as false positives."""
# Get false positives
fp_rows = db.conn.execute(
"""SELECT d.*, r.novelty, r.maturity, r.overlap, r.momentum, r.relevance,
r.summary, r.categories as r_categories, r.false_positive
FROM drafts d
JOIN ratings r ON d.name = r.draft_name
WHERE r.false_positive = 1
ORDER BY d.name"""
).fetchall()
fp_rows = db.false_positive_drafts_raw()
# Get non-FP rated drafts for comparison
nonfp_rows = db.conn.execute(
"""SELECT r.novelty, r.maturity, r.overlap, r.momentum, r.relevance,
r.categories as r_categories
FROM ratings r
WHERE COALESCE(r.false_positive, 0) = 0"""
).fetchall()
nonfp_rows = db.non_false_positive_ratings_raw()
total_rated = db.conn.execute("SELECT COUNT(*) FROM ratings").fetchone()[0]
total_rated = db.rated_count()
total_drafts = db.count_drafts(include_false_positives=True)
# Build FP list
@@ -2720,34 +2678,10 @@ def global_search(db: Database, query: str) -> SearchResults:
})
# 3. Authors via LIKE
rows = db.conn.execute(
"""SELECT person_id, name, affiliation FROM authors
WHERE name LIKE ? OR affiliation LIKE ?
ORDER BY name LIMIT 50""",
(like, like),
).fetchall()
for r in rows:
results["authors"].append({
"person_id": r["person_id"],
"name": r["name"],
"affiliation": r["affiliation"] or "",
})
results["authors"] = db.search_authors(q, limit=50)
# 4. Gaps via LIKE
rows = db.conn.execute(
"""SELECT id, topic, description, category, severity FROM gaps
WHERE topic LIKE ? OR description LIKE ?
ORDER BY id LIMIT 50""",
(like, like),
).fetchall()
for r in rows:
results["gaps"].append({
"id": r["id"],
"topic": r["topic"],
"description": (r["description"] or "")[:200],
"category": r["category"],
"severity": r["severity"],
})
results["gaps"] = db.search_gaps(q, limit=50)
return results
@@ -3148,11 +3082,7 @@ def get_complexity_data(db: Database) -> dict:
""").fetchall()
# Author counts
author_counts = {}
for row in conn.execute("""
SELECT draft_name, COUNT(*) AS cnt FROM draft_authors GROUP BY draft_name
""").fetchall():
author_counts[row["draft_name"]] = row["cnt"]
author_counts = db.draft_author_count_map()
# Citation counts (outgoing refs)
citation_counts = {}
@@ -3681,24 +3611,12 @@ def get_source_comparison(db: Database) -> dict:
def get_false_positive_profile(db: Database) -> dict:
"""Profile drafts flagged as false positives."""
# Get false positives
fp_rows = db.conn.execute(
"""SELECT d.*, r.novelty, r.maturity, r.overlap, r.momentum, r.relevance,
r.summary, r.categories as r_categories, r.false_positive
FROM drafts d
JOIN ratings r ON d.name = r.draft_name
WHERE r.false_positive = 1
ORDER BY d.name"""
).fetchall()
fp_rows = db.false_positive_drafts_raw()
# Get non-FP rated drafts for comparison
nonfp_rows = db.conn.execute(
"""SELECT r.novelty, r.maturity, r.overlap, r.momentum, r.relevance,
r.categories as r_categories
FROM ratings r
WHERE COALESCE(r.false_positive, 0) = 0"""
).fetchall()
nonfp_rows = db.non_false_positive_ratings_raw()
total_rated = db.conn.execute("SELECT COUNT(*) FROM ratings").fetchone()[0]
total_rated = db.rated_count()
total_drafts = db.count_drafts(include_false_positives=True)
# Build FP list
@@ -4142,34 +4060,10 @@ def global_search(db: Database, query: str) -> SearchResults:
})
# 3. Authors via LIKE
rows = db.conn.execute(
"""SELECT person_id, name, affiliation FROM authors
WHERE name LIKE ? OR affiliation LIKE ?
ORDER BY name LIMIT 50""",
(like, like),
).fetchall()
for r in rows:
results["authors"].append({
"person_id": r["person_id"],
"name": r["name"],
"affiliation": r["affiliation"] or "",
})
results["authors"] = db.search_authors(q, limit=50)
# 4. Gaps via LIKE
rows = db.conn.execute(
"""SELECT id, topic, description, category, severity FROM gaps
WHERE topic LIKE ? OR description LIKE ?
ORDER BY id LIMIT 50""",
(like, like),
).fetchall()
for r in rows:
results["gaps"].append({
"id": r["id"],
"topic": r["topic"],
"description": (r["description"] or "")[:200],
"category": r["category"],
"severity": r["severity"],
})
results["gaps"] = db.search_gaps(q, limit=50)
return results