phase 3 draft

Modified: February 14, 2026 7:58 PM Category: Coding Notes List: Projects Created: September 9, 2025 10:26 PM Master Type: Notes Hide: No Starred: No Status: Unassigned

#!/bin/bash
set -euo pipefail

# ---------------------------
# Load config
# ---------------------------
TOP_ARG="${1:-}"
ROOT_VOLUME="${TOP_ARG:-}"
CONFIG_FILE="$ROOT_VOLUME/config.sh"
[ -f "$CONFIG_FILE" ] || { echo "Config not found at $CONFIG_FILE"; exit 1; }
. "$CONFIG_FILE"

# ---------------------------
# Determine working folder
# ---------------------------
TOP="$TOP_BASE"
if [ -n "$TOP_ARG" ] && [ -d "$TOP_ARG" ] && [ "$TOP_ARG" != "$ROOT_VOLUME" ]; then
  TOP="$TOP_ARG"
fi

COLLECTION_DIR="$TOP/Collection"
DEFAULT_PROCESSED_DIR="$TOP/Processed"
DEST="$COLLECTION_DIR/Verso Positives"

IDLE_SECONDS=${PHASE3_IDLE_SECONDS}
SLEEP_INTERVAL=${PHASE3_SLEEP_INTERVAL}

[ -d "$COLLECTION_DIR" ] || echo "Warning: Collection folder not found: $COLLECTION_DIR"

echo "Processing Phase 3 from Processed roots into: $COLLECTION_DIR"

# ---------------------------
# Wait until folder idle
# ---------------------------
wait_until_idle() {
  dir="$1"
  while :; do
    latest=$(find "$dir" -type f -print0 2>/dev/null | xargs -0 stat -f '%m' 2>/dev/null | sort -n | tail -n1 || echo 0)
    now=$(date +%s)
    [ "$latest" -eq 0 ] || [ $((now - latest)) -ge "$IDLE_SECONDS" ] && break
    sleep "$SLEEP_INTERVAL"
  done
}

# ---------------------------
# Build Processed roots from config (keep compatibility)
# - If PROCESSED_DIRS array exists, use those (under $TOP).
# - Otherwise, fall back to $TOP/Processed.
# - If $TOP/Processed is present, prefer scanning only that root
#   (prevents duplicate scans of its subfolders if listed).
# ---------------------------
SRC_ROOTS=()
if declare -p PROCESSED_DIRS >/dev/null 2>&1; then
  for suffix in "${PROCESSED_DIRS[@]}"; do
    root="$TOP/$suffix"
    [ -d "$root" ] && SRC_ROOTS+=("$root")
  done
else
  [ -d "$DEFAULT_PROCESSED_DIR" ] && SRC_ROOTS+=("$DEFAULT_PROCESSED_DIR")
fi

# Prefer single root if $TOP/Processed exists
if [ -d "$DEFAULT_PROCESSED_DIR" ]; then
  SRC_ROOTS=("$DEFAULT_PROCESSED_DIR")
fi

# Idle wait on all sources + collection
for r in "${SRC_ROOTS[@]}"; do
  [ -d "$r" ] && wait_until_idle "$r"
done
[ -d "$COLLECTION_DIR" ] && wait_until_idle "$COLLECTION_DIR"

# Ensure Verso Positives base folders (no PSD here)
mkdir -p "$DEST/JPG" "$DEST/TIFF"

# ---------------------------
# Helpers (Bash 3–safe)
# ---------------------------
to_lower() { printf '%s' "$1" | tr '[:upper:]' '[:lower:]'; }
to_upper() { printf '%s' "$1" | tr '[:lower:]' '[:upper:]'; }

# ---------------------------
# Main move
# ---------------------------
SECONDS_IN_WEEK=$((7*24*60*60))
now=$(date +%s)

