Ryan Christian D. Deniega Claude Sonnet 4.6 commited on
Commit
2f3f71f
·
1 Parent(s): 9f6f344

Remove Firebase/Firestore from backend — use local JSON persistence only

Browse files

Deletes firebase_client.py and strips all Tier-1 Firestore code paths from
history.py, trends.py, and scoring/engine.py. Persistence now falls through
directly to the local JSON file (data/history.json) with in-memory fallback.
Eliminates the repeated "firebase-admin not installed" warnings in HF logs.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

api/routes/history.py CHANGED
@@ -3,9 +3,8 @@ PhilVerify — History Route
3
  GET /history — Returns past verification logs with pagination.
4
 
5
  Persistence tier order (best to worst):
6
- 1. Firestore requires Cloud Firestore API to be enabled in GCP console
7
- 2. Local JSON file data/history.json, survives server restarts, no setup needed
8
- 3. In-memory list — last resort, resets on every restart
9
  """
10
  import json
11
  import logging
@@ -70,18 +69,7 @@ def record_verification(entry: dict) -> None:
70
  async def get_history_entry(entry_id: str) -> dict:
71
  logger.info("GET /history/%s", entry_id)
72
 
73
- # Tier 1: Firestore
74
- try:
75
- from firebase_client import get_firestore
76
- db = get_firestore()
77
- if db:
78
- doc = db.collection("verifications").document(entry_id).get()
79
- if doc.exists:
80
- return doc.to_dict()
81
- except Exception as e:
82
- logger.debug("Firestore detail unavailable (%s) — trying local file", e)
83
-
84
- # Tier 2: Local JSON file
85
  try:
86
  records = _load_history_file()
87
  for r in records:
@@ -102,7 +90,7 @@ async def get_history_entry(entry_id: str) -> dict:
102
  "",
103
  response_model=HistoryResponse,
104
  summary="Get verification history",
105
- description="Returns past verifications ordered by most recent. Reads from Firestore when configured, falls back to in-memory store.",
106
  )
107
  async def get_history(
108
  page: int = Query(1, ge=1, description="Page number"),
@@ -111,33 +99,7 @@ async def get_history(
111
  ) -> HistoryResponse:
112
  logger.info("GET /history | page=%d limit=%d", page, limit)
113
 
114
- # ── Tier 1: Firestore ─────────────────────────────────────────────────────
115
- try:
116
- from firebase_client import get_verifications, get_verification_count
117
- vf = verdict_filter.value if verdict_filter else None
118
- offset = (page - 1) * limit
119
- entries_raw = await get_verifications(limit=limit, offset=offset, verdict_filter=vf)
120
- total = await get_verification_count(verdict_filter=vf)
121
- if entries_raw or total > 0:
122
- return HistoryResponse(
123
- total=total,
124
- entries=[
125
- HistoryEntry(
126
- id=e["id"],
127
- timestamp=e["timestamp"],
128
- input_type=e.get("input_type", "text"),
129
- text_preview=e.get("text_preview", "")[:120],
130
- verdict=Verdict(e["verdict"]),
131
- confidence=e["confidence"],
132
- final_score=e["final_score"],
133
- )
134
- for e in entries_raw
135
- ],
136
- )
137
- except Exception as e:
138
- logger.debug("Firestore history unavailable (%s) — trying local file", e)
139
-
140
- # ── Tier 2: Local JSON file ───────────────────────────────────────────────
141
  # Load from file rather than in-memory list so data survives restarts.
142
  file_entries = list(reversed(_load_history_file()))
143
  if file_entries:
 
3
  GET /history — Returns past verification logs with pagination.
4
 
5
  Persistence tier order (best to worst):
6
+ 1. Local JSON file data/history.json, survives server restarts
7
+ 2. In-memory listlast resort, resets on every restart
 
8
  """
9
  import json
10
  import logging
 
69
  async def get_history_entry(entry_id: str) -> dict:
70
  logger.info("GET /history/%s", entry_id)
71
 
72
+ # Tier 1: Local JSON file
 
 
 
 
 
 
 
 
 
 
 
