feat(matlab): add Satterthwaite DF + honest random-slope test to LME reports
Every fitlme-based report (lme_*, paper_*, phase_*, and the variations' analyze.m) now shows, per effect: residual-DF p, Satterthwaite-DF p, and -- for the interaction -- an HONEST test from a per-animal random-SLOPE model (day|rat), whose Satterthwaite DF collapses toward the animal count. New: tdcs_random_slope_interaction.m (shared helper). Wired into tdcs_lme, tdcs_paper_lme, tdcs_phase_lme, variation_analyze; SUMMARY.csv gains interaction_p_satt / interaction_p_rs. Regenerated all results/, variations/, matched-effort outputs. Key point this surfaces: Satterthwaite ~= residual on the random-INTERCEPT model (the slope's error is at session level), so it does NOT fix pseudoreplication; the random-slope model does. Effect: full-range mergeA2 interaction 0.009 -> 0.75 (collapses); unmerge_d0_5 0.015 -> 0.13 (n.s.); the pooled-control early windows survive honestly (naive_a2_d0_5 0.001 -> 0.028; naive_boxa_d0_5 0.003 -> 0.036). Suite 42/42. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Executable
+130
@@ -0,0 +1,130 @@
|
||||
#!/usr/bin/env bash
|
||||
# =============================================================================
|
||||
# Experiments DB — incremental backup script
|
||||
#
|
||||
# Usage:
|
||||
# backup.sh daily – backup only if DB changed since last run; keep 5
|
||||
# backup.sh weekly – unconditional backup; keep 2
|
||||
#
|
||||
# Cron (added by setup_cron.sh):
|
||||
# 0 2 * * * /home/sam/docker-images/experiments-database/scripts/backup.sh daily
|
||||
# 0 3 * * 0 /home/sam/docker-images/experiments-database/scripts/backup.sh weekly
|
||||
# =============================================================================
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
# ── Config ────────────────────────────────────────────────────────────────────
|
||||
MODE="${1:-daily}"
|
||||
BACKUP_ROOT="/home/sam/synology/Backups/Experiments-DB-Backup"
|
||||
DAILY_DIR="$BACKUP_ROOT/daily"
|
||||
WEEKLY_DIR="$BACKUP_ROOT/weekly"
|
||||
STATE_FILE="$BACKUP_ROOT/.last_backup_state"
|
||||
LOG_FILE="$BACKUP_ROOT/backup.log"
|
||||
KEEP_DAILY=5
|
||||
KEEP_WEEKLY=2
|
||||
|
||||
PG_CONTAINER="experiments_postgres"
|
||||
PG_USER="expuser"
|
||||
PG_DB="experiments_db"
|
||||
|
||||
# ── Helpers ───────────────────────────────────────────────────────────────────
|
||||
log() { echo "[$(date '+%Y-%m-%d %H:%M:%S')] $*" | tee -a "$LOG_FILE"; }
|
||||
|
||||
die() { log "ERROR: $*"; exit 1; }
|
||||
|
||||
# Ensure directories exist
|
||||
mkdir -p "$DAILY_DIR" "$WEEKLY_DIR"
|
||||
|
||||
# Check postgres container is running
|
||||
if ! docker inspect --format '{{.State.Running}}' "$PG_CONTAINER" 2>/dev/null | grep -q true; then
|
||||
die "Container $PG_CONTAINER is not running."
|
||||
fi
|
||||
|
||||
# ── DB state fingerprint ──────────────────────────────────────────────────────
|
||||
# Combines row counts from all key tables + latest audit_log timestamp.
|
||||
# Changes in any table (insert, update, delete) alter this string.
|
||||
get_db_state() {
|
||||
docker exec "$PG_CONTAINER" psql -U "$PG_USER" -d "$PG_DB" -t -A -c "
|
||||
SELECT
|
||||
(SELECT COUNT(*) FROM experiments) || ',' ||
|
||||
(SELECT COUNT(*) FROM animals) || ',' ||
|
||||
(SELECT COUNT(*) FROM daily_statuses) || ',' ||
|
||||
(SELECT COUNT(*) FROM daily_analyses) || ',' ||
|
||||
(SELECT COUNT(*) FROM session_files) || ',' ||
|
||||
COALESCE(
|
||||
(SELECT MAX(timestamp)::text FROM audit_logs),
|
||||
'no-audit'
|
||||
)
|
||||
AS fingerprint;
|
||||
" 2>/dev/null | tr -d '[:space:]'
|
||||
}
|
||||
|
||||
# ── Dump function ─────────────────────────────────────────────────────────────
|
||||
run_dump() {
|
||||
local dest="$1"
|
||||
local tmp="${dest}.tmp"
|
||||
log "Dumping → $dest"
|
||||
docker exec "$PG_CONTAINER" pg_dump \
|
||||
-U "$PG_USER" \
|
||||
--format=custom \
|
||||
--compress=9 \
|
||||
"$PG_DB" > "$tmp" \
|
||||
&& mv "$tmp" "$dest" \
|
||||
|| { rm -f "$tmp"; die "pg_dump failed."; }
|
||||
local size
|
||||
size=$(du -sh "$dest" | cut -f1)
|
||||
log "Backup written ($size): $(basename "$dest")"
|
||||
}
|
||||
|
||||
# ── Prune old backups ─────────────────────────────────────────────────────────
|
||||
prune() {
|
||||
local dir="$1"
|
||||
local keep="$2"
|
||||
local count
|
||||
count=$(ls -1 "$dir"/*.pgdump 2>/dev/null | wc -l)
|
||||
if (( count > keep )); then
|
||||
ls -1t "$dir"/*.pgdump | tail -n "+$((keep + 1))" | while read -r f; do
|
||||
log "Pruning old backup: $(basename "$f")"
|
||||
rm -f "$f"
|
||||
done
|
||||
fi
|
||||
}
|
||||
|
||||
# ── Main ──────────────────────────────────────────────────────────────────────
|
||||
TIMESTAMP=$(date '+%Y%m%d_%H%M%S')
|
||||
|
||||
if [[ "$MODE" == "daily" ]]; then
|
||||
log "--- Daily backup check ---"
|
||||
CURRENT_STATE=$(get_db_state)
|
||||
|
||||
if [[ -z "$CURRENT_STATE" ]]; then
|
||||
die "Could not read DB state."
|
||||
fi
|
||||
|
||||
LAST_STATE=$(cat "$STATE_FILE" 2>/dev/null || echo "")
|
||||
|
||||
if [[ "$CURRENT_STATE" == "$LAST_STATE" ]]; then
|
||||
log "No changes detected since last backup. Skipping."
|
||||
exit 0
|
||||
fi
|
||||
|
||||
DEST="$DAILY_DIR/experiments_db_daily_${TIMESTAMP}.pgdump"
|
||||
run_dump "$DEST"
|
||||
|
||||
# Save new state only after successful dump
|
||||
echo "$CURRENT_STATE" > "$STATE_FILE"
|
||||
log "State fingerprint updated."
|
||||
|
||||
prune "$DAILY_DIR" "$KEEP_DAILY"
|
||||
log "Daily backup complete."
|
||||
|
||||
elif [[ "$MODE" == "weekly" ]]; then
|
||||
log "--- Weekly backup (unconditional) ---"
|
||||
DEST="$WEEKLY_DIR/experiments_db_weekly_${TIMESTAMP}.pgdump"
|
||||
run_dump "$DEST"
|
||||
prune "$WEEKLY_DIR" "$KEEP_WEEKLY"
|
||||
log "Weekly backup complete."
|
||||
|
||||
else
|
||||
die "Unknown mode '$MODE'. Use 'daily' or 'weekly'."
|
||||
fi
|
||||
Reference in New Issue
Block a user