Files
periscope/backend/services/survey.py
T
Siddharth Kothari 6672d2be57 Pinscope open-source core
Agentic schematic validation: datasheet extraction via Claude Console
Skills, netlist/BOM design graph, per-IC direct datasheet review with
page citations, capacitor derating, Next.js report UI.

Extracted from the Pinscope cloud codebase. Auth and billing live in the
private gateway repo behind stable seams (billing_hook.py, adapter files
listed in CLAUDE.md).
2026-07-16 21:29:45 -07:00

99 lines
3.2 KiB
Python

"""Onboarding survey — appends responses to a Google Sheet and tracks completion."""
from __future__ import annotations
import asyncio
import json
import logging
from datetime import datetime, timezone
from backend.config import settings
from backend.services.storage import StorageBackend
logger = logging.getLogger(__name__)
_SURVEY_PREFIX = "admin/survey/"
def _status_key(user_id: str) -> str:
return f"{_SURVEY_PREFIX}{user_id}.json"
def is_completed(storage: StorageBackend, user_id: str) -> bool:
return storage.exists(_status_key(user_id))
def _mark_completed(storage: StorageBackend, user_id: str) -> None:
payload = {"completed": True, "timestamp": datetime.now(timezone.utc).isoformat()}
storage.write_json(_status_key(user_id), payload)
def _build_sheets_service():
"""Build an authenticated Google Sheets API service.
On Cloud Run, google.auth.default() returns Compute Engine credentials
which are auto-scoped. We just need the Sheets API enabled in the GCP
project and the service account shared on the sheet.
"""
try:
import google.auth
from googleapiclient.discovery import build
except ImportError:
logger.warning("google-api-python-client not installed; survey sheet disabled")
return None
try:
credentials, project = google.auth.default()
logger.debug("Sheets: credentials type=%s project=%s", type(credentials).__name__, project)
# Compute Engine credentials don't need explicit scopes — they use
# the access scopes set on the instance (which default to cloud-platform).
# For user/SA key credentials, we need to scope them.
if hasattr(credentials, "with_scopes"):
credentials = credentials.with_scopes(
["https://www.googleapis.com/auth/spreadsheets"]
)
return build("sheets", "v4", credentials=credentials, cache_discovery=False)
except Exception:
logger.exception("Could not build Sheets service")
return None
async def append_to_sheet(
user_id: str,
email: str,
name: str,
referral_source: str,
user_profile: str,
) -> bool:
"""Append a survey row to the configured Google Sheet. Returns True on success."""
sheet_id = settings.survey_sheet_id
if not sheet_id:
logger.warning("SURVEY_SHEET_ID not set; skipping sheet append for user %s", user_id)
return False
service = _build_sheets_service()
if not service:
return False
timestamp = datetime.now(timezone.utc).strftime("%Y-%m-%d %H:%M:%S UTC")
row = [timestamp, user_id, email, name, referral_source, user_profile]
try:
await asyncio.to_thread(
service.spreadsheets()
.values()
.append(
spreadsheetId=sheet_id,
range="Sheet1!A:F",
valueInputOption="RAW",
insertDataOption="INSERT_ROWS",
body={"values": [row]},
)
.execute
)
logger.info("Survey response appended for user %s", user_id)
return True
except Exception:
logger.exception("Failed to append survey response to Google Sheet for user %s", user_id)
return False