amitlals
commited on
Commit
·
a75037d
1
Parent(s):
dcfc478
Deploy to HF Spaces - Gradio app with Azure Container Apps support
Browse files- .dockerignore +74 -0
- DEPLOYMENT.md +0 -0
- DEPLOYMENT_INSTRUCTIONS.md +257 -0
- Dockerfile +48 -0
- README.md +26 -134
- README_HF.md +37 -0
- app.py +0 -451
- app_gradio.py +7 -1
- deploy-azure.ps1 +135 -0
- requirements.txt +1 -2
.dockerignore
ADDED
|
@@ -0,0 +1,74 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Python
|
| 2 |
+
__pycache__/
|
| 3 |
+
*.py[cod]
|
| 4 |
+
*$py.class
|
| 5 |
+
*.so
|
| 6 |
+
.Python
|
| 7 |
+
build/
|
| 8 |
+
develop-eggs/
|
| 9 |
+
dist/
|
| 10 |
+
downloads/
|
| 11 |
+
eggs/
|
| 12 |
+
.eggs/
|
| 13 |
+
lib/
|
| 14 |
+
lib64/
|
| 15 |
+
parts/
|
| 16 |
+
sdist/
|
| 17 |
+
var/
|
| 18 |
+
wheels/
|
| 19 |
+
*.egg-info/
|
| 20 |
+
.installed.cfg
|
| 21 |
+
*.egg
|
| 22 |
+
|
| 23 |
+
# Virtual environments
|
| 24 |
+
venv/
|
| 25 |
+
ENV/
|
| 26 |
+
env/
|
| 27 |
+
.venv/
|
| 28 |
+
|
| 29 |
+
# IDE
|
| 30 |
+
.idea/
|
| 31 |
+
.vscode/
|
| 32 |
+
*.swp
|
| 33 |
+
*.swo
|
| 34 |
+
*~
|
| 35 |
+
|
| 36 |
+
# Git
|
| 37 |
+
.git/
|
| 38 |
+
.gitignore
|
| 39 |
+
|
| 40 |
+
# Environment files (secrets)
|
| 41 |
+
.env
|
| 42 |
+
.env.local
|
| 43 |
+
.env.*.local
|
| 44 |
+
|
| 45 |
+
# Testing
|
| 46 |
+
.pytest_cache/
|
| 47 |
+
.coverage
|
| 48 |
+
htmlcov/
|
| 49 |
+
.tox/
|
| 50 |
+
|
| 51 |
+
# Documentation
|
| 52 |
+
docs/
|
| 53 |
+
*.md
|
| 54 |
+
!README.md
|
| 55 |
+
|
| 56 |
+
# Logs
|
| 57 |
+
*.log
|
| 58 |
+
logs/
|
| 59 |
+
|
| 60 |
+
# Temporary files
|
| 61 |
+
tmp/
|
| 62 |
+
temp/
|
| 63 |
+
*.tmp
|
| 64 |
+
|
| 65 |
+
# OS files
|
| 66 |
+
.DS_Store
|
| 67 |
+
Thumbs.db
|
| 68 |
+
|
| 69 |
+
# Deployment files (not needed in container)
|
| 70 |
+
.github/
|
| 71 |
+
*.yml
|
| 72 |
+
!requirements.txt
|
| 73 |
+
azure-deploy.sh
|
| 74 |
+
deploy.ps1
|
DEPLOYMENT.md
ADDED
|
File without changes
|
DEPLOYMENT_INSTRUCTIONS.md
ADDED
|
@@ -0,0 +1,257 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# SAP Finance Dashboard - Azure Container Apps Deployment Guide
|
| 2 |
+
|
| 3 |
+
## Prerequisites
|
| 4 |
+
|
| 5 |
+
1. **Azure CLI** - Install from: https://aka.ms/installazurecliwindows
|
| 6 |
+
2. **Azure Subscription** - Active Azure account
|
| 7 |
+
3. **Git** (optional) - For code updates
|
| 8 |
+
|
| 9 |
+
## Quick Deploy Steps
|
| 10 |
+
|
| 11 |
+
### Option 1: Automated Deployment Script
|
| 12 |
+
|
| 13 |
+
Open PowerShell as Administrator and run:
|
| 14 |
+
|
| 15 |
+
```powershell
|
| 16 |
+
cd "c:\Users\amlal\Downloads\VSCode-SAP-AI-Copilot-Projects2025\SAP-RPT-1-OSS-App"
|
| 17 |
+
.\deploy-azure.ps1
|
| 18 |
+
```
|
| 19 |
+
|
| 20 |
+
The script will:
|
| 21 |
+
- ✅ Create Azure Resource Group
|
| 22 |
+
- ✅ Create Azure Container Registry
|
| 23 |
+
- ✅ Build Docker image in Azure (no local Docker needed)
|
| 24 |
+
- ✅ Create Container Apps Environment
|
| 25 |
+
- ✅ Deploy your application
|
| 26 |
+
- ✅ Provide public URL
|
| 27 |
+
|
| 28 |
+
**Deployment Time:** ~10-15 minutes
|
| 29 |
+
|
| 30 |
+
### Option 2: Manual Deployment (Step-by-Step)
|
| 31 |
+
|
| 32 |
+
If the automated script doesn't work, follow these manual commands:
|
| 33 |
+
|
| 34 |
+
#### 1. Login to Azure
|
| 35 |
+
```bash
|
| 36 |
+
az login
|
| 37 |
+
```
|
| 38 |
+
|
| 39 |
+
#### 2. Set Variables
|
| 40 |
+
```bash
|
| 41 |
+
set RESOURCE_GROUP=rg-sap-finance-dashboard
|
| 42 |
+
set LOCATION=eastus
|
| 43 |
+
set ACR_NAME=acrsapfinance%RANDOM%
|
| 44 |
+
set ENVIRONMENT_NAME=env-sap-finance
|
| 45 |
+
set APP_NAME=sap-finance-dashboard
|
| 46 |
+
```
|
| 47 |
+
|
| 48 |
+
#### 3. Create Resource Group
|
| 49 |
+
```bash
|
| 50 |
+
az group create --name %RESOURCE_GROUP% --location %LOCATION%
|
| 51 |
+
```
|
| 52 |
+
|
| 53 |
+
#### 4. Create Container Registry
|
| 54 |
+
```bash
|
| 55 |
+
az acr create --resource-group %RESOURCE_GROUP% --name %ACR_NAME% --sku Basic --admin-enabled true
|
| 56 |
+
```
|
| 57 |
+
|
| 58 |
+
#### 5. Build Docker Image (Using Azure - No local Docker needed)
|
| 59 |
+
```bash
|
| 60 |
+
az acr build --registry %ACR_NAME% --image sap-finance-dashboard:latest .
|
| 61 |
+
```
|
| 62 |
+
|
| 63 |
+
#### 6. Create Container Apps Environment
|
| 64 |
+
```bash
|
| 65 |
+
az containerapp env create --name %ENVIRONMENT_NAME% --resource-group %RESOURCE_GROUP% --location %LOCATION%
|
| 66 |
+
```
|
| 67 |
+
|
| 68 |
+
#### 7. Deploy Container App
|
| 69 |
+
```bash
|
| 70 |
+
az containerapp create ^
|
| 71 |
+
--name %APP_NAME% ^
|
| 72 |
+
--resource-group %RESOURCE_GROUP% ^
|
| 73 |
+
--environment %ENVIRONMENT_NAME% ^
|
| 74 |
+
--image %ACR_NAME%.azurecr.io/sap-finance-dashboard:latest ^
|
| 75 |
+
--registry-server %ACR_NAME%.azurecr.io ^
|
| 76 |
+
--target-port 7862 ^
|
| 77 |
+
--ingress external ^
|
| 78 |
+
--cpu 2 ^
|
| 79 |
+
--memory 4Gi ^
|
| 80 |
+
--min-replicas 1 ^
|
| 81 |
+
--max-replicas 3
|
| 82 |
+
```
|
| 83 |
+
|
| 84 |
+
#### 8. Get Application URL
|
| 85 |
+
```bash
|
| 86 |
+
az containerapp show --name %APP_NAME% --resource-group %RESOURCE_GROUP% --query properties.configuration.ingress.fqdn -o tsv
|
| 87 |
+
```
|
| 88 |
+
|
| 89 |
+
## Post-Deployment Configuration
|
| 90 |
+
|
| 91 |
+
### Add Hugging Face Token (Required for AI Features)
|
| 92 |
+
|
| 93 |
+
1. Go to Azure Portal: https://portal.azure.com
|
| 94 |
+
2. Navigate to your Container App
|
| 95 |
+
3. Go to **Settings** > **Secrets**
|
| 96 |
+
4. Add secret:
|
| 97 |
+
- Name: `huggingface-token`
|
| 98 |
+
- Value: Your Hugging Face token (get from https://huggingface.co/settings/tokens)
|
| 99 |
+
5. Go to **Settings** > **Containers** > **Environment variables**
|
| 100 |
+
6. Add environment variable:
|
| 101 |
+
- Name: `HUGGINGFACE_TOKEN`
|
| 102 |
+
- Source: Reference a secret
|
| 103 |
+
- Value: Select `huggingface-token`
|
| 104 |
+
7. Click **Save** and wait for app to restart
|
| 105 |
+
|
| 106 |
+
### Optional: Add SAP OData Credentials
|
| 107 |
+
|
| 108 |
+
If connecting to SAP systems:
|
| 109 |
+
- `SAP_ODATA_BASE_URL`: Your SAP OData endpoint
|
| 110 |
+
- `SAP_USERNAME`: SAP username (as secret)
|
| 111 |
+
- `SAP_PASSWORD`: SAP password (as secret)
|
| 112 |
+
|
| 113 |
+
## Application Architecture
|
| 114 |
+
|
| 115 |
+
```
|
| 116 |
+
┌─────────────────────────────────────────┐
|
| 117 |
+
│ Azure Container Apps │
|
| 118 |
+
│ ┌───────────────────────────────────┐ │
|
| 119 |
+
│ │ SAP Finance Dashboard │ │
|
| 120 |
+
│ │ - Gradio Web Interface │ │
|
| 121 |
+
│ │ - RPT-1-OSS AI Model │ │
|
| 122 |
+
│ │ - Port 7862 │ │
|
| 123 |
+
│ └───────────────────────────────────┘ │
|
| 124 |
+
│ ↕ │
|
| 125 |
+
│ ┌───────────────────────────────────┐ │
|
| 126 |
+
│ │ Azure Container Registry │ │
|
| 127 |
+
│ │ - Docker Image Storage │ │
|
| 128 |
+
│ └───────────────────────────────────┘ │
|
| 129 |
+
└─────────────────────────────────────────┘
|
| 130 |
+
```
|
| 131 |
+
|
| 132 |
+
## Application Features
|
| 133 |
+
|
| 134 |
+
Once deployed, you'll have access to:
|
| 135 |
+
|
| 136 |
+
1. **📊 Dashboard** - Financial metrics and visualizations
|
| 137 |
+
2. **🔍 Data Explorer** - Browse datasets
|
| 138 |
+
3. **📤 Upload** - Upload custom CSV files
|
| 139 |
+
4. **🤖 AI Predictions** - SAP-RPT-1-OSS powered predictions
|
| 140 |
+
5. **🔗 OData** - Connect to SAP systems
|
| 141 |
+
6. **🎮 Playground** - Custom model training
|
| 142 |
+
|
| 143 |
+
## Scaling Configuration
|
| 144 |
+
|
| 145 |
+
The default configuration:
|
| 146 |
+
- **Min Replicas:** 1 (always running)
|
| 147 |
+
- **Max Replicas:** 3 (auto-scale under load)
|
| 148 |
+
- **CPU:** 2 cores
|
| 149 |
+
- **Memory:** 4 GB
|
| 150 |
+
|
| 151 |
+
To adjust scaling:
|
| 152 |
+
```bash
|
| 153 |
+
az containerapp update ^
|
| 154 |
+
--name %APP_NAME% ^
|
| 155 |
+
--resource-group %RESOURCE_GROUP% ^
|
| 156 |
+
--min-replicas 1 ^
|
| 157 |
+
--max-replicas 5 ^
|
| 158 |
+
--cpu 4 ^
|
| 159 |
+
--memory 8Gi
|
| 160 |
+
```
|
| 161 |
+
|
| 162 |
+
## Cost Optimization
|
| 163 |
+
|
| 164 |
+
**Estimated Monthly Cost (East US):**
|
| 165 |
+
- Container Apps (1 replica, 2 vCPU, 4GB): ~$60/month
|
| 166 |
+
- Container Registry (Basic): ~$5/month
|
| 167 |
+
- **Total:** ~$65/month
|
| 168 |
+
|
| 169 |
+
**To reduce costs:**
|
| 170 |
+
1. Set min-replicas to 0 (app sleeps when not in use)
|
| 171 |
+
2. Use smaller CPU/memory allocation
|
| 172 |
+
3. Delete when not needed:
|
| 173 |
+
```bash
|
| 174 |
+
az group delete --name %RESOURCE_GROUP% --yes
|
| 175 |
+
```
|
| 176 |
+
|
| 177 |
+
## Monitoring & Logs
|
| 178 |
+
|
| 179 |
+
### View Live Logs
|
| 180 |
+
```bash
|
| 181 |
+
az containerapp logs show --name %APP_NAME% --resource-group %RESOURCE_GROUP% --follow
|
| 182 |
+
```
|
| 183 |
+
|
| 184 |
+
### View Metrics (Azure Portal)
|
| 185 |
+
1. Go to your Container App
|
| 186 |
+
2. Click **Monitoring** > **Metrics**
|
| 187 |
+
3. View:
|
| 188 |
+
- CPU usage
|
| 189 |
+
- Memory usage
|
| 190 |
+
- Request count
|
| 191 |
+
- Response time
|
| 192 |
+
|
| 193 |
+
## Updating Your Application
|
| 194 |
+
|
| 195 |
+
When you make code changes:
|
| 196 |
+
|
| 197 |
+
```bash
|
| 198 |
+
# Build new image
|
| 199 |
+
az acr build --registry %ACR_NAME% --image sap-finance-dashboard:latest .
|
| 200 |
+
|
| 201 |
+
# Update container app
|
| 202 |
+
az containerapp update ^
|
| 203 |
+
--name %APP_NAME% ^
|
| 204 |
+
--resource-group %RESOURCE_GROUP% ^
|
| 205 |
+
--image %ACR_NAME%.azurecr.io/sap-finance-dashboard:latest
|
| 206 |
+
```
|
| 207 |
+
|
| 208 |
+
## Troubleshooting
|
| 209 |
+
|
| 210 |
+
### App not starting?
|
| 211 |
+
Check logs:
|
| 212 |
+
```bash
|
| 213 |
+
az containerapp logs show --name %APP_NAME% --resource-group %RESOURCE_GROUP% --tail 100
|
| 214 |
+
```
|
| 215 |
+
|
| 216 |
+
### Out of memory?
|
| 217 |
+
Increase memory allocation:
|
| 218 |
+
```bash
|
| 219 |
+
az containerapp update --name %APP_NAME% --resource-group %RESOURCE_GROUP% --memory 8Gi
|
| 220 |
+
```
|
| 221 |
+
|
| 222 |
+
### Port issues?
|
| 223 |
+
Verify port is set to 7862:
|
| 224 |
+
```bash
|
| 225 |
+
az containerapp show --name %APP_NAME% --resource-group %RESOURCE_GROUP% --query properties.configuration.ingress
|
| 226 |
+
```
|
| 227 |
+
|
| 228 |
+
### Can't access URL?
|
| 229 |
+
Check ingress is enabled:
|
| 230 |
+
```bash
|
| 231 |
+
az containerapp ingress show --name %APP_NAME% --resource-group %RESOURCE_GROUP%
|
| 232 |
+
```
|
| 233 |
+
|
| 234 |
+
## Security Best Practices
|
| 235 |
+
|
| 236 |
+
1. **Use Managed Identity** for Azure service connections
|
| 237 |
+
2. **Store secrets** in Azure Key Vault
|
| 238 |
+
3. **Enable HTTPS only** (default enabled)
|
| 239 |
+
4. **Restrict ingress** to specific IPs if needed
|
| 240 |
+
5. **Rotate tokens** regularly
|
| 241 |
+
|
| 242 |
+
## Support
|
| 243 |
+
|
| 244 |
+
- **Azure Container Apps Docs:** https://learn.microsoft.com/azure/container-apps/
|
| 245 |
+
- **SAP-RPT-1-OSS:** https://github.com/SAP-samples/sap-rpt-1-oss
|
| 246 |
+
- **Gradio Docs:** https://gradio.app/docs
|
| 247 |
+
|
| 248 |
+
## Clean Up
|
| 249 |
+
|
| 250 |
+
To delete all resources and stop charges:
|
| 251 |
+
```bash
|
| 252 |
+
az group delete --name %RESOURCE_GROUP% --yes --no-wait
|
| 253 |
+
```
|
| 254 |
+
|
| 255 |
+
---
|
| 256 |
+
|
| 257 |
+
**Ready to deploy?** Run the automated script or follow the manual steps above! 🚀
|
Dockerfile
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Dockerfile for SAP Finance Dashboard with RPT-1-OSS Model
|
| 2 |
+
# Optimized for Azure Container Apps deployment
|
| 3 |
+
|
| 4 |
+
FROM python:3.11-slim
|
| 5 |
+
|
| 6 |
+
# Set environment variables
|
| 7 |
+
ENV PYTHONDONTWRITEBYTECODE=1
|
| 8 |
+
ENV PYTHONUNBUFFERED=1
|
| 9 |
+
ENV GRADIO_SERVER_NAME=0.0.0.0
|
| 10 |
+
ENV GRADIO_SERVER_PORT=7862
|
| 11 |
+
|
| 12 |
+
# Install system dependencies
|
| 13 |
+
RUN apt-get update && apt-get install -y --no-install-recommends \
|
| 14 |
+
git \
|
| 15 |
+
curl \
|
| 16 |
+
&& rm -rf /var/lib/apt/lists/*
|
| 17 |
+
|
| 18 |
+
# Set working directory
|
| 19 |
+
WORKDIR /app
|
| 20 |
+
|
| 21 |
+
# Copy requirements first for better caching
|
| 22 |
+
COPY requirements.txt .
|
| 23 |
+
|
| 24 |
+
# Install Python dependencies
|
| 25 |
+
RUN pip install --no-cache-dir --upgrade pip && \
|
| 26 |
+
pip install --no-cache-dir -r requirements.txt
|
| 27 |
+
|
| 28 |
+
# Install SAP-RPT-1-OSS from GitHub
|
| 29 |
+
RUN pip install --no-cache-dir git+https://github.com/SAP-samples/sap-rpt-1-oss
|
| 30 |
+
|
| 31 |
+
# Force Gradio 4.x to be installed LAST (override any conflicting dependencies)
|
| 32 |
+
RUN pip install --no-cache-dir --force-reinstall "gradio>=4.0.0"
|
| 33 |
+
|
| 34 |
+
# Copy application code
|
| 35 |
+
COPY . .
|
| 36 |
+
|
| 37 |
+
# Create data directory if it doesn't exist
|
| 38 |
+
RUN mkdir -p /app/data
|
| 39 |
+
|
| 40 |
+
# Expose port
|
| 41 |
+
EXPOSE 7862
|
| 42 |
+
|
| 43 |
+
# Health check
|
| 44 |
+
HEALTHCHECK --interval=30s --timeout=10s --start-period=120s --retries=3 \
|
| 45 |
+
CMD curl -f http://localhost:7862/ || exit 1
|
| 46 |
+
|
| 47 |
+
# Run the application
|
| 48 |
+
CMD ["python", "app_gradio.py"]
|
README.md
CHANGED
|
@@ -1,145 +1,37 @@
|
|
| 1 |
-
|
| 2 |
-
|
| 3 |
-
|
| 4 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 5 |
|
| 6 |
## Features
|
| 7 |
|
| 8 |
-
- **Multiple Synthetic SAP Datasets**: General Ledger accounts, Financial Statements
|
| 9 |
-
- **
|
| 10 |
-
- **
|
| 11 |
-
- **
|
| 12 |
-
- **Live OData Connection**: Connect to SAP systems to fetch real-time sales order data
|
| 13 |
-
- **Playground Tab**: Upload datasets, configure model parameters, train, and download predictions
|
| 14 |
-
- **Modern UI**: Built with Gradio, a Python-based web framework
|
| 15 |
-
|
| 16 |
-
## Installation
|
| 17 |
-
|
| 18 |
-
### Prerequisites
|
| 19 |
-
|
| 20 |
-
- Python 3.11 or higher
|
| 21 |
-
- Hugging Face account (for SAP model access)
|
| 22 |
-
- SAP OData credentials (optional, for live data connection)
|
| 23 |
-
|
| 24 |
-
### Setup Steps
|
| 25 |
-
|
| 26 |
-
1. **Clone the repository**:
|
| 27 |
-
```bash
|
| 28 |
-
git clone <repository-url>
|
| 29 |
-
cd SAP-RPT-1-OSS-App
|
| 30 |
-
```
|
| 31 |
-
|
| 32 |
-
2. **Create a virtual environment**:
|
| 33 |
-
```bash
|
| 34 |
-
python -m venv venv
|
| 35 |
-
source venv/bin/activate # On Windows: venv\Scripts\activate
|
| 36 |
-
```
|
| 37 |
-
|
| 38 |
-
3. **Install dependencies**:
|
| 39 |
-
```bash
|
| 40 |
-
pip install -r requirements.txt
|
| 41 |
-
```
|
| 42 |
-
|
| 43 |
-
4. **Install SAP-RPT-OSS package**:
|
| 44 |
-
```bash
|
| 45 |
-
pip install git+https://github.com/SAP-samples/sap-rpt-1-oss
|
| 46 |
-
```
|
| 47 |
-
|
| 48 |
-
5. **Set up environment variables**:
|
| 49 |
-
- Copy `.env.example` to `.env`
|
| 50 |
-
- Fill in your SAP OData credentials
|
| 51 |
-
- Add your Hugging Face token for model access
|
| 52 |
-
|
| 53 |
-
6. **Authenticate with Hugging Face**:
|
| 54 |
-
```bash
|
| 55 |
-
pip install huggingface_hub
|
| 56 |
-
huggingface-cli login
|
| 57 |
-
```
|
| 58 |
-
Or set the `HUGGINGFACE_TOKEN` in your `.env` file.
|
| 59 |
-
|
| 60 |
-
7. **Generate synthetic data** (optional, if not already generated):
|
| 61 |
-
```bash
|
| 62 |
-
python -c "from utils.data_generator import generate_all_datasets; generate_all_datasets()"
|
| 63 |
-
```
|
| 64 |
|
| 65 |
## Usage
|
| 66 |
|
| 67 |
-
|
| 68 |
-
|
| 69 |
-
|
| 70 |
-
|
| 71 |
-
```bash
|
| 72 |
-
python app_gradio.py
|
| 73 |
-
```
|
| 74 |
-
|
| 75 |
-
The application will be available at `http://localhost:7862` (default Gradio port).
|
| 76 |
|
| 77 |
-
|
| 78 |
|
| 79 |
-
|
| 80 |
-
2. **Data Explorer**: Browse and filter datasets (GL, Financial Statements, Sales Orders)
|
| 81 |
-
3. **Upload**: Upload custom CSV datasets for analysis
|
| 82 |
-
4. **Predictions**: Use SAP-RPT-1-OSS model for predictions and analysis with pre-configured scenarios
|
| 83 |
-
5. **OData**: Connect to SAP OData services and fetch live data
|
| 84 |
-
6. **Playground**: Upload datasets, configure model parameters (task type, target column, test split, context size, bagging, GPU), train models, and download predictions
|
| 85 |
-
|
| 86 |
-
## SAP OData Connection Setup
|
| 87 |
-
|
| 88 |
-
1. Set the following environment variables in your `.env` file:
|
| 89 |
-
- `SAP_USERNAME`: Your SAP username
|
| 90 |
-
- `SAP_PASSWORD`: Your SAP password
|
| 91 |
-
- `SAP_SERVER`: SAP server URL (default: `https://sapes5.sapdevcenter.com/`)
|
| 92 |
-
- `SAP_CLIENT`: SAP client number (default: `002`)
|
| 93 |
-
|
| 94 |
-
2. The OData connector uses the base URL:
|
| 95 |
-
`https://sapes5.sapdevcenter.com/sap/opu/odata/IWBEP/GWSAMPLE_BASIC`
|
| 96 |
-
|
| 97 |
-
3. Available endpoints:
|
| 98 |
-
- Sales Orders: `SalesOrderSet`
|
| 99 |
-
- Products: `ProductSet`
|
| 100 |
-
- Line Items: `SalesOrderLineItemSet`
|
| 101 |
-
- Business Partners: `BusinessPartnerSet`
|
| 102 |
-
|
| 103 |
-
## Model Configuration
|
| 104 |
-
|
| 105 |
-
The SAP-RPT-1-OSS model supports both classification and regression tasks. For best performance:
|
| 106 |
-
|
| 107 |
-
- **Recommended**: GPU with at least 80 GB memory, context size 8192, bagging factor 8
|
| 108 |
-
- **Lightweight**: CPU with context size 2048, bagging factor 1
|
| 109 |
-
|
| 110 |
-
The application automatically detects available resources and adjusts settings accordingly.
|
| 111 |
-
|
| 112 |
-
## Project Structure
|
| 113 |
-
|
| 114 |
-
```
|
| 115 |
-
SAP-RPT-1-OSS-App/
|
| 116 |
-
├── app_gradio.py # Main Gradio application
|
| 117 |
-
├── models/
|
| 118 |
-
│ └── rpt_model.py # SAP-RPT-1-OSS model wrapper
|
| 119 |
-
├── data/
|
| 120 |
-
│ ├── synthetic_gl_accounts.csv
|
| 121 |
-
│ ├── synthetic_financial_statements.csv
|
| 122 |
-
│ └── synthetic_sales_orders.csv
|
| 123 |
-
├── utils/
|
| 124 |
-
│ ├── data_generator.py # Generate synthetic SAP finance data
|
| 125 |
-
│ ├── visualizations.py # Chart generation functions
|
| 126 |
-
│ ├── odata_connector.py # OData connection utilities
|
| 127 |
-
│ └── playground.py # Playground utilities for model training
|
| 128 |
-
├── requirements.txt
|
| 129 |
-
├── README.md
|
| 130 |
-
└── .env.example
|
| 131 |
-
```
|
| 132 |
|
| 133 |
## License
|
| 134 |
|
| 135 |
-
|
| 136 |
-
|
| 137 |
-
## Support
|
| 138 |
-
|
| 139 |
-
For issues or questions, please create an issue in this repository.
|
| 140 |
-
|
| 141 |
-
## Acknowledgments
|
| 142 |
-
|
| 143 |
-
- SAP-RPT-1-OSS model: [Hugging Face](https://huggingface.co/SAP/sap-rpt-1-oss)
|
| 144 |
-
- Gradio framework: [Gradio Documentation](https://www.gradio.app/docs/)
|
| 145 |
-
|
|
|
|
| 1 |
+
---
|
| 2 |
+
title: SAP Finance Dashboard with RPT-1-OSS
|
| 3 |
+
emoji: 📊
|
| 4 |
+
colorFrom: purple
|
| 5 |
+
colorTo: blue
|
| 6 |
+
sdk: gradio
|
| 7 |
+
sdk_version: 4.44.0
|
| 8 |
+
app_file: app_gradio.py
|
| 9 |
+
pinned: false
|
| 10 |
+
license: apache-2.0
|
| 11 |
+
---
|
| 12 |
+
|
| 13 |
+
# SAP Finance Dashboard with RPT-1-OSS Model
|
| 14 |
+
|
| 15 |
+
A comprehensive financial dashboard application built with Gradio that integrates the SAP-RPT-1-OSS model for predictive analysis on SAP finance datasets.
|
| 16 |
|
| 17 |
## Features
|
| 18 |
|
| 19 |
+
- **Multiple Synthetic SAP Finance Datasets**: General Ledger accounts, Financial Statements, Sales Orders
|
| 20 |
+
- **Interactive Visualizations**: Financial charts using Plotly
|
| 21 |
+
- **SAP-RPT-1-OSS Model Integration**: AI-powered predictions
|
| 22 |
+
- **Playground Tab**: Upload datasets, configure model parameters, train models
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 23 |
|
| 24 |
## Usage
|
| 25 |
|
| 26 |
+
1. **Dashboard**: View key financial metrics and visualizations
|
| 27 |
+
2. **Data Explorer**: Browse and filter datasets
|
| 28 |
+
3. **Predictions**: Use SAP-RPT-1-OSS model for AI predictions
|
| 29 |
+
4. **Playground**: Upload custom datasets and train models
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 30 |
|
| 31 |
+
## Model
|
| 32 |
|
| 33 |
+
This Space uses the [SAP-RPT-1-OSS](https://huggingface.co/SAP/sap-rpt-1-oss) model for predictions.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 34 |
|
| 35 |
## License
|
| 36 |
|
| 37 |
+
Apache 2.0
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
README_HF.md
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
---
|
| 2 |
+
title: SAP Finance Dashboard with RPT-1-OSS
|
| 3 |
+
emoji: 📊
|
| 4 |
+
colorFrom: purple
|
| 5 |
+
colorTo: blue
|
| 6 |
+
sdk: gradio
|
| 7 |
+
sdk_version: 4.44.0
|
| 8 |
+
app_file: app_gradio.py
|
| 9 |
+
pinned: false
|
| 10 |
+
license: apache-2.0
|
| 11 |
+
---
|
| 12 |
+
|
| 13 |
+
# SAP Finance Dashboard with RPT-1-OSS Model
|
| 14 |
+
|
| 15 |
+
A comprehensive financial dashboard application built with Gradio that integrates the SAP-RPT-1-OSS model for predictive analysis on SAP finance datasets.
|
| 16 |
+
|
| 17 |
+
## Features
|
| 18 |
+
|
| 19 |
+
- **Multiple Synthetic SAP Finance Datasets**: General Ledger accounts, Financial Statements, Sales Orders
|
| 20 |
+
- **Interactive Visualizations**: Financial charts using Plotly
|
| 21 |
+
- **SAP-RPT-1-OSS Model Integration**: AI-powered predictions
|
| 22 |
+
- **Playground Tab**: Upload datasets, configure model parameters, train models
|
| 23 |
+
|
| 24 |
+
## Usage
|
| 25 |
+
|
| 26 |
+
1. **Dashboard**: View key financial metrics and visualizations
|
| 27 |
+
2. **Data Explorer**: Browse and filter datasets
|
| 28 |
+
3. **Predictions**: Use SAP-RPT-1-OSS model for AI predictions
|
| 29 |
+
4. **Playground**: Upload custom datasets and train models
|
| 30 |
+
|
| 31 |
+
## Model
|
| 32 |
+
|
| 33 |
+
This Space uses the [SAP-RPT-1-OSS](https://huggingface.co/SAP/sap-rpt-1-oss) model for predictions.
|
| 34 |
+
|
| 35 |
+
## License
|
| 36 |
+
|
| 37 |
+
Apache 2.0
|
app.py
DELETED
|
@@ -1,451 +0,0 @@
|
|
| 1 |
-
"""
|
| 2 |
-
SAP Finance Dashboard with RPT-1-OSS Model
|
| 3 |
-
|
| 4 |
-
Main Mesop application with multiple pages:
|
| 5 |
-
- Dashboard: Overview with metrics and charts
|
| 6 |
-
- Data Explorer: Browse datasets
|
| 7 |
-
- Upload: Upload custom datasets
|
| 8 |
-
- Predictions: AI-powered predictions using SAP-RPT-1-OSS
|
| 9 |
-
- OData: Connect to SAP OData services
|
| 10 |
-
"""
|
| 11 |
-
|
| 12 |
-
import os
|
| 13 |
-
import mesop as me
|
| 14 |
-
import pandas as pd
|
| 15 |
-
import numpy as np
|
| 16 |
-
from pathlib import Path
|
| 17 |
-
import json
|
| 18 |
-
import base64
|
| 19 |
-
from typing import Optional, Dict, Any
|
| 20 |
-
import plotly.graph_objects as go
|
| 21 |
-
import plotly.io as pio
|
| 22 |
-
|
| 23 |
-
# Import utilities
|
| 24 |
-
from utils.data_generator import generate_all_datasets
|
| 25 |
-
from utils.visualizations import (
|
| 26 |
-
create_revenue_expense_chart,
|
| 27 |
-
create_balance_sheet_chart,
|
| 28 |
-
create_gl_summary_chart,
|
| 29 |
-
create_sales_analytics_chart,
|
| 30 |
-
create_sales_trend_chart,
|
| 31 |
-
get_summary_metrics
|
| 32 |
-
)
|
| 33 |
-
from utils.odata_connector import SAPFinanceConnector
|
| 34 |
-
from models.rpt_model import RPTModelWrapper, create_model
|
| 35 |
-
|
| 36 |
-
# Global state
|
| 37 |
-
from dataclasses import field
|
| 38 |
-
|
| 39 |
-
@me.stateclass
|
| 40 |
-
class State:
|
| 41 |
-
gl_data: pd.DataFrame = field(default_factory=pd.DataFrame)
|
| 42 |
-
financial_data: pd.DataFrame = field(default_factory=pd.DataFrame)
|
| 43 |
-
sales_data: pd.DataFrame = field(default_factory=pd.DataFrame)
|
| 44 |
-
uploaded_data: pd.DataFrame = field(default_factory=pd.DataFrame)
|
| 45 |
-
current_dataset_type: str = ""
|
| 46 |
-
odata_connector: Optional[SAPFinanceConnector] = None
|
| 47 |
-
odata_connected: bool = False
|
| 48 |
-
odata_data: pd.DataFrame = field(default_factory=pd.DataFrame)
|
| 49 |
-
model_wrapper: Optional[RPTModelWrapper] = None
|
| 50 |
-
predictions: Optional[np.ndarray] = None
|
| 51 |
-
prediction_proba: Optional[np.ndarray] = None
|
| 52 |
-
connection_message: str = ""
|
| 53 |
-
fetch_message: str = ""
|
| 54 |
-
model_initialized: bool = False
|
| 55 |
-
model_trained: bool = False
|
| 56 |
-
|
| 57 |
-
|
| 58 |
-
def load_datasets(state: State):
|
| 59 |
-
"""Load synthetic datasets if they exist."""
|
| 60 |
-
data_dir = Path("data")
|
| 61 |
-
|
| 62 |
-
if (data_dir / "synthetic_gl_accounts.csv").exists():
|
| 63 |
-
state.gl_data = pd.read_csv(data_dir / "synthetic_gl_accounts.csv")
|
| 64 |
-
|
| 65 |
-
if (data_dir / "synthetic_financial_statements.csv").exists():
|
| 66 |
-
state.financial_data = pd.read_csv(data_dir / "synthetic_financial_statements.csv")
|
| 67 |
-
|
| 68 |
-
if (data_dir / "synthetic_sales_orders.csv").exists():
|
| 69 |
-
state.sales_data = pd.read_csv(data_dir / "synthetic_sales_orders.csv")
|
| 70 |
-
|
| 71 |
-
|
| 72 |
-
def plotly_to_html(fig_dict: Dict[str, Any]) -> str:
|
| 73 |
-
"""Convert Plotly figure dict to HTML string."""
|
| 74 |
-
if not fig_dict:
|
| 75 |
-
return "<p>No chart data available</p>"
|
| 76 |
-
|
| 77 |
-
try:
|
| 78 |
-
fig = go.Figure(fig_dict)
|
| 79 |
-
html_str = pio.to_html(fig, include_plotlyjs='cdn', div_id="plotly-div")
|
| 80 |
-
return html_str
|
| 81 |
-
except Exception as e:
|
| 82 |
-
return f"<p>Error rendering chart: {str(e)}</p>"
|
| 83 |
-
|
| 84 |
-
|
| 85 |
-
@me.page(path="/", title="SAP Finance Dashboard")
|
| 86 |
-
def dashboard_page():
|
| 87 |
-
"""Main dashboard page with overview metrics and charts."""
|
| 88 |
-
state = me.state(State)
|
| 89 |
-
|
| 90 |
-
me.text("SAP Finance Dashboard", style=me.Style(font_size=32, font_weight="bold", margin=me.Margin(bottom=16)))
|
| 91 |
-
|
| 92 |
-
# Load datasets if not loaded
|
| 93 |
-
if state.gl_data.empty and state.financial_data.empty and state.sales_data.empty:
|
| 94 |
-
load_datasets(state)
|
| 95 |
-
|
| 96 |
-
# Generate datasets if they don't exist
|
| 97 |
-
if state.gl_data.empty or state.financial_data.empty or state.sales_data.empty:
|
| 98 |
-
with me.box(style=me.Style(padding=16, background="#fff3cd", border_radius=8, margin=me.Margin(bottom=16))):
|
| 99 |
-
me.text("Generating synthetic datasets...", style=me.Style(color="#856404"))
|
| 100 |
-
generate_all_datasets()
|
| 101 |
-
load_datasets(state)
|
| 102 |
-
|
| 103 |
-
# Summary metrics
|
| 104 |
-
with me.box(style=me.Style(display="grid", grid_template_columns="repeat(4, 1fr)", gap=16, margin=me.Margin(bottom=24))):
|
| 105 |
-
if not state.gl_data.empty:
|
| 106 |
-
gl_metrics = get_summary_metrics(state.gl_data, "gl")
|
| 107 |
-
with me.box(style=me.Style(padding=16, background="#f8f9fa", border_radius=8)):
|
| 108 |
-
me.text("GL Transactions", style=me.Style(font_weight="bold"))
|
| 109 |
-
me.text(f"{gl_metrics.get('Total Transactions', 0):,}")
|
| 110 |
-
|
| 111 |
-
if not state.financial_data.empty:
|
| 112 |
-
fin_metrics = get_summary_metrics(state.financial_data, "financial")
|
| 113 |
-
with me.box(style=me.Style(padding=16, background="#f8f9fa", border_radius=8)):
|
| 114 |
-
me.text("Latest Revenue", style=me.Style(font_weight="bold"))
|
| 115 |
-
me.text(f"${fin_metrics.get('Latest Revenue', 0):,.2f}")
|
| 116 |
-
|
| 117 |
-
if not state.sales_data.empty:
|
| 118 |
-
sales_metrics = get_summary_metrics(state.sales_data, "sales")
|
| 119 |
-
with me.box(style=me.Style(padding=16, background="#f8f9fa", border_radius=8)):
|
| 120 |
-
me.text("Total Sales", style=me.Style(font_weight="bold"))
|
| 121 |
-
me.text(f"${sales_metrics.get('Total Sales', 0):,.2f}")
|
| 122 |
-
|
| 123 |
-
with me.box(style=me.Style(padding=16, background="#f8f9fa", border_radius=8)):
|
| 124 |
-
me.text("Datasets", style=me.Style(font_weight="bold"))
|
| 125 |
-
count = sum([
|
| 126 |
-
not state.gl_data.empty,
|
| 127 |
-
not state.financial_data.empty,
|
| 128 |
-
not state.sales_data.empty,
|
| 129 |
-
not state.uploaded_data.empty
|
| 130 |
-
])
|
| 131 |
-
me.text(f"{count} loaded")
|
| 132 |
-
|
| 133 |
-
# Charts
|
| 134 |
-
if not state.financial_data.empty:
|
| 135 |
-
with me.box(style=me.Style(margin=me.Margin(bottom=24))):
|
| 136 |
-
me.text("Financial Trends", style=me.Style(font_size=20, font_weight="bold", margin=me.Margin(bottom=8)))
|
| 137 |
-
chart_data = create_revenue_expense_chart(state.financial_data)
|
| 138 |
-
if chart_data:
|
| 139 |
-
html_chart = plotly_to_html(chart_data)
|
| 140 |
-
me.html(html_chart)
|
| 141 |
-
|
| 142 |
-
if not state.financial_data.empty:
|
| 143 |
-
with me.box(style=me.Style(margin=me.Margin(bottom=24))):
|
| 144 |
-
me.text("Balance Sheet", style=me.Style(font_size=20, font_weight="bold", margin=me.Margin(bottom=8)))
|
| 145 |
-
chart_data = create_balance_sheet_chart(state.financial_data)
|
| 146 |
-
if chart_data:
|
| 147 |
-
html_chart = plotly_to_html(chart_data)
|
| 148 |
-
me.html(html_chart)
|
| 149 |
-
|
| 150 |
-
if not state.sales_data.empty:
|
| 151 |
-
with me.box(style=me.Style(margin=me.Margin(bottom=24))):
|
| 152 |
-
me.text("Sales Analytics", style=me.Style(font_size=20, font_weight="bold", margin=me.Margin(bottom=8)))
|
| 153 |
-
chart_data = create_sales_analytics_chart(state.sales_data)
|
| 154 |
-
if chart_data:
|
| 155 |
-
html_chart = plotly_to_html(chart_data)
|
| 156 |
-
me.html(html_chart)
|
| 157 |
-
|
| 158 |
-
|
| 159 |
-
@me.page(path="/explorer", title="Data Explorer")
|
| 160 |
-
def explorer_page():
|
| 161 |
-
"""Data explorer page to browse and filter datasets."""
|
| 162 |
-
state = me.state(State)
|
| 163 |
-
|
| 164 |
-
me.text("Data Explorer", style=me.Style(font_size=32, font_weight="bold", margin=me.Margin(bottom=16)))
|
| 165 |
-
|
| 166 |
-
# Dataset selector
|
| 167 |
-
with me.box(style=me.Style(margin=me.Margin(bottom=16))):
|
| 168 |
-
me.text("Select Dataset:", style=me.Style(font_weight="bold", margin=me.Margin(bottom=8)))
|
| 169 |
-
dataset_options = [
|
| 170 |
-
("GL Accounts", "gl"),
|
| 171 |
-
("Financial Statements", "financial"),
|
| 172 |
-
("Sales Orders", "sales"),
|
| 173 |
-
("Uploaded Data", "uploaded")
|
| 174 |
-
]
|
| 175 |
-
|
| 176 |
-
me.select(
|
| 177 |
-
label="Dataset",
|
| 178 |
-
options=[me.SelectOption(label=label, value=value) for label, value in dataset_options],
|
| 179 |
-
on_selection_change=on_dataset_selection_change,
|
| 180 |
-
value=state.current_dataset_type
|
| 181 |
-
)
|
| 182 |
-
|
| 183 |
-
# Display selected dataset
|
| 184 |
-
if state.current_dataset_type:
|
| 185 |
-
display_dataset(state, state.current_dataset_type)
|
| 186 |
-
|
| 187 |
-
|
| 188 |
-
def on_dataset_selection_change(e: me.SelectSelectionChangeEvent):
|
| 189 |
-
"""Handle dataset selection change."""
|
| 190 |
-
state = me.state(State)
|
| 191 |
-
state.current_dataset_type = e.value
|
| 192 |
-
|
| 193 |
-
|
| 194 |
-
def display_dataset(state: State, dataset_type: str):
|
| 195 |
-
"""Display the selected dataset."""
|
| 196 |
-
if dataset_type == "gl" and not state.gl_data.empty:
|
| 197 |
-
df = state.gl_data
|
| 198 |
-
me.text(f"GL Accounts ({len(df)} records)", style=me.Style(font_size=20, font_weight="bold", margin=me.Margin(bottom=8)))
|
| 199 |
-
chart_data = create_gl_summary_chart(df)
|
| 200 |
-
if chart_data:
|
| 201 |
-
html_chart = plotly_to_html(chart_data)
|
| 202 |
-
me.html(html_chart)
|
| 203 |
-
me.table(data=df.head(100).to_dict("records"), style=me.Style(margin=me.Margin(top=16)))
|
| 204 |
-
|
| 205 |
-
elif dataset_type == "financial" and not state.financial_data.empty:
|
| 206 |
-
df = state.financial_data
|
| 207 |
-
me.text(f"Financial Statements ({len(df)} records)", style=me.Style(font_size=20, font_weight="bold", margin=me.Margin(bottom=8)))
|
| 208 |
-
chart_data = create_revenue_expense_chart(df)
|
| 209 |
-
if chart_data:
|
| 210 |
-
html_chart = plotly_to_html(chart_data)
|
| 211 |
-
me.html(html_chart)
|
| 212 |
-
me.table(data=df.to_dict("records"), style=me.Style(margin=me.Margin(top=16)))
|
| 213 |
-
|
| 214 |
-
elif dataset_type == "sales" and not state.sales_data.empty:
|
| 215 |
-
df = state.sales_data
|
| 216 |
-
me.text(f"Sales Orders ({len(df)} records)", style=me.Style(font_size=20, font_weight="bold", margin=me.Margin(bottom=8)))
|
| 217 |
-
chart_data = create_sales_trend_chart(df)
|
| 218 |
-
if chart_data:
|
| 219 |
-
html_chart = plotly_to_html(chart_data)
|
| 220 |
-
me.html(html_chart)
|
| 221 |
-
me.table(data=df.head(100).to_dict("records"), style=me.Style(margin=me.Margin(top=16)))
|
| 222 |
-
|
| 223 |
-
elif dataset_type == "uploaded" and not state.uploaded_data.empty:
|
| 224 |
-
df = state.uploaded_data
|
| 225 |
-
me.text(f"Uploaded Data ({len(df)} records)", style=me.Style(font_size=20, font_weight="bold", margin=me.Margin(bottom=8)))
|
| 226 |
-
me.table(data=df.head(100).to_dict("records"), style=me.Style(margin=me.Margin(top=16)))
|
| 227 |
-
|
| 228 |
-
else:
|
| 229 |
-
me.text("No data available for this dataset type.", style=me.Style(color="#dc3545"))
|
| 230 |
-
|
| 231 |
-
|
| 232 |
-
@me.page(path="/upload", title="Upload Data")
|
| 233 |
-
def upload_page():
|
| 234 |
-
"""Upload page for custom datasets."""
|
| 235 |
-
state = me.state(State)
|
| 236 |
-
|
| 237 |
-
me.text("Upload Dataset", style=me.Style(font_size=32, font_weight="bold", margin=me.Margin(bottom=16)))
|
| 238 |
-
|
| 239 |
-
with me.box(style=me.Style(margin=me.Margin(bottom=16))):
|
| 240 |
-
me.text("Upload a CSV file to analyze:", style=me.Style(margin=me.Margin(bottom=8)))
|
| 241 |
-
me.file_upload(
|
| 242 |
-
label="Choose CSV File",
|
| 243 |
-
accept=".csv",
|
| 244 |
-
on_upload=handle_file_upload
|
| 245 |
-
)
|
| 246 |
-
|
| 247 |
-
if not state.uploaded_data.empty:
|
| 248 |
-
me.text("Uploaded Data Preview:", style=me.Style(font_size=20, font_weight="bold", margin=me.Margin(top=16, bottom=8)))
|
| 249 |
-
me.table(data=state.uploaded_data.head(50).to_dict("records"))
|
| 250 |
-
|
| 251 |
-
|
| 252 |
-
def handle_file_upload(e: me.UploadEvent):
|
| 253 |
-
"""Handle file upload."""
|
| 254 |
-
state = me.state(State)
|
| 255 |
-
try:
|
| 256 |
-
if e.files:
|
| 257 |
-
file = e.files[0]
|
| 258 |
-
df = pd.read_csv(file.getvalue())
|
| 259 |
-
state.uploaded_data = df
|
| 260 |
-
except Exception as ex:
|
| 261 |
-
pass
|
| 262 |
-
|
| 263 |
-
|
| 264 |
-
@me.page(path="/predictions", title="Predictions")
|
| 265 |
-
def predictions_page():
|
| 266 |
-
"""Predictions page using SAP-RPT-1-OSS model."""
|
| 267 |
-
state = me.state(State)
|
| 268 |
-
|
| 269 |
-
me.text("AI Predictions with SAP-RPT-1-OSS", style=me.Style(font_size=32, font_weight="bold", margin=me.Margin(bottom=16)))
|
| 270 |
-
|
| 271 |
-
with me.box(style=me.Style(margin=me.Margin(bottom=16))):
|
| 272 |
-
me.text("Model Configuration", style=me.Style(font_size=20, font_weight="bold", margin=me.Margin(bottom=8)))
|
| 273 |
-
|
| 274 |
-
me.select(
|
| 275 |
-
label="Model Type",
|
| 276 |
-
options=[
|
| 277 |
-
me.SelectOption(label="Classifier", value="classifier"),
|
| 278 |
-
me.SelectOption(label="Regressor", value="regressor")
|
| 279 |
-
]
|
| 280 |
-
)
|
| 281 |
-
|
| 282 |
-
me.checkbox(label="Use GPU (requires 80GB memory)", checked=False)
|
| 283 |
-
|
| 284 |
-
me.button("Initialize Model", on_click=on_init_model)
|
| 285 |
-
|
| 286 |
-
if state.model_initialized:
|
| 287 |
-
me.text("Model initialized successfully!", style=me.Style(color="#28a745", margin=me.Margin(bottom=16)))
|
| 288 |
-
|
| 289 |
-
# Dataset selection for training
|
| 290 |
-
with me.box(style=me.Style(margin=me.Margin(bottom=16))):
|
| 291 |
-
me.text("Select Training Data", style=me.Style(font_weight="bold", margin=me.Margin(bottom=8)))
|
| 292 |
-
dataset_options = [
|
| 293 |
-
("GL Accounts", "gl"),
|
| 294 |
-
("Financial Statements", "financial"),
|
| 295 |
-
("Sales Orders", "sales"),
|
| 296 |
-
("Uploaded Data", "uploaded")
|
| 297 |
-
]
|
| 298 |
-
|
| 299 |
-
me.select(
|
| 300 |
-
label="Dataset",
|
| 301 |
-
options=[me.SelectOption(label=label, value=value) for label, value in dataset_options]
|
| 302 |
-
)
|
| 303 |
-
|
| 304 |
-
me.button("Train Model", on_click=on_train_model)
|
| 305 |
-
|
| 306 |
-
if state.model_trained:
|
| 307 |
-
me.text("Model trained successfully!", style=me.Style(color="#28a745", margin=me.Margin(top=16)))
|
| 308 |
-
|
| 309 |
-
if state.predictions is not None:
|
| 310 |
-
me.text("Predictions:", style=me.Style(font_size=20, font_weight="bold", margin=me.Margin(top=16, bottom=8)))
|
| 311 |
-
me.text(str(state.predictions[:10])) # Show first 10 predictions
|
| 312 |
-
|
| 313 |
-
else:
|
| 314 |
-
with me.box(style=me.Style(padding=16, background="#fff3cd", border_radius=8)):
|
| 315 |
-
me.text("Please initialize the model first.", style=me.Style(color="#856404"))
|
| 316 |
-
|
| 317 |
-
|
| 318 |
-
def on_init_model(e: me.ClickEvent):
|
| 319 |
-
"""Initialize the model."""
|
| 320 |
-
state = me.state(State)
|
| 321 |
-
try:
|
| 322 |
-
state.model_wrapper = create_model(model_type="classifier", use_gpu=False)
|
| 323 |
-
state.model_initialized = True
|
| 324 |
-
except Exception as ex:
|
| 325 |
-
state.connection_message = f"Error initializing model: {str(ex)}"
|
| 326 |
-
|
| 327 |
-
|
| 328 |
-
def on_train_model(e: me.ClickEvent):
|
| 329 |
-
"""Train the model."""
|
| 330 |
-
state = me.state(State)
|
| 331 |
-
try:
|
| 332 |
-
if state.model_wrapper and not state.gl_data.empty:
|
| 333 |
-
# Simple example: use GL data
|
| 334 |
-
X = state.gl_data.select_dtypes(include=[np.number]).dropna()
|
| 335 |
-
if len(X) > 0:
|
| 336 |
-
# Create a simple target for classification
|
| 337 |
-
y = (X.iloc[:, 0] > X.iloc[:, 0].median()).astype(int)
|
| 338 |
-
state.model_wrapper.fit(X, y)
|
| 339 |
-
state.model_trained = True
|
| 340 |
-
except Exception as ex:
|
| 341 |
-
state.connection_message = f"Error training model: {str(ex)}"
|
| 342 |
-
|
| 343 |
-
|
| 344 |
-
@me.page(path="/odata", title="OData Connection")
|
| 345 |
-
def odata_page():
|
| 346 |
-
"""OData connection page for SAP data."""
|
| 347 |
-
state = me.state(State)
|
| 348 |
-
|
| 349 |
-
me.text("SAP OData Connection", style=me.Style(font_size=32, font_weight="bold", margin=me.Margin(bottom=16)))
|
| 350 |
-
|
| 351 |
-
# Connection status
|
| 352 |
-
with me.box(style=me.Style(margin=me.Margin(bottom=16))):
|
| 353 |
-
if state.odata_connector is None:
|
| 354 |
-
state.odata_connector = SAPFinanceConnector()
|
| 355 |
-
|
| 356 |
-
me.button("Test Connection", on_click=on_test_odata_connection)
|
| 357 |
-
|
| 358 |
-
if state.connection_message:
|
| 359 |
-
color = "#28a745" if state.odata_connected else "#dc3545"
|
| 360 |
-
me.text(state.connection_message, style=me.Style(color=color, margin=me.Margin(top=8)))
|
| 361 |
-
elif state.odata_connected:
|
| 362 |
-
me.text("✓ Connected to SAP OData", style=me.Style(color="#28a745", margin=me.Margin(top=8)))
|
| 363 |
-
else:
|
| 364 |
-
me.text("Not connected", style=me.Style(color="#dc3545", margin=me.Margin(top=8)))
|
| 365 |
-
|
| 366 |
-
# Fetch options
|
| 367 |
-
if state.odata_connected:
|
| 368 |
-
with me.box(style=me.Style(margin=me.Margin(bottom=16))):
|
| 369 |
-
me.text("Fetch Data", style=me.Style(font_size=20, font_weight="bold", margin=me.Margin(bottom=8)))
|
| 370 |
-
|
| 371 |
-
top_count = me.number_input(label="Number of records", value=100, min_value=1, max_value=1000)
|
| 372 |
-
|
| 373 |
-
with me.box(style=me.Style(display="grid", grid_template_columns="repeat(2, 1fr)", gap=8, margin=me.Margin(top=8))):
|
| 374 |
-
me.button("Fetch Sales Orders", on_click=lambda e: on_fetch_odata(e, "orders", top_count))
|
| 375 |
-
me.button("Fetch Products", on_click=lambda e: on_fetch_odata(e, "products", top_count))
|
| 376 |
-
me.button("Fetch Line Items", on_click=lambda e: on_fetch_odata(e, "line_items", top_count))
|
| 377 |
-
me.button("Fetch Partners", on_click=lambda e: on_fetch_odata(e, "partners", top_count))
|
| 378 |
-
|
| 379 |
-
if state.fetch_message:
|
| 380 |
-
me.text(state.fetch_message, style=me.Style(color="#28a745", margin=me.Margin(top=8)))
|
| 381 |
-
|
| 382 |
-
# Display fetched data
|
| 383 |
-
if not state.odata_data.empty:
|
| 384 |
-
me.text("Fetched Data:", style=me.Style(font_size=20, font_weight="bold", margin=me.Margin(top=16, bottom=8)))
|
| 385 |
-
me.table(data=state.odata_data.head(100).to_dict("records"))
|
| 386 |
-
|
| 387 |
-
|
| 388 |
-
def on_test_odata_connection(e: me.ClickEvent):
|
| 389 |
-
"""Test OData connection."""
|
| 390 |
-
state = me.state(State)
|
| 391 |
-
try:
|
| 392 |
-
if state.odata_connector is None:
|
| 393 |
-
state.odata_connector = SAPFinanceConnector()
|
| 394 |
-
|
| 395 |
-
connected, message = state.odata_connector.test_connection()
|
| 396 |
-
state.odata_connected = connected
|
| 397 |
-
state.connection_message = message
|
| 398 |
-
except Exception as ex:
|
| 399 |
-
state.connection_message = f"Error: {str(ex)}"
|
| 400 |
-
state.odata_connected = False
|
| 401 |
-
|
| 402 |
-
|
| 403 |
-
def on_fetch_odata(e: me.ClickEvent, entity_type: str, top: int):
|
| 404 |
-
"""Fetch data from OData service."""
|
| 405 |
-
state = me.state(State)
|
| 406 |
-
try:
|
| 407 |
-
if not state.odata_connected:
|
| 408 |
-
state.fetch_message = "Please connect first!"
|
| 409 |
-
return
|
| 410 |
-
|
| 411 |
-
if entity_type == "orders":
|
| 412 |
-
state.odata_data = state.odata_connector.fetch_orders_df(top)
|
| 413 |
-
elif entity_type == "products":
|
| 414 |
-
state.odata_data = state.odata_connector.fetch_products_df(top)
|
| 415 |
-
elif entity_type == "line_items":
|
| 416 |
-
state.odata_data = state.odata_connector.fetch_line_items_df(top)
|
| 417 |
-
elif entity_type == "partners":
|
| 418 |
-
state.odata_data = state.odata_connector.fetch_partners_df(top)
|
| 419 |
-
|
| 420 |
-
state.fetch_message = f"Fetched {len(state.odata_data)} records"
|
| 421 |
-
except Exception as ex:
|
| 422 |
-
state.fetch_message = f"Error fetching data: {str(ex)}"
|
| 423 |
-
|
| 424 |
-
|
| 425 |
-
# Navigation
|
| 426 |
-
@me.page(path="/nav", title="Navigation")
|
| 427 |
-
def nav_page():
|
| 428 |
-
"""Navigation page."""
|
| 429 |
-
me.text("Navigation", style=me.Style(font_size=32, font_weight="bold", margin=me.Margin(bottom=16)))
|
| 430 |
-
|
| 431 |
-
nav_links = [
|
| 432 |
-
("Dashboard", "/"),
|
| 433 |
-
("Data Explorer", "/explorer"),
|
| 434 |
-
("Upload", "/upload"),
|
| 435 |
-
("Predictions", "/predictions"),
|
| 436 |
-
("OData", "/odata")
|
| 437 |
-
]
|
| 438 |
-
|
| 439 |
-
for label, path in nav_links:
|
| 440 |
-
with me.box(style=me.Style(margin=me.Margin(bottom=8))):
|
| 441 |
-
me.link(label, path=path, style=me.Style(font_size=18, text_decoration="none"))
|
| 442 |
-
|
| 443 |
-
|
| 444 |
-
if __name__ == "__main__":
|
| 445 |
-
# Generate datasets if they don't exist
|
| 446 |
-
data_dir = Path("data")
|
| 447 |
-
if not (data_dir / "synthetic_gl_accounts.csv").exists():
|
| 448 |
-
generate_all_datasets()
|
| 449 |
-
|
| 450 |
-
me.run()
|
| 451 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
app_gradio.py
CHANGED
|
@@ -1415,8 +1415,14 @@ with gr.Blocks(title="SAP Finance Dashboard", theme=gr.themes.Soft(), css="""
|
|
| 1415 |
|
| 1416 |
|
| 1417 |
if __name__ == "__main__":
|
|
|
|
|
|
|
| 1418 |
# Load datasets on startup
|
| 1419 |
load_datasets()
|
| 1420 |
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1421 |
# Launch the app
|
| 1422 |
-
app.launch(share=False, server_name=
|
|
|
|
| 1415 |
|
| 1416 |
|
| 1417 |
if __name__ == "__main__":
|
| 1418 |
+
import os
|
| 1419 |
+
|
| 1420 |
# Load datasets on startup
|
| 1421 |
load_datasets()
|
| 1422 |
|
| 1423 |
+
# Get server configuration from environment variables (for container deployment)
|
| 1424 |
+
server_name = os.environ.get("GRADIO_SERVER_NAME", "0.0.0.0")
|
| 1425 |
+
server_port = int(os.environ.get("GRADIO_SERVER_PORT", 7862))
|
| 1426 |
+
|
| 1427 |
# Launch the app
|
| 1428 |
+
app.launch(share=False, server_name=server_name, server_port=server_port, quiet=False)
|
deploy-azure.ps1
ADDED
|
@@ -0,0 +1,135 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Azure Container Apps Deployment Script for SAP Finance Dashboard
|
| 2 |
+
# Run this script in PowerShell after logging in to Azure CLI
|
| 3 |
+
|
| 4 |
+
# ============================================
|
| 5 |
+
# CONFIGURATION - Update these values
|
| 6 |
+
# ============================================
|
| 7 |
+
$RESOURCE_GROUP = "rg-sap-finance-dashboard"
|
| 8 |
+
$LOCATION = "eastus"
|
| 9 |
+
$ACR_NAME = "acrsapfinance$(Get-Random -Maximum 9999)" # Must be globally unique
|
| 10 |
+
$ENVIRONMENT_NAME = "env-sap-finance"
|
| 11 |
+
$APP_NAME = "sap-finance-dashboard"
|
| 12 |
+
|
| 13 |
+
# Optional: Set your secrets (or configure later in Azure Portal)
|
| 14 |
+
$HUGGINGFACE_TOKEN = "" # Your Hugging Face token
|
| 15 |
+
$SAP_USERNAME = "" # SAP OData username
|
| 16 |
+
$SAP_PASSWORD = "" # SAP OData password
|
| 17 |
+
|
| 18 |
+
# ============================================
|
| 19 |
+
# STEP 1: Login to Azure (if not already logged in)
|
| 20 |
+
# ============================================
|
| 21 |
+
Write-Host "Step 1: Checking Azure login..." -ForegroundColor Cyan
|
| 22 |
+
$account = az account show 2>$null | ConvertFrom-Json
|
| 23 |
+
if (-not $account) {
|
| 24 |
+
Write-Host "Please login to Azure..." -ForegroundColor Yellow
|
| 25 |
+
az login
|
| 26 |
+
}
|
| 27 |
+
Write-Host "Logged in as: $($account.user.name)" -ForegroundColor Green
|
| 28 |
+
|
| 29 |
+
# ============================================
|
| 30 |
+
# STEP 2: Create Resource Group
|
| 31 |
+
# ============================================
|
| 32 |
+
Write-Host "`nStep 2: Creating Resource Group..." -ForegroundColor Cyan
|
| 33 |
+
az group create --name $RESOURCE_GROUP --location $LOCATION
|
| 34 |
+
Write-Host "Resource Group '$RESOURCE_GROUP' created in $LOCATION" -ForegroundColor Green
|
| 35 |
+
|
| 36 |
+
# ============================================
|
| 37 |
+
# STEP 3: Create Azure Container Registry
|
| 38 |
+
# ============================================
|
| 39 |
+
Write-Host "`nStep 3: Creating Azure Container Registry..." -ForegroundColor Cyan
|
| 40 |
+
az acr create --resource-group $RESOURCE_GROUP --name $ACR_NAME --sku Basic --admin-enabled true
|
| 41 |
+
Write-Host "Container Registry '$ACR_NAME' created" -ForegroundColor Green
|
| 42 |
+
|
| 43 |
+
# Get ACR credentials
|
| 44 |
+
$ACR_USERNAME = az acr credential show --name $ACR_NAME --query username -o tsv
|
| 45 |
+
$ACR_PASSWORD = az acr credential show --name $ACR_NAME --query "passwords[0].value" -o tsv
|
| 46 |
+
$ACR_LOGIN_SERVER = az acr show --name $ACR_NAME --query loginServer -o tsv
|
| 47 |
+
|
| 48 |
+
Write-Host "ACR Login Server: $ACR_LOGIN_SERVER" -ForegroundColor Yellow
|
| 49 |
+
|
| 50 |
+
# ============================================
|
| 51 |
+
# STEP 4: Build and Push Docker Image
|
| 52 |
+
# ============================================
|
| 53 |
+
Write-Host "`nStep 4: Building and pushing Docker image..." -ForegroundColor Cyan
|
| 54 |
+
Write-Host "This may take several minutes..." -ForegroundColor Yellow
|
| 55 |
+
|
| 56 |
+
# Build using ACR Tasks (no local Docker required)
|
| 57 |
+
az acr build --registry $ACR_NAME --image "${APP_NAME}:latest" .
|
| 58 |
+
|
| 59 |
+
Write-Host "Docker image built and pushed to ACR" -ForegroundColor Green
|
| 60 |
+
|
| 61 |
+
# ============================================
|
| 62 |
+
# STEP 5: Create Container Apps Environment
|
| 63 |
+
# ============================================
|
| 64 |
+
Write-Host "`nStep 5: Creating Container Apps Environment..." -ForegroundColor Cyan
|
| 65 |
+
az containerapp env create `
|
| 66 |
+
--name $ENVIRONMENT_NAME `
|
| 67 |
+
--resource-group $RESOURCE_GROUP `
|
| 68 |
+
--location $LOCATION
|
| 69 |
+
|
| 70 |
+
Write-Host "Container Apps Environment '$ENVIRONMENT_NAME' created" -ForegroundColor Green
|
| 71 |
+
|
| 72 |
+
# ============================================
|
| 73 |
+
# STEP 6: Deploy Container App
|
| 74 |
+
# ============================================
|
| 75 |
+
Write-Host "`nStep 6: Deploying Container App..." -ForegroundColor Cyan
|
| 76 |
+
|
| 77 |
+
# Build the secrets parameter if tokens are provided
|
| 78 |
+
$secretsParam = ""
|
| 79 |
+
$envVarsParam = ""
|
| 80 |
+
|
| 81 |
+
if ($HUGGINGFACE_TOKEN) {
|
| 82 |
+
$secretsParam = "--secrets huggingface-token=$HUGGINGFACE_TOKEN"
|
| 83 |
+
$envVarsParam = "--env-vars HUGGINGFACE_TOKEN=secretref:huggingface-token"
|
| 84 |
+
}
|
| 85 |
+
|
| 86 |
+
# Create the container app
|
| 87 |
+
az containerapp create `
|
| 88 |
+
--name $APP_NAME `
|
| 89 |
+
--resource-group $RESOURCE_GROUP `
|
| 90 |
+
--environment $ENVIRONMENT_NAME `
|
| 91 |
+
--image "${ACR_LOGIN_SERVER}/${APP_NAME}:latest" `
|
| 92 |
+
--registry-server $ACR_LOGIN_SERVER `
|
| 93 |
+
--registry-username $ACR_USERNAME `
|
| 94 |
+
--registry-password $ACR_PASSWORD `
|
| 95 |
+
--target-port 7862 `
|
| 96 |
+
--ingress external `
|
| 97 |
+
--cpu 2 `
|
| 98 |
+
--memory 4Gi `
|
| 99 |
+
--min-replicas 1 `
|
| 100 |
+
--max-replicas 3 `
|
| 101 |
+
--query properties.configuration.ingress.fqdn
|
| 102 |
+
|
| 103 |
+
Write-Host "`nContainer App deployed!" -ForegroundColor Green
|
| 104 |
+
|
| 105 |
+
# ============================================
|
| 106 |
+
# STEP 7: Get Application URL
|
| 107 |
+
# ============================================
|
| 108 |
+
Write-Host "`nStep 7: Getting Application URL..." -ForegroundColor Cyan
|
| 109 |
+
$APP_URL = az containerapp show `
|
| 110 |
+
--name $APP_NAME `
|
| 111 |
+
--resource-group $RESOURCE_GROUP `
|
| 112 |
+
--query properties.configuration.ingress.fqdn -o tsv
|
| 113 |
+
|
| 114 |
+
Write-Host "`n============================================" -ForegroundColor Green
|
| 115 |
+
Write-Host "DEPLOYMENT COMPLETE!" -ForegroundColor Green
|
| 116 |
+
Write-Host "============================================" -ForegroundColor Green
|
| 117 |
+
Write-Host "`nYour SAP Finance Dashboard is available at:" -ForegroundColor Cyan
|
| 118 |
+
Write-Host "https://$APP_URL" -ForegroundColor Yellow
|
| 119 |
+
Write-Host "`n============================================" -ForegroundColor Green
|
| 120 |
+
|
| 121 |
+
# ============================================
|
| 122 |
+
# NEXT STEPS
|
| 123 |
+
# ============================================
|
| 124 |
+
Write-Host "`nNEXT STEPS:" -ForegroundColor Cyan
|
| 125 |
+
Write-Host "1. Configure secrets in Azure Portal:" -ForegroundColor White
|
| 126 |
+
Write-Host " - HUGGINGFACE_TOKEN: Your Hugging Face API token" -ForegroundColor Gray
|
| 127 |
+
Write-Host " - SAP_USERNAME: SAP OData username (optional)" -ForegroundColor Gray
|
| 128 |
+
Write-Host " - SAP_PASSWORD: SAP OData password (optional)" -ForegroundColor Gray
|
| 129 |
+
Write-Host "`n2. To update the app after code changes:" -ForegroundColor White
|
| 130 |
+
Write-Host " az acr build --registry $ACR_NAME --image ${APP_NAME}:latest ." -ForegroundColor Gray
|
| 131 |
+
Write-Host " az containerapp update --name $APP_NAME --resource-group $RESOURCE_GROUP --image ${ACR_LOGIN_SERVER}/${APP_NAME}:latest" -ForegroundColor Gray
|
| 132 |
+
Write-Host "`n3. To view logs:" -ForegroundColor White
|
| 133 |
+
Write-Host " az containerapp logs show --name $APP_NAME --resource-group $RESOURCE_GROUP --follow" -ForegroundColor Gray
|
| 134 |
+
Write-Host "`n4. To delete all resources:" -ForegroundColor White
|
| 135 |
+
Write-Host " az group delete --name $RESOURCE_GROUP --yes --no-wait" -ForegroundColor Gray
|
requirements.txt
CHANGED
|
@@ -1,4 +1,3 @@
|
|
| 1 |
-
mesop>=0.4.0
|
| 2 |
pandas>=2.0.0
|
| 3 |
numpy>=1.24.0
|
| 4 |
#sap-rpt-oss
|
|
@@ -7,7 +6,7 @@ plotly>=5.17.0
|
|
| 7 |
requests>=2.31.0
|
| 8 |
python-dotenv>=1.0.0
|
| 9 |
scikit-learn>=1.3.0
|
| 10 |
-
gradio
|
| 11 |
pyarrow>=10.0.0
|
| 12 |
pyzmq>=25.0.0
|
| 13 |
|
|
|
|
|
|
|
| 1 |
pandas>=2.0.0
|
| 2 |
numpy>=1.24.0
|
| 3 |
#sap-rpt-oss
|
|
|
|
| 6 |
requests>=2.31.0
|
| 7 |
python-dotenv>=1.0.0
|
| 8 |
scikit-learn>=1.3.0
|
| 9 |
+
gradio>=4.0.0
|
| 10 |
pyarrow>=10.0.0
|
| 11 |
pyzmq>=25.0.0
|
| 12 |
|