Files
motovaultpro/apply-etl-plan.sh
2025-12-15 18:19:55 -06:00

332 lines
14 KiB
Bash
Executable File

#!/bin/bash
set -e
# Colors for output
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
NC='\033[0m' # No Color
echo -e "${YELLOW}=== MotoVaultPro ETL Plan V1 - Automated Application ===${NC}"
echo ""
# Get script directory
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
cd "$SCRIPT_DIR"
# Backup directory
BACKUP_DIR="$SCRIPT_DIR/.etl-plan-backup-$(date +%Y%m%d-%H%M%S)"
mkdir -p "$BACKUP_DIR"
echo -e "${GREEN}[1/4] Backing up files...${NC}"
# Backup ETL files
mkdir -p "$BACKUP_DIR/data/vehicle-etl"
cp data/vehicle-etl/vehapi_fetch_snapshot.py "$BACKUP_DIR/data/vehicle-etl/" 2>/dev/null || true
cp data/vehicle-etl/qa_validate.py "$BACKUP_DIR/data/vehicle-etl/" 2>/dev/null || true
# Backup backend files
mkdir -p "$BACKUP_DIR/backend/src/features/vehicles/api"
mkdir -p "$BACKUP_DIR/backend/src/features/vehicles/domain"
mkdir -p "$BACKUP_DIR/backend/src/features/platform/api"
mkdir -p "$BACKUP_DIR/backend/src/features/platform/domain"
mkdir -p "$BACKUP_DIR/backend/src/features/platform/data"
cp backend/src/features/vehicles/api/vehicles.controller.ts "$BACKUP_DIR/backend/src/features/vehicles/api/" 2>/dev/null || true
cp backend/src/features/vehicles/api/vehicles.routes.ts "$BACKUP_DIR/backend/src/features/vehicles/api/" 2>/dev/null || true
cp backend/src/features/vehicles/domain/vehicles.service.ts "$BACKUP_DIR/backend/src/features/vehicles/domain/" 2>/dev/null || true
cp backend/src/features/platform/api/platform.controller.ts "$BACKUP_DIR/backend/src/features/platform/api/" 2>/dev/null || true
cp backend/src/features/platform/index.ts "$BACKUP_DIR/backend/src/features/platform/" 2>/dev/null || true
cp backend/src/features/platform/domain/vin-decode.service.ts "$BACKUP_DIR/backend/src/features/platform/domain/" 2>/dev/null || true
cp backend/src/features/platform/data/vpic-client.ts "$BACKUP_DIR/backend/src/features/platform/data/" 2>/dev/null || true
# Backup frontend files
mkdir -p "$BACKUP_DIR/frontend/src/features/vehicles/components"
mkdir -p "$BACKUP_DIR/frontend/src/features/vehicles/api"
mkdir -p "$BACKUP_DIR/frontend/src/features/vehicles/types"
cp frontend/src/features/vehicles/components/VehicleForm.tsx "$BACKUP_DIR/frontend/src/features/vehicles/components/" 2>/dev/null || true
cp frontend/src/features/vehicles/api/vehicles.api.ts "$BACKUP_DIR/frontend/src/features/vehicles/api/" 2>/dev/null || true
cp frontend/src/features/vehicles/types/vehicles.types.ts "$BACKUP_DIR/frontend/src/features/vehicles/types/" 2>/dev/null || true
echo -e "${GREEN} Backup created at: $BACKUP_DIR${NC}"
echo ""
echo -e "${GREEN}[2/4] Applying ETL Configuration Changes...${NC}"
# Update vehapi_fetch_snapshot.py
if [ -f "data/vehicle-etl/vehapi_fetch_snapshot.py" ]; then
echo " - Updating DEFAULT_MIN_YEAR from 1980 to 2017..."
sed -i.bak 's/DEFAULT_MIN_YEAR = 1980/DEFAULT_MIN_YEAR = 2017/g' data/vehicle-etl/vehapi_fetch_snapshot.py
sed -i.bak 's/default env MIN_YEAR or 1980/default env MIN_YEAR or 2017/g' data/vehicle-etl/vehapi_fetch_snapshot.py
rm data/vehicle-etl/vehapi_fetch_snapshot.py.bak
echo -e " ${GREEN}${NC} vehapi_fetch_snapshot.py updated"
else
echo -e " ${RED}${NC} vehapi_fetch_snapshot.py not found"
fi
# Update qa_validate.py
if [ -f "data/vehicle-etl/qa_validate.py" ]; then
echo " - Updating year range validation from 1980-2022 to 2017-2022..."
sed -i.bak 's/year < 1980 OR year > 2022/year < 2017 OR year > 2022/g' data/vehicle-etl/qa_validate.py
rm data/vehicle-etl/qa_validate.py.bak
echo -e " ${GREEN}${NC} qa_validate.py updated"
else
echo -e " ${RED}${NC} qa_validate.py not found"
fi
# Delete old ETL documentation
echo " - Removing old ETL documentation..."
rm -f data/vehicle-etl/ETL-FIX-V2.md
rm -f data/vehicle-etl/ETL-FIXES.md
rm -f data/vehicle-etl/ETL-VEHAPI-PLAN.md
rm -f data/vehicle-etl/ETL-VEHAPI-REDESIGN.md
echo -e " ${GREEN}${NC} Old ETL documentation removed"
echo ""
echo -e "${GREEN}[3/4] Applying Backend Changes...${NC}"
# Update vehicles.controller.ts MIN_YEAR
if [ -f "backend/src/features/vehicles/api/vehicles.controller.ts" ]; then
echo " - Updating MIN_YEAR from 1980 to 2017..."
sed -i.bak 's/private static readonly MIN_YEAR = 1980;/private static readonly MIN_YEAR = 2017;/g' backend/src/features/vehicles/api/vehicles.controller.ts
rm backend/src/features/vehicles/api/vehicles.controller.ts.bak
echo -e " ${GREEN}${NC} vehicles.controller.ts MIN_YEAR updated"
else
echo -e " ${RED}${NC} vehicles.controller.ts not found"
fi
# Remove VIN decode route from vehicles.routes.ts
if [ -f "backend/src/features/vehicles/api/vehicles.routes.ts" ]; then
echo " - Removing VIN decode route..."
python3 << 'PYTHON_EOF'
import re
with open('backend/src/features/vehicles/api/vehicles.routes.ts', 'r') as f:
content = f.read()
# Remove the decode-vin route (multi-line pattern)
pattern = r"\n\s*fastify\.post<\{ Body: \{ vin: string \} \}>\('/vehicles/decode-vin',\s*\{[^}]*\}\);"
content = re.sub(pattern, '', content, flags=re.DOTALL)
with open('backend/src/features/vehicles/api/vehicles.routes.ts', 'w') as f:
f.write(content)
PYTHON_EOF
echo -e " ${GREEN}${NC} VIN decode route removed from vehicles.routes.ts"
else
echo -e " ${YELLOW}${NC} vehicles.routes.ts not found (may not exist)"
fi
# Remove VIN decode from vehicles.service.ts
if [ -f "backend/src/features/vehicles/domain/vehicles.service.ts" ]; then
echo " - Removing VIN decode logic from vehicles.service.ts..."
python3 << 'PYTHON_EOF'
import re
with open('backend/src/features/vehicles/domain/vehicles.service.ts', 'r') as f:
content = f.read()
# Remove import statement for vin-decode
content = re.sub(r"import\s*\{[^}]*getVINDecodeService[^}]*\}[^;]*;?\n?", '', content)
content = re.sub(r"import\s*\{[^}]*VINDecodeService[^}]*\}[^;]*;?\n?", '', content)
# Remove decodeVIN method if it exists
pattern = r'\n\s*async decodeVIN\([^)]*\)[^{]*\{(?:[^{}]|\{(?:[^{}]|\{[^{}]*\})*\})*\}\n'
content = re.sub(pattern, '\n', content, flags=re.DOTALL)
# Remove VIN decode logic from createVehicle method (between specific comments or patterns)
# This is more conservative - just remove obvious decode blocks
content = re.sub(r'\n\s*// VIN decode logic.*?(?=\n\s*(?:const|return|if|//|$))', '', content, flags=re.DOTALL)
with open('backend/src/features/vehicles/domain/vehicles.service.ts', 'w') as f:
f.write(content)
PYTHON_EOF
echo -e " ${GREEN}${NC} VIN decode logic removed from vehicles.service.ts"
else
echo -e " ${RED}${NC} vehicles.service.ts not found"
fi
# Remove VIN decode from platform.controller.ts
if [ -f "backend/src/features/platform/api/platform.controller.ts" ]; then
echo " - Removing VIN decode from platform.controller.ts..."
python3 << 'PYTHON_EOF'
import re
with open('backend/src/features/platform/api/platform.controller.ts', 'r') as f:
content = f.read()
# Remove VINDecodeService import
content = re.sub(r"import\s*\{[^}]*VINDecodeService[^}]*\}[^;]*;?\n?", '', content)
# Remove VINDecodeService from constructor
content = re.sub(r',?\s*private\s+vinDecodeService:\s*VINDecodeService', '', content)
content = re.sub(r'private\s+vinDecodeService:\s*VINDecodeService\s*,?', '', content)
# Remove decodeVIN method
pattern = r'\n\s*async decodeVIN\([^)]*\)[^{]*\{(?:[^{}]|\{(?:[^{}]|\{[^{}]*\})*\})*\}\n'
content = re.sub(pattern, '\n', content, flags=re.DOTALL)
with open('backend/src/features/platform/api/platform.controller.ts', 'w') as f:
f.write(content)
PYTHON_EOF
echo -e " ${GREEN}${NC} VIN decode removed from platform.controller.ts"
else
echo -e " ${YELLOW}${NC} platform.controller.ts not found"
fi
# Remove VINDecodeService from platform/index.ts
if [ -f "backend/src/features/platform/index.ts" ]; then
echo " - Removing VINDecodeService from platform/index.ts..."
python3 << 'PYTHON_EOF'
import re
with open('backend/src/features/platform/index.ts', 'r') as f:
content = f.read()
# Remove VINDecodeService import
content = re.sub(r"import\s*\{[^}]*VINDecodeService[^}]*\}[^;]*;?\n?", '', content)
# Remove VINDecodeService export
content = re.sub(r"export\s*\{[^}]*VINDecodeService[^}]*\}[^;]*;?\n?", '', content)
content = re.sub(r",\s*VINDecodeService\s*", '', content)
content = re.sub(r"VINDecodeService\s*,\s*", '', content)
# Remove getVINDecodeService function
pattern = r'\n\s*export\s+function\s+getVINDecodeService\([^)]*\)[^{]*\{(?:[^{}]|\{(?:[^{}]|\{[^{}]*\})*\})*\}\n'
content = re.sub(pattern, '\n', content, flags=re.DOTALL)
with open('backend/src/features/platform/index.ts', 'w') as f:
f.write(content)
PYTHON_EOF
echo -e " ${GREEN}${NC} VINDecodeService removed from platform/index.ts"
else
echo -e " ${YELLOW}${NC} platform/index.ts not found"
fi
# Delete VIN decode service files
echo " - Deleting VIN decode service files..."
rm -f backend/src/features/platform/domain/vin-decode.service.ts
rm -f backend/src/features/platform/data/vpic-client.ts
echo -e " ${GREEN}${NC} VIN decode service files deleted"
echo ""
echo -e "${GREEN}[4/4] Applying Frontend Changes...${NC}"
# Update VehicleForm.tsx
if [ -f "frontend/src/features/vehicles/components/VehicleForm.tsx" ]; then
echo " - Removing VIN decode UI from VehicleForm.tsx..."
python3 << 'PYTHON_EOF'
import re
with open('frontend/src/features/vehicles/components/VehicleForm.tsx', 'r') as f:
content = f.read()
# Remove decodingVIN and decodeSuccess state variables
content = re.sub(r"\n\s*const \[decodingVIN, setDecodingVIN\] = useState\(false\);", '', content)
content = re.sub(r"\n\s*const \[decodeSuccess, setDecodeSuccess\] = useState\(false\);", '', content)
content = re.sub(r"\n\s*const \[decodeSuccess, setDecodeSuccess\] = useState<boolean \| null>\(null\);", '', content)
# Remove watchedVIN if it exists
content = re.sub(r"\n\s*const watchedVIN = watch\('vin'\);", '', content)
# Remove handleDecodeVIN function
pattern = r'\n\s*const handleDecodeVIN = async \(\) => \{(?:[^{}]|\{(?:[^{}]|\{[^{}]*\})*\})*\};'
content = re.sub(pattern, '', content, flags=re.DOTALL)
# Update helper text
content = re.sub(
r'Enter VIN to auto-fill vehicle details OR manually select from dropdowns below',
'Enter vehicle VIN (optional)',
content
)
content = re.sub(
r'Enter VIN to auto-populate fields',
'Enter vehicle VIN (optional)',
content
)
# Remove the Decode VIN button and surrounding div
# Pattern 1: div with flex layout containing input and button
pattern1 = r'<div className="flex flex-col sm:flex-row gap-2">\s*<input\s+\{\.\.\.register\(\'vin\'\)\}[^>]*>\s*</input>\s*<Button[^>]*onClick=\{handleDecodeVIN\}[^>]*>.*?</Button>\s*</div>'
replacement1 = r'<input {...register(\'vin\')} className="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-primary-500 text-base" placeholder="Enter 17-character VIN (optional if License Plate provided)" style={{ fontSize: \'16px\' }} />'
content = re.sub(pattern1, replacement1, content, flags=re.DOTALL)
# Pattern 2: self-closing input with button after
pattern2 = r'<input\s+\{\.\.\.register\(\'vin\'\)\}[^/]*/>\s*<Button[^>]*onClick=\{handleDecodeVIN\}[^>]*>.*?</Button>'
content = re.sub(pattern2, replacement1, content, flags=re.DOTALL)
# Remove decode success message
content = re.sub(r'\n\s*\{decodeSuccess && \([^)]*\)\}', '', content, flags=re.DOTALL)
content = re.sub(r'\n\s*<p className="mt-1 text-sm text-green-600">VIN decoded successfully.*?</p>', '', content, flags=re.DOTALL)
with open('frontend/src/features/vehicles/components/VehicleForm.tsx', 'w') as f:
f.write(content)
PYTHON_EOF
echo -e " ${GREEN}${NC} VIN decode UI removed from VehicleForm.tsx"
else
echo -e " ${RED}${NC} VehicleForm.tsx not found"
fi
# Update vehicles.api.ts
if [ -f "frontend/src/features/vehicles/api/vehicles.api.ts" ]; then
echo " - Removing decodeVIN method from vehicles.api.ts..."
python3 << 'PYTHON_EOF'
import re
with open('frontend/src/features/vehicles/api/vehicles.api.ts', 'r') as f:
content = f.read()
# Remove VINDecodeResponse from imports
content = re.sub(r',\s*VINDecodeResponse', '', content)
content = re.sub(r'VINDecodeResponse\s*,', '', content)
# Remove decodeVIN method
pattern = r'\n\s*decodeVIN:\s*async\s*\([^)]*\)[^{]*\{[^}]*\},?'
content = re.sub(pattern, '', content, flags=re.DOTALL)
# Remove trailing comma before closing brace if present
content = re.sub(r',(\s*\};?\s*$)', r'\1', content)
with open('frontend/src/features/vehicles/api/vehicles.api.ts', 'w') as f:
f.write(content)
PYTHON_EOF
echo -e " ${GREEN}${NC} decodeVIN method removed from vehicles.api.ts"
else
echo -e " ${RED}${NC} vehicles.api.ts not found"
fi
# Update vehicles.types.ts
if [ -f "frontend/src/features/vehicles/types/vehicles.types.ts" ]; then
echo " - Removing VINDecodeResponse interface from vehicles.types.ts..."
python3 << 'PYTHON_EOF'
import re
with open('frontend/src/features/vehicles/types/vehicles.types.ts', 'r') as f:
content = f.read()
# Remove VINDecodeResponse interface
pattern = r'\n\s*export interface VINDecodeResponse \{[^}]*\}\n?'
content = re.sub(pattern, '\n', content, flags=re.DOTALL)
with open('frontend/src/features/vehicles/types/vehicles.types.ts', 'w') as f:
f.write(content)
PYTHON_EOF
echo -e " ${GREEN}${NC} VINDecodeResponse interface removed from vehicles.types.ts"
else
echo -e " ${RED}${NC} vehicles.types.ts not found"
fi
echo ""
echo -e "${GREEN}=== Changes Applied Successfully ===${NC}"
echo ""
echo -e "${YELLOW}Verification Commands:${NC}"
echo ""
echo "1. ETL Configuration:"
echo " cd data/vehicle-etl && python3 -c \"import vehapi_fetch_snapshot; print(vehapi_fetch_snapshot.DEFAULT_MIN_YEAR)\""
echo " Expected output: 2017"
echo ""
echo "2. Backend TypeScript:"
echo " cd backend && npx tsc --noEmit"
echo " Expected: No errors"
echo ""
echo "3. Frontend TypeScript:"
echo " cd frontend && npx tsc --noEmit"
echo " Expected: No errors"
echo ""
echo -e "${YELLOW}Next Steps:${NC}"
echo "1. Run the verification commands above"
echo "2. If all checks pass, proceed with Agent 4 (ETL Execution)"
echo "3. If there are issues, restore from backup: $BACKUP_DIR"
echo ""
echo -e "${GREEN}Backup location: $BACKUP_DIR${NC}"