for ROOT in "${SRC_ROOTS[@]}"; do
  # Scan all files under each root (depth doesn't matter)
  while IFS= read -r -d '' file; do
    [ -f "$file" ] || continue

    fname=$(basename "$file")
    ext="${fname##*.}"
    name_noext="${fname%.*}"
    ext_lc=$(to_lower "$ext")
    fname_lc=$(to_lower "$fname")

    # ---------------------------
    # Verso Positives: v-A / p-A
    # ---------------------------
    case "$fname_lc" in
      *v-a.*|*p-a.*)
        case "$ext_lc" in
          jpg|jpeg) mv -n "$file" "$DEST/JPG/$fname" ;;
          tif|tiff) mv -n "$file" "$DEST/TIFF/$fname" ;;
          *)        mv -n "$file" "$DEST/$fname" ;;  # do NOT create PSD here
        esac
        continue
      ;;
    esac

    # ---------------------------
    # Enclosure parsing (use prefix before first hyphen only)
    #   A. HBYYYY + [no code] + NN/NNN  -> binder (B)
    #   A. HBYYYY +  B        + NN/NNN  -> binder (B)
    #   B. HBYYYY +  AA       + NN/NNN  -> non-binder (AA)
    # Everything else -> unparsed bucket
    # ---------------------------
    base_prefix="${fname%%-*}"

    enclosure_long=""
    enclosure_short=""

    if [[ $base_prefix =~ ^HB[0-9]{4}B([0-9]{2,3})$ ]]; then
      num="${BASH_REMATCH[1]}"
      shortnum=$(printf '%s' "$num" | sed 's/^0*//'); [ -z "$shortnum" ] && shortnum=0
      enclosure_long=$(printf 'B%03d' "$shortnum")   # e.g., B002
      enclosure_short="B$shortnum"                   # e.g., B2
    elif [[ $base_prefix =~ ^HB[0-9]{4}([0-9]{2,3})$ ]]; then
      num="${BASH_REMATCH[1]}"
      shortnum=$(printf '%s' "$num" | sed 's/^0*//'); [ -z "$shortnum" ] && shortnum=0
      enclosure_long=$(printf 'B%03d' "$shortnum")   # binder without letter
      enclosure_short="B$shortnum"
    elif [[ $base_prefix =~ ^HB[0-9]{4}([A-Z]{2})([0-9]{2,3})$ ]]; then
      code="${BASH_REMATCH[1]}"
      num="${BASH_REMATCH[2]}"
      shortnum=$(printf '%s' "$num" | sed 's/^0*//'); [ -z "$shortnum" ] && shortnum=0
      # long folder keeps two digits if <=2, else three
      if [ ${#num} -ge 3 ]; then
        enclosure_long=$(printf '%s%03d' "$code" "$shortnum")   # e.g., FL190
      else
        enclosure_long=$(printf '%s%02d' "$code" "$shortnum")   # e.g., GP07, FL05
      fi
      short_letter=$(printf '%.1s' "$code")                     # FL->F, GP/GL->G, etc.
      enclosure_short="${short_letter}${shortnum}"              # e.g., F5, G10
    else
      target_dir="$COLLECTION_DIR/unparsed/_manual_check"
      mkdir -p "$target_dir"
      mv -n "$file" "$target_dir/$fname"
      continue
    fi

    # ---------------------------
    # Target subfolder from the *file's* extension (not its location in /Processed)
    # ---------------------------
    case "$ext_lc" in
      jpg|jpeg)
        mtime=$(stat -f %m "$file")
        age=$(( now - mtime ))
        [ "$age" -le "$SECONDS_IN_WEEK" ] && sub="Processing" || sub="JPG"
        ;;
      tif|tiff) sub="TIFF" ;;
      psd)      sub="PSD" ;;
      *)        sub=$(to_upper "$ext_lc") ;;
    esac

    # Final destination: Collection/EnclosureLong/EnclosureShort⎵SUB/file.ext
    dest="$COLLECTION_DIR/$enclosure_long/$enclosure_short $sub"
    [ -d "$dest" ] || mkdir -p "$dest"

    # Move without overwriting (prevents dupes)
    mv -n "$file" "$dest/$fname"

  done < <(find "$ROOT" -type f -print0)
done

echo "Phase 3 complete."