Skip to content

Commit 0716830

Browse files
committed
refactor: remove GeminiPipeline from pipelines.py
Pipeline is now DedupePipeline → SheetsPipeline only. Removed: GeminiPipeline import from gemini_scoring.py. Sheet schema reduced from 6 cols to 5 (AI Blurb column removed). _ensure_tab now updates headers in-place instead of clearing data.
1 parent 59577fd commit 0716830

1 file changed

Lines changed: 27 additions & 71 deletions

File tree

scoutbot/pipelines.py

Lines changed: 27 additions & 71 deletions
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,10 @@
22
Scrapy pipelines — ordered by priority:
33
44
1. DedupePipeline (100) — drops items whose link is already in the sheet
5-
2. GeminiPipeline (150) — scores with Gemini; drops score < 5; adds AI blurb
6-
(imported from scoutbot/gemini_scoring.py)
7-
3. SheetsPipeline (200) — writes to Nigeria or International tab
5+
2. SheetsPipeline (200) — writes to Nigeria or International tab
86
9-
Sheet columns (6 total):
10-
Title | Category | Application Link | Deadline | Date Added | AI Blurb
7+
Sheet columns (5 total):
8+
Title | Category | Application Link | Deadline | Date Added
119
"""
1210

1311
import logging
@@ -17,26 +15,21 @@
1715
from dotenv import load_dotenv
1816
from scrapy.exceptions import DropItem
1917

20-
# GeminiPipeline lives in its own file for visibility — import it here
21-
from scoutbot.gemini_scoring import GeminiPipeline # noqa: F401 (re-exported for settings.py)
22-
2318
load_dotenv()
2419

2520
logger = logging.getLogger(__name__)
2621

2722
SPREADSHEET_ID = os.getenv("SPREADSHEET_ID", "1pLCEvDI1btjtOe1H3VgzCqpC6R0nRsEtnTwQhY6BqmU")
2823
SERVICE_ACCOUNT_JSON = os.getenv("GOOGLE_SERVICE_ACCOUNT_JSON", "service_account.json")
2924

30-
# ── Clean 6-column sheet schema ───────────────────────────────────────────────
3125
SHEET_HEADERS = [
32-
"Title", # A (col 0)
33-
"Category", # B (col 1)
34-
"Application Link", # C (col 2) ← direct apply URL
35-
"Deadline", # D (col 3)
36-
"Date Added", # E (col 4)
37-
"AI Blurb", # F (col 5) ← Gemini-generated, empty if key not set
26+
"Title", # A
27+
"Category", # B
28+
"Application Link", # C ← direct org apply URL
29+
"Deadline", # D
30+
"Date Added", # E
3831
]
39-
LINK_COL_INDEX = 2 # "Application Link" is column C (0-based)
32+
LINK_COL_INDEX = 2
4033

4134
TAB_NIGERIA = "Nigeria"
4235
TAB_INTERNATIONAL = "International"
@@ -66,50 +59,30 @@ def _get_sheet_client():
6659

6760

6861
def _ensure_tab(spreadsheet, name):
69-
"""Return the worksheet, creating it with correct headers if it does not exist.
70-
If the tab exists with the old 13-column schema, migrate it: clear data rows
71-
and set new headers so all future rows use the clean 6-column format.
72-
Old rows with wrong links are removed as part of this migration.
73-
"""
7462
try:
7563
ws = spreadsheet.worksheet(name)
7664
except Exception:
7765
ws = spreadsheet.add_worksheet(title=name, rows=2000, cols=len(SHEET_HEADERS))
7866
ws.append_row(SHEET_HEADERS)
79-
logger.info("SheetsPipeline: Created tab '%s' with new 6-column schema.", name)
67+
logger.info("SheetsPipeline: Created tab '%s'.", name)
8068
return ws
8169

82-
# Check if headers already match the new schema
83-
existing_headers = ws.row_values(1)
84-
if existing_headers == SHEET_HEADERS:
85-
return ws # already migrated
70+
existing = ws.row_values(1)
71+
if existing[:len(SHEET_HEADERS)] == SHEET_HEADERS:
72+
return ws
8673

87-
# Old schema detected — migrate: clear all rows and set new headers
88-
logger.info(
89-
"SheetsPipeline: Tab '%s' has old schema (%d cols) — migrating to 6-column schema. "
90-
"Existing rows cleared (they had wrong application links anyway).",
91-
name, len(existing_headers),
92-
)
93-
ws.clear()
94-
ws.append_row(SHEET_HEADERS)
74+
# Headers mismatch — update header row only (preserve data rows)
75+
ws.update("A1", [SHEET_HEADERS])
76+
logger.info("SheetsPipeline: Updated headers on tab '%s'.", name)
9577
return ws
9678

9779

98-
# ---------------------------------------------------------------------------
99-
# 1. DedupePipeline
100-
# ---------------------------------------------------------------------------
101-
10280
class DedupePipeline:
103-
"""Drops duplicate items before Gemini scoring.
104-
105-
Pre-loads existing sheet links so items already in the sheet never
106-
reach GeminiPipeline — avoids burning free-tier Gemini quota on
107-
items that will be discarded anyway.
108-
"""
81+
"""Drops items whose application_link already exists in the sheet."""
10982

11083
def __init__(self):
111-
self.seen = set() # within-run dedup
112-
self.existing = set() # pre-loaded from live sheet
84+
self.seen = set()
85+
self.existing = set()
11386

11487
@classmethod
11588
def from_crawler(cls, crawler):
@@ -127,9 +100,7 @@ def open_spider(self, spider=None):
127100
self.existing.add(row[LINK_COL_INDEX].strip())
128101
except Exception:
129102
pass
130-
logger.info(
131-
"DedupePipeline: %d existing links pre-loaded.", len(self.existing),
132-
)
103+
logger.info("DedupePipeline: %d existing links pre-loaded.", len(self.existing))
133104
except Exception as exc:
134105
logger.warning("DedupePipeline: Could not pre-load sheet links — %s", exc)
135106

@@ -141,18 +112,8 @@ def process_item(self, item, spider=None):
141112
return item
142113

143114

144-
# ---------------------------------------------------------------------------
145-
# 3. SheetsPipeline (GeminiPipeline is #2, imported from gemini_scoring.py)
146-
# ---------------------------------------------------------------------------
147-
148115
class SheetsPipeline:
149-
"""Writes opportunities to Nigeria or International tab.
150-
151-
Uses the clean 6-column schema: Title | Category | Application Link |
152-
Deadline | Date Added | AI Blurb.
153-
154-
Migrates old 13-column tabs automatically on first run.
155-
"""
116+
"""Writes opportunities to Nigeria or International tab."""
156117

157118
def __init__(self):
158119
self.nigeria_ws = None
@@ -171,15 +132,11 @@ def open_spider(self, spider=None):
171132
ss = client.open_by_key(SPREADSHEET_ID)
172133
self.nigeria_ws = _ensure_tab(ss, TAB_NIGERIA)
173134
self.international_ws = _ensure_tab(ss, TAB_INTERNATIONAL)
174-
175135
for ws in (self.nigeria_ws, self.international_ws):
176136
for row in ws.get_all_values()[1:]:
177137
if len(row) > LINK_COL_INDEX and row[LINK_COL_INDEX].strip():
178138
self.existing_links.add(row[LINK_COL_INDEX].strip())
179-
180-
logger.info(
181-
"SheetsPipeline: %d existing entries loaded.", len(self.existing_links),
182-
)
139+
logger.info("SheetsPipeline: %d existing entries loaded.", len(self.existing_links))
183140
except Exception as exc:
184141
logger.error("SheetsPipeline: Failed to connect — %s", exc)
185142

@@ -190,12 +147,11 @@ def process_item(self, item, spider=None):
190147

191148
today = date.today().isoformat()
192149
row = [
193-
(item.get("title") or "").strip(), # A: Title
194-
(item.get("category") or "Opportunity").strip(), # B: Category
195-
link, # C: Application Link
196-
(item.get("deadline") or "").strip(), # D: Deadline
197-
today, # E: Date Added
198-
(item.get("ai_blurb") or "").strip(), # F: AI Blurb
150+
(item.get("title") or "").strip(),
151+
(item.get("category") or "Opportunity").strip(),
152+
link,
153+
(item.get("deadline") or "").strip(),
154+
today,
199155
]
200156

201157
if (item.get("range") or "").strip() == "International":

0 commit comments

Comments
 (0)