Spaces:
Sleeping
Sleeping
Upload 37 files
Browse files- Dockerfile +25 -0
- README.md +13 -0
- backend/__init__.py +0 -0
- backend/app/__init__.py +0 -0
- backend/app/api/__init__.py +0 -0
- backend/app/api/clusters.py +6 -0
- backend/app/api/compare.py +18 -0
- backend/app/api/datasets.py +30 -0
- backend/app/api/experiments.py +52 -0
- backend/app/api/exports.py +14 -0
- backend/app/api/health.py +6 -0
- backend/app/api/presets.py +12 -0
- backend/app/config.py +9 -0
- backend/app/db.py +16 -0
- backend/app/main.py +35 -0
- backend/app/ml/__init__.py +0 -0
- backend/app/models/__init__.py +0 -0
- backend/app/models/dataset.py +12 -0
- backend/app/models/experiment.py +24 -0
- backend/app/repositories/__init__.py +0 -0
- backend/app/repositories/dataset_repo.py +12 -0
- backend/app/repositories/experiment_repo.py +19 -0
- backend/app/schemas/__init__.py +0 -0
- backend/app/schemas/dataset.py +13 -0
- backend/app/schemas/experiment.py +25 -0
- backend/app/services/__init__.py +0 -0
- backend/app/services/clustering_service.py +21 -0
- backend/app/services/evaluation_service.py +16 -0
- backend/app/services/explainability_service.py +22 -0
- backend/app/services/preprocessing_service.py +16 -0
- backend/app/services/profiling_service.py +14 -0
- backend/app/services/storage_service.py +23 -0
- backend/app/utils/__init__.py +0 -0
- backend/app/utils/ids.py +3 -0
- backend/requirements.txt +9 -0
- data_samples/access_demo.csv +251 -0
- scripts/migrate_db.py +6 -0
Dockerfile
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
FROM python:3.11-slim
|
| 2 |
+
|
| 3 |
+
ENV PYTHONDONTWRITEBYTECODE=1 \
|
| 4 |
+
PYTHONUNBUFFERED=1 \
|
| 5 |
+
PORT=7860
|
| 6 |
+
|
| 7 |
+
WORKDIR /workspace
|
| 8 |
+
|
| 9 |
+
RUN apt-get update && apt-get install -y --no-install-recommends \
|
| 10 |
+
build-essential \
|
| 11 |
+
curl \
|
| 12 |
+
&& rm -rf /var/lib/apt/lists/*
|
| 13 |
+
|
| 14 |
+
COPY backend/requirements.txt /workspace/requirements.txt
|
| 15 |
+
RUN pip install --no-cache-dir -r /workspace/requirements.txt
|
| 16 |
+
|
| 17 |
+
COPY backend /workspace/backend
|
| 18 |
+
COPY scripts /workspace/scripts
|
| 19 |
+
COPY data_samples /workspace/data_samples
|
| 20 |
+
|
| 21 |
+
RUN mkdir -p /data/uploads /data/artifacts
|
| 22 |
+
|
| 23 |
+
EXPOSE 7860
|
| 24 |
+
|
| 25 |
+
CMD ["uvicorn", "backend.app.main:app", "--host", "0.0.0.0", "--port", "7860"]
|
README.md
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
---
|
| 2 |
+
title: ClusterForge API
|
| 3 |
+
emoji: 📊
|
| 4 |
+
colorFrom: blue
|
| 5 |
+
colorTo: purple
|
| 6 |
+
sdk: docker
|
| 7 |
+
app_port: 7860
|
| 8 |
+
pinned: false
|
| 9 |
+
---
|
| 10 |
+
|
| 11 |
+
# ClusterForge API
|
| 12 |
+
|
| 13 |
+
FastAPI backend for clustering experiments on Hugging Face Spaces.
|
backend/__init__.py
ADDED
|
File without changes
|
backend/app/__init__.py
ADDED
|
File without changes
|
backend/app/api/__init__.py
ADDED
|
File without changes
|
backend/app/api/clusters.py
ADDED
|
@@ -0,0 +1,6 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from fastapi import APIRouter
|
| 2 |
+
router = APIRouter(prefix="/clusters", tags=["clusters"])
|
| 3 |
+
|
| 4 |
+
@router.get("")
|
| 5 |
+
def list_clusters_info():
|
| 6 |
+
return {"message": "Use /api/experiments/{experiment_id}/results for cluster details."}
|
backend/app/api/compare.py
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from fastapi import APIRouter, Depends, HTTPException, Query
|
| 2 |
+
from sqlalchemy.orm import Session
|
| 3 |
+
from backend.app.db import get_db
|
| 4 |
+
from backend.app.repositories.experiment_repo import get_experiment
|
| 5 |
+
from backend.app.services.storage_service import loads
|
| 6 |
+
|
| 7 |
+
router = APIRouter(prefix="/compare", tags=["compare"])
|
| 8 |
+
|
| 9 |
+
@router.get("")
|
| 10 |
+
def compare_runs(experiment_ids: str = Query(...), db: Session = Depends(get_db)):
|
| 11 |
+
ids = [x.strip() for x in experiment_ids.split(",") if x.strip()]
|
| 12 |
+
runs = []
|
| 13 |
+
for exp_id in ids:
|
| 14 |
+
exp = get_experiment(db, exp_id)
|
| 15 |
+
if not exp:
|
| 16 |
+
raise HTTPException(status_code=404, detail=f"Experiment not found: {exp_id}")
|
| 17 |
+
runs.append({"experiment_id": exp.id, "name": exp.name, "algorithm": exp.algorithm, "metrics": loads(exp.metrics_json)})
|
| 18 |
+
return {"runs": runs}
|
backend/app/api/datasets.py
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import json
|
| 2 |
+
import pandas as pd
|
| 3 |
+
from fastapi import APIRouter, Depends, File, HTTPException, UploadFile
|
| 4 |
+
from sqlalchemy.orm import Session
|
| 5 |
+
from backend.app.db import get_db
|
| 6 |
+
from backend.app.repositories.dataset_repo import create_dataset, get_dataset
|
| 7 |
+
from backend.app.schemas.dataset import DatasetProfileResponse, DatasetUploadResponse
|
| 8 |
+
from backend.app.services.profiling_service import profile_dataframe
|
| 9 |
+
from backend.app.services.storage_service import dumps, save_upload
|
| 10 |
+
|
| 11 |
+
router = APIRouter(prefix="/datasets", tags=["datasets"])
|
| 12 |
+
|
| 13 |
+
@router.post("/upload", response_model=DatasetUploadResponse)
|
| 14 |
+
async def upload_dataset(file: UploadFile = File(...), db: Session = Depends(get_db)):
|
| 15 |
+
if not file.filename.lower().endswith(".csv"):
|
| 16 |
+
raise HTTPException(status_code=400, detail="Only CSV files are supported.")
|
| 17 |
+
content = await file.read()
|
| 18 |
+
dataset_id, path = save_upload(file.filename, content)
|
| 19 |
+
df = pd.read_csv(path)
|
| 20 |
+
profile = profile_dataframe(df)
|
| 21 |
+
create_dataset(db, id=dataset_id, name=file.filename, file_path=str(path), row_count=len(df), column_count=len(df.columns), schema_json=dumps({"columns": list(df.columns)}), profile_json=dumps(profile))
|
| 22 |
+
return DatasetUploadResponse(dataset_id=dataset_id, name=file.filename, row_count=len(df), column_count=len(df.columns))
|
| 23 |
+
|
| 24 |
+
@router.get("/{dataset_id}/profile", response_model=DatasetProfileResponse)
|
| 25 |
+
def dataset_profile(dataset_id: str, db: Session = Depends(get_db)):
|
| 26 |
+
dataset = get_dataset(db, dataset_id)
|
| 27 |
+
if not dataset:
|
| 28 |
+
raise HTTPException(status_code=404, detail="Dataset not found.")
|
| 29 |
+
profile = json.loads(dataset.profile_json)
|
| 30 |
+
return DatasetProfileResponse(dataset_id=dataset_id, columns=profile["columns"], recommended_algorithms=profile["recommended_algorithms"])
|
backend/app/api/experiments.py
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import pandas as pd
|
| 2 |
+
from fastapi import APIRouter, Depends, HTTPException
|
| 3 |
+
from sqlalchemy.orm import Session
|
| 4 |
+
from backend.app.db import get_db
|
| 5 |
+
from backend.app.models.experiment import ClusterResult
|
| 6 |
+
from backend.app.repositories.dataset_repo import get_dataset
|
| 7 |
+
from backend.app.repositories.experiment_repo import add_cluster_results, create_experiment, get_cluster_results, get_experiment
|
| 8 |
+
from backend.app.schemas.experiment import ExperimentResultResponse, ExperimentRunRequest, ExperimentRunResponse
|
| 9 |
+
from backend.app.services.clustering_service import run_clustering
|
| 10 |
+
from backend.app.services.evaluation_service import compute_metrics
|
| 11 |
+
from backend.app.services.explainability_service import summarize_clusters
|
| 12 |
+
from backend.app.services.preprocessing_service import build_preprocessor
|
| 13 |
+
from backend.app.services.storage_service import dumps, loads, save_output_csv
|
| 14 |
+
from backend.app.utils.ids import make_id
|
| 15 |
+
|
| 16 |
+
router = APIRouter(prefix="/experiments", tags=["experiments"])
|
| 17 |
+
|
| 18 |
+
@router.post("/run", response_model=ExperimentRunResponse)
|
| 19 |
+
def run_experiment(payload: ExperimentRunRequest, db: Session = Depends(get_db)):
|
| 20 |
+
dataset = get_dataset(db, payload.dataset_id)
|
| 21 |
+
if not dataset:
|
| 22 |
+
raise HTTPException(status_code=404, detail="Dataset not found.")
|
| 23 |
+
df = pd.read_csv(dataset.file_path)
|
| 24 |
+
missing = [c for c in payload.feature_columns if c not in df.columns]
|
| 25 |
+
if missing:
|
| 26 |
+
raise HTTPException(status_code=400, detail=f"Missing feature columns: {missing}")
|
| 27 |
+
X_frame, preprocessor = build_preprocessor(df, payload.feature_columns, payload.preprocessing.numeric_scaler)
|
| 28 |
+
X = preprocessor.fit_transform(X_frame)
|
| 29 |
+
labels, coords = run_clustering(payload.algorithm, X, payload.algorithm_params)
|
| 30 |
+
metrics = compute_metrics(X, labels)
|
| 31 |
+
summaries = summarize_clusters(df, labels, payload.feature_columns)
|
| 32 |
+
experiment_id = make_id("exp")
|
| 33 |
+
result_df = df.copy()
|
| 34 |
+
result_df["cluster_label"] = labels
|
| 35 |
+
result_df["is_outlier"] = (labels == -1).astype(int)
|
| 36 |
+
csv_path = save_output_csv(result_df, experiment_id)
|
| 37 |
+
create_experiment(db, id=experiment_id, dataset_id=payload.dataset_id, name=payload.name, algorithm=payload.algorithm, status="completed", config_json=dumps(payload.model_dump()), metrics_json=dumps(metrics), summaries_json=dumps(summaries), output_csv_path=csv_path)
|
| 38 |
+
rows = [ClusterResult(experiment_id=experiment_id, row_index=int(i), cluster_label=int(labels[i]), is_outlier=int(labels[i] == -1), x=float(coords[i][0]), y=float(coords[i][1])) for i in range(len(labels))]
|
| 39 |
+
add_cluster_results(db, rows)
|
| 40 |
+
return ExperimentRunResponse(experiment_id=experiment_id, status="completed")
|
| 41 |
+
|
| 42 |
+
@router.get("/{experiment_id}", response_model=ExperimentResultResponse)
|
| 43 |
+
def get_experiment_result(experiment_id: str, db: Session = Depends(get_db)):
|
| 44 |
+
experiment = get_experiment(db, experiment_id)
|
| 45 |
+
if not experiment:
|
| 46 |
+
raise HTTPException(status_code=404, detail="Experiment not found.")
|
| 47 |
+
rows = get_cluster_results(db, experiment_id)
|
| 48 |
+
return ExperimentResultResponse(experiment_id=experiment.id, algorithm=experiment.algorithm, metrics=loads(experiment.metrics_json), scatter_points=[{"row_index": r.row_index, "cluster_label": r.cluster_label, "is_outlier": bool(r.is_outlier), "x": r.x, "y": r.y} for r in rows], cluster_summaries=loads(experiment.summaries_json))
|
| 49 |
+
|
| 50 |
+
@router.get("/{experiment_id}/results", response_model=ExperimentResultResponse)
|
| 51 |
+
def get_experiment_results_alias(experiment_id: str, db: Session = Depends(get_db)):
|
| 52 |
+
return get_experiment_result(experiment_id, db)
|
backend/app/api/exports.py
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from fastapi import APIRouter, Depends, HTTPException
|
| 2 |
+
from fastapi.responses import FileResponse
|
| 3 |
+
from sqlalchemy.orm import Session
|
| 4 |
+
from backend.app.db import get_db
|
| 5 |
+
from backend.app.repositories.experiment_repo import get_experiment
|
| 6 |
+
|
| 7 |
+
router = APIRouter(prefix="/exports", tags=["exports"])
|
| 8 |
+
|
| 9 |
+
@router.get("/{experiment_id}")
|
| 10 |
+
def export_results(experiment_id: str, db: Session = Depends(get_db)):
|
| 11 |
+
exp = get_experiment(db, experiment_id)
|
| 12 |
+
if not exp:
|
| 13 |
+
raise HTTPException(status_code=404, detail="Experiment not found.")
|
| 14 |
+
return FileResponse(exp.output_csv_path, filename=f"{experiment_id}_results.csv", media_type="text/csv")
|
backend/app/api/health.py
ADDED
|
@@ -0,0 +1,6 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from fastapi import APIRouter
|
| 2 |
+
router = APIRouter(tags=["health"])
|
| 3 |
+
|
| 4 |
+
@router.get("/health")
|
| 5 |
+
def health():
|
| 6 |
+
return {"ok": True, "service": "clusterforge-api"}
|
backend/app/api/presets.py
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from fastapi import APIRouter
|
| 2 |
+
router = APIRouter(prefix="/presets", tags=["presets"])
|
| 3 |
+
|
| 4 |
+
@router.get("")
|
| 5 |
+
def get_presets():
|
| 6 |
+
return {"algorithms": [
|
| 7 |
+
{"name": "kmeans", "params": {"n_clusters": 4}},
|
| 8 |
+
{"name": "minibatch_kmeans", "params": {"n_clusters": 4, "batch_size": 256}},
|
| 9 |
+
{"name": "agglomerative", "params": {"n_clusters": 4, "linkage": "ward"}},
|
| 10 |
+
{"name": "dbscan", "params": {"eps": 0.8, "min_samples": 8}},
|
| 11 |
+
{"name": "birch", "params": {"n_clusters": 4, "threshold": 0.5}},
|
| 12 |
+
]}
|
backend/app/config.py
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from pathlib import Path
|
| 2 |
+
|
| 3 |
+
DATA_DIR = Path("/data")
|
| 4 |
+
DB_PATH = DATA_DIR / "clusterforge.db"
|
| 5 |
+
UPLOAD_DIR = DATA_DIR / "uploads"
|
| 6 |
+
ARTIFACT_DIR = DATA_DIR / "artifacts"
|
| 7 |
+
|
| 8 |
+
for p in [DATA_DIR, UPLOAD_DIR, ARTIFACT_DIR]:
|
| 9 |
+
p.mkdir(parents=True, exist_ok=True)
|
backend/app/db.py
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from sqlalchemy import create_engine
|
| 2 |
+
from sqlalchemy.orm import declarative_base, sessionmaker
|
| 3 |
+
|
| 4 |
+
from backend.app.config import DB_PATH
|
| 5 |
+
|
| 6 |
+
DATABASE_URL = f"sqlite:///{DB_PATH}"
|
| 7 |
+
engine = create_engine(DATABASE_URL, connect_args={"check_same_thread": False})
|
| 8 |
+
SessionLocal = sessionmaker(bind=engine, autoflush=False, autocommit=False)
|
| 9 |
+
Base = declarative_base()
|
| 10 |
+
|
| 11 |
+
def get_db():
|
| 12 |
+
db = SessionLocal()
|
| 13 |
+
try:
|
| 14 |
+
yield db
|
| 15 |
+
finally:
|
| 16 |
+
db.close()
|
backend/app/main.py
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from fastapi import FastAPI
|
| 2 |
+
from fastapi.middleware.cors import CORSMiddleware
|
| 3 |
+
|
| 4 |
+
from backend.app.api.clusters import router as clusters_router
|
| 5 |
+
from backend.app.api.compare import router as compare_router
|
| 6 |
+
from backend.app.api.datasets import router as datasets_router
|
| 7 |
+
from backend.app.api.exports import router as exports_router
|
| 8 |
+
from backend.app.api.experiments import router as experiments_router
|
| 9 |
+
from backend.app.api.health import router as health_router
|
| 10 |
+
from backend.app.api.presets import router as presets_router
|
| 11 |
+
from backend.app.db import Base, engine
|
| 12 |
+
|
| 13 |
+
Base.metadata.create_all(bind=engine)
|
| 14 |
+
|
| 15 |
+
app = FastAPI(title="ClusterForge API", version="1.0.0")
|
| 16 |
+
|
| 17 |
+
app.add_middleware(
|
| 18 |
+
CORSMiddleware,
|
| 19 |
+
allow_origins=["*"],
|
| 20 |
+
allow_credentials=True,
|
| 21 |
+
allow_methods=["*"],
|
| 22 |
+
allow_headers=["*"],
|
| 23 |
+
)
|
| 24 |
+
|
| 25 |
+
app.include_router(health_router, prefix="/api")
|
| 26 |
+
app.include_router(datasets_router, prefix="/api")
|
| 27 |
+
app.include_router(experiments_router, prefix="/api")
|
| 28 |
+
app.include_router(clusters_router, prefix="/api")
|
| 29 |
+
app.include_router(compare_router, prefix="/api")
|
| 30 |
+
app.include_router(exports_router, prefix="/api")
|
| 31 |
+
app.include_router(presets_router, prefix="/api")
|
| 32 |
+
|
| 33 |
+
@app.get("/")
|
| 34 |
+
def root():
|
| 35 |
+
return {"ok": True, "service": "clusterforge-api", "docs": "/docs"}
|
backend/app/ml/__init__.py
ADDED
|
File without changes
|
backend/app/models/__init__.py
ADDED
|
File without changes
|
backend/app/models/dataset.py
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from sqlalchemy import Column, Integer, String, Text
|
| 2 |
+
from backend.app.db import Base
|
| 3 |
+
|
| 4 |
+
class Dataset(Base):
|
| 5 |
+
__tablename__ = "datasets"
|
| 6 |
+
id = Column(String, primary_key=True, index=True)
|
| 7 |
+
name = Column(String, nullable=False)
|
| 8 |
+
file_path = Column(String, nullable=False)
|
| 9 |
+
row_count = Column(Integer, nullable=False)
|
| 10 |
+
column_count = Column(Integer, nullable=False)
|
| 11 |
+
schema_json = Column(Text, nullable=False)
|
| 12 |
+
profile_json = Column(Text, nullable=False)
|
backend/app/models/experiment.py
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from sqlalchemy import Column, Float, ForeignKey, Integer, String, Text
|
| 2 |
+
from backend.app.db import Base
|
| 3 |
+
|
| 4 |
+
class Experiment(Base):
|
| 5 |
+
__tablename__ = "experiments"
|
| 6 |
+
id = Column(String, primary_key=True, index=True)
|
| 7 |
+
dataset_id = Column(String, ForeignKey("datasets.id"), nullable=False, index=True)
|
| 8 |
+
name = Column(String, nullable=False)
|
| 9 |
+
algorithm = Column(String, nullable=False)
|
| 10 |
+
status = Column(String, nullable=False)
|
| 11 |
+
config_json = Column(Text, nullable=False)
|
| 12 |
+
metrics_json = Column(Text, nullable=False)
|
| 13 |
+
summaries_json = Column(Text, nullable=False)
|
| 14 |
+
output_csv_path = Column(String, nullable=False)
|
| 15 |
+
|
| 16 |
+
class ClusterResult(Base):
|
| 17 |
+
__tablename__ = "cluster_results"
|
| 18 |
+
id = Column(Integer, primary_key=True, autoincrement=True)
|
| 19 |
+
experiment_id = Column(String, ForeignKey("experiments.id"), nullable=False, index=True)
|
| 20 |
+
row_index = Column(Integer, nullable=False)
|
| 21 |
+
cluster_label = Column(Integer, nullable=False)
|
| 22 |
+
is_outlier = Column(Integer, nullable=False, default=0)
|
| 23 |
+
x = Column(Float, nullable=False, default=0.0)
|
| 24 |
+
y = Column(Float, nullable=False, default=0.0)
|
backend/app/repositories/__init__.py
ADDED
|
File without changes
|
backend/app/repositories/dataset_repo.py
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from sqlalchemy.orm import Session
|
| 2 |
+
from backend.app.models.dataset import Dataset
|
| 3 |
+
|
| 4 |
+
def create_dataset(db: Session, **kwargs) -> Dataset:
|
| 5 |
+
obj = Dataset(**kwargs)
|
| 6 |
+
db.add(obj)
|
| 7 |
+
db.commit()
|
| 8 |
+
db.refresh(obj)
|
| 9 |
+
return obj
|
| 10 |
+
|
| 11 |
+
def get_dataset(db: Session, dataset_id: str) -> Dataset | None:
|
| 12 |
+
return db.query(Dataset).filter(Dataset.id == dataset_id).first()
|
backend/app/repositories/experiment_repo.py
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from sqlalchemy.orm import Session
|
| 2 |
+
from backend.app.models.experiment import ClusterResult, Experiment
|
| 3 |
+
|
| 4 |
+
def create_experiment(db: Session, **kwargs) -> Experiment:
|
| 5 |
+
obj = Experiment(**kwargs)
|
| 6 |
+
db.add(obj)
|
| 7 |
+
db.commit()
|
| 8 |
+
db.refresh(obj)
|
| 9 |
+
return obj
|
| 10 |
+
|
| 11 |
+
def get_experiment(db: Session, experiment_id: str) -> Experiment | None:
|
| 12 |
+
return db.query(Experiment).filter(Experiment.id == experiment_id).first()
|
| 13 |
+
|
| 14 |
+
def add_cluster_results(db: Session, rows: list[ClusterResult]) -> None:
|
| 15 |
+
db.add_all(rows)
|
| 16 |
+
db.commit()
|
| 17 |
+
|
| 18 |
+
def get_cluster_results(db: Session, experiment_id: str) -> list[ClusterResult]:
|
| 19 |
+
return db.query(ClusterResult).filter(ClusterResult.experiment_id == experiment_id).all()
|
backend/app/schemas/__init__.py
ADDED
|
File without changes
|
backend/app/schemas/dataset.py
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from pydantic import BaseModel
|
| 2 |
+
from typing import Any
|
| 3 |
+
|
| 4 |
+
class DatasetUploadResponse(BaseModel):
|
| 5 |
+
dataset_id: str
|
| 6 |
+
name: str
|
| 7 |
+
row_count: int
|
| 8 |
+
column_count: int
|
| 9 |
+
|
| 10 |
+
class DatasetProfileResponse(BaseModel):
|
| 11 |
+
dataset_id: str
|
| 12 |
+
columns: list[dict[str, Any]]
|
| 13 |
+
recommended_algorithms: list[str]
|
backend/app/schemas/experiment.py
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from pydantic import BaseModel, Field
|
| 2 |
+
from typing import Any
|
| 3 |
+
|
| 4 |
+
class PreprocessingConfig(BaseModel):
|
| 5 |
+
numeric_scaler: str = "standard"
|
| 6 |
+
|
| 7 |
+
class ExperimentRunRequest(BaseModel):
|
| 8 |
+
dataset_id: str
|
| 9 |
+
name: str = "Clustering Run"
|
| 10 |
+
algorithm: str = Field(default="kmeans")
|
| 11 |
+
feature_columns: list[str]
|
| 12 |
+
preprocessing: PreprocessingConfig = PreprocessingConfig()
|
| 13 |
+
algorithm_params: dict[str, Any] = {}
|
| 14 |
+
outlier_detection_enabled: bool = True
|
| 15 |
+
|
| 16 |
+
class ExperimentRunResponse(BaseModel):
|
| 17 |
+
experiment_id: str
|
| 18 |
+
status: str
|
| 19 |
+
|
| 20 |
+
class ExperimentResultResponse(BaseModel):
|
| 21 |
+
experiment_id: str
|
| 22 |
+
algorithm: str
|
| 23 |
+
metrics: dict[str, Any]
|
| 24 |
+
scatter_points: list[dict[str, Any]]
|
| 25 |
+
cluster_summaries: list[dict[str, Any]]
|
backend/app/services/__init__.py
ADDED
|
File without changes
|
backend/app/services/clustering_service.py
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import numpy as np
|
| 2 |
+
from sklearn.cluster import AgglomerativeClustering, Birch, DBSCAN, KMeans, MiniBatchKMeans
|
| 3 |
+
from sklearn.decomposition import PCA
|
| 4 |
+
|
| 5 |
+
def run_clustering(algorithm: str, X, params: dict):
|
| 6 |
+
algo = algorithm.lower()
|
| 7 |
+
if algo == "kmeans":
|
| 8 |
+
labels = KMeans(n_clusters=int(params.get("n_clusters", 4)), random_state=int(params.get("random_state", 42)), n_init=10).fit_predict(X)
|
| 9 |
+
elif algo == "minibatch_kmeans":
|
| 10 |
+
labels = MiniBatchKMeans(n_clusters=int(params.get("n_clusters", 4)), random_state=int(params.get("random_state", 42)), batch_size=int(params.get("batch_size", 256)), n_init=10).fit_predict(X)
|
| 11 |
+
elif algo == "agglomerative":
|
| 12 |
+
labels = AgglomerativeClustering(n_clusters=int(params.get("n_clusters", 4)), linkage=str(params.get("linkage", "ward"))).fit_predict(X.toarray() if hasattr(X, "toarray") else X)
|
| 13 |
+
elif algo == "dbscan":
|
| 14 |
+
labels = DBSCAN(eps=float(params.get("eps", 0.8)), min_samples=int(params.get("min_samples", 8))).fit_predict(X)
|
| 15 |
+
elif algo == "birch":
|
| 16 |
+
labels = Birch(n_clusters=int(params.get("n_clusters", 4)), threshold=float(params.get("threshold", 0.5))).fit_predict(X)
|
| 17 |
+
else:
|
| 18 |
+
raise ValueError(f"Unsupported algorithm: {algorithm}")
|
| 19 |
+
dense = X.toarray() if hasattr(X, "toarray") else X
|
| 20 |
+
coords = PCA(n_components=2, random_state=42).fit_transform(dense)
|
| 21 |
+
return np.array(labels), coords
|
backend/app/services/evaluation_service.py
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import numpy as np
|
| 2 |
+
from sklearn.metrics import calinski_harabasz_score, davies_bouldin_score, silhouette_score
|
| 3 |
+
|
| 4 |
+
def compute_metrics(X, labels) -> dict:
|
| 5 |
+
dense = X.toarray() if hasattr(X, "toarray") else X
|
| 6 |
+
unique = sorted(set(int(x) for x in labels))
|
| 7 |
+
non_noise = [u for u in unique if u != -1]
|
| 8 |
+
metrics = {"cluster_count": len(non_noise), "noise_ratio": round(float(np.mean(labels == -1)), 4) if len(labels) else 0.0, "silhouette_score": None, "davies_bouldin_score": None, "calinski_harabasz_score": None}
|
| 9 |
+
mask = labels != -1
|
| 10 |
+
valid_labels = labels[mask]
|
| 11 |
+
valid_X = dense[mask]
|
| 12 |
+
if len(set(valid_labels.tolist())) >= 2 and len(valid_X) > len(set(valid_labels.tolist())):
|
| 13 |
+
metrics["silhouette_score"] = round(float(silhouette_score(valid_X, valid_labels)), 4)
|
| 14 |
+
metrics["davies_bouldin_score"] = round(float(davies_bouldin_score(valid_X, valid_labels)), 4)
|
| 15 |
+
metrics["calinski_harabasz_score"] = round(float(calinski_harabasz_score(valid_X, valid_labels)), 4)
|
| 16 |
+
return metrics
|
backend/app/services/explainability_service.py
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import pandas as pd
|
| 2 |
+
|
| 3 |
+
def summarize_clusters(df: pd.DataFrame, labels, feature_columns: list[str]) -> list[dict]:
|
| 4 |
+
work = df[feature_columns].copy()
|
| 5 |
+
work["_cluster_label"] = labels
|
| 6 |
+
out = []
|
| 7 |
+
for label in sorted(work["_cluster_label"].unique().tolist()):
|
| 8 |
+
cdf = work[work["_cluster_label"] == label]
|
| 9 |
+
if label == -1:
|
| 10 |
+
desc = "Noise or outlier records that did not fit a stable cluster."
|
| 11 |
+
else:
|
| 12 |
+
parts = []
|
| 13 |
+
for col in feature_columns[:4]:
|
| 14 |
+
if pd.api.types.is_numeric_dtype(cdf[col]):
|
| 15 |
+
parts.append(f"{col} avg {round(float(cdf[col].mean()), 2)}")
|
| 16 |
+
else:
|
| 17 |
+
mode = cdf[col].mode(dropna=True)
|
| 18 |
+
if not mode.empty:
|
| 19 |
+
parts.append(f"{col}={mode.iloc[0]}")
|
| 20 |
+
desc = "Cluster profile: " + ", ".join(parts) if parts else "Cluster summary unavailable."
|
| 21 |
+
out.append({"cluster_label": int(label), "size": int(len(cdf)), "description_text": desc})
|
| 22 |
+
return out
|
backend/app/services/preprocessing_service.py
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import pandas as pd
|
| 2 |
+
from sklearn.compose import ColumnTransformer
|
| 3 |
+
from sklearn.impute import SimpleImputer
|
| 4 |
+
from sklearn.pipeline import Pipeline
|
| 5 |
+
from sklearn.preprocessing import OneHotEncoder, RobustScaler, StandardScaler
|
| 6 |
+
|
| 7 |
+
def build_preprocessor(df: pd.DataFrame, feature_columns: list[str], scaler_name: str = "standard"):
|
| 8 |
+
X = df[feature_columns].copy()
|
| 9 |
+
num = [c for c in feature_columns if pd.api.types.is_numeric_dtype(X[c])]
|
| 10 |
+
cat = [c for c in feature_columns if c not in num]
|
| 11 |
+
scaler = StandardScaler() if scaler_name == "standard" else RobustScaler()
|
| 12 |
+
pre = ColumnTransformer(transformers=[
|
| 13 |
+
("num", Pipeline([("imputer", SimpleImputer(strategy="median")), ("scaler", scaler)]), num),
|
| 14 |
+
("cat", Pipeline([("imputer", SimpleImputer(strategy="most_frequent")), ("onehot", OneHotEncoder(handle_unknown="ignore"))]), cat),
|
| 15 |
+
])
|
| 16 |
+
return X, pre
|
backend/app/services/profiling_service.py
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import pandas as pd
|
| 2 |
+
|
| 3 |
+
def profile_dataframe(df: pd.DataFrame) -> dict:
|
| 4 |
+
columns = []
|
| 5 |
+
for col in df.columns:
|
| 6 |
+
s = df[col]
|
| 7 |
+
dtype = "numeric" if pd.api.types.is_numeric_dtype(s) else "categorical"
|
| 8 |
+
columns.append({"name": col, "inferred_type": dtype, "missing_pct": round(float(s.isna().mean()) * 100, 2), "cardinality": int(s.nunique(dropna=True))})
|
| 9 |
+
recommended = ["kmeans", "birch"]
|
| 10 |
+
if any(c["inferred_type"] == "categorical" for c in columns):
|
| 11 |
+
recommended.append("agglomerative")
|
| 12 |
+
if len(df) >= 50:
|
| 13 |
+
recommended.append("dbscan")
|
| 14 |
+
return {"columns": columns, "recommended_algorithms": recommended}
|
backend/app/services/storage_service.py
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import json
|
| 2 |
+
from pathlib import Path
|
| 3 |
+
import pandas as pd
|
| 4 |
+
from backend.app.config import ARTIFACT_DIR, UPLOAD_DIR
|
| 5 |
+
from backend.app.utils.ids import make_id
|
| 6 |
+
|
| 7 |
+
def save_upload(filename: str, content: bytes) -> tuple[str, Path]:
|
| 8 |
+
dataset_id = make_id("ds")
|
| 9 |
+
suffix = Path(filename).suffix or ".csv"
|
| 10 |
+
path = UPLOAD_DIR / f"{dataset_id}{suffix}"
|
| 11 |
+
path.write_bytes(content)
|
| 12 |
+
return dataset_id, path
|
| 13 |
+
|
| 14 |
+
def save_output_csv(df: pd.DataFrame, experiment_id: str) -> str:
|
| 15 |
+
path = ARTIFACT_DIR / f"{experiment_id}_results.csv"
|
| 16 |
+
df.to_csv(path, index=False)
|
| 17 |
+
return str(path)
|
| 18 |
+
|
| 19 |
+
def dumps(data) -> str:
|
| 20 |
+
return json.dumps(data)
|
| 21 |
+
|
| 22 |
+
def loads(data: str):
|
| 23 |
+
return json.loads(data)
|
backend/app/utils/__init__.py
ADDED
|
File without changes
|
backend/app/utils/ids.py
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import uuid
|
| 2 |
+
def make_id(prefix: str) -> str:
|
| 3 |
+
return f"{prefix}_{uuid.uuid4().hex[:12]}"
|
backend/requirements.txt
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
fastapi==0.115.12
|
| 2 |
+
uvicorn[standard]==0.34.2
|
| 3 |
+
sqlalchemy==2.0.40
|
| 4 |
+
pydantic==2.11.3
|
| 5 |
+
pandas==2.2.3
|
| 6 |
+
numpy==2.2.5
|
| 7 |
+
scikit-learn==1.6.1
|
| 8 |
+
python-multipart==0.0.20
|
| 9 |
+
joblib==1.4.2
|
data_samples/access_demo.csv
ADDED
|
@@ -0,0 +1,251 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
user_id,department,title,location,entitlement_count,privileged_access_flag,risk_score,peer_access_overlap_score,last_login_days_ago,review_count_6m
|
| 2 |
+
user_0000,Engineering,Engineer,SF,21,0,47.52,0.3573,6,4
|
| 3 |
+
user_0001,Engineering,Engineer,NY,14,0,47.04,0.7667,20,6
|
| 4 |
+
user_0002,HR,Manager,NY,8,0,57.03,0.5425,18,2
|
| 5 |
+
user_0003,Sales,Manager,Remote,10,0,39.26,0.6048,24,4
|
| 6 |
+
user_0004,Engineering,Manager,Remote,14,1,59.93,0.8193,6,2
|
| 7 |
+
user_0005,Finance,Engineer,Austin,14,0,74.35,0.4502,22,4
|
| 8 |
+
user_0006,HR,Analyst,Austin,12,0,55.43,0.5601,22,5
|
| 9 |
+
user_0007,Engineering,Manager,Remote,15,0,37.8,0.8742,11,5
|
| 10 |
+
user_0008,Engineering,Analyst,Remote,18,0,44.69,0.7567,27,3
|
| 11 |
+
user_0009,HR,Admin,NY,6,1,55.93,0.4121,12,4
|
| 12 |
+
user_0010,Engineering,Analyst,Austin,18,0,45.0,0.6036,24,2
|
| 13 |
+
user_0011,Finance,Analyst,NY,7,0,73.9,0.4796,20,4
|
| 14 |
+
user_0012,Engineering,Manager,SF,16,1,54.36,0.3969,0,1
|
| 15 |
+
user_0013,Sales,Manager,Remote,10,0,45.39,0.4966,27,2
|
| 16 |
+
user_0014,HR,Analyst,NY,10,0,56.72,0.2909,24,4
|
| 17 |
+
user_0015,Finance,Manager,Remote,9,0,71.86,0.5533,36,3
|
| 18 |
+
user_0016,Finance,Admin,SF,17,1,91.68,0.6035,22,1
|
| 19 |
+
user_0017,Finance,Manager,Remote,6,0,75.08,0.5167,5,1
|
| 20 |
+
user_0018,Engineering,Analyst,NY,25,0,63.31,0.7122,10,0
|
| 21 |
+
user_0019,HR,Admin,SF,9,0,48.87,0.7099,21,3
|
| 22 |
+
user_0020,Engineering,Manager,NY,17,0,54.14,0.6695,29,3
|
| 23 |
+
user_0021,Finance,Analyst,NY,9,0,87.03,0.4268,28,2
|
| 24 |
+
user_0022,Sales,Engineer,SF,11,0,34.67,0.4518,24,3
|
| 25 |
+
user_0023,HR,Admin,Remote,18,0,51.28,0.5322,22,6
|
| 26 |
+
user_0024,Engineering,Manager,Austin,19,1,67.29,0.6775,0,1
|
| 27 |
+
user_0025,Finance,Analyst,Remote,11,0,66.93,0.7692,8,2
|
| 28 |
+
user_0026,Engineering,Manager,Remote,17,0,42.7,0.8611,20,5
|
| 29 |
+
user_0027,Finance,Manager,Austin,7,0,62.97,0.4999,29,1
|
| 30 |
+
user_0028,Finance,Manager,NY,10,0,67.38,0.7004,25,6
|
| 31 |
+
user_0029,HR,Admin,Remote,12,1,66.41,0.3873,8,4
|
| 32 |
+
user_0030,Sales,Analyst,Austin,8,0,32.7,0.3305,14,4
|
| 33 |
+
user_0031,Finance,Manager,Remote,3,0,68.7,0.3906,14,2
|
| 34 |
+
user_0032,Engineering,Admin,Remote,26,0,29.7,0.4855,38,9
|
| 35 |
+
user_0033,Finance,Manager,Austin,18,1,77.11,0.5132,15,4
|
| 36 |
+
user_0034,HR,Engineer,NY,6,0,48.09,0.51,22,2
|
| 37 |
+
user_0035,Engineering,Engineer,Austin,18,0,42.81,0.658,20,2
|
| 38 |
+
user_0036,Finance,Manager,NY,20,1,97.59,0.6079,0,1
|
| 39 |
+
user_0037,Engineering,Analyst,NY,19,0,26.04,0.7891,24,1
|
| 40 |
+
user_0038,Engineering,Engineer,Remote,18,0,37.66,0.6345,17,4
|
| 41 |
+
user_0039,Sales,Admin,SF,12,1,52.37,0.4942,0,4
|
| 42 |
+
user_0040,Finance,Manager,SF,10,1,84.51,0.3921,4,6
|
| 43 |
+
user_0041,Sales,Admin,SF,13,1,63.95,0.6158,16,3
|
| 44 |
+
user_0042,Engineering,Analyst,SF,21,0,30.57,0.7674,18,6
|
| 45 |
+
user_0043,Finance,Engineer,Austin,14,0,57.42,0.54,8,2
|
| 46 |
+
user_0044,Engineering,Manager,Remote,13,0,40.26,0.4675,13,4
|
| 47 |
+
user_0045,Engineering,Analyst,Austin,8,0,42.43,0.6608,24,7
|
| 48 |
+
user_0046,Engineering,Analyst,Remote,20,0,27.35,0.8714,23,5
|
| 49 |
+
user_0047,Engineering,Analyst,Austin,22,0,41.86,0.6852,22,2
|
| 50 |
+
user_0048,HR,Manager,NY,15,1,56.53,0.5312,22,2
|
| 51 |
+
user_0049,Sales,Manager,Remote,12,0,45.63,0.6785,28,5
|
| 52 |
+
user_0050,Sales,Admin,Austin,18,1,46.34,0.5662,21,0
|
| 53 |
+
user_0051,HR,Admin,SF,13,1,69.19,0.5914,0,0
|
| 54 |
+
user_0052,Sales,Engineer,SF,13,0,40.62,0.5707,27,4
|
| 55 |
+
user_0053,Finance,Analyst,NY,11,0,71.57,0.6731,16,5
|
| 56 |
+
user_0054,Finance,Analyst,NY,15,0,54.06,0.3555,5,0
|
| 57 |
+
user_0055,Finance,Engineer,Remote,10,0,71.58,0.7134,33,3
|
| 58 |
+
user_0056,Finance,Engineer,Austin,17,0,70.74,0.4244,14,1
|
| 59 |
+
user_0057,Finance,Analyst,SF,15,0,83.77,0.3427,23,1
|
| 60 |
+
user_0058,Finance,Analyst,Remote,4,0,77.43,0.4592,14,1
|
| 61 |
+
user_0059,Engineering,Engineer,Remote,17,0,42.63,0.7043,33,3
|
| 62 |
+
user_0060,Engineering,Engineer,Austin,16,0,48.92,0.7075,18,4
|
| 63 |
+
user_0061,Engineering,Analyst,NY,18,0,41.28,0.8114,11,4
|
| 64 |
+
user_0062,Engineering,Admin,SF,11,1,38.42,0.4824,3,3
|
| 65 |
+
user_0063,Engineering,Analyst,NY,14,0,42.78,0.5889,17,4
|
| 66 |
+
user_0064,HR,Analyst,Remote,16,0,40.86,0.551,45,4
|
| 67 |
+
user_0065,Engineering,Engineer,Remote,20,0,39.55,0.6244,12,4
|
| 68 |
+
user_0066,Engineering,Manager,SF,20,1,63.68,0.705,5,4
|
| 69 |
+
user_0067,Finance,Engineer,SF,4,0,67.32,0.2514,5,6
|
| 70 |
+
user_0068,Finance,Analyst,Austin,5,0,46.28,0.4685,44,4
|
| 71 |
+
user_0069,HR,Admin,Austin,10,0,50.8,0.5371,27,4
|
| 72 |
+
user_0070,Finance,Engineer,NY,15,0,83.03,0.4045,11,6
|
| 73 |
+
user_0071,Finance,Engineer,SF,10,0,81.64,0.5697,22,7
|
| 74 |
+
user_0072,Finance,Manager,NY,13,0,57.47,0.6456,14,6
|
| 75 |
+
user_0073,Engineering,Analyst,NY,11,0,33.39,0.6871,18,3
|
| 76 |
+
user_0074,Engineering,Engineer,NY,13,0,45.65,0.7494,23,5
|
| 77 |
+
user_0075,Engineering,Engineer,Remote,17,0,37.54,0.537,9,1
|
| 78 |
+
user_0076,Engineering,Admin,Austin,17,1,57.67,0.6577,0,5
|
| 79 |
+
user_0077,Finance,Manager,NY,14,0,71.5,0.3328,19,4
|
| 80 |
+
user_0078,Engineering,Engineer,Austin,12,0,50.69,0.8372,17,4
|
| 81 |
+
user_0079,Engineering,Manager,Remote,13,0,45.72,0.9496,8,2
|
| 82 |
+
user_0080,Engineering,Manager,Remote,18,0,54.57,0.4022,7,3
|
| 83 |
+
user_0081,Engineering,Admin,Remote,10,0,44.12,0.5634,32,2
|
| 84 |
+
user_0082,Engineering,Manager,Remote,19,0,26.52,0.7307,9,4
|
| 85 |
+
user_0083,Engineering,Engineer,Remote,14,0,29.74,0.7571,22,2
|
| 86 |
+
user_0084,Engineering,Analyst,Remote,18,0,42.15,0.5571,24,2
|
| 87 |
+
user_0085,Sales,Engineer,Austin,7,0,15.7,0.7916,45,3
|
| 88 |
+
user_0086,Engineering,Manager,SF,17,1,46.09,0.7369,13,1
|
| 89 |
+
user_0087,Engineering,Engineer,Remote,18,0,37.3,0.6287,26,0
|
| 90 |
+
user_0088,HR,Manager,Remote,11,0,63.34,0.3844,25,4
|
| 91 |
+
user_0089,Engineering,Admin,Austin,16,1,41.46,0.6474,0,3
|
| 92 |
+
user_0090,Sales,Manager,NY,13,1,45.82,0.1653,15,4
|
| 93 |
+
user_0091,HR,Admin,NY,13,1,84.06,0.613,11,3
|
| 94 |
+
user_0092,HR,Admin,SF,9,0,46.52,0.4781,27,6
|
| 95 |
+
user_0093,Engineering,Manager,Remote,17,0,47.62,0.3122,11,2
|
| 96 |
+
user_0094,Finance,Engineer,NY,8,0,68.39,0.7169,17,1
|
| 97 |
+
user_0095,HR,Admin,NY,8,0,54.47,0.5168,26,3
|
| 98 |
+
user_0096,Finance,Manager,Remote,11,1,75.27,0.3155,14,3
|
| 99 |
+
user_0097,Engineering,Engineer,SF,22,0,21.75,0.4255,10,6
|
| 100 |
+
user_0098,HR,Analyst,Remote,7,0,41.04,0.6172,20,5
|
| 101 |
+
user_0099,Finance,Analyst,Remote,12,0,76.22,0.6711,3,0
|
| 102 |
+
user_0100,Finance,Analyst,Austin,7,0,55.12,0.5649,29,7
|
| 103 |
+
user_0101,Finance,Admin,Remote,12,0,67.61,0.3977,40,7
|
| 104 |
+
user_0102,HR,Engineer,Remote,15,0,55.12,0.6164,32,5
|
| 105 |
+
user_0103,HR,Admin,NY,4,1,65.67,0.4666,0,7
|
| 106 |
+
user_0104,Engineering,Admin,SF,23,0,40.1,0.6804,9,4
|
| 107 |
+
user_0105,Engineering,Analyst,Remote,17,0,39.36,0.9196,28,4
|
| 108 |
+
user_0106,Sales,Analyst,Remote,11,0,26.34,0.5273,12,1
|
| 109 |
+
user_0107,HR,Manager,Austin,11,0,57.97,0.7089,30,4
|
| 110 |
+
user_0108,Engineering,Analyst,Austin,19,0,43.03,0.8431,30,3
|
| 111 |
+
user_0109,Sales,Analyst,SF,12,0,33.38,0.4633,22,2
|
| 112 |
+
user_0110,Finance,Admin,Remote,12,1,87.19,0.3791,3,6
|
| 113 |
+
user_0111,Sales,Admin,Remote,3,1,35.11,0.5544,8,3
|
| 114 |
+
user_0112,Engineering,Engineer,Austin,19,0,52.49,0.4808,16,2
|
| 115 |
+
user_0113,Engineering,Engineer,NY,23,0,54.7,0.5996,39,4
|
| 116 |
+
user_0114,Engineering,Admin,Remote,19,1,80.43,0.7777,0,5
|
| 117 |
+
user_0115,Sales,Engineer,SF,15,0,35.25,0.5918,20,4
|
| 118 |
+
user_0116,Engineering,Analyst,SF,20,0,32.14,0.4337,19,4
|
| 119 |
+
user_0117,HR,Manager,NY,10,1,60.32,0.543,10,1
|
| 120 |
+
user_0118,Sales,Analyst,Austin,4,0,19.33,0.5482,17,3
|
| 121 |
+
user_0119,Sales,Admin,Remote,7,0,25.62,0.386,22,6
|
| 122 |
+
user_0120,Engineering,Manager,NY,14,0,35.54,0.5887,20,3
|
| 123 |
+
user_0121,Sales,Manager,Austin,13,0,22.71,0.6085,21,3
|
| 124 |
+
user_0122,Finance,Engineer,Remote,20,0,68.2,0.3584,20,1
|
| 125 |
+
user_0123,Finance,Engineer,SF,14,0,65.02,0.6981,31,6
|
| 126 |
+
user_0124,Engineering,Engineer,NY,10,0,37.46,0.5199,21,2
|
| 127 |
+
user_0125,Engineering,Manager,SF,19,0,38.14,0.6614,20,4
|
| 128 |
+
user_0126,Engineering,Admin,Remote,20,1,45.79,0.6368,17,5
|
| 129 |
+
user_0127,Sales,Analyst,SF,13,0,44.6,0.5934,16,4
|
| 130 |
+
user_0128,Sales,Analyst,Remote,3,0,21.21,0.6178,24,1
|
| 131 |
+
user_0129,Finance,Admin,SF,20,0,69.98,0.6112,36,4
|
| 132 |
+
user_0130,HR,Manager,Austin,6,1,62.95,0.4387,17,4
|
| 133 |
+
user_0131,Finance,Admin,Austin,14,1,77.0,0.3938,19,3
|
| 134 |
+
user_0132,Engineering,Analyst,SF,19,0,27.75,0.6844,27,4
|
| 135 |
+
user_0133,Finance,Admin,SF,10,0,66.14,0.6806,34,7
|
| 136 |
+
user_0134,Engineering,Manager,SF,21,0,40.97,0.6696,28,3
|
| 137 |
+
user_0135,HR,Engineer,SF,14,0,50.02,0.601,26,3
|
| 138 |
+
user_0136,Engineering,Manager,NY,23,1,50.99,0.4017,0,1
|
| 139 |
+
user_0137,Finance,Admin,SF,14,1,87.72,0.5974,12,5
|
| 140 |
+
user_0138,Engineering,Admin,Remote,18,0,39.96,0.8068,31,4
|
| 141 |
+
user_0139,Engineering,Engineer,NY,15,0,39.99,0.7979,14,5
|
| 142 |
+
user_0140,Sales,Manager,SF,14,0,35.82,0.4357,11,2
|
| 143 |
+
user_0141,Sales,Manager,Remote,7,0,33.37,0.5664,44,1
|
| 144 |
+
user_0142,Finance,Engineer,Austin,13,0,70.95,0.6167,18,6
|
| 145 |
+
user_0143,Engineering,Manager,Remote,17,1,46.42,0.7183,22,3
|
| 146 |
+
user_0144,Engineering,Engineer,NY,17,0,27.26,0.5131,22,6
|
| 147 |
+
user_0145,Engineering,Admin,Remote,18,0,38.79,0.7149,20,4
|
| 148 |
+
user_0146,Engineering,Manager,NY,20,0,37.79,0.5606,20,5
|
| 149 |
+
user_0147,Engineering,Analyst,Remote,22,0,52.63,0.7242,29,6
|
| 150 |
+
user_0148,Engineering,Analyst,SF,20,0,44.16,0.4854,27,1
|
| 151 |
+
user_0149,Engineering,Admin,SF,23,1,52.83,0.4772,5,4
|
| 152 |
+
user_0150,Sales,Admin,NY,13,1,42.09,0.6175,8,2
|
| 153 |
+
user_0151,Engineering,Analyst,NY,16,0,54.17,0.6352,17,0
|
| 154 |
+
user_0152,Engineering,Engineer,Austin,15,0,51.05,0.6253,32,4
|
| 155 |
+
user_0153,Engineering,Analyst,Austin,18,0,53.75,0.3021,0,2
|
| 156 |
+
user_0154,Engineering,Admin,NY,16,1,63.15,0.8045,9,5
|
| 157 |
+
user_0155,Finance,Admin,Austin,12,1,85.01,0.7709,0,1
|
| 158 |
+
user_0156,Finance,Manager,Austin,5,1,77.12,0.4209,32,7
|
| 159 |
+
user_0157,Finance,Engineer,NY,7,0,54.73,0.5325,10,3
|
| 160 |
+
user_0158,Engineering,Manager,Austin,19,1,58.34,0.4519,16,2
|
| 161 |
+
user_0159,Finance,Manager,NY,10,1,87.92,0.6419,6,7
|
| 162 |
+
user_0160,Engineering,Admin,Remote,25,1,70.68,0.4658,0,6
|
| 163 |
+
user_0161,Finance,Manager,NY,13,1,84.18,0.4524,1,2
|
| 164 |
+
user_0162,Finance,Engineer,SF,13,0,77.09,0.4766,18,2
|
| 165 |
+
user_0163,Finance,Admin,NY,3,1,75.44,0.7487,7,6
|
| 166 |
+
user_0164,Engineering,Analyst,Remote,14,0,44.19,0.7593,14,5
|
| 167 |
+
user_0165,Engineering,Admin,NY,13,0,40.69,0.6991,12,4
|
| 168 |
+
user_0166,HR,Analyst,NY,18,0,60.22,0.3998,36,2
|
| 169 |
+
user_0167,HR,Admin,Austin,4,0,48.63,0.3161,29,7
|
| 170 |
+
user_0168,Engineering,Manager,Austin,15,0,46.2,0.6442,37,3
|
| 171 |
+
user_0169,Engineering,Admin,SF,16,1,54.6,0.6327,0,5
|
| 172 |
+
user_0170,Engineering,Analyst,Austin,24,0,51.59,0.5938,23,2
|
| 173 |
+
user_0171,Finance,Engineer,NY,18,0,72.91,0.5173,31,0
|
| 174 |
+
user_0172,Engineering,Analyst,SF,18,0,42.0,0.451,19,8
|
| 175 |
+
user_0173,Engineering,Analyst,SF,16,0,36.84,0.3306,22,5
|
| 176 |
+
user_0174,Sales,Admin,Remote,8,1,50.43,0.2373,14,5
|
| 177 |
+
user_0175,Engineering,Engineer,Remote,14,0,49.86,0.2333,16,1
|
| 178 |
+
user_0176,Engineering,Analyst,Remote,19,0,44.52,0.9254,17,4
|
| 179 |
+
user_0177,Engineering,Manager,Austin,15,1,54.82,0.4156,10,5
|
| 180 |
+
user_0178,Engineering,Admin,NY,20,1,39.98,0.4888,16,4
|
| 181 |
+
user_0179,Engineering,Manager,Remote,20,1,76.69,0.6179,0,4
|
| 182 |
+
user_0180,Finance,Analyst,Austin,7,0,88.48,0.599,12,5
|
| 183 |
+
user_0181,Sales,Manager,NY,6,0,37.82,0.8369,24,3
|
| 184 |
+
user_0182,Sales,Admin,Austin,10,1,57.33,0.29,16,3
|
| 185 |
+
user_0183,HR,Analyst,Remote,17,0,59.97,0.6939,4,5
|
| 186 |
+
user_0184,Engineering,Manager,Remote,19,1,59.45,0.6766,10,6
|
| 187 |
+
user_0185,Engineering,Admin,SF,17,0,25.98,0.6641,32,1
|
| 188 |
+
user_0186,Engineering,Engineer,SF,15,0,51.82,0.5686,24,0
|
| 189 |
+
user_0187,Sales,Admin,NY,14,0,23.72,0.7274,16,3
|
| 190 |
+
user_0188,Finance,Analyst,NY,14,0,72.05,0.6478,19,5
|
| 191 |
+
user_0189,Engineering,Admin,Austin,23,0,50.94,0.5222,42,5
|
| 192 |
+
user_0190,Sales,Admin,SF,11,0,38.12,0.7602,21,2
|
| 193 |
+
user_0191,HR,Admin,NY,14,0,47.83,0.4107,11,0
|
| 194 |
+
user_0192,Engineering,Analyst,Austin,16,0,16.36,0.4629,31,2
|
| 195 |
+
user_0193,Finance,Engineer,Austin,17,0,68.85,0.5935,43,7
|
| 196 |
+
user_0194,Sales,Manager,SF,14,1,49.16,0.4907,22,3
|
| 197 |
+
user_0195,Sales,Admin,Austin,13,1,57.7,0.4413,0,4
|
| 198 |
+
user_0196,Engineering,Manager,Austin,12,0,28.58,0.3849,21,6
|
| 199 |
+
user_0197,Engineering,Analyst,Austin,17,0,38.7,0.9644,5,3
|
| 200 |
+
user_0198,Finance,Manager,SF,12,0,72.64,0.6666,11,5
|
| 201 |
+
user_0199,Engineering,Admin,NY,15,1,54.82,0.5566,0,3
|
| 202 |
+
user_0200,Engineering,Analyst,Remote,13,0,45.7,0.4728,14,2
|
| 203 |
+
user_0201,HR,Analyst,NY,11,0,53.99,0.2339,19,7
|
| 204 |
+
user_0202,HR,Manager,NY,15,1,49.02,0.2977,10,2
|
| 205 |
+
user_0203,Engineering,Engineer,Austin,10,0,29.86,0.2375,17,3
|
| 206 |
+
user_0204,Engineering,Manager,SF,17,1,37.92,0.7426,13,4
|
| 207 |
+
user_0205,Engineering,Engineer,Remote,26,0,32.32,0.7624,3,2
|
| 208 |
+
user_0206,Sales,Analyst,Remote,15,0,36.9,0.5343,33,3
|
| 209 |
+
user_0207,HR,Admin,Austin,13,1,52.46,0.4413,2,3
|
| 210 |
+
user_0208,Engineering,Analyst,Austin,15,0,46.17,0.7714,18,3
|
| 211 |
+
user_0209,Finance,Manager,Austin,8,0,64.92,0.3755,21,5
|
| 212 |
+
user_0210,HR,Engineer,SF,11,0,52.73,0.5481,5,1
|
| 213 |
+
user_0211,Finance,Engineer,Austin,3,0,72.77,0.7284,18,5
|
| 214 |
+
user_0212,Finance,Analyst,SF,5,0,51.25,0.6914,10,3
|
| 215 |
+
user_0213,Engineering,Engineer,Remote,17,0,34.16,0.7067,24,4
|
| 216 |
+
user_0214,Sales,Analyst,NY,7,0,34.56,0.7199,32,1
|
| 217 |
+
user_0215,Finance,Manager,SF,12,1,83.26,0.4924,10,3
|
| 218 |
+
user_0216,HR,Manager,NY,14,0,73.31,0.2835,23,2
|
| 219 |
+
user_0217,Finance,Admin,Austin,16,0,71.54,0.7198,0,7
|
| 220 |
+
user_0218,Finance,Admin,Remote,8,1,86.17,0.5474,10,6
|
| 221 |
+
user_0219,Engineering,Admin,SF,17,0,28.23,0.7255,0,5
|
| 222 |
+
user_0220,Finance,Manager,Austin,16,0,68.57,0.4459,14,6
|
| 223 |
+
user_0221,Engineering,Manager,Austin,11,0,43.11,0.386,27,4
|
| 224 |
+
user_0222,Sales,Manager,SF,19,0,44.53,0.4456,22,4
|
| 225 |
+
user_0223,HR,Engineer,Remote,11,0,37.22,0.5703,24,3
|
| 226 |
+
user_0224,HR,Analyst,Austin,13,0,44.19,0.5079,4,1
|
| 227 |
+
user_0225,Engineering,Engineer,SF,15,0,45.12,0.5693,22,3
|
| 228 |
+
user_0226,Sales,Admin,NY,11,0,23.94,0.4702,7,4
|
| 229 |
+
user_0227,Engineering,Admin,NY,18,1,45.98,0.576,9,0
|
| 230 |
+
user_0228,Engineering,Analyst,SF,16,0,42.08,0.8037,18,2
|
| 231 |
+
user_0229,HR,Admin,Austin,7,1,66.01,0.4624,2,5
|
| 232 |
+
user_0230,Finance,Analyst,NY,6,0,64.7,0.8384,31,4
|
| 233 |
+
user_0231,Engineering,Engineer,Austin,18,0,40.3,0.6364,19,3
|
| 234 |
+
user_0232,Engineering,Manager,SF,12,1,53.48,0.527,9,3
|
| 235 |
+
user_0233,Finance,Admin,Remote,14,1,78.8,0.4635,11,3
|
| 236 |
+
user_0234,Finance,Manager,Austin,9,0,65.47,0.7149,26,7
|
| 237 |
+
user_0235,Engineering,Analyst,Austin,18,0,30.59,0.5334,13,1
|
| 238 |
+
user_0236,HR,Analyst,SF,7,0,56.74,0.4123,48,3
|
| 239 |
+
user_0237,Engineering,Admin,SF,22,0,42.38,0.448,22,1
|
| 240 |
+
user_0238,Engineering,Analyst,NY,18,0,20.51,0.5025,26,6
|
| 241 |
+
user_0239,Sales,Engineer,SF,13,0,43.12,0.3012,19,4
|
| 242 |
+
user_0240,Engineering,Engineer,Austin,16,0,37.38,0.6278,14,3
|
| 243 |
+
user_0241,Engineering,Analyst,SF,16,0,36.72,0.6639,0,4
|
| 244 |
+
user_0242,Engineering,Analyst,Remote,21,0,24.38,0.3539,22,3
|
| 245 |
+
user_0243,HR,Engineer,Austin,14,0,57.46,0.6226,17,0
|
| 246 |
+
user_0244,Sales,Manager,NY,8,0,40.39,0.6791,35,2
|
| 247 |
+
user_0245,Sales,Engineer,Remote,12,0,26.06,0.6122,25,4
|
| 248 |
+
user_0246,Engineering,Manager,SF,14,0,35.3,0.6516,28,1
|
| 249 |
+
user_0247,HR,Analyst,NY,12,0,41.05,0.4923,26,2
|
| 250 |
+
user_0248,Engineering,Admin,SF,19,1,49.46,0.6247,18,4
|
| 251 |
+
user_0249,Engineering,Analyst,SF,13,0,51.3,0.8036,22,4
|
scripts/migrate_db.py
ADDED
|
@@ -0,0 +1,6 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from backend.app.db import Base, engine
|
| 2 |
+
from backend.app.models.dataset import Dataset
|
| 3 |
+
from backend.app.models.experiment import ClusterResult, Experiment
|
| 4 |
+
|
| 5 |
+
Base.metadata.create_all(bind=engine)
|
| 6 |
+
print("Database initialized.")
|