diff --git a/Dockerfile b/Dockerfile index 13b7dda..33c7362 100644 --- a/Dockerfile +++ b/Dockerfile @@ -14,4 +14,10 @@ COPY . . ENV PYTHONPATH=/app ENV PYTHONUNBUFFERED=1 -EXPOSE 8000 \ No newline at end of file +# Make entrypoint executable +RUN chmod +x scripts/entrypoint.sh + +EXPOSE 8000 + +# Default command: conditional data gen + train + serve +CMD ["/app/scripts/entrypoint.sh"] diff --git a/docker-compose.yml b/docker-compose.yml index 050d230..a0a2947 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -49,44 +49,45 @@ services: api: build: . - depends_on: - kafka-setup: - condition: service_completed_successfully ports: - "8000:8000" volumes: - ./data:/app/data - ./ml/artifacts:/app/ml/artifacts - command: > - bash -c " - python -m data.generate_data && - python -m ml.train && - uvicorn api.main:app --host 0.0.0.0 --port 8000 - " + # API is completely independent of Kafka — it only serves the scoring model. + # Producer/consumer gate on api health to ensure data & artifacts exist. healthcheck: - test: ["CMD", "curl", "-s", "http://localhost:8000/health", "||", "exit", "1"] + # CMD-SHELL gives us a real shell so || works; -f makes curl fail on 4xx/5xx + test: ["CMD-SHELL", "curl -sf http://localhost:8000/health || exit 1"] interval: 10s timeout: 5s retries: 5 + restart: unless-stopped producer: build: . depends_on: api: condition: service_healthy + kafka-setup: + condition: service_completed_successfully volumes: - ./data:/app/data command: python -m kafka.producer --servers kafka:29092 --rate 50 + restart: unless-stopped consumer: build: . depends_on: api: condition: service_healthy + kafka-setup: + condition: service_completed_successfully volumes: - ./data:/app/data - ./ml/artifacts:/app/ml/artifacts command: python -m kafka.consumer --servers kafka:29092 --mode direct --print-flagged + restart: unless-stopped control-center: image: confluentinc/cp-enterprise-control-center:7.5.0 @@ -103,4 +104,4 @@ services: CONTROL_CENTER_INTERNAL_TOPICS_PARTITIONS: 1 CONTROL_CENTER_MONITORING_INTERCEPTOR_TOPIC_PARTITIONS: 1 CONFLUENT_METRICS_TOPIC_REPLICATION: 1 - PORT: 9021 \ No newline at end of file + PORT: 9021 diff --git a/scripts/entrypoint.sh b/scripts/entrypoint.sh new file mode 100644 index 0000000..cfbf1bf --- /dev/null +++ b/scripts/entrypoint.sh @@ -0,0 +1,28 @@ +#!/bin/bash +set -euo pipefail + +ARTIFACTS_DIR="ml/artifacts" +DATA_FILE="data/transactions.csv" + +echo "[entrypoint] Fraud Radar API container starting..." + +# Generate synthetic data if missing (idempotent — same seed every time) +if [ ! -f "$DATA_FILE" ]; then + echo "[entrypoint] Synthetic data not found at $DATA_FILE — generating..." + python -m data.generate_data +else + echo "[entrypoint] Using existing data: $DATA_FILE ($(wc -l < "$DATA_FILE" | tr -d ' ') rows)" +fi + +# Train model if any artifact is missing — avoids retraining on every restart +if [ ! -f "$ARTIFACTS_DIR/isolation_forest.pkl" ] || \ + [ ! -f "$ARTIFACTS_DIR/scaler.pkl" ] || \ + [ ! -f "$ARTIFACTS_DIR/calibration.json" ]; then + echo "[entrypoint] Model artifacts missing — training Isolation Forest..." + python -m ml.train +else + echo "[entrypoint] Using existing model artifacts." +fi + +echo "[entrypoint] Starting API server on 0.0.0.0:8000..." +exec uvicorn api.main:app --host 0.0.0.0 --port 8000