#!/usr/bin/env bash # CancerIndex — ordered first ingestion (idempotent; safe to re-run). # # Runs connectors in the recommended order (terminology → genes → evidence/genomics/variants → # trials → literature → epidemiology) with per-connector time budgets, then rebuilds counters and # rankings. Connectors are restartable (cursor) and idempotent (payload hash), so re-running this # script only fetches what is missing. A connector that is not registered yet, paused, awaiting # credentials or under license review is skipped with a message — never treated as an error. # # Usage: cd ~/apps/cancerindex && bash deploy/first-run.sh [--only id,id] [--skip id,id] [--dry-run] set -uo pipefail cd "$(dirname "$0")/.." || exit 1 export DATABASE_URL="${DATABASE_URL:-postgres://localhost:5432/cancerindex}" export CI_DATA_DIR="${CI_DATA_DIR:-$PWD/data}" export CI_SERVICE=first-run LOG_DIR="${LOG_DIR:-$PWD/logs}" mkdir -p "$LOG_DIR" data/raw data/cache # connector id : time budget (minutes) : max attempts within this script ORDER=( "ncit-evs:45:2" "oncotree:10:2" "hgnc:15:2" "civic:30:2" "gdc:30:2" "clinvar:45:2" "clinicaltrials:45:3" "pubmed:45:3" "cdc-wonder:30:2" ) ONLY=""; SKIP=""; DRY="" while [ $# -gt 0 ]; do case "$1" in --only) ONLY="$2"; shift 2 ;; --skip) SKIP="$2"; shift 2 ;; --dry-run) DRY="--mode dry_run --max-records 100"; shift ;; *) echo "unknown option $1"; exit 2 ;; esac done ts() { date -u +%Y-%m-%dT%H:%M:%SZ; } log() { echo "[$(ts)] $*" | tee -a "$LOG_DIR/first-run.log"; } in_list() { [ -z "$2" ] && return 1; echo ",$2," | grep -q ",$1,"; } log "== CancerIndex first run (db=$DATABASE_URL, data=$CI_DATA_DIR) ==" pnpm cix sources:sync >>"$LOG_DIR/first-run.log" 2>&1 || log "warning: sources:sync failed (continuing)" REGISTERED="$(pnpm --silent cix connectors 2>/dev/null | awk '{print $1}' | tr '\n' ',')" last_status() { psql "$DATABASE_URL" -Atc "SELECT status FROM ingest_runs WHERE connector_id = '$1' ORDER BY started_at DESC LIMIT 1" 2>/dev/null } cursor_health() { psql "$DATABASE_URL" -Atc "SELECT coalesce(health,'unknown') || '|' || coalesce(paused::text,'false') FROM connector_cursors WHERE connector_id = '$1'" 2>/dev/null } for entry in "${ORDER[@]}"; do IFS=: read -r id minutes attempts <<<"$entry" if [ -n "$ONLY" ] && ! in_list "$id" "$ONLY"; then continue; fi if in_list "$id" "$SKIP"; then log "-- $id: skipped (--skip)"; continue; fi if ! echo ",$REGISTERED" | grep -q ",$id,"; then log "-- $id: not registered in packages/connectors/src/registry.ts — skipped"; continue; fi hp="$(cursor_health "$id")" case "$hp" in awaiting_credentials*) log "-- $id: awaiting credentials — skipped"; continue ;; *"|true") log "-- $id: paused — skipped"; continue ;; esac if [ -z "$DRY" ] && [ "$(last_status "$id")" = "succeeded" ] && [ "${FORCE:-}" != "1" ]; then log "-- $id: last run already succeeded — running incremental refresh (FORCE=1 to force full)" fi n=0 while [ "$n" -lt "$attempts" ]; do n=$((n + 1)) log "-> $id attempt $n/$attempts (budget ${minutes} min)" # shellcheck disable=SC2086 if pnpm cix run "$id" --max-minutes "$minutes" $DRY >>"$LOG_DIR/first-run.$id.log" 2>&1; then st="$(last_status "$id")" log " $id: $st" # partial = time budget hit with cursor saved → run again while attempts remain [ "$st" = "partial" ] && [ "$n" -lt "$attempts" ] && continue break else st="$(last_status "$id")" log " $id: FAILED (status=${st:-none}); see $LOG_DIR/first-run.$id.log" case "$st" in aborted) break ;; esac # license/credentials gate: do not retry fi done done if [ -z "$DRY" ]; then log "-> counters" pnpm cix counters >>"$LOG_DIR/first-run.log" 2>&1 && log " counters ok" || log " counters FAILED" log "-> rank" pnpm cix rank >>"$LOG_DIR/first-run.log" 2>&1 && log " rank ok" || log " rank FAILED" fi log "== summary ==" pnpm --silent cix connectors 2>/dev/null | tee -a "$LOG_DIR/first-run.log" pnpm --silent cix stats 2>/dev/null | tee -a "$LOG_DIR/first-run.log" log "== done =="