73
  try:
74
  records = _load_history_file()
75
  for r in records:
 
90
  "",
91
  response_model=HistoryResponse,
92
  summary="Get verification history",
93
+ description="Returns past verifications ordered by most recent. Reads from local JSON file, falls back to in-memory store.",
94
  )
95
  async def get_history(
96
  page: int = Query(1, ge=1, description="Page number"),
 
99
  ) -> HistoryResponse:
100
  logger.info("GET /history | page=%d limit=%d", page, limit)
101
 
102
+ # ── Tier 1: Local JSON file ───────────────────────────────────────────────
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
103
  # Load from file rather than in-memory list so data survives restarts.
104
  file_entries = list(reversed(_load_history_file()))
105
  if file_entries:
api/routes/trends.py CHANGED
@@ -14,18 +14,9 @@ router = APIRouter(prefix="/trends", tags=["Trends"])
14
  def _load_all_history() -> list[dict]:
15
  """
16
  Return all history records from the best available source:
17
- 1. Firestore 2. Local JSON file 3. In-memory list (fallback)
18
  """
19
- # Tier 1: Firestore
20
- try:
21
- from firebase_client import get_all_verifications_sync
22
- records = get_all_verifications_sync()
23
- if records:
24
- return records
25
- except Exception:
26
- pass
27
-
28
- # Tier 2: Local JSON file (persists across restarts)
29
  try:
30
  from api.routes.history import _load_history_file
31
  records = _load_history_file()
 
14
  def _load_all_history() -> list[dict]:
15
  """
16
  Return all history records from the best available source:
17
+ 1. Local JSON file 2. In-memory list (fallback)
18
  """
19
+ # Tier 1: Local JSON file (persists across restarts)
 
 
 
 
 
 
 
 
 
20
  try:
21
  from api.routes.history import _load_history_file
22
  records = _load_history_file()
