Spaces:
Runtime error
Runtime error
Commit Β·
e93a798
1
Parent(s): e749f25
build error - fix 1
Browse files- .dockerignore +46 -0
- .gitattributes +9 -32
- API_CLIENT_EXAMPLES.py +377 -0
- API_CURL_EXAMPLES.sh +187 -0
- API_JAVASCRIPT_EXAMPLE.js +381 -0
- DEPLOYMENT_FIX.md +164 -0
- QUICK_FIX_SUMMARY.md +134 -0
- app.py +12 -41
.dockerignore
ADDED
|
@@ -0,0 +1,46 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Don't include these in Docker build
|
| 2 |
+
__pycache__/
|
| 3 |
+
*.pyc
|
| 4 |
+
*.pyo
|
| 5 |
+
*.pyd
|
| 6 |
+
.Python
|
| 7 |
+
*.so
|
| 8 |
+
*.egg-info/
|
| 9 |
+
|
| 10 |
+
# Development files
|
| 11 |
+
.git/
|
| 12 |
+
.gitignore
|
| 13 |
+
.gitattributes
|
| 14 |
+
.env
|
| 15 |
+
.env.*
|
| 16 |
+
|
| 17 |
+
# Documentation and examples (not needed for runtime)
|
| 18 |
+
API_CLIENT_EXAMPLES.py
|
| 19 |
+
API_CURL_EXAMPLES.sh
|
| 20 |
+
API_JAVASCRIPT_EXAMPLE.js
|
| 21 |
+
API_KEYS_FOR_COLLEAGUE.md
|
| 22 |
+
API_USAGE.md
|
| 23 |
+
API_USAGE_UPDATED.md
|
| 24 |
+
CHANGELOG_API_UPDATE.md
|
| 25 |
+
FRONTEND_README.md
|
| 26 |
+
frontend.html
|
| 27 |
+
index_v2.html
|
| 28 |
+
setup_instructions.md
|
| 29 |
+
SETUP_MODEL_HUB.md
|
| 30 |
+
|
| 31 |
+
# Unused files (pattern detection removed)
|
| 32 |
+
patterbs.json
|
| 33 |
+
confidence_scores.js
|
| 34 |
+
metadata.js
|
| 35 |
+
|
| 36 |
+
# Test files
|
| 37 |
+
test_images/
|
| 38 |
+
*.local.*
|
| 39 |
+
|
| 40 |
+
# IDE files
|
| 41 |
+
.vscode/
|
| 42 |
+
.idea/
|
| 43 |
+
*.swp
|
| 44 |
+
*.swo
|
| 45 |
+
.DS_Store
|
| 46 |
+
|
.gitattributes
CHANGED
|
@@ -1,35 +1,12 @@
|
|
| 1 |
-
*.
|
| 2 |
-
*.arrow filter=lfs diff=lfs merge=lfs -text
|
| 3 |
*.bin filter=lfs diff=lfs merge=lfs -text
|
| 4 |
-
*.
|
| 5 |
-
*.
|
| 6 |
-
*.ftz filter=lfs diff=lfs merge=lfs -text
|
| 7 |
-
*.gz filter=lfs diff=lfs merge=lfs -text
|
| 8 |
*.h5 filter=lfs diff=lfs merge=lfs -text
|
| 9 |
-
*.joblib filter=lfs diff=lfs merge=lfs -text
|
| 10 |
-
*.lfs.* filter=lfs diff=lfs merge=lfs -text
|
| 11 |
-
*.mlmodel filter=lfs diff=lfs merge=lfs -text
|
| 12 |
-
*.model filter=lfs diff=lfs merge=lfs -text
|
| 13 |
-
*.msgpack filter=lfs diff=lfs merge=lfs -text
|
| 14 |
-
*.npy filter=lfs diff=lfs merge=lfs -text
|
| 15 |
-
*.npz filter=lfs diff=lfs merge=lfs -text
|
| 16 |
-
*.onnx filter=lfs diff=lfs merge=lfs -text
|
| 17 |
-
*.ot filter=lfs diff=lfs merge=lfs -text
|
| 18 |
-
*.parquet filter=lfs diff=lfs merge=lfs -text
|
| 19 |
-
*.pb filter=lfs diff=lfs merge=lfs -text
|
| 20 |
-
*.pickle filter=lfs diff=lfs merge=lfs -text
|
| 21 |
-
*.pkl filter=lfs diff=lfs merge=lfs -text
|
| 22 |
*.pt filter=lfs diff=lfs merge=lfs -text
|
| 23 |
-
*.
|
| 24 |
-
*.
|
| 25 |
-
*.
|
| 26 |
-
|
| 27 |
-
*.
|
| 28 |
-
*.
|
| 29 |
-
*.tflite filter=lfs diff=lfs merge=lfs -text
|
| 30 |
-
*.tgz filter=lfs diff=lfs merge=lfs -text
|
| 31 |
-
*.wasm filter=lfs diff=lfs merge=lfs -text
|
| 32 |
-
*.xz filter=lfs diff=lfs merge=lfs -text
|
| 33 |
-
*.zip filter=lfs diff=lfs merge=lfs -text
|
| 34 |
-
*.zst filter=lfs diff=lfs merge=lfs -text
|
| 35 |
-
*tfevents* filter=lfs diff=lfs merge=lfs -text
|
|
|
|
| 1 |
+
*.pth filter=lfs diff=lfs merge=lfs -text
|
|
|
|
| 2 |
*.bin filter=lfs diff=lfs merge=lfs -text
|
| 3 |
+
*.gguf filter=lfs diff=lfs merge=lfs -text
|
| 4 |
+
*.safetensors filter=lfs diff=lfs merge=lfs -text
|
|
|
|
|
|
|
| 5 |
*.h5 filter=lfs diff=lfs merge=lfs -text
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 6 |
*.pt filter=lfs diff=lfs merge=lfs -text
|
| 7 |
+
*.pb filter=lfs diff=lfs merge=lfs -text
|
| 8 |
+
*.onnx filter=lfs diff=lfs merge=lfs -text
|
| 9 |
+
*.msgpack filter=lfs diff=lfs merge=lfs -text
|
| 10 |
+
*.arrow filter=lfs diff=lfs merge=lfs -text
|
| 11 |
+
*.ftz filter=lfs diff=lfs merge=lfs -text
|
| 12 |
+
*.joblib filter=lfs diff=lfs merge=lfs -text
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
API_CLIENT_EXAMPLES.py
ADDED
|
@@ -0,0 +1,377 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
A/B Test Predictor - API Client Examples
|
| 3 |
+
==========================================
|
| 4 |
+
|
| 5 |
+
This file shows how to send requests to the A/B Test Predictor API.
|
| 6 |
+
"""
|
| 7 |
+
|
| 8 |
+
# ============================================================================
|
| 9 |
+
# Option 1: Gradio Python Client (Recommended)
|
| 10 |
+
# ============================================================================
|
| 11 |
+
|
| 12 |
+
from gradio_client import Client
|
| 13 |
+
from PIL import Image
|
| 14 |
+
import json
|
| 15 |
+
|
| 16 |
+
# Initialize the client
|
| 17 |
+
# For local deployment:
|
| 18 |
+
client = Client("http://localhost:7860")
|
| 19 |
+
|
| 20 |
+
# For Hugging Face Spaces deployment:
|
| 21 |
+
# client = Client("your-username/ABTestPredictor")
|
| 22 |
+
|
| 23 |
+
def predict_with_gradio_client(control_image_path, variant_image_path,
|
| 24 |
+
business_model, customer_type, conversion_type,
|
| 25 |
+
industry, page_type):
|
| 26 |
+
"""
|
| 27 |
+
Send prediction request using Gradio Client
|
| 28 |
+
|
| 29 |
+
Args:
|
| 30 |
+
control_image_path: Path to control image file
|
| 31 |
+
variant_image_path: Path to variant image file
|
| 32 |
+
business_model: One of ["E-Commerce", "Lead Generation", "Other*", "SaaS"]
|
| 33 |
+
customer_type: One of ["B2B", "B2C", "Both", "Other*"]
|
| 34 |
+
conversion_type: One of ["Direct Purchase", "High-Intent Lead Gen",
|
| 35 |
+
"Info/Content Lead Gen", "Location Search",
|
| 36 |
+
"Non-Profit/Community", "Other Conversion"]
|
| 37 |
+
industry: One of the 14 industry categories
|
| 38 |
+
page_type: One of ["Awareness & Discovery", "Consideration & Evaluation",
|
| 39 |
+
"Conversion", "Internal & Navigation", "Post-Conversion & Other"]
|
| 40 |
+
|
| 41 |
+
Returns:
|
| 42 |
+
dict: Prediction results with confidence scores
|
| 43 |
+
"""
|
| 44 |
+
result = client.predict(
|
| 45 |
+
control_image_path, # Control image file path
|
| 46 |
+
variant_image_path, # Variant image file path
|
| 47 |
+
business_model, # Business Model dropdown
|
| 48 |
+
customer_type, # Customer Type dropdown
|
| 49 |
+
conversion_type, # Conversion Type dropdown
|
| 50 |
+
industry, # Industry dropdown
|
| 51 |
+
page_type, # Page Type dropdown
|
| 52 |
+
api_name="/predict_with_categorical_data" # The function endpoint
|
| 53 |
+
)
|
| 54 |
+
|
| 55 |
+
return result
|
| 56 |
+
|
| 57 |
+
|
| 58 |
+
# Example usage
|
| 59 |
+
if __name__ == "__main__":
|
| 60 |
+
# Example 1: Basic prediction
|
| 61 |
+
result = predict_with_gradio_client(
|
| 62 |
+
control_image_path="path/to/control_image.jpg",
|
| 63 |
+
variant_image_path="path/to/variant_image.jpg",
|
| 64 |
+
business_model="SaaS",
|
| 65 |
+
customer_type="B2B",
|
| 66 |
+
conversion_type="High-Intent Lead Gen",
|
| 67 |
+
industry="B2B Software & Tech",
|
| 68 |
+
page_type="Awareness & Discovery"
|
| 69 |
+
)
|
| 70 |
+
|
| 71 |
+
print("Prediction Results:")
|
| 72 |
+
print(json.dumps(result, indent=2))
|
| 73 |
+
|
| 74 |
+
# Access specific fields
|
| 75 |
+
win_probability = result['predictionResults']['probability']
|
| 76 |
+
confidence = result['predictionResults']['modelConfidence']
|
| 77 |
+
|
| 78 |
+
print(f"\nWin Probability: {win_probability}")
|
| 79 |
+
print(f"Model Confidence: {confidence}%")
|
| 80 |
+
|
| 81 |
+
|
| 82 |
+
# ============================================================================
|
| 83 |
+
# Option 2: Direct HTTP POST Request (cURL equivalent in Python)
|
| 84 |
+
# ============================================================================
|
| 85 |
+
|
| 86 |
+
import requests
|
| 87 |
+
import base64
|
| 88 |
+
|
| 89 |
+
def predict_with_http_request(control_image_path, variant_image_path,
|
| 90 |
+
business_model, customer_type, conversion_type,
|
| 91 |
+
industry, page_type, api_url="http://localhost:7860"):
|
| 92 |
+
"""
|
| 93 |
+
Send prediction request using direct HTTP POST
|
| 94 |
+
|
| 95 |
+
Note: This requires converting images to base64 for Gradio's API format
|
| 96 |
+
"""
|
| 97 |
+
|
| 98 |
+
# Read and encode images
|
| 99 |
+
with open(control_image_path, "rb") as f:
|
| 100 |
+
control_b64 = base64.b64encode(f.read()).decode()
|
| 101 |
+
|
| 102 |
+
with open(variant_image_path, "rb") as f:
|
| 103 |
+
variant_b64 = base64.b64encode(f.read()).decode()
|
| 104 |
+
|
| 105 |
+
# Prepare the request payload (Gradio format)
|
| 106 |
+
payload = {
|
| 107 |
+
"data": [
|
| 108 |
+
f"data:image/jpeg;base64,{control_b64}", # Control image
|
| 109 |
+
f"data:image/jpeg;base64,{variant_b64}", # Variant image
|
| 110 |
+
business_model,
|
| 111 |
+
customer_type,
|
| 112 |
+
conversion_type,
|
| 113 |
+
industry,
|
| 114 |
+
page_type
|
| 115 |
+
]
|
| 116 |
+
}
|
| 117 |
+
|
| 118 |
+
# Send POST request to Gradio API
|
| 119 |
+
response = requests.post(
|
| 120 |
+
f"{api_url}/api/predict",
|
| 121 |
+
json=payload,
|
| 122 |
+
headers={"Content-Type": "application/json"}
|
| 123 |
+
)
|
| 124 |
+
|
| 125 |
+
if response.status_code == 200:
|
| 126 |
+
return response.json()['data'][0] # Gradio wraps response in 'data' array
|
| 127 |
+
else:
|
| 128 |
+
raise Exception(f"API request failed: {response.status_code} - {response.text}")
|
| 129 |
+
|
| 130 |
+
|
| 131 |
+
# ============================================================================
|
| 132 |
+
# Option 3: Using PIL Images (in-memory)
|
| 133 |
+
# ============================================================================
|
| 134 |
+
|
| 135 |
+
import numpy as np
|
| 136 |
+
from PIL import Image
|
| 137 |
+
|
| 138 |
+
def predict_with_pil_images(control_img, variant_img,
|
| 139 |
+
business_model, customer_type, conversion_type,
|
| 140 |
+
industry, page_type):
|
| 141 |
+
"""
|
| 142 |
+
Send prediction with PIL Image objects (useful for programmatic image generation)
|
| 143 |
+
|
| 144 |
+
Args:
|
| 145 |
+
control_img: PIL Image object
|
| 146 |
+
variant_img: PIL Image object
|
| 147 |
+
"""
|
| 148 |
+
|
| 149 |
+
# Convert PIL images to numpy arrays (Gradio expects numpy arrays)
|
| 150 |
+
control_array = np.array(control_img)
|
| 151 |
+
variant_array = np.array(variant_img)
|
| 152 |
+
|
| 153 |
+
# Use the Gradio client
|
| 154 |
+
result = client.predict(
|
| 155 |
+
control_array,
|
| 156 |
+
variant_array,
|
| 157 |
+
business_model,
|
| 158 |
+
customer_type,
|
| 159 |
+
conversion_type,
|
| 160 |
+
industry,
|
| 161 |
+
page_type,
|
| 162 |
+
api_name="/predict_with_categorical_data"
|
| 163 |
+
)
|
| 164 |
+
|
| 165 |
+
return result
|
| 166 |
+
|
| 167 |
+
|
| 168 |
+
# Example with PIL
|
| 169 |
+
if __name__ == "__main__":
|
| 170 |
+
# Load images using PIL
|
| 171 |
+
control_img = Image.open("control.jpg")
|
| 172 |
+
variant_img = Image.open("variant.jpg")
|
| 173 |
+
|
| 174 |
+
result = predict_with_pil_images(
|
| 175 |
+
control_img=control_img,
|
| 176 |
+
variant_img=variant_img,
|
| 177 |
+
business_model="SaaS",
|
| 178 |
+
customer_type="B2B",
|
| 179 |
+
conversion_type="High-Intent Lead Gen",
|
| 180 |
+
industry="B2B Software & Tech",
|
| 181 |
+
page_type="Awareness & Discovery"
|
| 182 |
+
)
|
| 183 |
+
|
| 184 |
+
|
| 185 |
+
# ============================================================================
|
| 186 |
+
# Option 4: Batch Processing Multiple Tests
|
| 187 |
+
# ============================================================================
|
| 188 |
+
|
| 189 |
+
def batch_predict(test_cases, output_file="results.json"):
|
| 190 |
+
"""
|
| 191 |
+
Process multiple A/B tests in batch
|
| 192 |
+
|
| 193 |
+
Args:
|
| 194 |
+
test_cases: List of dicts with test parameters
|
| 195 |
+
output_file: Where to save results
|
| 196 |
+
|
| 197 |
+
Example test_cases:
|
| 198 |
+
[
|
| 199 |
+
{
|
| 200 |
+
"control_image": "test1_control.jpg",
|
| 201 |
+
"variant_image": "test1_variant.jpg",
|
| 202 |
+
"business_model": "SaaS",
|
| 203 |
+
"customer_type": "B2B",
|
| 204 |
+
"conversion_type": "High-Intent Lead Gen",
|
| 205 |
+
"industry": "B2B Software & Tech",
|
| 206 |
+
"page_type": "Awareness & Discovery"
|
| 207 |
+
},
|
| 208 |
+
# ... more tests
|
| 209 |
+
]
|
| 210 |
+
"""
|
| 211 |
+
|
| 212 |
+
results = []
|
| 213 |
+
|
| 214 |
+
for i, test in enumerate(test_cases):
|
| 215 |
+
print(f"Processing test {i+1}/{len(test_cases)}...")
|
| 216 |
+
|
| 217 |
+
try:
|
| 218 |
+
result = predict_with_gradio_client(
|
| 219 |
+
control_image_path=test["control_image"],
|
| 220 |
+
variant_image_path=test["variant_image"],
|
| 221 |
+
business_model=test["business_model"],
|
| 222 |
+
customer_type=test["customer_type"],
|
| 223 |
+
conversion_type=test["conversion_type"],
|
| 224 |
+
industry=test["industry"],
|
| 225 |
+
page_type=test["page_type"]
|
| 226 |
+
)
|
| 227 |
+
|
| 228 |
+
results.append({
|
| 229 |
+
"test_id": i + 1,
|
| 230 |
+
"input": test,
|
| 231 |
+
"prediction": result
|
| 232 |
+
})
|
| 233 |
+
|
| 234 |
+
except Exception as e:
|
| 235 |
+
print(f"Error processing test {i+1}: {e}")
|
| 236 |
+
results.append({
|
| 237 |
+
"test_id": i + 1,
|
| 238 |
+
"input": test,
|
| 239 |
+
"error": str(e)
|
| 240 |
+
})
|
| 241 |
+
|
| 242 |
+
# Save results
|
| 243 |
+
with open(output_file, "w") as f:
|
| 244 |
+
json.dump(results, f, indent=2)
|
| 245 |
+
|
| 246 |
+
print(f"\nBatch processing complete! Results saved to {output_file}")
|
| 247 |
+
return results
|
| 248 |
+
|
| 249 |
+
|
| 250 |
+
# ============================================================================
|
| 251 |
+
# Valid Category Values (for reference)
|
| 252 |
+
# ============================================================================
|
| 253 |
+
|
| 254 |
+
VALID_CATEGORIES = {
|
| 255 |
+
"business_model": [
|
| 256 |
+
"E-Commerce",
|
| 257 |
+
"Lead Generation",
|
| 258 |
+
"Other*",
|
| 259 |
+
"SaaS"
|
| 260 |
+
],
|
| 261 |
+
|
| 262 |
+
"customer_type": [
|
| 263 |
+
"B2B",
|
| 264 |
+
"B2C",
|
| 265 |
+
"Both",
|
| 266 |
+
"Other*"
|
| 267 |
+
],
|
| 268 |
+
|
| 269 |
+
"conversion_type": [
|
| 270 |
+
"Direct Purchase",
|
| 271 |
+
"High-Intent Lead Gen",
|
| 272 |
+
"Info/Content Lead Gen",
|
| 273 |
+
"Location Search",
|
| 274 |
+
"Non-Profit/Community",
|
| 275 |
+
"Other Conversion"
|
| 276 |
+
],
|
| 277 |
+
|
| 278 |
+
"industry": [
|
| 279 |
+
"Automotive & Transportation",
|
| 280 |
+
"B2B Services",
|
| 281 |
+
"B2B Software & Tech",
|
| 282 |
+
"Consumer Services",
|
| 283 |
+
"Consumer Software & Apps",
|
| 284 |
+
"Education",
|
| 285 |
+
"Finance, Insurance & Real Estate",
|
| 286 |
+
"Food, Hospitality & Travel",
|
| 287 |
+
"Health & Wellness",
|
| 288 |
+
"Industrial & Manufacturing",
|
| 289 |
+
"Media & Entertainment",
|
| 290 |
+
"Non-Profit & Government",
|
| 291 |
+
"Other",
|
| 292 |
+
"Retail & E-commerce"
|
| 293 |
+
],
|
| 294 |
+
|
| 295 |
+
"page_type": [
|
| 296 |
+
"Awareness & Discovery",
|
| 297 |
+
"Consideration & Evaluation",
|
| 298 |
+
"Conversion",
|
| 299 |
+
"Internal & Navigation",
|
| 300 |
+
"Post-Conversion & Other"
|
| 301 |
+
]
|
| 302 |
+
}
|
| 303 |
+
|
| 304 |
+
|
| 305 |
+
def validate_categories(business_model, customer_type, conversion_type,
|
| 306 |
+
industry, page_type):
|
| 307 |
+
"""Validate that all categories are valid"""
|
| 308 |
+
|
| 309 |
+
errors = []
|
| 310 |
+
|
| 311 |
+
if business_model not in VALID_CATEGORIES["business_model"]:
|
| 312 |
+
errors.append(f"Invalid business_model: {business_model}")
|
| 313 |
+
|
| 314 |
+
if customer_type not in VALID_CATEGORIES["customer_type"]:
|
| 315 |
+
errors.append(f"Invalid customer_type: {customer_type}")
|
| 316 |
+
|
| 317 |
+
if conversion_type not in VALID_CATEGORIES["conversion_type"]:
|
| 318 |
+
errors.append(f"Invalid conversion_type: {conversion_type}")
|
| 319 |
+
|
| 320 |
+
if industry not in VALID_CATEGORIES["industry"]:
|
| 321 |
+
errors.append(f"Invalid industry: {industry}")
|
| 322 |
+
|
| 323 |
+
if page_type not in VALID_CATEGORIES["page_type"]:
|
| 324 |
+
errors.append(f"Invalid page_type: {page_type}")
|
| 325 |
+
|
| 326 |
+
if errors:
|
| 327 |
+
raise ValueError("Category validation failed:\n" + "\n".join(errors))
|
| 328 |
+
|
| 329 |
+
return True
|
| 330 |
+
|
| 331 |
+
|
| 332 |
+
# ============================================================================
|
| 333 |
+
# Error Handling Example
|
| 334 |
+
# ============================================================================
|
| 335 |
+
|
| 336 |
+
def safe_predict(control_image_path, variant_image_path,
|
| 337 |
+
business_model, customer_type, conversion_type,
|
| 338 |
+
industry, page_type):
|
| 339 |
+
"""
|
| 340 |
+
Safe prediction with error handling and validation
|
| 341 |
+
"""
|
| 342 |
+
|
| 343 |
+
try:
|
| 344 |
+
# Validate categories first
|
| 345 |
+
validate_categories(business_model, customer_type, conversion_type,
|
| 346 |
+
industry, page_type)
|
| 347 |
+
|
| 348 |
+
# Make prediction
|
| 349 |
+
result = predict_with_gradio_client(
|
| 350 |
+
control_image_path=control_image_path,
|
| 351 |
+
variant_image_path=variant_image_path,
|
| 352 |
+
business_model=business_model,
|
| 353 |
+
customer_type=customer_type,
|
| 354 |
+
conversion_type=conversion_type,
|
| 355 |
+
industry=industry,
|
| 356 |
+
page_type=page_type
|
| 357 |
+
)
|
| 358 |
+
|
| 359 |
+
return {
|
| 360 |
+
"success": True,
|
| 361 |
+
"result": result
|
| 362 |
+
}
|
| 363 |
+
|
| 364 |
+
except ValueError as e:
|
| 365 |
+
return {
|
| 366 |
+
"success": False,
|
| 367 |
+
"error": "Validation Error",
|
| 368 |
+
"message": str(e)
|
| 369 |
+
}
|
| 370 |
+
|
| 371 |
+
except Exception as e:
|
| 372 |
+
return {
|
| 373 |
+
"success": False,
|
| 374 |
+
"error": "API Error",
|
| 375 |
+
"message": str(e)
|
| 376 |
+
}
|
| 377 |
+
|
API_CURL_EXAMPLES.sh
ADDED
|
@@ -0,0 +1,187 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/bin/bash
|
| 2 |
+
|
| 3 |
+
# ============================================================================
|
| 4 |
+
# A/B Test Predictor - cURL API Examples
|
| 5 |
+
# ============================================================================
|
| 6 |
+
|
| 7 |
+
# Configuration
|
| 8 |
+
API_URL="http://localhost:7860" # Change to your deployment URL
|
| 9 |
+
# For Hugging Face Spaces: API_URL="https://your-username-abtestpredictor.hf.space"
|
| 10 |
+
|
| 11 |
+
# ============================================================================
|
| 12 |
+
# Example 1: Basic Prediction with Image Files
|
| 13 |
+
# ============================================================================
|
| 14 |
+
|
| 15 |
+
# Convert images to base64
|
| 16 |
+
CONTROL_IMAGE_B64=$(base64 -i control_image.jpg)
|
| 17 |
+
VARIANT_IMAGE_B64=$(base64 -i variant_image.jpg)
|
| 18 |
+
|
| 19 |
+
# Send POST request to Gradio API
|
| 20 |
+
curl -X POST "${API_URL}/api/predict" \
|
| 21 |
+
-H "Content-Type: application/json" \
|
| 22 |
+
-d '{
|
| 23 |
+
"data": [
|
| 24 |
+
"data:image/jpeg;base64,'"${CONTROL_IMAGE_B64}"'",
|
| 25 |
+
"data:image/jpeg;base64,'"${VARIANT_IMAGE_B64}"'",
|
| 26 |
+
"SaaS",
|
| 27 |
+
"B2B",
|
| 28 |
+
"High-Intent Lead Gen",
|
| 29 |
+
"B2B Software & Tech",
|
| 30 |
+
"Awareness & Discovery"
|
| 31 |
+
],
|
| 32 |
+
"fn_index": 0
|
| 33 |
+
}'
|
| 34 |
+
|
| 35 |
+
# ============================================================================
|
| 36 |
+
# Example 2: Using a Function to Send Requests
|
| 37 |
+
# ============================================================================
|
| 38 |
+
|
| 39 |
+
predict_abtest() {
|
| 40 |
+
local CONTROL_IMG=$1
|
| 41 |
+
local VARIANT_IMG=$2
|
| 42 |
+
local BUSINESS_MODEL=$3
|
| 43 |
+
local CUSTOMER_TYPE=$4
|
| 44 |
+
local CONVERSION_TYPE=$5
|
| 45 |
+
local INDUSTRY=$6
|
| 46 |
+
local PAGE_TYPE=$7
|
| 47 |
+
|
| 48 |
+
# Encode images
|
| 49 |
+
local CONTROL_B64=$(base64 -i "$CONTROL_IMG")
|
| 50 |
+
local VARIANT_B64=$(base64 -i "$VARIANT_IMG")
|
| 51 |
+
|
| 52 |
+
# Make API call
|
| 53 |
+
curl -X POST "${API_URL}/api/predict" \
|
| 54 |
+
-H "Content-Type: application/json" \
|
| 55 |
+
-d '{
|
| 56 |
+
"data": [
|
| 57 |
+
"data:image/jpeg;base64,'"${CONTROL_B64}"'",
|
| 58 |
+
"data:image/jpeg;base64,'"${VARIANT_B64}"'",
|
| 59 |
+
"'"${BUSINESS_MODEL}"'",
|
| 60 |
+
"'"${CUSTOMER_TYPE}"'",
|
| 61 |
+
"'"${CONVERSION_TYPE}"'",
|
| 62 |
+
"'"${INDUSTRY}"'",
|
| 63 |
+
"'"${PAGE_TYPE}"'"
|
| 64 |
+
]
|
| 65 |
+
}' | jq .
|
| 66 |
+
}
|
| 67 |
+
|
| 68 |
+
# Usage
|
| 69 |
+
predict_abtest \
|
| 70 |
+
"control.jpg" \
|
| 71 |
+
"variant.jpg" \
|
| 72 |
+
"SaaS" \
|
| 73 |
+
"B2B" \
|
| 74 |
+
"High-Intent Lead Gen" \
|
| 75 |
+
"B2B Software & Tech" \
|
| 76 |
+
"Awareness & Discovery"
|
| 77 |
+
|
| 78 |
+
# ============================================================================
|
| 79 |
+
# Example 3: Multiple Predictions in a Loop
|
| 80 |
+
# ============================================================================
|
| 81 |
+
|
| 82 |
+
# Read test cases from CSV
|
| 83 |
+
while IFS=',' read -r control variant business customer conversion industry page
|
| 84 |
+
do
|
| 85 |
+
echo "Processing: $control vs $variant"
|
| 86 |
+
|
| 87 |
+
predict_abtest \
|
| 88 |
+
"$control" \
|
| 89 |
+
"$variant" \
|
| 90 |
+
"$business" \
|
| 91 |
+
"$customer" \
|
| 92 |
+
"$conversion" \
|
| 93 |
+
"$industry" \
|
| 94 |
+
"$page"
|
| 95 |
+
|
| 96 |
+
sleep 1 # Rate limiting
|
| 97 |
+
done < test_cases.csv
|
| 98 |
+
|
| 99 |
+
# ============================================================================
|
| 100 |
+
# Example 4: Save Results to File
|
| 101 |
+
# ============================================================================
|
| 102 |
+
|
| 103 |
+
predict_and_save() {
|
| 104 |
+
local OUTPUT_FILE=$1
|
| 105 |
+
|
| 106 |
+
predict_abtest \
|
| 107 |
+
"control.jpg" \
|
| 108 |
+
"variant.jpg" \
|
| 109 |
+
"SaaS" \
|
| 110 |
+
"B2B" \
|
| 111 |
+
"High-Intent Lead Gen" \
|
| 112 |
+
"B2B Software & Tech" \
|
| 113 |
+
"Awareness & Discovery" > "$OUTPUT_FILE"
|
| 114 |
+
|
| 115 |
+
echo "Results saved to $OUTPUT_FILE"
|
| 116 |
+
}
|
| 117 |
+
|
| 118 |
+
predict_and_save "prediction_result.json"
|
| 119 |
+
|
| 120 |
+
# ============================================================================
|
| 121 |
+
# Example 5: Parse and Extract Specific Fields
|
| 122 |
+
# ============================================================================
|
| 123 |
+
|
| 124 |
+
# Get just the win probability
|
| 125 |
+
get_win_probability() {
|
| 126 |
+
predict_abtest "$@" | jq -r '.data[0].predictionResults.probability'
|
| 127 |
+
}
|
| 128 |
+
|
| 129 |
+
# Get model confidence
|
| 130 |
+
get_confidence() {
|
| 131 |
+
predict_abtest "$@" | jq -r '.data[0].predictionResults.modelConfidence'
|
| 132 |
+
}
|
| 133 |
+
|
| 134 |
+
# Usage
|
| 135 |
+
PROB=$(get_win_probability "control.jpg" "variant.jpg" "SaaS" "B2B" "High-Intent Lead Gen" "B2B Software & Tech" "Awareness & Discovery")
|
| 136 |
+
CONF=$(get_confidence "control.jpg" "variant.jpg" "SaaS" "B2B" "High-Intent Lead Gen" "B2B Software & Tech" "Awareness & Discovery")
|
| 137 |
+
|
| 138 |
+
echo "Win Probability: $PROB"
|
| 139 |
+
echo "Model Confidence: $CONF%"
|
| 140 |
+
|
| 141 |
+
# ============================================================================
|
| 142 |
+
# Valid Category Values (for reference)
|
| 143 |
+
# ============================================================================
|
| 144 |
+
|
| 145 |
+
# Business Model options:
|
| 146 |
+
# - "E-Commerce"
|
| 147 |
+
# - "Lead Generation"
|
| 148 |
+
# - "Other*"
|
| 149 |
+
# - "SaaS"
|
| 150 |
+
|
| 151 |
+
# Customer Type options:
|
| 152 |
+
# - "B2B"
|
| 153 |
+
# - "B2C"
|
| 154 |
+
# - "Both"
|
| 155 |
+
# - "Other*"
|
| 156 |
+
|
| 157 |
+
# Conversion Type options:
|
| 158 |
+
# - "Direct Purchase"
|
| 159 |
+
# - "High-Intent Lead Gen"
|
| 160 |
+
# - "Info/Content Lead Gen"
|
| 161 |
+
# - "Location Search"
|
| 162 |
+
# - "Non-Profit/Community"
|
| 163 |
+
# - "Other Conversion"
|
| 164 |
+
|
| 165 |
+
# Industry options:
|
| 166 |
+
# - "Automotive & Transportation"
|
| 167 |
+
# - "B2B Services"
|
| 168 |
+
# - "B2B Software & Tech"
|
| 169 |
+
# - "Consumer Services"
|
| 170 |
+
# - "Consumer Software & Apps"
|
| 171 |
+
# - "Education"
|
| 172 |
+
# - "Finance, Insurance & Real Estate"
|
| 173 |
+
# - "Food, Hospitality & Travel"
|
| 174 |
+
# - "Health & Wellness"
|
| 175 |
+
# - "Industrial & Manufacturing"
|
| 176 |
+
# - "Media & Entertainment"
|
| 177 |
+
# - "Non-Profit & Government"
|
| 178 |
+
# - "Other"
|
| 179 |
+
# - "Retail & E-commerce"
|
| 180 |
+
|
| 181 |
+
# Page Type options:
|
| 182 |
+
# - "Awareness & Discovery"
|
| 183 |
+
# - "Consideration & Evaluation"
|
| 184 |
+
# - "Conversion"
|
| 185 |
+
# - "Internal & Navigation"
|
| 186 |
+
# - "Post-Conversion & Other"
|
| 187 |
+
|
API_JAVASCRIPT_EXAMPLE.js
ADDED
|
@@ -0,0 +1,381 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
/**
|
| 2 |
+
* A/B Test Predictor - JavaScript/Node.js API Examples
|
| 3 |
+
*/
|
| 4 |
+
|
| 5 |
+
// ============================================================================
|
| 6 |
+
// Option 1: Using Fetch API (Browser/Node.js)
|
| 7 |
+
// ============================================================================
|
| 8 |
+
|
| 9 |
+
async function predictABTest(controlImagePath, variantImagePath, categories) {
|
| 10 |
+
const apiUrl = 'http://localhost:7860/api/predict'; // Change to your deployment URL
|
| 11 |
+
|
| 12 |
+
// Read and encode images to base64
|
| 13 |
+
const fs = require('fs').promises;
|
| 14 |
+
|
| 15 |
+
const controlImage = await fs.readFile(controlImagePath);
|
| 16 |
+
const variantImage = await fs.readFile(variantImagePath);
|
| 17 |
+
|
| 18 |
+
const controlB64 = `data:image/jpeg;base64,${controlImage.toString('base64')}`;
|
| 19 |
+
const variantB64 = `data:image/jpeg;base64,${variantImage.toString('base64')}`;
|
| 20 |
+
|
| 21 |
+
// Prepare request payload
|
| 22 |
+
const payload = {
|
| 23 |
+
data: [
|
| 24 |
+
controlB64,
|
| 25 |
+
variantB64,
|
| 26 |
+
categories.businessModel,
|
| 27 |
+
categories.customerType,
|
| 28 |
+
categories.conversionType,
|
| 29 |
+
categories.industry,
|
| 30 |
+
categories.pageType
|
| 31 |
+
]
|
| 32 |
+
};
|
| 33 |
+
|
| 34 |
+
// Send POST request
|
| 35 |
+
const response = await fetch(apiUrl, {
|
| 36 |
+
method: 'POST',
|
| 37 |
+
headers: {
|
| 38 |
+
'Content-Type': 'application/json'
|
| 39 |
+
},
|
| 40 |
+
body: JSON.stringify(payload)
|
| 41 |
+
});
|
| 42 |
+
|
| 43 |
+
if (!response.ok) {
|
| 44 |
+
throw new Error(`API request failed: ${response.status} ${response.statusText}`);
|
| 45 |
+
}
|
| 46 |
+
|
| 47 |
+
const result = await response.json();
|
| 48 |
+
return result.data[0]; // Gradio wraps response in 'data' array
|
| 49 |
+
}
|
| 50 |
+
|
| 51 |
+
// Example usage
|
| 52 |
+
(async () => {
|
| 53 |
+
try {
|
| 54 |
+
const result = await predictABTest(
|
| 55 |
+
'control.jpg',
|
| 56 |
+
'variant.jpg',
|
| 57 |
+
{
|
| 58 |
+
businessModel: 'SaaS',
|
| 59 |
+
customerType: 'B2B',
|
| 60 |
+
conversionType: 'High-Intent Lead Gen',
|
| 61 |
+
industry: 'B2B Software & Tech',
|
| 62 |
+
pageType: 'Awareness & Discovery'
|
| 63 |
+
}
|
| 64 |
+
);
|
| 65 |
+
|
| 66 |
+
console.log('Prediction Results:');
|
| 67 |
+
console.log(JSON.stringify(result, null, 2));
|
| 68 |
+
|
| 69 |
+
console.log('\nWin Probability:', result.predictionResults.probability);
|
| 70 |
+
console.log('Model Confidence:', result.predictionResults.modelConfidence + '%');
|
| 71 |
+
|
| 72 |
+
} catch (error) {
|
| 73 |
+
console.error('Error:', error.message);
|
| 74 |
+
}
|
| 75 |
+
})();
|
| 76 |
+
|
| 77 |
+
|
| 78 |
+
// ============================================================================
|
| 79 |
+
// Option 2: Using Axios (More Robust)
|
| 80 |
+
// ============================================================================
|
| 81 |
+
|
| 82 |
+
const axios = require('axios');
|
| 83 |
+
const fs = require('fs').promises;
|
| 84 |
+
|
| 85 |
+
class ABTestPredictorClient {
|
| 86 |
+
constructor(apiUrl = 'http://localhost:7860') {
|
| 87 |
+
this.apiUrl = apiUrl;
|
| 88 |
+
this.endpoint = `${apiUrl}/api/predict`;
|
| 89 |
+
}
|
| 90 |
+
|
| 91 |
+
async encodeImage(imagePath) {
|
| 92 |
+
const imageBuffer = await fs.readFile(imagePath);
|
| 93 |
+
return `data:image/jpeg;base64,${imageBuffer.toString('base64')}`;
|
| 94 |
+
}
|
| 95 |
+
|
| 96 |
+
async predict(controlImagePath, variantImagePath, categories) {
|
| 97 |
+
try {
|
| 98 |
+
// Encode images
|
| 99 |
+
const controlB64 = await this.encodeImage(controlImagePath);
|
| 100 |
+
const variantB64 = await this.encodeImage(variantImagePath);
|
| 101 |
+
|
| 102 |
+
// Validate categories
|
| 103 |
+
this.validateCategories(categories);
|
| 104 |
+
|
| 105 |
+
// Prepare payload
|
| 106 |
+
const payload = {
|
| 107 |
+
data: [
|
| 108 |
+
controlB64,
|
| 109 |
+
variantB64,
|
| 110 |
+
categories.businessModel,
|
| 111 |
+
categories.customerType,
|
| 112 |
+
categories.conversionType,
|
| 113 |
+
categories.industry,
|
| 114 |
+
categories.pageType
|
| 115 |
+
]
|
| 116 |
+
};
|
| 117 |
+
|
| 118 |
+
// Make API call
|
| 119 |
+
const response = await axios.post(this.endpoint, payload, {
|
| 120 |
+
headers: {
|
| 121 |
+
'Content-Type': 'application/json'
|
| 122 |
+
},
|
| 123 |
+
timeout: 30000 // 30 second timeout
|
| 124 |
+
});
|
| 125 |
+
|
| 126 |
+
return {
|
| 127 |
+
success: true,
|
| 128 |
+
data: response.data.data[0]
|
| 129 |
+
};
|
| 130 |
+
|
| 131 |
+
} catch (error) {
|
| 132 |
+
return {
|
| 133 |
+
success: false,
|
| 134 |
+
error: error.message,
|
| 135 |
+
details: error.response?.data
|
| 136 |
+
};
|
| 137 |
+
}
|
| 138 |
+
}
|
| 139 |
+
|
| 140 |
+
validateCategories(categories) {
|
| 141 |
+
const validCategories = {
|
| 142 |
+
businessModel: ['E-Commerce', 'Lead Generation', 'Other*', 'SaaS'],
|
| 143 |
+
customerType: ['B2B', 'B2C', 'Both', 'Other*'],
|
| 144 |
+
conversionType: [
|
| 145 |
+
'Direct Purchase',
|
| 146 |
+
'High-Intent Lead Gen',
|
| 147 |
+
'Info/Content Lead Gen',
|
| 148 |
+
'Location Search',
|
| 149 |
+
'Non-Profit/Community',
|
| 150 |
+
'Other Conversion'
|
| 151 |
+
],
|
| 152 |
+
industry: [
|
| 153 |
+
'Automotive & Transportation',
|
| 154 |
+
'B2B Services',
|
| 155 |
+
'B2B Software & Tech',
|
| 156 |
+
'Consumer Services',
|
| 157 |
+
'Consumer Software & Apps',
|
| 158 |
+
'Education',
|
| 159 |
+
'Finance, Insurance & Real Estate',
|
| 160 |
+
'Food, Hospitality & Travel',
|
| 161 |
+
'Health & Wellness',
|
| 162 |
+
'Industrial & Manufacturing',
|
| 163 |
+
'Media & Entertainment',
|
| 164 |
+
'Non-Profit & Government',
|
| 165 |
+
'Other',
|
| 166 |
+
'Retail & E-commerce'
|
| 167 |
+
],
|
| 168 |
+
pageType: [
|
| 169 |
+
'Awareness & Discovery',
|
| 170 |
+
'Consideration & Evaluation',
|
| 171 |
+
'Conversion',
|
| 172 |
+
'Internal & Navigation',
|
| 173 |
+
'Post-Conversion & Other'
|
| 174 |
+
]
|
| 175 |
+
};
|
| 176 |
+
|
| 177 |
+
// Validate each category
|
| 178 |
+
for (const [key, value] of Object.entries(categories)) {
|
| 179 |
+
if (!validCategories[key]?.includes(value)) {
|
| 180 |
+
throw new Error(`Invalid ${key}: ${value}`);
|
| 181 |
+
}
|
| 182 |
+
}
|
| 183 |
+
|
| 184 |
+
return true;
|
| 185 |
+
}
|
| 186 |
+
|
| 187 |
+
async batchPredict(testCases) {
|
| 188 |
+
const results = [];
|
| 189 |
+
|
| 190 |
+
for (let i = 0; i < testCases.length; i++) {
|
| 191 |
+
console.log(`Processing test ${i + 1}/${testCases.length}...`);
|
| 192 |
+
|
| 193 |
+
const testCase = testCases[i];
|
| 194 |
+
const result = await this.predict(
|
| 195 |
+
testCase.controlImage,
|
| 196 |
+
testCase.variantImage,
|
| 197 |
+
testCase.categories
|
| 198 |
+
);
|
| 199 |
+
|
| 200 |
+
results.push({
|
| 201 |
+
testId: i + 1,
|
| 202 |
+
input: testCase,
|
| 203 |
+
result: result
|
| 204 |
+
});
|
| 205 |
+
|
| 206 |
+
// Rate limiting
|
| 207 |
+
await new Promise(resolve => setTimeout(resolve, 1000));
|
| 208 |
+
}
|
| 209 |
+
|
| 210 |
+
return results;
|
| 211 |
+
}
|
| 212 |
+
}
|
| 213 |
+
|
| 214 |
+
// Example usage
|
| 215 |
+
(async () => {
|
| 216 |
+
const client = new ABTestPredictorClient('http://localhost:7860');
|
| 217 |
+
|
| 218 |
+
// Single prediction
|
| 219 |
+
const result = await client.predict(
|
| 220 |
+
'control.jpg',
|
| 221 |
+
'variant.jpg',
|
| 222 |
+
{
|
| 223 |
+
businessModel: 'SaaS',
|
| 224 |
+
customerType: 'B2B',
|
| 225 |
+
conversionType: 'High-Intent Lead Gen',
|
| 226 |
+
industry: 'B2B Software & Tech',
|
| 227 |
+
pageType: 'Awareness & Discovery'
|
| 228 |
+
}
|
| 229 |
+
);
|
| 230 |
+
|
| 231 |
+
if (result.success) {
|
| 232 |
+
console.log('Prediction successful!');
|
| 233 |
+
console.log('Win Probability:', result.data.predictionResults.probability);
|
| 234 |
+
console.log('Confidence:', result.data.predictionResults.modelConfidence + '%');
|
| 235 |
+
} else {
|
| 236 |
+
console.error('Prediction failed:', result.error);
|
| 237 |
+
}
|
| 238 |
+
|
| 239 |
+
// Batch predictions
|
| 240 |
+
const testCases = [
|
| 241 |
+
{
|
| 242 |
+
controlImage: 'test1_control.jpg',
|
| 243 |
+
variantImage: 'test1_variant.jpg',
|
| 244 |
+
categories: {
|
| 245 |
+
businessModel: 'SaaS',
|
| 246 |
+
customerType: 'B2B',
|
| 247 |
+
conversionType: 'High-Intent Lead Gen',
|
| 248 |
+
industry: 'B2B Software & Tech',
|
| 249 |
+
pageType: 'Awareness & Discovery'
|
| 250 |
+
}
|
| 251 |
+
},
|
| 252 |
+
// Add more test cases...
|
| 253 |
+
];
|
| 254 |
+
|
| 255 |
+
const batchResults = await client.batchPredict(testCases);
|
| 256 |
+
console.log('Batch results:', JSON.stringify(batchResults, null, 2));
|
| 257 |
+
})();
|
| 258 |
+
|
| 259 |
+
|
| 260 |
+
// ============================================================================
|
| 261 |
+
// Option 3: Browser Example (Using File Input)
|
| 262 |
+
// ============================================================================
|
| 263 |
+
|
| 264 |
+
// HTML:
|
| 265 |
+
// <input type="file" id="controlImage" accept="image/*">
|
| 266 |
+
// <input type="file" id="variantImage" accept="image/*">
|
| 267 |
+
// <button onclick="predictFromBrowser()">Predict</button>
|
| 268 |
+
// <div id="results"></div>
|
| 269 |
+
|
| 270 |
+
async function predictFromBrowser() {
|
| 271 |
+
const controlFile = document.getElementById('controlImage').files[0];
|
| 272 |
+
const variantFile = document.getElementById('variantImage').files[0];
|
| 273 |
+
|
| 274 |
+
if (!controlFile || !variantFile) {
|
| 275 |
+
alert('Please select both images');
|
| 276 |
+
return;
|
| 277 |
+
}
|
| 278 |
+
|
| 279 |
+
// Convert files to base64
|
| 280 |
+
const controlB64 = await fileToBase64(controlFile);
|
| 281 |
+
const variantB64 = await fileToBase64(variantFile);
|
| 282 |
+
|
| 283 |
+
// Prepare payload
|
| 284 |
+
const payload = {
|
| 285 |
+
data: [
|
| 286 |
+
controlB64,
|
| 287 |
+
variantB64,
|
| 288 |
+
'SaaS',
|
| 289 |
+
'B2B',
|
| 290 |
+
'High-Intent Lead Gen',
|
| 291 |
+
'B2B Software & Tech',
|
| 292 |
+
'Awareness & Discovery'
|
| 293 |
+
]
|
| 294 |
+
};
|
| 295 |
+
|
| 296 |
+
try {
|
| 297 |
+
const response = await fetch('http://localhost:7860/api/predict', {
|
| 298 |
+
method: 'POST',
|
| 299 |
+
headers: {
|
| 300 |
+
'Content-Type': 'application/json'
|
| 301 |
+
},
|
| 302 |
+
body: JSON.stringify(payload)
|
| 303 |
+
});
|
| 304 |
+
|
| 305 |
+
const result = await response.json();
|
| 306 |
+
displayResults(result.data[0]);
|
| 307 |
+
|
| 308 |
+
} catch (error) {
|
| 309 |
+
alert('Error: ' + error.message);
|
| 310 |
+
}
|
| 311 |
+
}
|
| 312 |
+
|
| 313 |
+
function fileToBase64(file) {
|
| 314 |
+
return new Promise((resolve, reject) => {
|
| 315 |
+
const reader = new FileReader();
|
| 316 |
+
reader.onload = () => resolve(reader.result);
|
| 317 |
+
reader.onerror = reject;
|
| 318 |
+
reader.readAsDataURL(file);
|
| 319 |
+
});
|
| 320 |
+
}
|
| 321 |
+
|
| 322 |
+
function displayResults(data) {
|
| 323 |
+
const resultsDiv = document.getElementById('results');
|
| 324 |
+
resultsDiv.innerHTML = `
|
| 325 |
+
<h3>Prediction Results</h3>
|
| 326 |
+
<p>Win Probability: ${data.predictionResults.probability}</p>
|
| 327 |
+
<p>Model Confidence: ${data.predictionResults.modelConfidence}%</p>
|
| 328 |
+
<p>Training Samples: ${data.predictionResults.trainingDataSamples}</p>
|
| 329 |
+
<p>Total Predictions: ${data.predictionResults.totalPredictions}</p>
|
| 330 |
+
`;
|
| 331 |
+
}
|
| 332 |
+
|
| 333 |
+
|
| 334 |
+
// ============================================================================
|
| 335 |
+
// Option 4: Express.js Server Example
|
| 336 |
+
// ============================================================================
|
| 337 |
+
|
| 338 |
+
const express = require('express');
|
| 339 |
+
const multer = require('multer');
|
| 340 |
+
const upload = multer({ dest: 'uploads/' });
|
| 341 |
+
|
| 342 |
+
const app = express();
|
| 343 |
+
const client = new ABTestPredictorClient('http://localhost:7860');
|
| 344 |
+
|
| 345 |
+
app.post('/predict', upload.fields([
|
| 346 |
+
{ name: 'control', maxCount: 1 },
|
| 347 |
+
{ name: 'variant', maxCount: 1 }
|
| 348 |
+
]), async (req, res) => {
|
| 349 |
+
try {
|
| 350 |
+
const controlPath = req.files['control'][0].path;
|
| 351 |
+
const variantPath = req.files['variant'][0].path;
|
| 352 |
+
|
| 353 |
+
const categories = {
|
| 354 |
+
businessModel: req.body.businessModel,
|
| 355 |
+
customerType: req.body.customerType,
|
| 356 |
+
conversionType: req.body.conversionType,
|
| 357 |
+
industry: req.body.industry,
|
| 358 |
+
pageType: req.body.pageType
|
| 359 |
+
};
|
| 360 |
+
|
| 361 |
+
const result = await client.predict(controlPath, variantPath, categories);
|
| 362 |
+
|
| 363 |
+
// Clean up uploaded files
|
| 364 |
+
const fs = require('fs');
|
| 365 |
+
fs.unlinkSync(controlPath);
|
| 366 |
+
fs.unlinkSync(variantPath);
|
| 367 |
+
|
| 368 |
+
res.json(result);
|
| 369 |
+
|
| 370 |
+
} catch (error) {
|
| 371 |
+
res.status(500).json({
|
| 372 |
+
success: false,
|
| 373 |
+
error: error.message
|
| 374 |
+
});
|
| 375 |
+
}
|
| 376 |
+
});
|
| 377 |
+
|
| 378 |
+
app.listen(3000, () => {
|
| 379 |
+
console.log('Proxy server running on port 3000');
|
| 380 |
+
});
|
| 381 |
+
|
DEPLOYMENT_FIX.md
ADDED
|
@@ -0,0 +1,164 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Hugging Face Spaces Deployment Fix
|
| 2 |
+
|
| 3 |
+
## Problem
|
| 4 |
+
Build was failing with exit code 1 due to cache issues and potentially large/unnecessary files being uploaded.
|
| 5 |
+
|
| 6 |
+
## Solutions Applied
|
| 7 |
+
|
| 8 |
+
### 1. Created `.dockerignore`
|
| 9 |
+
This file prevents unnecessary files from being included in the Docker build:
|
| 10 |
+
- Documentation files (API examples, setup guides, etc.)
|
| 11 |
+
- Development files (.git, .env, etc.)
|
| 12 |
+
- Unused files from previous version (patterbs.json, metadata.js, confidence_scores.js)
|
| 13 |
+
- IDE and test files
|
| 14 |
+
|
| 15 |
+
### 2. Created `.gitattributes`
|
| 16 |
+
Ensures large model files are properly tracked with Git LFS:
|
| 17 |
+
- *.pth files (your model)
|
| 18 |
+
- Other common model formats
|
| 19 |
+
|
| 20 |
+
### 3. Files That Can Be Deleted (No Longer Needed)
|
| 21 |
+
|
| 22 |
+
These files are from the old AI API version and are no longer used:
|
| 23 |
+
```bash
|
| 24 |
+
# Can be safely deleted
|
| 25 |
+
rm patterbs.json # 269KB - Pattern descriptions (no longer used)
|
| 26 |
+
rm confidence_scores.js # 15KB - JS version of confidence data
|
| 27 |
+
rm metadata.js # 14KB - JS version of metadata
|
| 28 |
+
rm frontend.html # Old frontend (if not using)
|
| 29 |
+
rm index_v2.html # Old frontend (if not using)
|
| 30 |
+
rm API_USAGE.md # Old API docs (replaced by API_USAGE_UPDATED.md)
|
| 31 |
+
```
|
| 32 |
+
|
| 33 |
+
### 4. Essential Files for Deployment
|
| 34 |
+
|
| 35 |
+
Keep these files:
|
| 36 |
+
```
|
| 37 |
+
β
app.py # Main application
|
| 38 |
+
β
requirements.txt # Python dependencies
|
| 39 |
+
β
packages.txt # System dependencies (tesseract-ocr)
|
| 40 |
+
β
README.md # Project documentation
|
| 41 |
+
β
confidence_scores.json # Confidence data (NEEDED)
|
| 42 |
+
β
model/multimodal_gated_model_2.7_GGG.pth # Model weights
|
| 43 |
+
β
model/multimodal_cat_mappings_GGG.json # Category mappings
|
| 44 |
+
β
.gitattributes # Git LFS configuration
|
| 45 |
+
β
.dockerignore # Docker build optimization
|
| 46 |
+
```
|
| 47 |
+
|
| 48 |
+
### 5. Deployment Steps
|
| 49 |
+
|
| 50 |
+
1. **Clean up unnecessary files:**
|
| 51 |
+
```bash
|
| 52 |
+
cd /Users/nitish/Spiralyze/HuggingFace/Spaces/ABTestPredictor
|
| 53 |
+
|
| 54 |
+
# Optional: Delete unused files
|
| 55 |
+
rm patterbs.json confidence_scores.js metadata.js frontend.html index_v2.html API_USAGE.md
|
| 56 |
+
```
|
| 57 |
+
|
| 58 |
+
2. **Verify Git LFS is tracking the model:**
|
| 59 |
+
```bash
|
| 60 |
+
git lfs ls-files
|
| 61 |
+
# Should show: model/multimodal_gated_model_2.7_GGG.pth
|
| 62 |
+
```
|
| 63 |
+
|
| 64 |
+
3. **Commit and push changes:**
|
| 65 |
+
```bash
|
| 66 |
+
git add .
|
| 67 |
+
git commit -m "Fix: Remove AI API dependencies and optimize for deployment"
|
| 68 |
+
git push
|
| 69 |
+
```
|
| 70 |
+
|
| 71 |
+
4. **If model file isn't tracked by LFS:**
|
| 72 |
+
```bash
|
| 73 |
+
# Track it
|
| 74 |
+
git lfs track "*.pth"
|
| 75 |
+
git add .gitattributes
|
| 76 |
+
git add model/multimodal_gated_model_2.7_GGG.pth
|
| 77 |
+
git commit -m "Track model file with Git LFS"
|
| 78 |
+
git push
|
| 79 |
+
```
|
| 80 |
+
|
| 81 |
+
### 6. Hugging Face Spaces Settings
|
| 82 |
+
|
| 83 |
+
Make sure your Space is configured correctly:
|
| 84 |
+
- **SDK**: Gradio
|
| 85 |
+
- **SDK Version**: 4.44.0 (as specified in README.md)
|
| 86 |
+
- **Hardware**: GPU (T4, A10G, or better recommended)
|
| 87 |
+
- **No environment variables needed** (we removed API keys)
|
| 88 |
+
|
| 89 |
+
### 7. Common Build Issues & Fixes
|
| 90 |
+
|
| 91 |
+
#### Issue: "File too large"
|
| 92 |
+
**Solution**: Use Git LFS for files > 10MB
|
| 93 |
+
```bash
|
| 94 |
+
git lfs track "*.pth"
|
| 95 |
+
git add .gitattributes
|
| 96 |
+
git commit -m "Add Git LFS tracking"
|
| 97 |
+
```
|
| 98 |
+
|
| 99 |
+
#### Issue: "Package installation failed"
|
| 100 |
+
**Solution**: Check requirements.txt for conflicts
|
| 101 |
+
```bash
|
| 102 |
+
# Test locally first
|
| 103 |
+
pip install -r requirements.txt
|
| 104 |
+
```
|
| 105 |
+
|
| 106 |
+
#### Issue: "Tesseract not found"
|
| 107 |
+
**Solution**: Ensure packages.txt contains `tesseract-ocr`
|
| 108 |
+
|
| 109 |
+
#### Issue: "Model file not found"
|
| 110 |
+
**Solution**: Verify model files are in the correct location:
|
| 111 |
+
- `model/multimodal_gated_model_2.7_GGG.pth`
|
| 112 |
+
- `model/multimodal_cat_mappings_GGG.json`
|
| 113 |
+
|
| 114 |
+
### 8. Testing Deployment
|
| 115 |
+
|
| 116 |
+
Once deployed, test the API:
|
| 117 |
+
|
| 118 |
+
**Using Gradio Interface:**
|
| 119 |
+
1. Go to your Space URL
|
| 120 |
+
2. Upload test images
|
| 121 |
+
3. Select categories
|
| 122 |
+
4. Click "Predict"
|
| 123 |
+
|
| 124 |
+
**Using Python Client:**
|
| 125 |
+
```python
|
| 126 |
+
from gradio_client import Client
|
| 127 |
+
|
| 128 |
+
client = Client("your-username/ABTestPredictor")
|
| 129 |
+
result = client.predict(
|
| 130 |
+
"control.jpg",
|
| 131 |
+
"variant.jpg",
|
| 132 |
+
"SaaS", "B2B", "High-Intent Lead Gen",
|
| 133 |
+
"B2B Software & Tech", "Awareness & Discovery",
|
| 134 |
+
api_name="/predict_with_categorical_data"
|
| 135 |
+
)
|
| 136 |
+
print(result)
|
| 137 |
+
```
|
| 138 |
+
|
| 139 |
+
### 9. Monitoring
|
| 140 |
+
|
| 141 |
+
Check build logs in Hugging Face Spaces:
|
| 142 |
+
- Go to your Space
|
| 143 |
+
- Click "Logs" tab
|
| 144 |
+
- Look for any errors during build or runtime
|
| 145 |
+
|
| 146 |
+
### 10. Rollback Plan
|
| 147 |
+
|
| 148 |
+
If issues persist:
|
| 149 |
+
1. Check previous working commit: `git log`
|
| 150 |
+
2. Revert: `git revert <commit-hash>`
|
| 151 |
+
3. Or reset: `git reset --hard <working-commit-hash>`
|
| 152 |
+
4. Force push: `git push --force` (β οΈ only if needed)
|
| 153 |
+
|
| 154 |
+
## Expected Build Time
|
| 155 |
+
- First build: 5-10 minutes (downloading model, installing packages)
|
| 156 |
+
- Subsequent builds: 2-3 minutes (cached layers)
|
| 157 |
+
|
| 158 |
+
## Success Indicators
|
| 159 |
+
β
Build completes without errors
|
| 160 |
+
β
Space status shows "Running"
|
| 161 |
+
β
Gradio interface loads correctly
|
| 162 |
+
β
Can make predictions successfully
|
| 163 |
+
β
Response includes confidence scores
|
| 164 |
+
|
QUICK_FIX_SUMMARY.md
ADDED
|
@@ -0,0 +1,134 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Quick Fix for Build Error - Summary
|
| 2 |
+
|
| 3 |
+
## What Was the Problem?
|
| 4 |
+
The Hugging Face Spaces build was failing because:
|
| 5 |
+
1. Node.js dependency for loading metadata.js (no longer needed)
|
| 6 |
+
2. Unnecessary large files (patterbs.json - 269KB) being uploaded
|
| 7 |
+
3. No `.dockerignore` to exclude documentation files
|
| 8 |
+
4. Git LFS might not be properly configured
|
| 9 |
+
|
| 10 |
+
## What Was Fixed?
|
| 11 |
+
|
| 12 |
+
### 1. β
Simplified Category Mapping Loading
|
| 13 |
+
**Changed**: Removed Node.js dependency for loading metadata.js
|
| 14 |
+
**File**: `app.py` lines 172-184
|
| 15 |
+
**Result**: Now creates default mappings directly in Python
|
| 16 |
+
|
| 17 |
+
### 2. β
Created `.dockerignore`
|
| 18 |
+
**Purpose**: Excludes unnecessary files from Docker build:
|
| 19 |
+
- Documentation files (API examples, guides)
|
| 20 |
+
- Unused files (patterbs.json, metadata.js, confidence_scores.js)
|
| 21 |
+
- Development files (.git, .env, IDEs)
|
| 22 |
+
|
| 23 |
+
### 3. β
Created `.gitattributes`
|
| 24 |
+
**Purpose**: Ensures model files are properly tracked with Git LFS
|
| 25 |
+
|
| 26 |
+
## Next Steps
|
| 27 |
+
|
| 28 |
+
### Step 1: Clean Up (Optional but Recommended)
|
| 29 |
+
```bash
|
| 30 |
+
cd /Users/nitish/Spiralyze/HuggingFace/Spaces/ABTestPredictor
|
| 31 |
+
|
| 32 |
+
# Delete files that are no longer needed
|
| 33 |
+
rm patterbs.json # Pattern descriptions (no longer used)
|
| 34 |
+
rm confidence_scores.js # JS version (not needed, we use .json)
|
| 35 |
+
rm metadata.js # JS version (not needed)
|
| 36 |
+
rm frontend.html # Old frontend (if using Gradio only)
|
| 37 |
+
rm index_v2.html # Old frontend (if using Gradio only)
|
| 38 |
+
```
|
| 39 |
+
|
| 40 |
+
### Step 2: Commit and Push
|
| 41 |
+
```bash
|
| 42 |
+
# Add all changes
|
| 43 |
+
git add .
|
| 44 |
+
|
| 45 |
+
# Commit
|
| 46 |
+
git commit -m "Fix: Remove Node.js dependency and optimize for Hugging Face Spaces deployment"
|
| 47 |
+
|
| 48 |
+
# Push to Hugging Face Spaces
|
| 49 |
+
git push
|
| 50 |
+
```
|
| 51 |
+
|
| 52 |
+
### Step 3: Verify Model LFS Tracking
|
| 53 |
+
```bash
|
| 54 |
+
# Check if model is tracked by Git LFS
|
| 55 |
+
git lfs ls-files
|
| 56 |
+
|
| 57 |
+
# Should show: model/multimodal_gated_model_2.7_GGG.pth
|
| 58 |
+
```
|
| 59 |
+
|
| 60 |
+
If the model ISN'T shown, track it:
|
| 61 |
+
```bash
|
| 62 |
+
git lfs track "model/*.pth"
|
| 63 |
+
git add .gitattributes
|
| 64 |
+
git add model/multimodal_gated_model_2.7_GGG.pth
|
| 65 |
+
git commit -m "Add Git LFS tracking for model file"
|
| 66 |
+
git push
|
| 67 |
+
```
|
| 68 |
+
|
| 69 |
+
## Files That MUST Exist for Deployment
|
| 70 |
+
|
| 71 |
+
```
|
| 72 |
+
β
app.py # Main application
|
| 73 |
+
β
requirements.txt # Python dependencies
|
| 74 |
+
β
packages.txt # System dependencies
|
| 75 |
+
β
README.md # Documentation
|
| 76 |
+
β
confidence_scores.json # Confidence data
|
| 77 |
+
β
model/multimodal_gated_model_2.7_GGG.pth # Model (tracked by LFS)
|
| 78 |
+
β
model/multimodal_cat_mappings_GGG.json # Category mappings
|
| 79 |
+
β
.gitattributes # LFS configuration
|
| 80 |
+
β
.dockerignore # Build optimization
|
| 81 |
+
```
|
| 82 |
+
|
| 83 |
+
## Expected Result After Fix
|
| 84 |
+
|
| 85 |
+
- β
Build should complete successfully in 5-10 minutes
|
| 86 |
+
- β
No Node.js errors
|
| 87 |
+
- β
Smaller Docker image (excluded unnecessary files)
|
| 88 |
+
- β
Space status: "Running"
|
| 89 |
+
- β
API accessible and functional
|
| 90 |
+
|
| 91 |
+
## Test After Deployment
|
| 92 |
+
|
| 93 |
+
```python
|
| 94 |
+
from gradio_client import Client
|
| 95 |
+
|
| 96 |
+
client = Client("your-username/ABTestPredictor")
|
| 97 |
+
result = client.predict(
|
| 98 |
+
"control.jpg",
|
| 99 |
+
"variant.jpg",
|
| 100 |
+
"SaaS", "B2B", "High-Intent Lead Gen",
|
| 101 |
+
"B2B Software & Tech", "Awareness & Discovery",
|
| 102 |
+
api_name="/predict_with_categorical_data"
|
| 103 |
+
)
|
| 104 |
+
print(result)
|
| 105 |
+
```
|
| 106 |
+
|
| 107 |
+
## If Build Still Fails
|
| 108 |
+
|
| 109 |
+
1. Check logs in Hugging Face Spaces
|
| 110 |
+
2. Verify all required files are present
|
| 111 |
+
3. Check that model file is tracked by Git LFS: `git lfs ls-files`
|
| 112 |
+
4. Ensure packages.txt contains `tesseract-ocr`
|
| 113 |
+
5. Review DEPLOYMENT_FIX.md for detailed troubleshooting
|
| 114 |
+
|
| 115 |
+
## Changes Made to Files
|
| 116 |
+
|
| 117 |
+
### app.py
|
| 118 |
+
- Removed Node.js subprocess calls
|
| 119 |
+
- Simplified category mapping creation
|
| 120 |
+
- No external dependencies
|
| 121 |
+
|
| 122 |
+
### New Files
|
| 123 |
+
- `.dockerignore` - Excludes unnecessary files from build
|
| 124 |
+
- `.gitattributes` - Ensures proper Git LFS tracking
|
| 125 |
+
- `DEPLOYMENT_FIX.md` - Detailed troubleshooting guide
|
| 126 |
+
- `QUICK_FIX_SUMMARY.md` - This file
|
| 127 |
+
|
| 128 |
+
## Key Improvements
|
| 129 |
+
|
| 130 |
+
π **Faster builds**: Excluded documentation reduces build time
|
| 131 |
+
π§ **Simpler deployment**: No Node.js dependency
|
| 132 |
+
π¦ **Smaller images**: Only essential files included
|
| 133 |
+
β
**Better reliability**: Standard Python-only approach
|
| 134 |
+
|
app.py
CHANGED
|
@@ -170,47 +170,18 @@ if not os.path.exists(MODEL_DIR):
|
|
| 170 |
os.makedirs(MODEL_DIR)
|
| 171 |
|
| 172 |
if not os.path.exists(CAT_MAPPINGS_SAVE_PATH):
|
| 173 |
-
print(f"β οΈ GGG Category mappings not found.
|
| 174 |
-
#
|
| 175 |
-
|
| 176 |
-
|
| 177 |
-
|
| 178 |
-
|
| 179 |
-
|
| 180 |
-
|
| 181 |
-
|
| 182 |
-
|
| 183 |
-
|
| 184 |
-
|
| 185 |
-
if result.returncode == 0:
|
| 186 |
-
category_mappings_from_js = json.loads(result.stdout.strip())
|
| 187 |
-
print(f"β
Successfully loaded category mappings from metadata.js for GGG model")
|
| 188 |
-
with open(CAT_MAPPINGS_SAVE_PATH, 'w') as f:
|
| 189 |
-
json.dump(category_mappings_from_js, f, indent=2)
|
| 190 |
-
else:
|
| 191 |
-
print(f"β οΈ Failed to load from metadata.js: {result.stderr}")
|
| 192 |
-
print("Creating GGG-compatible dummy mappings as fallback...")
|
| 193 |
-
dummy_mappings = {
|
| 194 |
-
"Business Model": {"num_categories": 4, "categories": ["E-Commerce", "Lead Generation", "Other*", "SaaS"]},
|
| 195 |
-
"Customer Type": {"num_categories": 4, "categories": ["B2B", "B2C", "Both", "Other*"]},
|
| 196 |
-
"grouped_conversion_type": {"num_categories": 6, "categories": ["Direct Purchase", "High-Intent Lead Gen", "Info/Content Lead Gen", "Location Search", "Non-Profit/Community", "Other Conversion"]},
|
| 197 |
-
"grouped_industry": {"num_categories": 14, "categories": ["Automotive & Transportation", "B2B Services", "B2B Software & Tech", "Consumer Services", "Consumer Software & Apps", "Education", "Finance, Insurance & Real Estate", "Food, Hospitality & Travel", "Health & Wellness", "Industrial & Manufacturing", "Media & Entertainment", "Non-Profit & Government", "Other", "Retail & E-commerce"]},
|
| 198 |
-
"grouped_page_type": {"num_categories": 5, "categories": ["Awareness & Discovery", "Consideration & Evaluation", "Conversion", "Internal & Navigation", "Post-Conversion & Other"]}
|
| 199 |
-
}
|
| 200 |
-
with open(CAT_MAPPINGS_SAVE_PATH, 'w') as f:
|
| 201 |
-
json.dump(dummy_mappings, f, indent=2)
|
| 202 |
-
except Exception as e:
|
| 203 |
-
print(f"β οΈ Error loading metadata.js: {e}")
|
| 204 |
-
print("Creating GGG-compatible dummy mappings as fallback...")
|
| 205 |
-
dummy_mappings = {
|
| 206 |
-
"Business Model": {"num_categories": 4, "categories": ["E-Commerce", "Lead Generation", "Other*", "SaaS"]},
|
| 207 |
-
"Customer Type": {"num_categories": 4, "categories": ["B2B", "B2C", "Both", "Other*"]},
|
| 208 |
-
"grouped_conversion_type": {"num_categories": 6, "categories": ["Direct Purchase", "High-Intent Lead Gen", "Info/Content Lead Gen", "Location Search", "Non-Profit/Community", "Other Conversion"]},
|
| 209 |
-
"grouped_industry": {"num_categories": 14, "categories": ["Automotive & Transportation", "B2B Services", "B2B Software & Tech", "Consumer Services", "Consumer Software & Apps", "Education", "Finance, Insurance & Real Estate", "Food, Hospitality & Travel", "Health & Wellness", "Industrial & Manufacturing", "Media & Entertainment", "Non-Profit & Government", "Other", "Retail & E-commerce"]},
|
| 210 |
-
"grouped_page_type": {"num_categories": 5, "categories": ["Awareness & Discovery", "Consideration & Evaluation", "Conversion", "Internal & Navigation", "Post-Conversion & Other"]}
|
| 211 |
-
}
|
| 212 |
-
with open(CAT_MAPPINGS_SAVE_PATH, 'w') as f:
|
| 213 |
-
json.dump(dummy_mappings, f, indent=2)
|
| 214 |
|
| 215 |
with open(CAT_MAPPINGS_SAVE_PATH, 'r') as f:
|
| 216 |
category_mappings = json.load(f)
|
|
|
|
| 170 |
os.makedirs(MODEL_DIR)
|
| 171 |
|
| 172 |
if not os.path.exists(CAT_MAPPINGS_SAVE_PATH):
|
| 173 |
+
print(f"β οΈ GGG Category mappings not found. Creating default mappings...")
|
| 174 |
+
# Create the standard category mappings expected by the model
|
| 175 |
+
default_mappings = {
|
| 176 |
+
"Business Model": {"num_categories": 4, "categories": ["E-Commerce", "Lead Generation", "Other*", "SaaS"]},
|
| 177 |
+
"Customer Type": {"num_categories": 4, "categories": ["B2B", "B2C", "Both", "Other*"]},
|
| 178 |
+
"grouped_conversion_type": {"num_categories": 6, "categories": ["Direct Purchase", "High-Intent Lead Gen", "Info/Content Lead Gen", "Location Search", "Non-Profit/Community", "Other Conversion"]},
|
| 179 |
+
"grouped_industry": {"num_categories": 14, "categories": ["Automotive & Transportation", "B2B Services", "B2B Software & Tech", "Consumer Services", "Consumer Software & Apps", "Education", "Finance, Insurance & Real Estate", "Food, Hospitality & Travel", "Health & Wellness", "Industrial & Manufacturing", "Media & Entertainment", "Non-Profit & Government", "Other", "Retail & E-commerce"]},
|
| 180 |
+
"grouped_page_type": {"num_categories": 5, "categories": ["Awareness & Discovery", "Consideration & Evaluation", "Conversion", "Internal & Navigation", "Post-Conversion & Other"]}
|
| 181 |
+
}
|
| 182 |
+
with open(CAT_MAPPINGS_SAVE_PATH, 'w') as f:
|
| 183 |
+
json.dump(default_mappings, f, indent=2)
|
| 184 |
+
print(f"β
Created default category mappings at {CAT_MAPPINGS_SAVE_PATH}")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 185 |
|
| 186 |
with open(CAT_MAPPINGS_SAVE_PATH, 'r') as f:
|
| 187 |
category_mappings = json.load(f)
|