firebase_client.py DELETED
@@ -1,143 +0,0 @@
1
- """
2
- PhilVerify — Firebase / Firestore Client
3
- Initializes firebase-admin SDK and provides typed helpers for persistence.
4
-
5
- Setup:
6
- 1. Go to Firebase Console → Project Settings → Service Accounts
7
- 2. Click "Generate new private key" → save as `serviceAccountKey.json`
8
- in the PhilVerify project root (already in .gitignore)
9
- 3. Set FIREBASE_PROJECT_ID in .env
10
-
11
- Collections:
12
- verifications/ — one doc per verification run
13
- trends/summary — aggregated entity/topic counters
14
- """
15
- import logging
16
- import os
17
- from functools import lru_cache
18
- from pathlib import Path
19
-
20
- logger = logging.getLogger(__name__)
21
-
22
- _SERVICEACCOUNT_PATH = Path(__file__).parent / "serviceAccountKey.json"
23
- _db = None # Firestore client singleton
24
-
25
-
26
- def get_firestore():
27
- """Return the Firestore client, or None if Firebase is not configured."""
28
- global _db
29
- if _db is not None:
30
- return _db
31
-
32
- try:
33
- import firebase_admin
34
- from firebase_admin import credentials, firestore
35
-
36
- if firebase_admin._DEFAULT_APP_NAME in firebase_admin._apps:
37
- _db = firestore.client()
38
- return _db
39
-
40
- if _SERVICEACCOUNT_PATH.exists():
41
- # Service account key file available (local dev + CI)
42
- cred = credentials.Certificate(str(_SERVICEACCOUNT_PATH))
43
- firebase_admin.initialize_app(cred)
44
- logger.info("Firebase initialized via service account key")
45
- elif os.getenv("GOOGLE_APPLICATION_CREDENTIALS") or os.getenv("K_SERVICE"):
46
- # Cloud Run (K_SERVICE is always set) or explicit ADC path
47
- cred = credentials.ApplicationDefault()
48
- firebase_admin.initialize_app(cred)
49
- logger.info("Firebase initialized via Application Default Credentials")
50
- else:
51
- logger.warning(
52
- "Firebase not configured — no serviceAccountKey.json and no "
53
- "GOOGLE_APPLICATION_CREDENTIALS env var. History will use in-memory store."
54
- )
55
- return None
56
-
57
- _db = firestore.client()
58
- return _db
59
-
60
- except ImportError:
61
- logger.warning("firebase-admin not installed — Firestore disabled")
62
- return None
63
- except Exception as e:
64
- logger.error("Firebase init error: %s — falling back to in-memory store", e)
65
- return None
66
-
67
-
68
- async def save_verification(data: dict) -> bool:
69
- """
70
- Persist a verification result to Firestore.
71
- Returns True on success, False if Firebase is unavailable.
72
- """
73
- db = get_firestore()
74
- if db is None:
75
- return False
76
- try:
77
- db.collection("verifications").document(data["id"]).set(data)
78
- logger.debug("Verification %s saved to Firestore", data["id"])
79
- return True
80
- except Exception as e:
81
- logger.error("Firestore write error: %s", e)
82
- return False
83
-
84
-
85
- async def get_verifications(
86
- limit: int = 20,
87
- offset: int = 0,
88
- verdict_filter: str | None = None,
89
- ) -> list[dict]:
90
- """Fetch verification history from Firestore ordered by timestamp desc."""
91
- db = get_firestore()
92
- if db is None:
93
- return []
94
- try:
95
- from google.cloud.firestore_v1.base_query import FieldFilter
96
- query = (
97
- db.collection("verifications")
98
- .order_by("timestamp", direction="DESCENDING")
99
- )
100
- if verdict_filter:
101
- query = query.where(filter=FieldFilter("verdict", "==", verdict_filter))
102
- docs = query.limit(limit + offset).stream()
103
- results = [doc.to_dict() for doc in docs]
104
- return results[offset : offset + limit]
105
- except Exception as e:
106
- logger.error("Firestore read error: %s", e)
107
- return []
108
-
109
-
110
- def get_all_verifications_sync() -> list[dict]:
111
- """Synchronously fetch ALL verification records from Firestore (used by trends aggregation)."""
112
- db = get_firestore()
113
- if db is None:
114
- return []
115
- try:
116
- docs = (
117
- db.collection("verifications")
118
- .order_by("timestamp", direction="DESCENDING")
119
- .limit(10_000) # hard cap — more than enough for trends analysis
120
- .stream()
121
- )
122
- return [doc.to_dict() for doc in docs]
123
- except Exception as e:
124
- logger.error("Firestore get_all_verifications_sync error: %s", e)
125
- return []
126
-
127
-
128
- async def get_verification_count(verdict_filter: str | None = None) -> int:
129
- """Return total count of verifications (with optional verdict filter)."""
130
- db = get_firestore()
131
- if db is None:
132
- return 0
133
- try:
134
- from google.cloud.firestore_v1.base_query import FieldFilter
135
- query = db.collection("verifications")
136
- if verdict_filter:
137
- query = query.where(filter=FieldFilter("verdict", "==", verdict_filter))
138
- # Use aggregation query (Firestore native count)
139
- result = query.count().get()
140
- return result[0][0].value
141
- except Exception as e:
142
- logger.error("Firestore count error: %s", e)
143
- return 0
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
scoring/engine.py CHANGED
@@ -323,18 +323,9 @@ async def run_verification(
323
  "language": language.value,
324
  }
325
  try:
326
- from firebase_client import save_verification
327
- saved = await save_verification(history_entry)
328
- if not saved:
329
- # Firestore unavailable — fall back to in-memory store
330
- from api.routes.history import record_verification
331
- record_verification(history_entry)
332
  except Exception as e:
333
  logger.warning("Failed to record history: %s", e)
334
- try:
335
- from api.routes.history import record_verification
336
- record_verification(history_entry)
337
- except Exception:
338
- pass
339
 
340
  return result
 
323
  "language": language.value,
324
  }
325
  try:
326
+ from api.routes.history import record_verification
327
+ record_verification(history_entry)
 
 
 
 
328
  except Exception as e:
329
  logger.warning("Failed to record history: %s", e)
 
 
 
 
 
330
 
331
  return result