diff --git a/.dockerignore b/.dockerignore
new file mode 100644
index 0000000000000000000000000000000000000000..ae2f17f36380507a8f1b1858763c53e82ea5abdd
--- /dev/null
+++ b/.dockerignore
@@ -0,0 +1,9 @@
+venv/
+env/
+.env
+backend/chroma_db/
+backend/__pycache__/
+middleware/__pycache__/
+frontend/node_modules/
+frontend/dist/
+.git/
diff --git a/.gitattributes b/.gitattributes
new file mode 100644
index 0000000000000000000000000000000000000000..1a929c17174f7ddd94c421f9f71758571c562441
--- /dev/null
+++ b/.gitattributes
@@ -0,0 +1,8 @@
+*.db filter=lfs diff=lfs merge=lfs -text
+*.pt filter=lfs diff=lfs merge=lfs -text
+*.png filter=lfs diff=lfs merge=lfs -text
+*.jpg filter=lfs diff=lfs merge=lfs -text
+*.mp4 filter=lfs diff=lfs merge=lfs -text
+*.mp3 filter=lfs diff=lfs merge=lfs -text
+*.pkl filter=lfs diff=lfs merge=lfs -text
+*.cache filter=lfs diff=lfs merge=lfs -text
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000000000000000000000000000000000000..df938d971313bead3e41c8cca5b990483241d046
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,28 @@
+# Virtual Environment
+venv/
+env/
+.env
+
+# Python Caches
+__pycache__/
+*.py[cod]
+*$py.class
+
+# Large data & model files
+data/
+runs/
+*.cache
+*.db-journal
+*.sqlite3-journal
+
+# Keep these for production
+!middleware/wafer_control.db
+!middleware/material_model.pkl
+!middleware/best.pt
+
+
+# OS generated files
+.DS_Store
+
+# Vector DB
+backend/chroma_db/
diff --git a/Dockerfile b/Dockerfile
new file mode 100644
index 0000000000000000000000000000000000000000..bf92591b6b706009fb145fa56120708b9dce3777
--- /dev/null
+++ b/Dockerfile
@@ -0,0 +1,21 @@
+FROM python:3.12-slim
+
+WORKDIR /app
+
+# Install system dependencies
+RUN apt-get update && apt-get install -y build-essential && rm -rf /var/lib/apt/lists/*
+
+# Copy and install dependencies
+COPY backend/requirements.txt ./backend/
+RUN pip install --no-cache-dir -r backend/requirements.txt
+
+# Copy everything needed
+COPY backend/ ./backend/
+COPY middleware/ ./middleware/
+COPY runs/ ./runs/
+
+# Hugging Face requires port 7860
+EXPOSE 7860
+
+# Start the server
+CMD ["uvicorn", "backend.main:app", "--host", "0.0.0.0", "--port", "7860"]
diff --git a/README.md b/README.md
new file mode 100644
index 0000000000000000000000000000000000000000..ef9889e51cd3bef42c3da079d815182817c63092
--- /dev/null
+++ b/README.md
@@ -0,0 +1,87 @@
+# Semiconductor Wafer Defect Detection: End-to-End AI Pipeline
+
+## Project Overview
+This project is a complete, end-to-end Applied AI pipeline designed for the semiconductor manufacturing industry. It takes raw mathematical array data representing defective semiconductor wafers, engineers them into an AI-ready computer vision dataset, trains a custom YOLOv8 object detection model, and feeds the results into a predictive material waste model and real-time dashboard.
+
+**Final YOLOv8 Model Performance:** `0.962 mAP@50` (96.2% overall accuracy on unseen validation data).
+**Predictive Waste Model Performance:** `R² = 0.9637` (Highly accurate material waste prediction).
+
+## Business Value
+In semiconductor fabrication, identifying microscopic defects early in the manufacturing process saves millions in scrapped materials. This project automates quality control by transitioning from manual coordinate analysis to real-time, AI-driven visual defect detection, while simultaneously forecasting future material waste to optimize supply chain planning.
+
+## The Technical Pipeline
+
+### Phase 1: Data Engineering (`src/data_prep.py`)
+* **The Challenge:** The original dataset consisted of raw `.txt` files containing numeric 2D arrays (0=background, 1=good chip, 2=defect). YOLOv8 cannot read text arrays; it requires physical images and normalized bounding box coordinates.
+* **The Solution:** Built a custom Python pipeline using `NumPy` and `OpenCV` to parse over 25,000 text files.
+* **The Math:** Programmatically identified the spatial extremes (`xmin`, `ymin`, `xmax`, `ymax`) of the `2` values, normalized them to YOLO's strict `0.0 - 1.0` format, and dynamically rendered high-contrast `.jpg` images alongside corresponding `.txt` label files.
+
+### Phase 2: Dataset Architecture (`src/split_data.py`)
+* Used `scikit-learn` to execute a mathematically rigorous 80/20 train/validation split.
+* Programmatically generated the strict directory architecture required by YOLO, migrating over 50,000 individual files into structured `train` and `val` directories.
+
+### Phase 3: Model Training (`src/model_train.py`)
+* Initialized a pre-trained **YOLOv8 Nano** (`yolov8n.pt`) model for lightweight, high-speed inference.
+* Trained on 20,415 wafer images for 10 epochs.
+* Mapped 8 specific manufacturing defect classes (Center, Donut, Edge-Loc, Edge-Ring, Loc, Random, Scratch, Near-full).
+
+### Phase 4: Batch Inference & Evaluation (`src/batch_inference.py` & `src/model_eval.py`)
+* Deployed the custom-trained `best.pt` weights to run batch inference on unseen validation images.
+* Model successfully drew accurate bounding boxes and assigned confidence scores entirely autonomously.
+
+### Phase 5: Production Middleware, Predictive Modeling & Dashboard
+* **Robotic Scanner Simulation (`middleware/robot_controller.py`):** Operates on a massive hybrid dataset of **823,953 wafers** (Mixed-type + WM-811K datasets) with a realistic 95.5% pass rate. It automatically routes passed wafers and runs YOLOv8 inference on defective ones, logging everything into a centralized SQLite database (`wafer_control.db`).
+* **Material Waste Predictor (`middleware/material_predictor.py`):** A Random Forest Regressor trained on the historical scan database. It accurately predicts the average percentage of material wasted within defective wafers, allowing fabs to estimate future material needs.
+* **Real-time Dashboard (`middleware/dashboard.py`):** A **Plotly Dash** web application that visualizes historical defect rates, defect distributions, routing actions, and integrates interactive material forecasting inputs.
+
+## Upcoming Feature: LLM Troubleshooting Assistant (Planned)
+**Goal:** Integrate an intelligent Large Language Model (LLM) bot to assist fab engineers directly on the factory floor.
+* **Functionality:** When the dashboard flags a sudden spike in a specific defect type (e.g., "Edge-Ring" defects), the engineer can consult the LLM bot.
+* **Use Case:** The bot will analyze the defect trends, cross-reference historical manufacturing guidelines, and suggest potential root causes (such as misaligned etching tools or incorrect gas pressure), drastically reducing troubleshooting and downtime.
+*(Note: This feature is currently in the design phase and not yet implemented).*
+
+## Performance Metrics
+The YOLOv8 model achieved phenomenal results on the blind validation set:
+
+| Metric | Score | Note |
+| :--- | :--- | :--- |
+| **mAP50 (All Classes)** | **96.2%** | Overall model accuracy at a 50% confidence threshold. |
+| **Recall** | **93.1%** | The model successfully located 93.1% of all physical defects. |
+| **Edge-Ring (mAP50)** | **99.4%** | Near-flawless detection of Edge-Ring anomalies. |
+
+The Random Forest Material Waste Predictor achieved:
+| Metric | Score | Note |
+| :--- | :--- | :--- |
+| **R² Score** | **0.9637** | Excellent correlation on predictive targets. |
+| **MAE** | **0.09%** | Average prediction error is less than one-tenth of a percent. |
+
+## Tech Stack
+* **Languages:** Python
+* **Computer Vision:** Ultralytics (YOLOv8), OpenCV (`cv2`)
+* **Machine Learning & Data:** Pandas, NumPy, Scikit-learn, SQLite
+* **Web UI & Visualization:** Plotly, Dash
+
+## Deployment (Docker)
+
+This application is fully containerized for easy deployment.
+
+1. **Clone the repository:**
+ ```bash
+ git clone https://github.com/Udayan2001/Semiconductor_defect_detection.git
+ cd Semiconductor_defect_detection
+ ```
+2. **Add API Key:**
+ Create a `.env` file in the `backend/` directory and add your Google Gemini API key:
+ ```
+ GEMINI_API_KEY=your_api_key_here
+ ```
+3. **Start the Application:**
+ Run the following command from the root directory to build and start both the backend and frontend servers:
+ ```bash
+ docker compose up --build
+ ```
+4. **Access the Dashboard:**
+ Open your browser and navigate to `http://localhost:5173`.
+
+---
+*Designed and engineered by Udayan Shashank Shukla.*
\ No newline at end of file
diff --git a/backend/Dockerfile b/backend/Dockerfile
new file mode 100644
index 0000000000000000000000000000000000000000..c5fb920a07db524c8e44305c8064a621d867716e
--- /dev/null
+++ b/backend/Dockerfile
@@ -0,0 +1,19 @@
+FROM python:3.12-slim
+
+WORKDIR /app
+
+# Install essential system dependencies
+RUN apt-get update && apt-get install -y build-essential && rm -rf /var/lib/apt/lists/*
+
+# Copy and install python dependencies
+COPY backend/requirements.txt ./backend/
+RUN pip install --no-cache-dir -r backend/requirements.txt
+
+# Copy backend and middleware code
+COPY backend/ ./backend/
+COPY middleware/ ./middleware/
+
+EXPOSE 8000
+
+# Run ingestion first to ensure vector DB is seeded, then start the server
+CMD ["sh", "-c", "python backend/ingest_knowledge.py && uvicorn backend.main:app --host 0.0.0.0 --port 8000"]
diff --git a/backend/ingest_knowledge.py b/backend/ingest_knowledge.py
new file mode 100644
index 0000000000000000000000000000000000000000..2b2992e48380672a8a48e3f97bab880c45a95d96
--- /dev/null
+++ b/backend/ingest_knowledge.py
@@ -0,0 +1,65 @@
+import os
+import chromadb
+from chromadb.config import Settings
+
+# Engineering knowledge base regarding semiconductor wafer defects
+KNOWLEDGE_BASE = [
+ {
+ "id": "defect_edge_ring",
+ "title": "Edge-Ring Defect Troubleshooting",
+ "content": "Edge-Ring defects typically appear as a continuous ring of failing dies around the outer edge of the wafer. Common Root Causes: 1. Uneven gas distribution in the etching chamber. 2. Non-uniform chuck temperature during deposition or etching. 3. Edge-bead removal issues during photolithography. Recommended Action: Inspect gas flow regulators and recalibrate chuck temperature sensors. Schedule maintenance for edge-bead removal module."
+ },
+ {
+ "id": "defect_center",
+ "title": "Center Defect Troubleshooting",
+ "content": "Center defects are concentrated in the middle of the wafer. Common Root Causes: 1. Poor spin-coating uniformity (photoresist pooling in the center). 2. Center-heavy deposition profile. 3. Excessive center heating on the electrostatic chuck. Recommended Action: Verify spin speed and acceleration in the coating track. Check gas showerhead for clogging in the center region."
+ },
+ {
+ "id": "defect_scratch",
+ "title": "Scratch Defect Troubleshooting",
+ "content": "Scratch defects manifest as linear patterns of failing dies, often crossing the wafer. Common Root Causes: 1. Mechanical handling damage by robotic arms or end-effectors. 2. Particulate contamination causing dragging during CMP (Chemical Mechanical Polishing). 3. Cassette or FOUP abrasion. Recommended Action: Check robot alignment and end-effector cleanliness. Inspect CMP pad conditioning and slurry filtration system."
+ },
+ {
+ "id": "defect_donut",
+ "title": "Donut Defect Troubleshooting",
+ "content": "Donut defects appear as a ring, but not at the very edge (like Edge-Ring), leaving the center and extreme edge relatively clean. Common Root Causes: 1. Radially dependent temperature non-uniformity during rapid thermal processing (RTP). 2. Specific gas flow dynamics creating standing waves or depletion zones in the chamber. Recommended Action: Recalibrate RTP lamp zones. Inspect gas showerhead and exhaust pumping symmetry."
+ },
+ {
+ "id": "general_forecast_strategy",
+ "title": "Material Forecast & Yield Strategy",
+ "content": "When the predicted material waste percentage rises above 5%, the factory must proactively increase raw material orders (wafers, photoresist, precursor gases) for the next quarter to compensate for the lower yield. High fail rates typically necessitate a temporary slow-down of production throughput to allow for deep tool maintenance and recalibration."
+ }
+]
+
+def ingest_data():
+ print("Initializing ChromaDB Persistent Client...")
+ db_path = os.path.join(os.path.dirname(__file__), "chroma_db")
+ client = chromadb.PersistentClient(path=db_path)
+
+ # Create or get collection
+ collection = client.get_or_create_collection(
+ name="semiconductor_knowledge",
+ metadata={"hnsw:space": "cosine"}
+ )
+
+ # Clear existing data if any (for idempotency)
+ existing_ids = collection.get()['ids']
+ if existing_ids:
+ collection.delete(ids=existing_ids)
+
+ # Prepare data for insertion
+ ids = [item['id'] for item in KNOWLEDGE_BASE]
+ documents = [item['content'] for item in KNOWLEDGE_BASE]
+ metadatas = [{"title": item['title']} for item in KNOWLEDGE_BASE]
+
+ print(f"Adding {len(documents)} documents to the knowledge base...")
+ collection.add(
+ documents=documents,
+ metadatas=metadatas,
+ ids=ids
+ )
+
+ print("Ingestion complete. ChromaDB is ready.")
+
+if __name__ == "__main__":
+ ingest_data()
diff --git a/backend/main.py b/backend/main.py
new file mode 100644
index 0000000000000000000000000000000000000000..f516d671ee10278ba47b12446051dca146986ee1
--- /dev/null
+++ b/backend/main.py
@@ -0,0 +1,257 @@
+import sys
+import os
+import pickle
+import sqlite3
+import pandas as pd
+from fastapi import FastAPI
+from fastapi.middleware.cors import CORSMiddleware
+from pydantic import BaseModel
+from dotenv import load_dotenv
+from google import genai
+import chromadb
+from typing import List, Dict
+
+env_path = os.path.join(os.path.dirname(__file__), '.env')
+load_dotenv(env_path)
+
+# Add parent dir to path so we can import from middleware
+sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), '..')))
+from middleware.material_predictor import predict_material_needs
+
+app = FastAPI(title="Wafer Defect API")
+
+app.add_middleware(
+ CORSMiddleware,
+ allow_origins=["*"],
+ allow_credentials=True,
+ allow_methods=["*"],
+ allow_headers=["*"],
+)
+
+# Robust paths for Docker/Hosting
+BASE_DIR = os.path.dirname(os.path.abspath(__file__))
+DB_PATH = os.path.join(BASE_DIR, '..', 'middleware', 'wafer_control.db')
+MODEL_PATH = os.path.join(BASE_DIR, '..', 'middleware', 'material_model.pkl')
+CHROMA_PATH = os.path.join(BASE_DIR, 'chroma_db')
+
+# Ensure directories exist
+os.makedirs(CHROMA_PATH, exist_ok=True)
+
+DEFECT_COLORS = {
+ 'Center': '#ef4444', 'Donut': '#f59e0b', 'Edge-Loc': '#10b981',
+ 'Edge-Ring': '#3b82f6', 'Loc': '#8b5cf6', 'Random': '#ec4899',
+ 'Scratch': '#06b6d4', 'Near-full': '#f97316', 'None': '#6b7280',
+ 'Undetected': '#374151',
+}
+
+# Globally load data so we don't block requests
+df = pd.DataFrame()
+if os.path.exists(DB_PATH):
+ print(f"Loading DB from {DB_PATH}...")
+ conn = sqlite3.connect(DB_PATH)
+ df = pd.read_sql_query("SELECT * FROM wafer_logs", conn)
+ conn.close()
+ df['scan_time'] = pd.to_datetime(df['scan_time'])
+ df['scan_date'] = df['scan_time'].dt.date
+else:
+ print(f"Warning: DB not found at {DB_PATH}. Dashboard will be empty.")
+
+# Setup Vector DB and LLM
+print(f"Connecting to ChromaDB at {CHROMA_PATH}...")
+try:
+ chroma_client = chromadb.PersistentClient(path=CHROMA_PATH)
+ collection = chroma_client.get_or_create_collection(name="semiconductor_knowledge")
+except Exception as e:
+ print(f"Warning: Could not connect to ChromaDB collection. Error: {e}")
+ collection = None
+
+print("Initializing Gemini API...")
+gemini_client = None
+if os.getenv("GEMINI_API_KEY"):
+ gemini_client = genai.Client(api_key=os.getenv("GEMINI_API_KEY"))
+else:
+ print("Warning: GEMINI_API_KEY not found in environment.")
+
+print("Loading ML model...")
+model_pkg = None
+if os.path.exists(MODEL_PATH):
+ with open(MODEL_PATH, 'rb') as f:
+ model_pkg = pickle.load(f)
+
+@app.get("/api/kpi")
+def get_kpis():
+ total_scans = len(df)
+ fail_df = df[df['status'] == 'FAIL']
+ fail_count = len(fail_df)
+ pass_count = len(df[df['status'] == 'PASS'])
+ pass_rate = round((pass_count / total_scans) * 100, 1) if total_scans else 0
+ scrap_count = len(df[df['action'] == 'ROUTE_TO_SCRAP'])
+ avg_waste = round(fail_df['material_wasted_pct'].mean(), 2) if fail_count else 0
+ avg_confidence = round(fail_df['confidence'].mean(), 2) if fail_count else 0
+
+ return {
+ "total_scans": total_scans,
+ "pass_count": pass_count,
+ "pass_rate": pass_rate,
+ "fail_count": fail_count,
+ "fail_rate": round(100 - pass_rate, 1),
+ "scrap_count": scrap_count,
+ "avg_waste": avg_waste,
+ "avg_confidence": avg_confidence
+ }
+
+@app.get("/api/charts/defects")
+def get_defects():
+ fail_df = df[df['status'] == 'FAIL']
+ defect_counts = fail_df['defect_type'].value_counts().reset_index()
+ defect_counts.columns = ['defect_type', 'count']
+
+ gt_counts = fail_df['ground_truth'].value_counts().reset_index()
+ gt_counts.columns = ['ground_truth', 'count']
+
+ return {
+ "predictions": defect_counts.to_dict(orient="records"),
+ "ground_truth": gt_counts.head(15).to_dict(orient="records")
+ }
+
+@app.get("/api/charts/waste")
+def get_waste():
+ fail_df = df[df['status'] == 'FAIL']
+
+ waste_by_type = fail_df.groupby('defect_type').agg(
+ total_waste=('material_wasted_pct', lambda x: x.sum() / 100.0)
+ ).reset_index().sort_values('total_waste', ascending=True)
+
+ action_counts = df['action'].value_counts().reset_index()
+ action_counts.columns = ['action', 'count']
+
+ return {
+ "waste_by_type": waste_by_type.to_dict(orient="records"),
+ "actions": action_counts.to_dict(orient="records")
+ }
+
+@app.get("/api/charts/trends")
+def get_trends():
+ daily = df.groupby('scan_date').agg(
+ scans=('id', 'count'),
+ fails=('status', lambda x: (x == 'FAIL').sum()),
+ waste=('material_wasted_pct', lambda x: x.sum() / 100.0)
+ ).reset_index()
+ daily['fail_rate'] = round((daily['fails'] / daily['scans']) * 100, 1)
+
+ return {
+ "dates": daily['scan_date'].astype(str).tolist(),
+ "fail_rate": daily['fail_rate'].tolist(),
+ "waste": daily['waste'].tolist()
+ }
+
+@app.get("/api/model/status")
+def model_status():
+ if not model_pkg:
+ return {"loaded": False}
+
+ m = model_pkg['metrics']
+ imp = model_pkg['metrics']['importances']
+ imp_df = pd.DataFrame({'feature': list(imp.keys()), 'importance': list(imp.values())})
+ imp_df = imp_df.sort_values('importance', ascending=True).tail(10)
+
+ return {
+ "loaded": True,
+ "metrics": {"r2": round(m['r2'], 4), "mae": round(m['mae'], 2)},
+ "importance": imp_df.to_dict(orient="records")
+ }
+
+class PredictionRequest(BaseModel):
+ scans: int
+ fail_rate: float
+
+@app.post("/api/predict")
+def predict_waste(req: PredictionRequest):
+ if not model_pkg:
+ return {"error": "No model loaded"}
+
+ fail_df = df[df['status'] == 'FAIL']
+ dist = fail_df['defect_type'].value_counts(normalize=True).to_dict()
+
+ pred = predict_material_needs(model_pkg['model'], model_pkg['feature_cols'], req.scans, req.fail_rate / 100.0, dist)
+ pred['fail_rate'] = req.fail_rate
+ return pred
+
+
+class ChatMessage(BaseModel):
+ role: str
+ content: str
+
+class ChatRequest(BaseModel):
+ messages: List[ChatMessage]
+
+@app.post("/api/chat")
+def chat_with_bot(req: ChatRequest):
+ if not gemini_client:
+ return {"error": "Gemini API key not configured"}
+
+ user_message = req.messages[-1].content if req.messages else ""
+
+ # 1. RAG Retrieval from ChromaDB
+ context_docs = ""
+ if collection and user_message:
+ try:
+ results = collection.query(query_texts=[user_message], n_results=2)
+ if results and results['documents'] and results['documents'][0]:
+ context_docs = "\n".join(results['documents'][0])
+ except Exception as e:
+ print(f"ChromaDB Query Error: {e}")
+
+ # 2. Get Live Dashboard Context
+ total_scans = len(df)
+ fail_df = df[df['status'] == 'FAIL']
+ fail_count = len(fail_df)
+ pass_rate = round(((total_scans - fail_count) / total_scans) * 100, 1) if total_scans else 0
+ top_defects = fail_df['defect_type'].value_counts().head(3).to_dict()
+
+ live_kpis = f"""
+ Current Dashboard State:
+ - Total Wafers Scanned: {total_scans}
+ - Current Pass Rate: {pass_rate}%
+ - Total Defective Wafers: {fail_count}
+ - Top Defect Types Right Now: {top_defects}
+ """
+
+ # 3. Construct System Prompt
+ system_instruction = f"""
+ You are the 'Gorilla Semiconductors Engineering Assistant', an expert semiconductor manufacturing assistant.
+ You help engineers understand dashboard data and troubleshoot wafer defects.
+ Maintain a strictly professional, analytical, and authoritative engineering tone.
+
+ Here is the LIVE DATA from the dashboard:
+ {live_kpis}
+
+ Here is retrieved technical context from our engineering database based on the user's query:
+ {context_docs if context_docs else "No specific engineering docs retrieved."}
+
+ Use the live data to answer questions about 'current status' or 'dashboard'.
+ Use the engineering docs to answer questions about 'why' a defect happens.
+ """
+
+ try:
+ # Convert messages to format expected by google-genai
+ contents = []
+ for msg in req.messages:
+ role = "user" if msg.role == "user" else "model"
+ contents.append(
+ genai.types.Content(role=role, parts=[genai.types.Part.from_text(text=msg.content)])
+ )
+
+ response = gemini_client.models.generate_content(
+ model='gemini-2.5-flash-lite',
+ contents=contents,
+ config=genai.types.GenerateContentConfig(
+ system_instruction=system_instruction,
+ temperature=0.3
+ )
+ )
+ return {"response": response.text}
+ except Exception as e:
+ print(f"Gemini API Error: {e}")
+ return {"error": str(e)}
+
diff --git a/backend/requirements.txt b/backend/requirements.txt
new file mode 100644
index 0000000000000000000000000000000000000000..72b952876c47c9beb56d9f61a288de722d501179
--- /dev/null
+++ b/backend/requirements.txt
@@ -0,0 +1,8 @@
+fastapi>=0.100.0
+uvicorn>=0.22.0
+pandas>=2.0.0
+scikit-learn>=1.3.0
+pydantic>=2.0.0
+google-genai>=0.3.0
+chromadb>=0.4.24
+python-dotenv>=1.0.0
diff --git a/dataset.yaml b/dataset.yaml
new file mode 100644
index 0000000000000000000000000000000000000000..c3cee45df37494747b604b25e23e2a55458dfbb6
--- /dev/null
+++ b/dataset.yaml
@@ -0,0 +1,19 @@
+# YOLOv8 Dataset Configuration File
+
+# The base path to your dataset folder
+path: data/yolo_dataset
+
+# The subfolders for training and validation images
+train: images/train
+val: images/val
+
+# The 8 defect classes we mapped earlier
+names:
+ 0: Center
+ 1: Donut
+ 2: Edge-Loc
+ 3: Edge-Ring
+ 4: Loc
+ 5: Random
+ 6: Scratch
+ 7: Near-full
\ No newline at end of file
diff --git a/docker-compose.yml b/docker-compose.yml
new file mode 100644
index 0000000000000000000000000000000000000000..d42417fc3d39ad3ca99bc4ea910e0871ef347056
--- /dev/null
+++ b/docker-compose.yml
@@ -0,0 +1,23 @@
+services:
+ backend:
+ build:
+ context: .
+ dockerfile: backend/Dockerfile
+ ports:
+ - "8000:8000"
+ # Ensure the container has access to the environment variables
+ env_file:
+ - ./backend/.env
+ # Optional: Mount the SQLite DB so changes persist
+ volumes:
+ - ./middleware/wafer_control.db:/app/middleware/wafer_control.db
+
+ frontend:
+ build:
+ context: ./frontend
+ dockerfile: Dockerfile
+ ports:
+ # Map the Nginx internal port 80 to 5173 to match the dev environment
+ - "5173:80"
+ depends_on:
+ - backend
diff --git a/frontend/.dockerignore b/frontend/.dockerignore
new file mode 100644
index 0000000000000000000000000000000000000000..deed335be2e2ec2e2ac0bba075b6586c3214ba2b
--- /dev/null
+++ b/frontend/.dockerignore
@@ -0,0 +1,3 @@
+node_modules/
+dist/
+.env
diff --git a/frontend/.gitignore b/frontend/.gitignore
new file mode 100644
index 0000000000000000000000000000000000000000..a547bf36d8d11a4f89c59c144f24795749086dd1
--- /dev/null
+++ b/frontend/.gitignore
@@ -0,0 +1,24 @@
+# Logs
+logs
+*.log
+npm-debug.log*
+yarn-debug.log*
+yarn-error.log*
+pnpm-debug.log*
+lerna-debug.log*
+
+node_modules
+dist
+dist-ssr
+*.local
+
+# Editor directories and files
+.vscode/*
+!.vscode/extensions.json
+.idea
+.DS_Store
+*.suo
+*.ntvs*
+*.njsproj
+*.sln
+*.sw?
diff --git a/frontend/Assets/Gorilla_Chest_Thumping_Animation_Generated.mp4 b/frontend/Assets/Gorilla_Chest_Thumping_Animation_Generated.mp4
new file mode 100644
index 0000000000000000000000000000000000000000..111587272661b6e660149cdc9423f534c4d313d1
--- /dev/null
+++ b/frontend/Assets/Gorilla_Chest_Thumping_Animation_Generated.mp4
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:95a1c3d740e86a5ec1c9ef8f26063000280eb1219d173ecbc2c4ca6c3d5ccbe8
+size 945018
diff --git a/frontend/Assets/freesound_community-monkey-30631.mp3 b/frontend/Assets/freesound_community-monkey-30631.mp3
new file mode 100644
index 0000000000000000000000000000000000000000..5856d6b145d68aab4e6c7dd208ee318e3600c091
--- /dev/null
+++ b/frontend/Assets/freesound_community-monkey-30631.mp3
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:4960fd96e5623ecc3f90c262e78e1f63ba7bb01e619cc52104e59a80fbc49c16
+size 429600
diff --git a/frontend/Assets/g.png b/frontend/Assets/g.png
new file mode 100644
index 0000000000000000000000000000000000000000..986857a381ab6aeedd1ca162d09dc5cc5f64abba
--- /dev/null
+++ b/frontend/Assets/g.png
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:16967011f43355d0888a71975a277f0d555f0a7ce17b9e473283c8fb9553db72
+size 4829843
diff --git a/frontend/Dockerfile b/frontend/Dockerfile
new file mode 100644
index 0000000000000000000000000000000000000000..d31f0974a994dd58ee4fd394e89c3c87bfb785c0
--- /dev/null
+++ b/frontend/Dockerfile
@@ -0,0 +1,20 @@
+# Build stage
+FROM node:20-alpine AS builder
+
+WORKDIR /app
+
+# Install dependencies
+COPY package*.json ./
+RUN npm install
+
+# Copy source code and build
+COPY . .
+RUN npm run build
+
+# Production stage
+FROM nginx:alpine
+# Copy built assets to Nginx
+COPY --from=builder /app/dist /usr/share/nginx/html
+
+EXPOSE 80
+CMD ["nginx", "-g", "daemon off;"]
diff --git a/frontend/README.md b/frontend/README.md
new file mode 100644
index 0000000000000000000000000000000000000000..a36934d874c7fbc51aecd1c66dffc106f60693a9
--- /dev/null
+++ b/frontend/README.md
@@ -0,0 +1,16 @@
+# React + Vite
+
+This template provides a minimal setup to get React working in Vite with HMR and some ESLint rules.
+
+Currently, two official plugins are available:
+
+- [@vitejs/plugin-react](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react) uses [Oxc](https://oxc.rs)
+- [@vitejs/plugin-react-swc](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react-swc) uses [SWC](https://swc.rs/)
+
+## React Compiler
+
+The React Compiler is not enabled on this template because of its impact on dev & build performances. To add it, see [this documentation](https://react.dev/learn/react-compiler/installation).
+
+## Expanding the ESLint configuration
+
+If you are developing a production application, we recommend using TypeScript with type-aware lint rules enabled. Check out the [TS template](https://github.com/vitejs/vite/tree/main/packages/create-vite/template-react-ts) for information on how to integrate TypeScript and [`typescript-eslint`](https://typescript-eslint.io) in your project.
diff --git a/frontend/eslint.config.js b/frontend/eslint.config.js
new file mode 100644
index 0000000000000000000000000000000000000000..4fa125da29e01fa85529cfa06a83a7c0ce240d55
--- /dev/null
+++ b/frontend/eslint.config.js
@@ -0,0 +1,29 @@
+import js from '@eslint/js'
+import globals from 'globals'
+import reactHooks from 'eslint-plugin-react-hooks'
+import reactRefresh from 'eslint-plugin-react-refresh'
+import { defineConfig, globalIgnores } from 'eslint/config'
+
+export default defineConfig([
+ globalIgnores(['dist']),
+ {
+ files: ['**/*.{js,jsx}'],
+ extends: [
+ js.configs.recommended,
+ reactHooks.configs.flat.recommended,
+ reactRefresh.configs.vite,
+ ],
+ languageOptions: {
+ ecmaVersion: 2020,
+ globals: globals.browser,
+ parserOptions: {
+ ecmaVersion: 'latest',
+ ecmaFeatures: { jsx: true },
+ sourceType: 'module',
+ },
+ },
+ rules: {
+ 'no-unused-vars': ['error', { varsIgnorePattern: '^[A-Z_]' }],
+ },
+ },
+])
diff --git a/frontend/index.html b/frontend/index.html
new file mode 100644
index 0000000000000000000000000000000000000000..4c33f60a9275f56ecdcfc6cc5ad0ad07c52d353d
--- /dev/null
+++ b/frontend/index.html
@@ -0,0 +1,16 @@
+
+
+
+
+
+
+
+
+
+ Gorilla Semiconductors
+
+
+
+
+
+
diff --git a/frontend/package-lock.json b/frontend/package-lock.json
new file mode 100644
index 0000000000000000000000000000000000000000..b210ad1e6642f27be8e56b97934e53da825ef35b
--- /dev/null
+++ b/frontend/package-lock.json
@@ -0,0 +1,2936 @@
+{
+ "name": "frontend",
+ "version": "0.0.0",
+ "lockfileVersion": 3,
+ "requires": true,
+ "packages": {
+ "": {
+ "name": "frontend",
+ "version": "0.0.0",
+ "dependencies": {
+ "axios": "^1.14.0",
+ "chart.js": "^4.5.1",
+ "lucide-react": "^1.7.0",
+ "react": "^19.2.4",
+ "react-chartjs-2": "^5.3.1",
+ "react-dom": "^19.2.4"
+ },
+ "devDependencies": {
+ "@eslint/js": "^9.39.4",
+ "@types/react": "^19.2.14",
+ "@types/react-dom": "^19.2.3",
+ "@vitejs/plugin-react": "^6.0.1",
+ "eslint": "^9.39.4",
+ "eslint-plugin-react-hooks": "^7.0.1",
+ "eslint-plugin-react-refresh": "^0.5.2",
+ "globals": "^17.4.0",
+ "vite": "^8.0.1"
+ }
+ },
+ "node_modules/@babel/code-frame": {
+ "version": "7.29.0",
+ "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.29.0.tgz",
+ "integrity": "sha512-9NhCeYjq9+3uxgdtp20LSiJXJvN0FeCtNGpJxuMFZ1Kv3cWUNb6DOhJwUvcVCzKGR66cw4njwM6hrJLqgOwbcw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-validator-identifier": "^7.28.5",
+ "js-tokens": "^4.0.0",
+ "picocolors": "^1.1.1"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/compat-data": {
+ "version": "7.29.0",
+ "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.29.0.tgz",
+ "integrity": "sha512-T1NCJqT/j9+cn8fvkt7jtwbLBfLC/1y1c7NtCeXFRgzGTsafi68MRv8yzkYSapBnFA6L3U2VSc02ciDzoAJhJg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/core": {
+ "version": "7.29.0",
+ "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.29.0.tgz",
+ "integrity": "sha512-CGOfOJqWjg2qW/Mb6zNsDm+u5vFQ8DxXfbM09z69p5Z6+mE1ikP2jUXw+j42Pf1XTYED2Rni5f95npYeuwMDQA==",
+ "dev": true,
+ "license": "MIT",
+ "peer": true,
+ "dependencies": {
+ "@babel/code-frame": "^7.29.0",
+ "@babel/generator": "^7.29.0",
+ "@babel/helper-compilation-targets": "^7.28.6",
+ "@babel/helper-module-transforms": "^7.28.6",
+ "@babel/helpers": "^7.28.6",
+ "@babel/parser": "^7.29.0",
+ "@babel/template": "^7.28.6",
+ "@babel/traverse": "^7.29.0",
+ "@babel/types": "^7.29.0",
+ "@jridgewell/remapping": "^2.3.5",
+ "convert-source-map": "^2.0.0",
+ "debug": "^4.1.0",
+ "gensync": "^1.0.0-beta.2",
+ "json5": "^2.2.3",
+ "semver": "^6.3.1"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/babel"
+ }
+ },
+ "node_modules/@babel/generator": {
+ "version": "7.29.1",
+ "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.29.1.tgz",
+ "integrity": "sha512-qsaF+9Qcm2Qv8SRIMMscAvG4O3lJ0F1GuMo5HR/Bp02LopNgnZBC/EkbevHFeGs4ls/oPz9v+Bsmzbkbe+0dUw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/parser": "^7.29.0",
+ "@babel/types": "^7.29.0",
+ "@jridgewell/gen-mapping": "^0.3.12",
+ "@jridgewell/trace-mapping": "^0.3.28",
+ "jsesc": "^3.0.2"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helper-compilation-targets": {
+ "version": "7.28.6",
+ "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.28.6.tgz",
+ "integrity": "sha512-JYtls3hqi15fcx5GaSNL7SCTJ2MNmjrkHXg4FSpOA/grxK8KwyZ5bubHsCq8FXCkua6xhuaaBit+3b7+VZRfcA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/compat-data": "^7.28.6",
+ "@babel/helper-validator-option": "^7.27.1",
+ "browserslist": "^4.24.0",
+ "lru-cache": "^5.1.1",
+ "semver": "^6.3.1"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helper-globals": {
+ "version": "7.28.0",
+ "resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.28.0.tgz",
+ "integrity": "sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helper-module-imports": {
+ "version": "7.28.6",
+ "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.28.6.tgz",
+ "integrity": "sha512-l5XkZK7r7wa9LucGw9LwZyyCUscb4x37JWTPz7swwFE/0FMQAGpiWUZn8u9DzkSBWEcK25jmvubfpw2dnAMdbw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/traverse": "^7.28.6",
+ "@babel/types": "^7.28.6"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helper-module-transforms": {
+ "version": "7.28.6",
+ "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.28.6.tgz",
+ "integrity": "sha512-67oXFAYr2cDLDVGLXTEABjdBJZ6drElUSI7WKp70NrpyISso3plG9SAGEF6y7zbha/wOzUByWWTJvEDVNIUGcA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-module-imports": "^7.28.6",
+ "@babel/helper-validator-identifier": "^7.28.5",
+ "@babel/traverse": "^7.28.6"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0"
+ }
+ },
+ "node_modules/@babel/helper-string-parser": {
+ "version": "7.27.1",
+ "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz",
+ "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helper-validator-identifier": {
+ "version": "7.28.5",
+ "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz",
+ "integrity": "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helper-validator-option": {
+ "version": "7.27.1",
+ "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.27.1.tgz",
+ "integrity": "sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helpers": {
+ "version": "7.29.2",
+ "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.29.2.tgz",
+ "integrity": "sha512-HoGuUs4sCZNezVEKdVcwqmZN8GoHirLUcLaYVNBK2J0DadGtdcqgr3BCbvH8+XUo4NGjNl3VOtSjEKNzqfFgKw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/template": "^7.28.6",
+ "@babel/types": "^7.29.0"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/parser": {
+ "version": "7.29.2",
+ "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.2.tgz",
+ "integrity": "sha512-4GgRzy/+fsBa72/RZVJmGKPmZu9Byn8o4MoLpmNe1m8ZfYnz5emHLQz3U4gLud6Zwl0RZIcgiLD7Uq7ySFuDLA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/types": "^7.29.0"
+ },
+ "bin": {
+ "parser": "bin/babel-parser.js"
+ },
+ "engines": {
+ "node": ">=6.0.0"
+ }
+ },
+ "node_modules/@babel/template": {
+ "version": "7.28.6",
+ "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.28.6.tgz",
+ "integrity": "sha512-YA6Ma2KsCdGb+WC6UpBVFJGXL58MDA6oyONbjyF/+5sBgxY/dwkhLogbMT2GXXyU84/IhRw/2D1Os1B/giz+BQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/code-frame": "^7.28.6",
+ "@babel/parser": "^7.28.6",
+ "@babel/types": "^7.28.6"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/traverse": {
+ "version": "7.29.0",
+ "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.29.0.tgz",
+ "integrity": "sha512-4HPiQr0X7+waHfyXPZpWPfWL/J7dcN1mx9gL6WdQVMbPnF3+ZhSMs8tCxN7oHddJE9fhNE7+lxdnlyemKfJRuA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/code-frame": "^7.29.0",
+ "@babel/generator": "^7.29.0",
+ "@babel/helper-globals": "^7.28.0",
+ "@babel/parser": "^7.29.0",
+ "@babel/template": "^7.28.6",
+ "@babel/types": "^7.29.0",
+ "debug": "^4.3.1"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/types": {
+ "version": "7.29.0",
+ "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.0.tgz",
+ "integrity": "sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-string-parser": "^7.27.1",
+ "@babel/helper-validator-identifier": "^7.28.5"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@emnapi/core": {
+ "version": "1.9.2",
+ "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.9.2.tgz",
+ "integrity": "sha512-UC+ZhH3XtczQYfOlu3lNEkdW/p4dsJ1r/bP7H8+rhao3TTTMO1ATq/4DdIi23XuGoFY+Cz0JmCbdVl0hz9jZcA==",
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "peer": true,
+ "dependencies": {
+ "@emnapi/wasi-threads": "1.2.1",
+ "tslib": "^2.4.0"
+ }
+ },
+ "node_modules/@emnapi/runtime": {
+ "version": "1.9.2",
+ "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.9.2.tgz",
+ "integrity": "sha512-3U4+MIWHImeyu1wnmVygh5WlgfYDtyf0k8AbLhMFxOipihf6nrWC4syIm/SwEeec0mNSafiiNnMJwbza/Is6Lw==",
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "peer": true,
+ "dependencies": {
+ "tslib": "^2.4.0"
+ }
+ },
+ "node_modules/@emnapi/wasi-threads": {
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.2.1.tgz",
+ "integrity": "sha512-uTII7OYF+/Mes/MrcIOYp5yOtSMLBWSIoLPpcgwipoiKbli6k322tcoFsxoIIxPDqW01SQGAgko4EzZi2BNv2w==",
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "dependencies": {
+ "tslib": "^2.4.0"
+ }
+ },
+ "node_modules/@eslint-community/eslint-utils": {
+ "version": "4.9.1",
+ "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.9.1.tgz",
+ "integrity": "sha512-phrYmNiYppR7znFEdqgfWHXR6NCkZEK7hwWDHZUjit/2/U0r6XvkDl0SYnoM51Hq7FhCGdLDT6zxCCOY1hexsQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "eslint-visitor-keys": "^3.4.3"
+ },
+ "engines": {
+ "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/eslint"
+ },
+ "peerDependencies": {
+ "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0"
+ }
+ },
+ "node_modules/@eslint-community/eslint-utils/node_modules/eslint-visitor-keys": {
+ "version": "3.4.3",
+ "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz",
+ "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "engines": {
+ "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/eslint"
+ }
+ },
+ "node_modules/@eslint-community/regexpp": {
+ "version": "4.12.2",
+ "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.12.2.tgz",
+ "integrity": "sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": "^12.0.0 || ^14.0.0 || >=16.0.0"
+ }
+ },
+ "node_modules/@eslint/config-array": {
+ "version": "0.21.2",
+ "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.21.2.tgz",
+ "integrity": "sha512-nJl2KGTlrf9GjLimgIru+V/mzgSK0ABCDQRvxw5BjURL7WfH5uoWmizbH7QB6MmnMBd8cIC9uceWnezL1VZWWw==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@eslint/object-schema": "^2.1.7",
+ "debug": "^4.3.1",
+ "minimatch": "^3.1.5"
+ },
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ }
+ },
+ "node_modules/@eslint/config-helpers": {
+ "version": "0.4.2",
+ "resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.4.2.tgz",
+ "integrity": "sha512-gBrxN88gOIf3R7ja5K9slwNayVcZgK6SOUORm2uBzTeIEfeVaIhOpCtTox3P6R7o2jLFwLFTLnC7kU/RGcYEgw==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@eslint/core": "^0.17.0"
+ },
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ }
+ },
+ "node_modules/@eslint/core": {
+ "version": "0.17.0",
+ "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.17.0.tgz",
+ "integrity": "sha512-yL/sLrpmtDaFEiUj1osRP4TI2MDz1AddJL+jZ7KSqvBuliN4xqYY54IfdN8qD8Toa6g1iloph1fxQNkjOxrrpQ==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@types/json-schema": "^7.0.15"
+ },
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ }
+ },
+ "node_modules/@eslint/eslintrc": {
+ "version": "3.3.5",
+ "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.3.5.tgz",
+ "integrity": "sha512-4IlJx0X0qftVsN5E+/vGujTRIFtwuLbNsVUe7TO6zYPDR1O6nFwvwhIKEKSrl6dZchmYBITazxKoUYOjdtjlRg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "ajv": "^6.14.0",
+ "debug": "^4.3.2",
+ "espree": "^10.0.1",
+ "globals": "^14.0.0",
+ "ignore": "^5.2.0",
+ "import-fresh": "^3.2.1",
+ "js-yaml": "^4.1.1",
+ "minimatch": "^3.1.5",
+ "strip-json-comments": "^3.1.1"
+ },
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/eslint"
+ }
+ },
+ "node_modules/@eslint/eslintrc/node_modules/globals": {
+ "version": "14.0.0",
+ "resolved": "https://registry.npmjs.org/globals/-/globals-14.0.0.tgz",
+ "integrity": "sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=18"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/@eslint/js": {
+ "version": "9.39.4",
+ "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.39.4.tgz",
+ "integrity": "sha512-nE7DEIchvtiFTwBw4Lfbu59PG+kCofhjsKaCWzxTpt4lfRjRMqG6uMBzKXuEcyXhOHoUp9riAm7/aWYGhXZ9cw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ },
+ "funding": {
+ "url": "https://eslint.org/donate"
+ }
+ },
+ "node_modules/@eslint/object-schema": {
+ "version": "2.1.7",
+ "resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-2.1.7.tgz",
+ "integrity": "sha512-VtAOaymWVfZcmZbp6E2mympDIHvyjXs/12LqWYjVw6qjrfF+VK+fyG33kChz3nnK+SU5/NeHOqrTEHS8sXO3OA==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ }
+ },
+ "node_modules/@eslint/plugin-kit": {
+ "version": "0.4.1",
+ "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.4.1.tgz",
+ "integrity": "sha512-43/qtrDUokr7LJqoF2c3+RInu/t4zfrpYdoSDfYyhg52rwLV6TnOvdG4fXm7IkSB3wErkcmJS9iEhjVtOSEjjA==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@eslint/core": "^0.17.0",
+ "levn": "^0.4.1"
+ },
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ }
+ },
+ "node_modules/@humanfs/core": {
+ "version": "0.19.1",
+ "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.1.tgz",
+ "integrity": "sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "engines": {
+ "node": ">=18.18.0"
+ }
+ },
+ "node_modules/@humanfs/node": {
+ "version": "0.16.7",
+ "resolved": "https://registry.npmjs.org/@humanfs/node/-/node-0.16.7.tgz",
+ "integrity": "sha512-/zUx+yOsIrG4Y43Eh2peDeKCxlRt/gET6aHfaKpuq267qXdYDFViVHfMaLyygZOnl0kGWxFIgsBy8QFuTLUXEQ==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@humanfs/core": "^0.19.1",
+ "@humanwhocodes/retry": "^0.4.0"
+ },
+ "engines": {
+ "node": ">=18.18.0"
+ }
+ },
+ "node_modules/@humanwhocodes/module-importer": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz",
+ "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "engines": {
+ "node": ">=12.22"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/nzakas"
+ }
+ },
+ "node_modules/@humanwhocodes/retry": {
+ "version": "0.4.3",
+ "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.4.3.tgz",
+ "integrity": "sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "engines": {
+ "node": ">=18.18"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/nzakas"
+ }
+ },
+ "node_modules/@jridgewell/gen-mapping": {
+ "version": "0.3.13",
+ "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz",
+ "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@jridgewell/sourcemap-codec": "^1.5.0",
+ "@jridgewell/trace-mapping": "^0.3.24"
+ }
+ },
+ "node_modules/@jridgewell/remapping": {
+ "version": "2.3.5",
+ "resolved": "https://registry.npmjs.org/@jridgewell/remapping/-/remapping-2.3.5.tgz",
+ "integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@jridgewell/gen-mapping": "^0.3.5",
+ "@jridgewell/trace-mapping": "^0.3.24"
+ }
+ },
+ "node_modules/@jridgewell/resolve-uri": {
+ "version": "3.1.2",
+ "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz",
+ "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.0.0"
+ }
+ },
+ "node_modules/@jridgewell/sourcemap-codec": {
+ "version": "1.5.5",
+ "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz",
+ "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/@jridgewell/trace-mapping": {
+ "version": "0.3.31",
+ "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz",
+ "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@jridgewell/resolve-uri": "^3.1.0",
+ "@jridgewell/sourcemap-codec": "^1.4.14"
+ }
+ },
+ "node_modules/@kurkle/color": {
+ "version": "0.3.4",
+ "resolved": "https://registry.npmjs.org/@kurkle/color/-/color-0.3.4.tgz",
+ "integrity": "sha512-M5UknZPHRu3DEDWoipU6sE8PdkZ6Z/S+v4dD+Ke8IaNlpdSQah50lz1KtcFBa2vsdOnwbbnxJwVM4wty6udA5w==",
+ "license": "MIT"
+ },
+ "node_modules/@napi-rs/wasm-runtime": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-1.1.2.tgz",
+ "integrity": "sha512-sNXv5oLJ7ob93xkZ1XnxisYhGYXfaG9f65/ZgYuAu3qt7b3NadcOEhLvx28hv31PgX8SZJRYrAIPQilQmFpLVw==",
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "dependencies": {
+ "@tybys/wasm-util": "^0.10.1"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/Brooooooklyn"
+ },
+ "peerDependencies": {
+ "@emnapi/core": "^1.7.1",
+ "@emnapi/runtime": "^1.7.1"
+ }
+ },
+ "node_modules/@oxc-project/types": {
+ "version": "0.122.0",
+ "resolved": "https://registry.npmjs.org/@oxc-project/types/-/types-0.122.0.tgz",
+ "integrity": "sha512-oLAl5kBpV4w69UtFZ9xqcmTi+GENWOcPF7FCrczTiBbmC0ibXxCwyvZGbO39rCVEuLGAZM84DH0pUIyyv/YJzA==",
+ "dev": true,
+ "license": "MIT",
+ "funding": {
+ "url": "https://github.com/sponsors/Boshen"
+ }
+ },
+ "node_modules/@rolldown/binding-android-arm64": {
+ "version": "1.0.0-rc.12",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm64/-/binding-android-arm64-1.0.0-rc.12.tgz",
+ "integrity": "sha512-pv1y2Fv0JybcykuiiD3qBOBdz6RteYojRFY1d+b95WVuzx211CRh+ytI/+9iVyWQ6koTh5dawe4S/yRfOFjgaA==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ }
+ },
+ "node_modules/@rolldown/binding-darwin-arm64": {
+ "version": "1.0.0-rc.12",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-arm64/-/binding-darwin-arm64-1.0.0-rc.12.tgz",
+ "integrity": "sha512-cFYr6zTG/3PXXF3pUO+umXxt1wkRK/0AYT8lDwuqvRC+LuKYWSAQAQZjCWDQpAH172ZV6ieYrNnFzVVcnSflAg==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ }
+ },
+ "node_modules/@rolldown/binding-darwin-x64": {
+ "version": "1.0.0-rc.12",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-x64/-/binding-darwin-x64-1.0.0-rc.12.tgz",
+ "integrity": "sha512-ZCsYknnHzeXYps0lGBz8JrF37GpE9bFVefrlmDrAQhOEi4IOIlcoU1+FwHEtyXGx2VkYAvhu7dyBf75EJQffBw==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ }
+ },
+ "node_modules/@rolldown/binding-freebsd-x64": {
+ "version": "1.0.0-rc.12",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-freebsd-x64/-/binding-freebsd-x64-1.0.0-rc.12.tgz",
+ "integrity": "sha512-dMLeprcVsyJsKolRXyoTH3NL6qtsT0Y2xeuEA8WQJquWFXkEC4bcu1rLZZSnZRMtAqwtrF/Ib9Ddtpa/Gkge9Q==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "freebsd"
+ ],
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ }
+ },
+ "node_modules/@rolldown/binding-linux-arm-gnueabihf": {
+ "version": "1.0.0-rc.12",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.0.0-rc.12.tgz",
+ "integrity": "sha512-YqWjAgGC/9M1lz3GR1r1rP79nMgo3mQiiA+Hfo+pvKFK1fAJ1bCi0ZQVh8noOqNacuY1qIcfyVfP6HoyBRZ85Q==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ }
+ },
+ "node_modules/@rolldown/binding-linux-arm64-gnu": {
+ "version": "1.0.0-rc.12",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.0.0-rc.12.tgz",
+ "integrity": "sha512-/I5AS4cIroLpslsmzXfwbe5OmWvSsrFuEw3mwvbQ1kDxJ822hFHIx+vsN/TAzNVyepI/j/GSzrtCIwQPeKCLIg==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ }
+ },
+ "node_modules/@rolldown/binding-linux-arm64-musl": {
+ "version": "1.0.0-rc.12",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.0.0-rc.12.tgz",
+ "integrity": "sha512-V6/wZztnBqlx5hJQqNWwFdxIKN0m38p8Jas+VoSfgH54HSj9tKTt1dZvG6JRHcjh6D7TvrJPWFGaY9UBVOaWPw==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ }
+ },
+ "node_modules/@rolldown/binding-linux-ppc64-gnu": {
+ "version": "1.0.0-rc.12",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-1.0.0-rc.12.tgz",
+ "integrity": "sha512-AP3E9BpcUYliZCxa3w5Kwj9OtEVDYK6sVoUzy4vTOJsjPOgdaJZKFmN4oOlX0Wp0RPV2ETfmIra9x1xuayFB7g==",
+ "cpu": [
+ "ppc64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ }
+ },
+ "node_modules/@rolldown/binding-linux-s390x-gnu": {
+ "version": "1.0.0-rc.12",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-1.0.0-rc.12.tgz",
+ "integrity": "sha512-nWwpvUSPkoFmZo0kQazZYOrT7J5DGOJ/+QHHzjvNlooDZED8oH82Yg67HvehPPLAg5fUff7TfWFHQS8IV1n3og==",
+ "cpu": [
+ "s390x"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ }
+ },
+ "node_modules/@rolldown/binding-linux-x64-gnu": {
+ "version": "1.0.0-rc.12",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.0.0-rc.12.tgz",
+ "integrity": "sha512-RNrafz5bcwRy+O9e6P8Z/OCAJW/A+qtBczIqVYwTs14pf4iV1/+eKEjdOUta93q2TsT/FI0XYDP3TCky38LMAg==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ }
+ },
+ "node_modules/@rolldown/binding-linux-x64-musl": {
+ "version": "1.0.0-rc.12",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-musl/-/binding-linux-x64-musl-1.0.0-rc.12.tgz",
+ "integrity": "sha512-Jpw/0iwoKWx3LJ2rc1yjFrj+T7iHZn2JDg1Yny1ma0luviFS4mhAIcd1LFNxK3EYu3DHWCps0ydXQ5i/rrJ2ig==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ }
+ },
+ "node_modules/@rolldown/binding-openharmony-arm64": {
+ "version": "1.0.0-rc.12",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-openharmony-arm64/-/binding-openharmony-arm64-1.0.0-rc.12.tgz",
+ "integrity": "sha512-vRugONE4yMfVn0+7lUKdKvN4D5YusEiPilaoO2sgUWpCvrncvWgPMzK00ZFFJuiPgLwgFNP5eSiUlv2tfc+lpA==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "openharmony"
+ ],
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ }
+ },
+ "node_modules/@rolldown/binding-wasm32-wasi": {
+ "version": "1.0.0-rc.12",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-wasm32-wasi/-/binding-wasm32-wasi-1.0.0-rc.12.tgz",
+ "integrity": "sha512-ykGiLr/6kkiHc0XnBfmFJuCjr5ZYKKofkx+chJWDjitX+KsJuAmrzWhwyOMSHzPhzOHOy7u9HlFoa5MoAOJ/Zg==",
+ "cpu": [
+ "wasm32"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "dependencies": {
+ "@napi-rs/wasm-runtime": "^1.1.1"
+ },
+ "engines": {
+ "node": ">=14.0.0"
+ }
+ },
+ "node_modules/@rolldown/binding-win32-arm64-msvc": {
+ "version": "1.0.0-rc.12",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.0.0-rc.12.tgz",
+ "integrity": "sha512-5eOND4duWkwx1AzCxadcOrNeighiLwMInEADT0YM7xeEOOFcovWZCq8dadXgcRHSf3Ulh1kFo/qvzoFiCLOL1Q==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ }
+ },
+ "node_modules/@rolldown/binding-win32-x64-msvc": {
+ "version": "1.0.0-rc.12",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.0.0-rc.12.tgz",
+ "integrity": "sha512-PyqoipaswDLAZtot351MLhrlrh6lcZPo2LSYE+VDxbVk24LVKAGOuE4hb8xZQmrPAuEtTZW8E6D2zc5EUZX4Lw==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ }
+ },
+ "node_modules/@rolldown/pluginutils": {
+ "version": "1.0.0-rc.7",
+ "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-rc.7.tgz",
+ "integrity": "sha512-qujRfC8sFVInYSPPMLQByRh7zhwkGFS4+tyMQ83srV1qrxL4g8E2tyxVVyxd0+8QeBM1mIk9KbWxkegRr76XzA==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/@tybys/wasm-util": {
+ "version": "0.10.1",
+ "resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.10.1.tgz",
+ "integrity": "sha512-9tTaPJLSiejZKx+Bmog4uSubteqTvFrVrURwkmHixBo0G4seD0zUxp98E1DzUBJxLQ3NPwXrGKDiVjwx/DpPsg==",
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "dependencies": {
+ "tslib": "^2.4.0"
+ }
+ },
+ "node_modules/@types/estree": {
+ "version": "1.0.8",
+ "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz",
+ "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/@types/json-schema": {
+ "version": "7.0.15",
+ "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz",
+ "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/@types/react": {
+ "version": "19.2.14",
+ "resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.14.tgz",
+ "integrity": "sha512-ilcTH/UniCkMdtexkoCN0bI7pMcJDvmQFPvuPvmEaYA/NSfFTAgdUSLAoVjaRJm7+6PvcM+q1zYOwS4wTYMF9w==",
+ "dev": true,
+ "license": "MIT",
+ "peer": true,
+ "dependencies": {
+ "csstype": "^3.2.2"
+ }
+ },
+ "node_modules/@types/react-dom": {
+ "version": "19.2.3",
+ "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-19.2.3.tgz",
+ "integrity": "sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ==",
+ "dev": true,
+ "license": "MIT",
+ "peerDependencies": {
+ "@types/react": "^19.2.0"
+ }
+ },
+ "node_modules/@vitejs/plugin-react": {
+ "version": "6.0.1",
+ "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-6.0.1.tgz",
+ "integrity": "sha512-l9X/E3cDb+xY3SWzlG1MOGt2usfEHGMNIaegaUGFsLkb3RCn/k8/TOXBcab+OndDI4TBtktT8/9BwwW8Vi9KUQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@rolldown/pluginutils": "1.0.0-rc.7"
+ },
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ },
+ "peerDependencies": {
+ "@rolldown/plugin-babel": "^0.1.7 || ^0.2.0",
+ "babel-plugin-react-compiler": "^1.0.0",
+ "vite": "^8.0.0"
+ },
+ "peerDependenciesMeta": {
+ "@rolldown/plugin-babel": {
+ "optional": true
+ },
+ "babel-plugin-react-compiler": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/acorn": {
+ "version": "8.16.0",
+ "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.16.0.tgz",
+ "integrity": "sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==",
+ "dev": true,
+ "license": "MIT",
+ "peer": true,
+ "bin": {
+ "acorn": "bin/acorn"
+ },
+ "engines": {
+ "node": ">=0.4.0"
+ }
+ },
+ "node_modules/acorn-jsx": {
+ "version": "5.3.2",
+ "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz",
+ "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==",
+ "dev": true,
+ "license": "MIT",
+ "peerDependencies": {
+ "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0"
+ }
+ },
+ "node_modules/ajv": {
+ "version": "6.14.0",
+ "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.14.0.tgz",
+ "integrity": "sha512-IWrosm/yrn43eiKqkfkHis7QioDleaXQHdDVPKg0FSwwd/DuvyX79TZnFOnYpB7dcsFAMmtFztZuXPDvSePkFw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "fast-deep-equal": "^3.1.1",
+ "fast-json-stable-stringify": "^2.0.0",
+ "json-schema-traverse": "^0.4.1",
+ "uri-js": "^4.2.2"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/epoberezkin"
+ }
+ },
+ "node_modules/ansi-styles": {
+ "version": "4.3.0",
+ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
+ "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "color-convert": "^2.0.1"
+ },
+ "engines": {
+ "node": ">=8"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/ansi-styles?sponsor=1"
+ }
+ },
+ "node_modules/argparse": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz",
+ "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==",
+ "dev": true,
+ "license": "Python-2.0"
+ },
+ "node_modules/asynckit": {
+ "version": "0.4.0",
+ "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz",
+ "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==",
+ "license": "MIT"
+ },
+ "node_modules/axios": {
+ "version": "1.14.0",
+ "resolved": "https://registry.npmjs.org/axios/-/axios-1.14.0.tgz",
+ "integrity": "sha512-3Y8yrqLSwjuzpXuZ0oIYZ/XGgLwUIBU3uLvbcpb0pidD9ctpShJd43KSlEEkVQg6DS0G9NKyzOvBfUtDKEyHvQ==",
+ "license": "MIT",
+ "dependencies": {
+ "follow-redirects": "^1.15.11",
+ "form-data": "^4.0.5",
+ "proxy-from-env": "^2.1.0"
+ }
+ },
+ "node_modules/balanced-match": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
+ "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/baseline-browser-mapping": {
+ "version": "2.10.13",
+ "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.10.13.tgz",
+ "integrity": "sha512-BL2sTuHOdy0YT1lYieUxTw/QMtPBC3pmlJC6xk8BBYVv6vcw3SGdKemQ+Xsx9ik2F/lYDO9tqsFQH1r9PFuHKw==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "bin": {
+ "baseline-browser-mapping": "dist/cli.cjs"
+ },
+ "engines": {
+ "node": ">=6.0.0"
+ }
+ },
+ "node_modules/brace-expansion": {
+ "version": "1.1.13",
+ "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.13.tgz",
+ "integrity": "sha512-9ZLprWS6EENmhEOpjCYW2c8VkmOvckIJZfkr7rBW6dObmfgJ/L1GpSYW5Hpo9lDz4D1+n0Ckz8rU7FwHDQiG/w==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "balanced-match": "^1.0.0",
+ "concat-map": "0.0.1"
+ }
+ },
+ "node_modules/browserslist": {
+ "version": "4.28.2",
+ "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.2.tgz",
+ "integrity": "sha512-48xSriZYYg+8qXna9kwqjIVzuQxi+KYWp2+5nCYnYKPTr0LvD89Jqk2Or5ogxz0NUMfIjhh2lIUX/LyX9B4oIg==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/browserslist"
+ },
+ {
+ "type": "tidelift",
+ "url": "https://tidelift.com/funding/github/npm/browserslist"
+ },
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ],
+ "license": "MIT",
+ "peer": true,
+ "dependencies": {
+ "baseline-browser-mapping": "^2.10.12",
+ "caniuse-lite": "^1.0.30001782",
+ "electron-to-chromium": "^1.5.328",
+ "node-releases": "^2.0.36",
+ "update-browserslist-db": "^1.2.3"
+ },
+ "bin": {
+ "browserslist": "cli.js"
+ },
+ "engines": {
+ "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7"
+ }
+ },
+ "node_modules/call-bind-apply-helpers": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz",
+ "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==",
+ "license": "MIT",
+ "dependencies": {
+ "es-errors": "^1.3.0",
+ "function-bind": "^1.1.2"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/callsites": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz",
+ "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/caniuse-lite": {
+ "version": "1.0.30001784",
+ "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001784.tgz",
+ "integrity": "sha512-WU346nBTklUV9YfUl60fqRbU5ZqyXlqvo1SgigE1OAXK5bFL8LL9q1K7aap3N739l4BvNqnkm3YrGHiY9sfUQw==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/browserslist"
+ },
+ {
+ "type": "tidelift",
+ "url": "https://tidelift.com/funding/github/npm/caniuse-lite"
+ },
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ],
+ "license": "CC-BY-4.0"
+ },
+ "node_modules/chalk": {
+ "version": "4.1.2",
+ "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz",
+ "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "ansi-styles": "^4.1.0",
+ "supports-color": "^7.1.0"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/chalk?sponsor=1"
+ }
+ },
+ "node_modules/chart.js": {
+ "version": "4.5.1",
+ "resolved": "https://registry.npmjs.org/chart.js/-/chart.js-4.5.1.tgz",
+ "integrity": "sha512-GIjfiT9dbmHRiYi6Nl2yFCq7kkwdkp1W/lp2J99rX0yo9tgJGn3lKQATztIjb5tVtevcBtIdICNWqlq5+E8/Pw==",
+ "license": "MIT",
+ "peer": true,
+ "dependencies": {
+ "@kurkle/color": "^0.3.0"
+ },
+ "engines": {
+ "pnpm": ">=8"
+ }
+ },
+ "node_modules/color-convert": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
+ "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "color-name": "~1.1.4"
+ },
+ "engines": {
+ "node": ">=7.0.0"
+ }
+ },
+ "node_modules/color-name": {
+ "version": "1.1.4",
+ "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
+ "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/combined-stream": {
+ "version": "1.0.8",
+ "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz",
+ "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==",
+ "license": "MIT",
+ "dependencies": {
+ "delayed-stream": "~1.0.0"
+ },
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/concat-map": {
+ "version": "0.0.1",
+ "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
+ "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/convert-source-map": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz",
+ "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/cross-spawn": {
+ "version": "7.0.6",
+ "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz",
+ "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "path-key": "^3.1.0",
+ "shebang-command": "^2.0.0",
+ "which": "^2.0.1"
+ },
+ "engines": {
+ "node": ">= 8"
+ }
+ },
+ "node_modules/csstype": {
+ "version": "3.2.3",
+ "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz",
+ "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/debug": {
+ "version": "4.4.3",
+ "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz",
+ "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "ms": "^2.1.3"
+ },
+ "engines": {
+ "node": ">=6.0"
+ },
+ "peerDependenciesMeta": {
+ "supports-color": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/deep-is": {
+ "version": "0.1.4",
+ "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz",
+ "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/delayed-stream": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz",
+ "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.4.0"
+ }
+ },
+ "node_modules/detect-libc": {
+ "version": "2.1.2",
+ "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz",
+ "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/dunder-proto": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz",
+ "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==",
+ "license": "MIT",
+ "dependencies": {
+ "call-bind-apply-helpers": "^1.0.1",
+ "es-errors": "^1.3.0",
+ "gopd": "^1.2.0"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/electron-to-chromium": {
+ "version": "1.5.331",
+ "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.331.tgz",
+ "integrity": "sha512-IbxXrsTlD3hRodkLnbxAPP4OuJYdWCeM3IOdT+CpcMoIwIoDfCmRpEtSPfwBXxVkg9xmBeY7Lz2Eo2TDn/HC3Q==",
+ "dev": true,
+ "license": "ISC"
+ },
+ "node_modules/es-define-property": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz",
+ "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/es-errors": {
+ "version": "1.3.0",
+ "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz",
+ "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/es-object-atoms": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz",
+ "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==",
+ "license": "MIT",
+ "dependencies": {
+ "es-errors": "^1.3.0"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/es-set-tostringtag": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz",
+ "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==",
+ "license": "MIT",
+ "dependencies": {
+ "es-errors": "^1.3.0",
+ "get-intrinsic": "^1.2.6",
+ "has-tostringtag": "^1.0.2",
+ "hasown": "^2.0.2"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/escalade": {
+ "version": "3.2.0",
+ "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz",
+ "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/escape-string-regexp": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz",
+ "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/eslint": {
+ "version": "9.39.4",
+ "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.39.4.tgz",
+ "integrity": "sha512-XoMjdBOwe/esVgEvLmNsD3IRHkm7fbKIUGvrleloJXUZgDHig2IPWNniv+GwjyJXzuNqVjlr5+4yVUZjycJwfQ==",
+ "dev": true,
+ "license": "MIT",
+ "peer": true,
+ "dependencies": {
+ "@eslint-community/eslint-utils": "^4.8.0",
+ "@eslint-community/regexpp": "^4.12.1",
+ "@eslint/config-array": "^0.21.2",
+ "@eslint/config-helpers": "^0.4.2",
+ "@eslint/core": "^0.17.0",
+ "@eslint/eslintrc": "^3.3.5",
+ "@eslint/js": "9.39.4",
+ "@eslint/plugin-kit": "^0.4.1",
+ "@humanfs/node": "^0.16.6",
+ "@humanwhocodes/module-importer": "^1.0.1",
+ "@humanwhocodes/retry": "^0.4.2",
+ "@types/estree": "^1.0.6",
+ "ajv": "^6.14.0",
+ "chalk": "^4.0.0",
+ "cross-spawn": "^7.0.6",
+ "debug": "^4.3.2",
+ "escape-string-regexp": "^4.0.0",
+ "eslint-scope": "^8.4.0",
+ "eslint-visitor-keys": "^4.2.1",
+ "espree": "^10.4.0",
+ "esquery": "^1.5.0",
+ "esutils": "^2.0.2",
+ "fast-deep-equal": "^3.1.3",
+ "file-entry-cache": "^8.0.0",
+ "find-up": "^5.0.0",
+ "glob-parent": "^6.0.2",
+ "ignore": "^5.2.0",
+ "imurmurhash": "^0.1.4",
+ "is-glob": "^4.0.0",
+ "json-stable-stringify-without-jsonify": "^1.0.1",
+ "lodash.merge": "^4.6.2",
+ "minimatch": "^3.1.5",
+ "natural-compare": "^1.4.0",
+ "optionator": "^0.9.3"
+ },
+ "bin": {
+ "eslint": "bin/eslint.js"
+ },
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ },
+ "funding": {
+ "url": "https://eslint.org/donate"
+ },
+ "peerDependencies": {
+ "jiti": "*"
+ },
+ "peerDependenciesMeta": {
+ "jiti": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/eslint-plugin-react-hooks": {
+ "version": "7.0.1",
+ "resolved": "https://registry.npmjs.org/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-7.0.1.tgz",
+ "integrity": "sha512-O0d0m04evaNzEPoSW+59Mezf8Qt0InfgGIBJnpC0h3NH/WjUAR7BIKUfysC6todmtiZ/A0oUVS8Gce0WhBrHsA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/core": "^7.24.4",
+ "@babel/parser": "^7.24.4",
+ "hermes-parser": "^0.25.1",
+ "zod": "^3.25.0 || ^4.0.0",
+ "zod-validation-error": "^3.5.0 || ^4.0.0"
+ },
+ "engines": {
+ "node": ">=18"
+ },
+ "peerDependencies": {
+ "eslint": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0 || ^9.0.0"
+ }
+ },
+ "node_modules/eslint-plugin-react-refresh": {
+ "version": "0.5.2",
+ "resolved": "https://registry.npmjs.org/eslint-plugin-react-refresh/-/eslint-plugin-react-refresh-0.5.2.tgz",
+ "integrity": "sha512-hmgTH57GfzoTFjVN0yBwTggnsVUF2tcqi7RJZHqi9lIezSs4eFyAMktA68YD4r5kNw1mxyY4dmkyoFDb3FIqrA==",
+ "dev": true,
+ "license": "MIT",
+ "peerDependencies": {
+ "eslint": "^9 || ^10"
+ }
+ },
+ "node_modules/eslint-scope": {
+ "version": "8.4.0",
+ "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.4.0.tgz",
+ "integrity": "sha512-sNXOfKCn74rt8RICKMvJS7XKV/Xk9kA7DyJr8mJik3S7Cwgy3qlkkmyS2uQB3jiJg6VNdZd/pDBJu0nvG2NlTg==",
+ "dev": true,
+ "license": "BSD-2-Clause",
+ "dependencies": {
+ "esrecurse": "^4.3.0",
+ "estraverse": "^5.2.0"
+ },
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/eslint"
+ }
+ },
+ "node_modules/eslint-visitor-keys": {
+ "version": "4.2.1",
+ "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz",
+ "integrity": "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/eslint"
+ }
+ },
+ "node_modules/espree": {
+ "version": "10.4.0",
+ "resolved": "https://registry.npmjs.org/espree/-/espree-10.4.0.tgz",
+ "integrity": "sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ==",
+ "dev": true,
+ "license": "BSD-2-Clause",
+ "dependencies": {
+ "acorn": "^8.15.0",
+ "acorn-jsx": "^5.3.2",
+ "eslint-visitor-keys": "^4.2.1"
+ },
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/eslint"
+ }
+ },
+ "node_modules/esquery": {
+ "version": "1.7.0",
+ "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.7.0.tgz",
+ "integrity": "sha512-Ap6G0WQwcU/LHsvLwON1fAQX9Zp0A2Y6Y/cJBl9r/JbW90Zyg4/zbG6zzKa2OTALELarYHmKu0GhpM5EO+7T0g==",
+ "dev": true,
+ "license": "BSD-3-Clause",
+ "dependencies": {
+ "estraverse": "^5.1.0"
+ },
+ "engines": {
+ "node": ">=0.10"
+ }
+ },
+ "node_modules/esrecurse": {
+ "version": "4.3.0",
+ "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz",
+ "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==",
+ "dev": true,
+ "license": "BSD-2-Clause",
+ "dependencies": {
+ "estraverse": "^5.2.0"
+ },
+ "engines": {
+ "node": ">=4.0"
+ }
+ },
+ "node_modules/estraverse": {
+ "version": "5.3.0",
+ "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz",
+ "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==",
+ "dev": true,
+ "license": "BSD-2-Clause",
+ "engines": {
+ "node": ">=4.0"
+ }
+ },
+ "node_modules/esutils": {
+ "version": "2.0.3",
+ "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz",
+ "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==",
+ "dev": true,
+ "license": "BSD-2-Clause",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/fast-deep-equal": {
+ "version": "3.1.3",
+ "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
+ "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/fast-json-stable-stringify": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz",
+ "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/fast-levenshtein": {
+ "version": "2.0.6",
+ "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz",
+ "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/fdir": {
+ "version": "6.5.0",
+ "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz",
+ "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=12.0.0"
+ },
+ "peerDependencies": {
+ "picomatch": "^3 || ^4"
+ },
+ "peerDependenciesMeta": {
+ "picomatch": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/file-entry-cache": {
+ "version": "8.0.0",
+ "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-8.0.0.tgz",
+ "integrity": "sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "flat-cache": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=16.0.0"
+ }
+ },
+ "node_modules/find-up": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz",
+ "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "locate-path": "^6.0.0",
+ "path-exists": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/flat-cache": {
+ "version": "4.0.1",
+ "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-4.0.1.tgz",
+ "integrity": "sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "flatted": "^3.2.9",
+ "keyv": "^4.5.4"
+ },
+ "engines": {
+ "node": ">=16"
+ }
+ },
+ "node_modules/flatted": {
+ "version": "3.4.2",
+ "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.4.2.tgz",
+ "integrity": "sha512-PjDse7RzhcPkIJwy5t7KPWQSZ9cAbzQXcafsetQoD7sOJRQlGikNbx7yZp2OotDnJyrDcbyRq3Ttb18iYOqkxA==",
+ "dev": true,
+ "license": "ISC"
+ },
+ "node_modules/follow-redirects": {
+ "version": "1.15.11",
+ "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.11.tgz",
+ "integrity": "sha512-deG2P0JfjrTxl50XGCDyfI97ZGVCxIpfKYmfyrQ54n5FO/0gfIES8C/Psl6kWVDolizcaaxZJnTS0QSMxvnsBQ==",
+ "funding": [
+ {
+ "type": "individual",
+ "url": "https://github.com/sponsors/RubenVerborgh"
+ }
+ ],
+ "license": "MIT",
+ "engines": {
+ "node": ">=4.0"
+ },
+ "peerDependenciesMeta": {
+ "debug": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/form-data": {
+ "version": "4.0.5",
+ "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.5.tgz",
+ "integrity": "sha512-8RipRLol37bNs2bhoV67fiTEvdTrbMUYcFTiy3+wuuOnUog2QBHCZWXDRijWQfAkhBj2Uf5UnVaiWwA5vdd82w==",
+ "license": "MIT",
+ "dependencies": {
+ "asynckit": "^0.4.0",
+ "combined-stream": "^1.0.8",
+ "es-set-tostringtag": "^2.1.0",
+ "hasown": "^2.0.2",
+ "mime-types": "^2.1.12"
+ },
+ "engines": {
+ "node": ">= 6"
+ }
+ },
+ "node_modules/fsevents": {
+ "version": "2.3.3",
+ "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz",
+ "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==",
+ "dev": true,
+ "hasInstallScript": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": "^8.16.0 || ^10.6.0 || >=11.0.0"
+ }
+ },
+ "node_modules/function-bind": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz",
+ "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==",
+ "license": "MIT",
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/gensync": {
+ "version": "1.0.0-beta.2",
+ "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz",
+ "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/get-intrinsic": {
+ "version": "1.3.0",
+ "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz",
+ "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==",
+ "license": "MIT",
+ "dependencies": {
+ "call-bind-apply-helpers": "^1.0.2",
+ "es-define-property": "^1.0.1",
+ "es-errors": "^1.3.0",
+ "es-object-atoms": "^1.1.1",
+ "function-bind": "^1.1.2",
+ "get-proto": "^1.0.1",
+ "gopd": "^1.2.0",
+ "has-symbols": "^1.1.0",
+ "hasown": "^2.0.2",
+ "math-intrinsics": "^1.1.0"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/get-proto": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz",
+ "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==",
+ "license": "MIT",
+ "dependencies": {
+ "dunder-proto": "^1.0.1",
+ "es-object-atoms": "^1.0.0"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/glob-parent": {
+ "version": "6.0.2",
+ "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz",
+ "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "is-glob": "^4.0.3"
+ },
+ "engines": {
+ "node": ">=10.13.0"
+ }
+ },
+ "node_modules/globals": {
+ "version": "17.4.0",
+ "resolved": "https://registry.npmjs.org/globals/-/globals-17.4.0.tgz",
+ "integrity": "sha512-hjrNztw/VajQwOLsMNT1cbJiH2muO3OROCHnbehc8eY5JyD2gqz4AcMHPqgaOR59DjgUjYAYLeH699g/eWi2jw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=18"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/gopd": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz",
+ "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/has-flag": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
+ "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/has-symbols": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz",
+ "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/has-tostringtag": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz",
+ "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==",
+ "license": "MIT",
+ "dependencies": {
+ "has-symbols": "^1.0.3"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/hasown": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz",
+ "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==",
+ "license": "MIT",
+ "dependencies": {
+ "function-bind": "^1.1.2"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/hermes-estree": {
+ "version": "0.25.1",
+ "resolved": "https://registry.npmjs.org/hermes-estree/-/hermes-estree-0.25.1.tgz",
+ "integrity": "sha512-0wUoCcLp+5Ev5pDW2OriHC2MJCbwLwuRx+gAqMTOkGKJJiBCLjtrvy4PWUGn6MIVefecRpzoOZ/UV6iGdOr+Cw==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/hermes-parser": {
+ "version": "0.25.1",
+ "resolved": "https://registry.npmjs.org/hermes-parser/-/hermes-parser-0.25.1.tgz",
+ "integrity": "sha512-6pEjquH3rqaI6cYAXYPcz9MS4rY6R4ngRgrgfDshRptUZIc3lw0MCIJIGDj9++mfySOuPTHB4nrSW99BCvOPIA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "hermes-estree": "0.25.1"
+ }
+ },
+ "node_modules/ignore": {
+ "version": "5.3.2",
+ "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz",
+ "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 4"
+ }
+ },
+ "node_modules/import-fresh": {
+ "version": "3.3.1",
+ "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz",
+ "integrity": "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "parent-module": "^1.0.0",
+ "resolve-from": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=6"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/imurmurhash": {
+ "version": "0.1.4",
+ "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz",
+ "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.8.19"
+ }
+ },
+ "node_modules/is-extglob": {
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz",
+ "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/is-glob": {
+ "version": "4.0.3",
+ "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz",
+ "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "is-extglob": "^2.1.1"
+ },
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/isexe": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz",
+ "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==",
+ "dev": true,
+ "license": "ISC"
+ },
+ "node_modules/js-tokens": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
+ "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/js-yaml": {
+ "version": "4.1.1",
+ "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.1.tgz",
+ "integrity": "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "argparse": "^2.0.1"
+ },
+ "bin": {
+ "js-yaml": "bin/js-yaml.js"
+ }
+ },
+ "node_modules/jsesc": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz",
+ "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==",
+ "dev": true,
+ "license": "MIT",
+ "bin": {
+ "jsesc": "bin/jsesc"
+ },
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/json-buffer": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz",
+ "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/json-schema-traverse": {
+ "version": "0.4.1",
+ "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz",
+ "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/json-stable-stringify-without-jsonify": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz",
+ "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/json5": {
+ "version": "2.2.3",
+ "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz",
+ "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==",
+ "dev": true,
+ "license": "MIT",
+ "bin": {
+ "json5": "lib/cli.js"
+ },
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/keyv": {
+ "version": "4.5.4",
+ "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz",
+ "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "json-buffer": "3.0.1"
+ }
+ },
+ "node_modules/levn": {
+ "version": "0.4.1",
+ "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz",
+ "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "prelude-ls": "^1.2.1",
+ "type-check": "~0.4.0"
+ },
+ "engines": {
+ "node": ">= 0.8.0"
+ }
+ },
+ "node_modules/lightningcss": {
+ "version": "1.32.0",
+ "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.32.0.tgz",
+ "integrity": "sha512-NXYBzinNrblfraPGyrbPoD19C1h9lfI/1mzgWYvXUTe414Gz/X1FD2XBZSZM7rRTrMA8JL3OtAaGifrIKhQ5yQ==",
+ "dev": true,
+ "license": "MPL-2.0",
+ "dependencies": {
+ "detect-libc": "^2.0.3"
+ },
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ },
+ "optionalDependencies": {
+ "lightningcss-android-arm64": "1.32.0",
+ "lightningcss-darwin-arm64": "1.32.0",
+ "lightningcss-darwin-x64": "1.32.0",
+ "lightningcss-freebsd-x64": "1.32.0",
+ "lightningcss-linux-arm-gnueabihf": "1.32.0",
+ "lightningcss-linux-arm64-gnu": "1.32.0",
+ "lightningcss-linux-arm64-musl": "1.32.0",
+ "lightningcss-linux-x64-gnu": "1.32.0",
+ "lightningcss-linux-x64-musl": "1.32.0",
+ "lightningcss-win32-arm64-msvc": "1.32.0",
+ "lightningcss-win32-x64-msvc": "1.32.0"
+ }
+ },
+ "node_modules/lightningcss-android-arm64": {
+ "version": "1.32.0",
+ "resolved": "https://registry.npmjs.org/lightningcss-android-arm64/-/lightningcss-android-arm64-1.32.0.tgz",
+ "integrity": "sha512-YK7/ClTt4kAK0vo6w3X+Pnm0D2cf2vPHbhOXdoNti1Ga0al1P4TBZhwjATvjNwLEBCnKvjJc2jQgHXH0NEwlAg==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MPL-2.0",
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/lightningcss-darwin-arm64": {
+ "version": "1.32.0",
+ "resolved": "https://registry.npmjs.org/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.32.0.tgz",
+ "integrity": "sha512-RzeG9Ju5bag2Bv1/lwlVJvBE3q6TtXskdZLLCyfg5pt+HLz9BqlICO7LZM7VHNTTn/5PRhHFBSjk5lc4cmscPQ==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MPL-2.0",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/lightningcss-darwin-x64": {
+ "version": "1.32.0",
+ "resolved": "https://registry.npmjs.org/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.32.0.tgz",
+ "integrity": "sha512-U+QsBp2m/s2wqpUYT/6wnlagdZbtZdndSmut/NJqlCcMLTWp5muCrID+K5UJ6jqD2BFshejCYXniPDbNh73V8w==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MPL-2.0",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/lightningcss-freebsd-x64": {
+ "version": "1.32.0",
+ "resolved": "https://registry.npmjs.org/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.32.0.tgz",
+ "integrity": "sha512-JCTigedEksZk3tHTTthnMdVfGf61Fky8Ji2E4YjUTEQX14xiy/lTzXnu1vwiZe3bYe0q+SpsSH/CTeDXK6WHig==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MPL-2.0",
+ "optional": true,
+ "os": [
+ "freebsd"
+ ],
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/lightningcss-linux-arm-gnueabihf": {
+ "version": "1.32.0",
+ "resolved": "https://registry.npmjs.org/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.32.0.tgz",
+ "integrity": "sha512-x6rnnpRa2GL0zQOkt6rts3YDPzduLpWvwAF6EMhXFVZXD4tPrBkEFqzGowzCsIWsPjqSK+tyNEODUBXeeVHSkw==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "license": "MPL-2.0",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/lightningcss-linux-arm64-gnu": {
+ "version": "1.32.0",
+ "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.32.0.tgz",
+ "integrity": "sha512-0nnMyoyOLRJXfbMOilaSRcLH3Jw5z9HDNGfT/gwCPgaDjnx0i8w7vBzFLFR1f6CMLKF8gVbebmkUN3fa/kQJpQ==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MPL-2.0",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/lightningcss-linux-arm64-musl": {
+ "version": "1.32.0",
+ "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.32.0.tgz",
+ "integrity": "sha512-UpQkoenr4UJEzgVIYpI80lDFvRmPVg6oqboNHfoH4CQIfNA+HOrZ7Mo7KZP02dC6LjghPQJeBsvXhJod/wnIBg==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MPL-2.0",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/lightningcss-linux-x64-gnu": {
+ "version": "1.32.0",
+ "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.32.0.tgz",
+ "integrity": "sha512-V7Qr52IhZmdKPVr+Vtw8o+WLsQJYCTd8loIfpDaMRWGUZfBOYEJeyJIkqGIDMZPwPx24pUMfwSxxI8phr/MbOA==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MPL-2.0",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/lightningcss-linux-x64-musl": {
+ "version": "1.32.0",
+ "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.32.0.tgz",
+ "integrity": "sha512-bYcLp+Vb0awsiXg/80uCRezCYHNg1/l3mt0gzHnWV9XP1W5sKa5/TCdGWaR/zBM2PeF/HbsQv/j2URNOiVuxWg==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MPL-2.0",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/lightningcss-win32-arm64-msvc": {
+ "version": "1.32.0",
+ "resolved": "https://registry.npmjs.org/lightningcss-win32-arm64-msvc/-/lightningcss-win32-arm64-msvc-1.32.0.tgz",
+ "integrity": "sha512-8SbC8BR40pS6baCM8sbtYDSwEVQd4JlFTOlaD3gWGHfThTcABnNDBda6eTZeqbofalIJhFx0qKzgHJmcPTnGdw==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MPL-2.0",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/lightningcss-win32-x64-msvc": {
+ "version": "1.32.0",
+ "resolved": "https://registry.npmjs.org/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.32.0.tgz",
+ "integrity": "sha512-Amq9B/SoZYdDi1kFrojnoqPLxYhQ4Wo5XiL8EVJrVsB8ARoC1PWW6VGtT0WKCemjy8aC+louJnjS7U18x3b06Q==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MPL-2.0",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/locate-path": {
+ "version": "6.0.0",
+ "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz",
+ "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "p-locate": "^5.0.0"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/lodash.merge": {
+ "version": "4.6.2",
+ "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz",
+ "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/lru-cache": {
+ "version": "5.1.1",
+ "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz",
+ "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "yallist": "^3.0.2"
+ }
+ },
+ "node_modules/lucide-react": {
+ "version": "1.7.0",
+ "resolved": "https://registry.npmjs.org/lucide-react/-/lucide-react-1.7.0.tgz",
+ "integrity": "sha512-yI7BeItCLZJTXikmK4KNUGCKoGzSvbKlfCvw44bU4fXAL6v3gYS4uHD1jzsLkfwODYwI6Drw5Tu9Z5ulDe0TSg==",
+ "license": "ISC",
+ "peerDependencies": {
+ "react": "^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0"
+ }
+ },
+ "node_modules/math-intrinsics": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz",
+ "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/mime-db": {
+ "version": "1.52.0",
+ "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz",
+ "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/mime-types": {
+ "version": "2.1.35",
+ "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz",
+ "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==",
+ "license": "MIT",
+ "dependencies": {
+ "mime-db": "1.52.0"
+ },
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/minimatch": {
+ "version": "3.1.5",
+ "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz",
+ "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "brace-expansion": "^1.1.7"
+ },
+ "engines": {
+ "node": "*"
+ }
+ },
+ "node_modules/ms": {
+ "version": "2.1.3",
+ "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
+ "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/nanoid": {
+ "version": "3.3.11",
+ "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz",
+ "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ],
+ "license": "MIT",
+ "bin": {
+ "nanoid": "bin/nanoid.cjs"
+ },
+ "engines": {
+ "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1"
+ }
+ },
+ "node_modules/natural-compare": {
+ "version": "1.4.0",
+ "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz",
+ "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/node-releases": {
+ "version": "2.0.37",
+ "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.37.tgz",
+ "integrity": "sha512-1h5gKZCF+pO/o3Iqt5Jp7wc9rH3eJJ0+nh/CIoiRwjRxde/hAHyLPXYN4V3CqKAbiZPSeJFSWHmJsbkicta0Eg==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/optionator": {
+ "version": "0.9.4",
+ "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz",
+ "integrity": "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "deep-is": "^0.1.3",
+ "fast-levenshtein": "^2.0.6",
+ "levn": "^0.4.1",
+ "prelude-ls": "^1.2.1",
+ "type-check": "^0.4.0",
+ "word-wrap": "^1.2.5"
+ },
+ "engines": {
+ "node": ">= 0.8.0"
+ }
+ },
+ "node_modules/p-limit": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz",
+ "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "yocto-queue": "^0.1.0"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/p-locate": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz",
+ "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "p-limit": "^3.0.2"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/parent-module": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz",
+ "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "callsites": "^3.0.0"
+ },
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/path-exists": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz",
+ "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/path-key": {
+ "version": "3.1.1",
+ "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz",
+ "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/picocolors": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz",
+ "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==",
+ "dev": true,
+ "license": "ISC"
+ },
+ "node_modules/picomatch": {
+ "version": "4.0.4",
+ "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz",
+ "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==",
+ "dev": true,
+ "license": "MIT",
+ "peer": true,
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/jonschlinkert"
+ }
+ },
+ "node_modules/postcss": {
+ "version": "8.5.8",
+ "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.8.tgz",
+ "integrity": "sha512-OW/rX8O/jXnm82Ey1k44pObPtdblfiuWnrd8X7GJ7emImCOstunGbXUpp7HdBrFQX6rJzn3sPT397Wp5aCwCHg==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/postcss/"
+ },
+ {
+ "type": "tidelift",
+ "url": "https://tidelift.com/funding/github/npm/postcss"
+ },
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "nanoid": "^3.3.11",
+ "picocolors": "^1.1.1",
+ "source-map-js": "^1.2.1"
+ },
+ "engines": {
+ "node": "^10 || ^12 || >=14"
+ }
+ },
+ "node_modules/prelude-ls": {
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz",
+ "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.8.0"
+ }
+ },
+ "node_modules/proxy-from-env": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-2.1.0.tgz",
+ "integrity": "sha512-cJ+oHTW1VAEa8cJslgmUZrc+sjRKgAKl3Zyse6+PV38hZe/V6Z14TbCuXcan9F9ghlz4QrFr2c92TNF82UkYHA==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/punycode": {
+ "version": "2.3.1",
+ "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz",
+ "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/react": {
+ "version": "19.2.4",
+ "resolved": "https://registry.npmjs.org/react/-/react-19.2.4.tgz",
+ "integrity": "sha512-9nfp2hYpCwOjAN+8TZFGhtWEwgvWHXqESH8qT89AT/lWklpLON22Lc8pEtnpsZz7VmawabSU0gCjnj8aC0euHQ==",
+ "license": "MIT",
+ "peer": true,
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/react-chartjs-2": {
+ "version": "5.3.1",
+ "resolved": "https://registry.npmjs.org/react-chartjs-2/-/react-chartjs-2-5.3.1.tgz",
+ "integrity": "sha512-h5IPXKg9EXpjoBzUfyWJvllMjG2mQ4EiuHQFhms/AjUm0XSZHhyRy2xVmLXHKrtcdrPO4mnGqRtYoD0vp95A0A==",
+ "license": "MIT",
+ "peerDependencies": {
+ "chart.js": "^4.1.1",
+ "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0"
+ }
+ },
+ "node_modules/react-dom": {
+ "version": "19.2.4",
+ "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.4.tgz",
+ "integrity": "sha512-AXJdLo8kgMbimY95O2aKQqsz2iWi9jMgKJhRBAxECE4IFxfcazB2LmzloIoibJI3C12IlY20+KFaLv+71bUJeQ==",
+ "license": "MIT",
+ "dependencies": {
+ "scheduler": "^0.27.0"
+ },
+ "peerDependencies": {
+ "react": "^19.2.4"
+ }
+ },
+ "node_modules/resolve-from": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz",
+ "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/rolldown": {
+ "version": "1.0.0-rc.12",
+ "resolved": "https://registry.npmjs.org/rolldown/-/rolldown-1.0.0-rc.12.tgz",
+ "integrity": "sha512-yP4USLIMYrwpPHEFB5JGH1uxhcslv6/hL0OyvTuY+3qlOSJvZ7ntYnoWpehBxufkgN0cvXxppuTu5hHa/zPh+A==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@oxc-project/types": "=0.122.0",
+ "@rolldown/pluginutils": "1.0.0-rc.12"
+ },
+ "bin": {
+ "rolldown": "bin/cli.mjs"
+ },
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ },
+ "optionalDependencies": {
+ "@rolldown/binding-android-arm64": "1.0.0-rc.12",
+ "@rolldown/binding-darwin-arm64": "1.0.0-rc.12",
+ "@rolldown/binding-darwin-x64": "1.0.0-rc.12",
+ "@rolldown/binding-freebsd-x64": "1.0.0-rc.12",
+ "@rolldown/binding-linux-arm-gnueabihf": "1.0.0-rc.12",
+ "@rolldown/binding-linux-arm64-gnu": "1.0.0-rc.12",
+ "@rolldown/binding-linux-arm64-musl": "1.0.0-rc.12",
+ "@rolldown/binding-linux-ppc64-gnu": "1.0.0-rc.12",
+ "@rolldown/binding-linux-s390x-gnu": "1.0.0-rc.12",
+ "@rolldown/binding-linux-x64-gnu": "1.0.0-rc.12",
+ "@rolldown/binding-linux-x64-musl": "1.0.0-rc.12",
+ "@rolldown/binding-openharmony-arm64": "1.0.0-rc.12",
+ "@rolldown/binding-wasm32-wasi": "1.0.0-rc.12",
+ "@rolldown/binding-win32-arm64-msvc": "1.0.0-rc.12",
+ "@rolldown/binding-win32-x64-msvc": "1.0.0-rc.12"
+ }
+ },
+ "node_modules/rolldown/node_modules/@rolldown/pluginutils": {
+ "version": "1.0.0-rc.12",
+ "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-rc.12.tgz",
+ "integrity": "sha512-HHMwmarRKvoFsJorqYlFeFRzXZqCt2ETQlEDOb9aqssrnVBB1/+xgTGtuTrIk5vzLNX1MjMtTf7W9z3tsSbrxw==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/scheduler": {
+ "version": "0.27.0",
+ "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.27.0.tgz",
+ "integrity": "sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==",
+ "license": "MIT"
+ },
+ "node_modules/semver": {
+ "version": "6.3.1",
+ "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz",
+ "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==",
+ "dev": true,
+ "license": "ISC",
+ "bin": {
+ "semver": "bin/semver.js"
+ }
+ },
+ "node_modules/shebang-command": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz",
+ "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "shebang-regex": "^3.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/shebang-regex": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz",
+ "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/source-map-js": {
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz",
+ "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==",
+ "dev": true,
+ "license": "BSD-3-Clause",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/strip-json-comments": {
+ "version": "3.1.1",
+ "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz",
+ "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/supports-color": {
+ "version": "7.2.0",
+ "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
+ "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "has-flag": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/tinyglobby": {
+ "version": "0.2.15",
+ "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.15.tgz",
+ "integrity": "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "fdir": "^6.5.0",
+ "picomatch": "^4.0.3"
+ },
+ "engines": {
+ "node": ">=12.0.0"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/SuperchupuDev"
+ }
+ },
+ "node_modules/tslib": {
+ "version": "2.8.1",
+ "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz",
+ "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==",
+ "dev": true,
+ "license": "0BSD",
+ "optional": true
+ },
+ "node_modules/type-check": {
+ "version": "0.4.0",
+ "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz",
+ "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "prelude-ls": "^1.2.1"
+ },
+ "engines": {
+ "node": ">= 0.8.0"
+ }
+ },
+ "node_modules/update-browserslist-db": {
+ "version": "1.2.3",
+ "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.2.3.tgz",
+ "integrity": "sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/browserslist"
+ },
+ {
+ "type": "tidelift",
+ "url": "https://tidelift.com/funding/github/npm/browserslist"
+ },
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "escalade": "^3.2.0",
+ "picocolors": "^1.1.1"
+ },
+ "bin": {
+ "update-browserslist-db": "cli.js"
+ },
+ "peerDependencies": {
+ "browserslist": ">= 4.21.0"
+ }
+ },
+ "node_modules/uri-js": {
+ "version": "4.4.1",
+ "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz",
+ "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==",
+ "dev": true,
+ "license": "BSD-2-Clause",
+ "dependencies": {
+ "punycode": "^2.1.0"
+ }
+ },
+ "node_modules/vite": {
+ "version": "8.0.3",
+ "resolved": "https://registry.npmjs.org/vite/-/vite-8.0.3.tgz",
+ "integrity": "sha512-B9ifbFudT1TFhfltfaIPgjo9Z3mDynBTJSUYxTjOQruf/zHH+ezCQKcoqO+h7a9Pw9Nm/OtlXAiGT1axBgwqrQ==",
+ "dev": true,
+ "license": "MIT",
+ "peer": true,
+ "dependencies": {
+ "lightningcss": "^1.32.0",
+ "picomatch": "^4.0.4",
+ "postcss": "^8.5.8",
+ "rolldown": "1.0.0-rc.12",
+ "tinyglobby": "^0.2.15"
+ },
+ "bin": {
+ "vite": "bin/vite.js"
+ },
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ },
+ "funding": {
+ "url": "https://github.com/vitejs/vite?sponsor=1"
+ },
+ "optionalDependencies": {
+ "fsevents": "~2.3.3"
+ },
+ "peerDependencies": {
+ "@types/node": "^20.19.0 || >=22.12.0",
+ "@vitejs/devtools": "^0.1.0",
+ "esbuild": "^0.27.0",
+ "jiti": ">=1.21.0",
+ "less": "^4.0.0",
+ "sass": "^1.70.0",
+ "sass-embedded": "^1.70.0",
+ "stylus": ">=0.54.8",
+ "sugarss": "^5.0.0",
+ "terser": "^5.16.0",
+ "tsx": "^4.8.1",
+ "yaml": "^2.4.2"
+ },
+ "peerDependenciesMeta": {
+ "@types/node": {
+ "optional": true
+ },
+ "@vitejs/devtools": {
+ "optional": true
+ },
+ "esbuild": {
+ "optional": true
+ },
+ "jiti": {
+ "optional": true
+ },
+ "less": {
+ "optional": true
+ },
+ "sass": {
+ "optional": true
+ },
+ "sass-embedded": {
+ "optional": true
+ },
+ "stylus": {
+ "optional": true
+ },
+ "sugarss": {
+ "optional": true
+ },
+ "terser": {
+ "optional": true
+ },
+ "tsx": {
+ "optional": true
+ },
+ "yaml": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/which": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",
+ "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "isexe": "^2.0.0"
+ },
+ "bin": {
+ "node-which": "bin/node-which"
+ },
+ "engines": {
+ "node": ">= 8"
+ }
+ },
+ "node_modules/word-wrap": {
+ "version": "1.2.5",
+ "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz",
+ "integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/yallist": {
+ "version": "3.1.1",
+ "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz",
+ "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==",
+ "dev": true,
+ "license": "ISC"
+ },
+ "node_modules/yocto-queue": {
+ "version": "0.1.0",
+ "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz",
+ "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/zod": {
+ "version": "4.3.6",
+ "resolved": "https://registry.npmjs.org/zod/-/zod-4.3.6.tgz",
+ "integrity": "sha512-rftlrkhHZOcjDwkGlnUtZZkvaPHCsDATp4pGpuOOMDaTdDDXF91wuVDJoWoPsKX/3YPQ5fHuF3STjcYyKr+Qhg==",
+ "dev": true,
+ "license": "MIT",
+ "peer": true,
+ "funding": {
+ "url": "https://github.com/sponsors/colinhacks"
+ }
+ },
+ "node_modules/zod-validation-error": {
+ "version": "4.0.2",
+ "resolved": "https://registry.npmjs.org/zod-validation-error/-/zod-validation-error-4.0.2.tgz",
+ "integrity": "sha512-Q6/nZLe6jxuU80qb/4uJ4t5v2VEZ44lzQjPDhYJNztRQ4wyWc6VF3D3Kb/fAuPetZQnhS3hnajCf9CsWesghLQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=18.0.0"
+ },
+ "peerDependencies": {
+ "zod": "^3.25.0 || ^4.0.0"
+ }
+ }
+ }
+}
diff --git a/frontend/package.json b/frontend/package.json
new file mode 100644
index 0000000000000000000000000000000000000000..74fb77990d1d4990b95d975e4fd789f69b7d46ab
--- /dev/null
+++ b/frontend/package.json
@@ -0,0 +1,31 @@
+{
+ "name": "frontend",
+ "private": true,
+ "version": "0.0.0",
+ "type": "module",
+ "scripts": {
+ "dev": "vite",
+ "build": "vite build",
+ "lint": "eslint .",
+ "preview": "vite preview"
+ },
+ "dependencies": {
+ "axios": "^1.14.0",
+ "chart.js": "^4.5.1",
+ "lucide-react": "^1.7.0",
+ "react": "^19.2.4",
+ "react-chartjs-2": "^5.3.1",
+ "react-dom": "^19.2.4"
+ },
+ "devDependencies": {
+ "@eslint/js": "^9.39.4",
+ "@types/react": "^19.2.14",
+ "@types/react-dom": "^19.2.3",
+ "@vitejs/plugin-react": "^6.0.1",
+ "eslint": "^9.39.4",
+ "eslint-plugin-react-hooks": "^7.0.1",
+ "eslint-plugin-react-refresh": "^0.5.2",
+ "globals": "^17.4.0",
+ "vite": "^8.0.1"
+ }
+}
diff --git a/frontend/public/favicon.svg b/frontend/public/favicon.svg
new file mode 100644
index 0000000000000000000000000000000000000000..6893eb13237060adc0c968a690149a49faa2d7d3
--- /dev/null
+++ b/frontend/public/favicon.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/frontend/public/icons.svg b/frontend/public/icons.svg
new file mode 100644
index 0000000000000000000000000000000000000000..e9522193d9f796a9748e9ad8c952a5df73c87db9
--- /dev/null
+++ b/frontend/public/icons.svg
@@ -0,0 +1,24 @@
+
diff --git a/frontend/src/App.css b/frontend/src/App.css
new file mode 100644
index 0000000000000000000000000000000000000000..f90339d8f765fa2c69d9a341959a8ddb9fff5720
--- /dev/null
+++ b/frontend/src/App.css
@@ -0,0 +1,184 @@
+.counter {
+ font-size: 16px;
+ padding: 5px 10px;
+ border-radius: 5px;
+ color: var(--accent);
+ background: var(--accent-bg);
+ border: 2px solid transparent;
+ transition: border-color 0.3s;
+ margin-bottom: 24px;
+
+ &:hover {
+ border-color: var(--accent-border);
+ }
+ &:focus-visible {
+ outline: 2px solid var(--accent);
+ outline-offset: 2px;
+ }
+}
+
+.hero {
+ position: relative;
+
+ .base,
+ .framework,
+ .vite {
+ inset-inline: 0;
+ margin: 0 auto;
+ }
+
+ .base {
+ width: 170px;
+ position: relative;
+ z-index: 0;
+ }
+
+ .framework,
+ .vite {
+ position: absolute;
+ }
+
+ .framework {
+ z-index: 1;
+ top: 34px;
+ height: 28px;
+ transform: perspective(2000px) rotateZ(300deg) rotateX(44deg) rotateY(39deg)
+ scale(1.4);
+ }
+
+ .vite {
+ z-index: 0;
+ top: 107px;
+ height: 26px;
+ width: auto;
+ transform: perspective(2000px) rotateZ(300deg) rotateX(40deg) rotateY(39deg)
+ scale(0.8);
+ }
+}
+
+#center {
+ display: flex;
+ flex-direction: column;
+ gap: 25px;
+ place-content: center;
+ place-items: center;
+ flex-grow: 1;
+
+ @media (max-width: 1024px) {
+ padding: 32px 20px 24px;
+ gap: 18px;
+ }
+}
+
+#next-steps {
+ display: flex;
+ border-top: 1px solid var(--border);
+ text-align: left;
+
+ & > div {
+ flex: 1 1 0;
+ padding: 32px;
+ @media (max-width: 1024px) {
+ padding: 24px 20px;
+ }
+ }
+
+ .icon {
+ margin-bottom: 16px;
+ width: 22px;
+ height: 22px;
+ }
+
+ @media (max-width: 1024px) {
+ flex-direction: column;
+ text-align: center;
+ }
+}
+
+#docs {
+ border-right: 1px solid var(--border);
+
+ @media (max-width: 1024px) {
+ border-right: none;
+ border-bottom: 1px solid var(--border);
+ }
+}
+
+#next-steps ul {
+ list-style: none;
+ padding: 0;
+ display: flex;
+ gap: 8px;
+ margin: 32px 0 0;
+
+ .logo {
+ height: 18px;
+ }
+
+ a {
+ color: var(--text-h);
+ font-size: 16px;
+ border-radius: 6px;
+ background: var(--social-bg);
+ display: flex;
+ padding: 6px 12px;
+ align-items: center;
+ gap: 8px;
+ text-decoration: none;
+ transition: box-shadow 0.3s;
+
+ &:hover {
+ box-shadow: var(--shadow);
+ }
+ .button-icon {
+ height: 18px;
+ width: 18px;
+ }
+ }
+
+ @media (max-width: 1024px) {
+ margin-top: 20px;
+ flex-wrap: wrap;
+ justify-content: center;
+
+ li {
+ flex: 1 1 calc(50% - 8px);
+ }
+
+ a {
+ width: 100%;
+ justify-content: center;
+ box-sizing: border-box;
+ }
+ }
+}
+
+#spacer {
+ height: 88px;
+ border-top: 1px solid var(--border);
+ @media (max-width: 1024px) {
+ height: 48px;
+ }
+}
+
+.ticks {
+ position: relative;
+ width: 100%;
+
+ &::before,
+ &::after {
+ content: '';
+ position: absolute;
+ top: -4.5px;
+ border: 5px solid transparent;
+ }
+
+ &::before {
+ left: 0;
+ border-left-color: var(--border);
+ }
+ &::after {
+ right: 0;
+ border-right-color: var(--border);
+ }
+}
diff --git a/frontend/src/App.jsx b/frontend/src/App.jsx
new file mode 100644
index 0000000000000000000000000000000000000000..88ff44347a349aafd524d4718253e2a19b0ce1fb
--- /dev/null
+++ b/frontend/src/App.jsx
@@ -0,0 +1,68 @@
+import React, { useState } from 'react';
+import './index.css';
+import { HistoricalAnalytics } from './components/HistoricalAnalytics.jsx';
+import { MaterialPredictor } from './components/MaterialPredictor.jsx';
+import { ChatBot } from './components/ChatBot.jsx';
+import logo from '../Assets/g.png';
+import thumpVideo from '../Assets/Gorilla_Chest_Thumping_Animation_Generated.mp4';
+
+function App() {
+ const [activeTab, setActiveTab] = useState('waste');
+ const [showChat, setShowChat] = useState(false);
+ const [showVideo, setShowVideo] = useState(false);
+
+ const handleGorillaClick = () => {
+ setShowVideo(true);
+ setShowChat(false);
+ };
+
+ return (
+ <>
+
+
+

+
Gorilla Semiconductors
+
+
+
+
+
+
+
+
+
+ {activeTab === 'waste' ? : }
+
+
+ setShowChat(false)} />
+
+ {showVideo && (
+ { setShowVideo(false); setShowChat(true); }}>
+
+ )}
+ >
+ );
+}
+
+export default App;
diff --git a/frontend/src/apiConfig.js b/frontend/src/apiConfig.js
new file mode 100644
index 0000000000000000000000000000000000000000..6db258c54748530c2a24bd7c6d5660b5257f8589
--- /dev/null
+++ b/frontend/src/apiConfig.js
@@ -0,0 +1,3 @@
+const API_BASE_URL = import.meta.env.VITE_API_URL || 'http://localhost:8000';
+
+export default API_BASE_URL;
diff --git a/frontend/src/assets/hero.png b/frontend/src/assets/hero.png
new file mode 100644
index 0000000000000000000000000000000000000000..2d58a13c6c916ee1d261fc82517761fa3acf2c8d
--- /dev/null
+++ b/frontend/src/assets/hero.png
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:72a860570eddf1dd9988f26c7106c67be286bc9f2fd3303c465ce87edb1ae6cd
+size 44919
diff --git a/frontend/src/assets/react.svg b/frontend/src/assets/react.svg
new file mode 100644
index 0000000000000000000000000000000000000000..6c87de9bb3358469122cc991d5cf578927246184
--- /dev/null
+++ b/frontend/src/assets/react.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/frontend/src/assets/vite.svg b/frontend/src/assets/vite.svg
new file mode 100644
index 0000000000000000000000000000000000000000..5101b674df391399da71c767aa5c976426c9dc7a
--- /dev/null
+++ b/frontend/src/assets/vite.svg
@@ -0,0 +1 @@
+
diff --git a/frontend/src/components/ChatBot.jsx b/frontend/src/components/ChatBot.jsx
new file mode 100644
index 0000000000000000000000000000000000000000..db61b7ee4005ccffca28fed80a0c10ecbd3cc3dc
--- /dev/null
+++ b/frontend/src/components/ChatBot.jsx
@@ -0,0 +1,131 @@
+import React, { useState, useRef, useEffect } from 'react';
+import axios from 'axios';
+
+import API_BASE_URL from '../apiConfig';
+
+export const ChatBot = ({ isOpen, onClose }) => {
+ const [messages, setMessages] = useState([]);
+ const [input, setInput] = useState('');
+ const [isLoading, setIsLoading] = useState(false);
+ const messagesEndRef = useRef(null);
+
+ // Dragging state
+ const [position, setPosition] = useState({
+ x: typeof window !== 'undefined' ? window.innerWidth - 400 : 0,
+ y: typeof window !== 'undefined' ? window.innerHeight - 650 : 0
+ });
+ const [isDragging, setIsDragging] = useState(false);
+ const dragRef = useRef({ startX: 0, startY: 0, initialX: 0, initialY: 0 });
+
+ const scrollToBottom = () => {
+ messagesEndRef.current?.scrollIntoView({ behavior: 'smooth' });
+ };
+
+ useEffect(() => {
+ scrollToBottom();
+ }, [messages]);
+
+ // Handle Dragging
+ const handlePointerDown = (e) => {
+ // Don't drag if clicking the close button
+ if (e.target.tagName.toLowerCase() === 'button') return;
+ setIsDragging(true);
+ dragRef.current = {
+ startX: e.clientX,
+ startY: e.clientY,
+ initialX: position.x,
+ initialY: position.y
+ };
+ e.currentTarget.setPointerCapture(e.pointerId);
+ };
+
+ const handlePointerMove = (e) => {
+ if (!isDragging) return;
+ const dx = e.clientX - dragRef.current.startX;
+ const dy = e.clientY - dragRef.current.startY;
+ setPosition({
+ x: dragRef.current.initialX + dx,
+ y: dragRef.current.initialY + dy
+ });
+ };
+
+ const handlePointerUp = (e) => {
+ setIsDragging(false);
+ e.currentTarget.releasePointerCapture(e.pointerId);
+ };
+
+ const handleSend = async (e) => {
+ e.preventDefault();
+ if (!input.trim()) return;
+
+ const userMessage = { role: 'user', content: input };
+ setMessages(prev => [...prev, userMessage]);
+ setInput('');
+ setIsLoading(true);
+
+ try {
+ const response = await axios.post(`${API_BASE_URL}/api/chat`, {
+ messages: [...messages, userMessage].map(m => ({ role: m.role, content: m.content }))
+ });
+
+ if (response.data.response) {
+ setMessages(prev => [...prev, { role: 'model', content: response.data.response }]);
+ } else if (response.data.error) {
+ setMessages(prev => [...prev, { role: 'model', content: `Error: ${response.data.error}` }]);
+ }
+ } catch (error) {
+ console.error('Chat error:', error);
+ setMessages(prev => [...prev, { role: 'model', content: "GRRR... I couldn't reach the server. Is it running?" }]);
+ } finally {
+ setIsLoading(false);
+ }
+ };
+
+ if (!isOpen) return null;
+
+ return (
+
+
+
🦍 Gorilla Bot
+
+
+
+ {messages.map((msg, idx) => (
+
+ ))}
+ {isLoading && (
+
+ )}
+
+
+
+
+ );
+};
diff --git a/frontend/src/components/HistoricalAnalytics.jsx b/frontend/src/components/HistoricalAnalytics.jsx
new file mode 100644
index 0000000000000000000000000000000000000000..ae89f9758827264f4d9730662ba335f6e6672d6e
--- /dev/null
+++ b/frontend/src/components/HistoricalAnalytics.jsx
@@ -0,0 +1,150 @@
+import React, { useState, useEffect } from 'react';
+import axios from 'axios';
+import { Chart as ChartJS, ArcElement, Tooltip, Legend, CategoryScale, LinearScale, PointElement, LineElement, BarElement } from 'chart.js';
+import { Pie, Bar, Line } from 'react-chartjs-2';
+import KPICard from './KPICard';
+
+import API_BASE_URL from '../apiConfig';
+
+ChartJS.register(ArcElement, Tooltip, Legend, CategoryScale, LinearScale, PointElement, LineElement, BarElement);
+
+const COLORS = {
+ accent: '#f472b6',
+ accent2: '#38bdf8',
+ accent3: '#4ade80',
+ danger: '#fb7185',
+ warning: '#fbbf24',
+ text: '#000000',
+ textMuted: '#3f3f46'
+};
+
+const DEFECT_COLORS = {
+ 'Center': '#ef4444', 'Donut': '#f59e0b', 'Edge-Loc': '#10b981',
+ 'Edge-Ring': '#3b82f6', 'Loc': '#8b5cf6', 'Random': '#ec4899',
+ 'Scratch': '#06b6d4', 'Near-full': '#f97316', 'None': '#6b7280',
+ 'Undetected': '#374151'
+};
+
+const chartOptions = {
+ color: COLORS.text,
+ plugins: {
+ legend: {
+ labels: { color: COLORS.textMuted }
+ }
+ },
+ scales: {
+ x: { ticks: { color: COLORS.textMuted }, grid: { color: 'rgba(0,0,0,0.1)' } },
+ y: { ticks: { color: COLORS.textMuted }, grid: { color: 'rgba(0,0,0,0.1)' } }
+ }
+};
+const pieOptions = {
+ color: COLORS.text,
+ plugins: { legend: { labels: { color: COLORS.textMuted } } }
+};
+
+export const HistoricalAnalytics = () => {
+ const [kpis, setKpis] = useState(null);
+ const [defects, setDefects] = useState(null);
+ const [waste, setWaste] = useState(null);
+ const [trends, setTrends] = useState(null);
+
+ useEffect(() => {
+ const fetchData = async () => {
+ const [kRes, dRes, wRes, tRes] = await Promise.all([
+ axios.get(`${API_BASE_URL}/api/kpi`),
+ axios.get(`${API_BASE_URL}/api/charts/defects`),
+ axios.get(`${API_BASE_URL}/api/charts/waste`),
+ axios.get(`${API_BASE_URL}/api/charts/trends`)
+ ]);
+ setKpis(kRes.data);
+ setDefects(dRes.data);
+ setWaste(wRes.data);
+ setTrends(tRes.data);
+ };
+ fetchData();
+ }, []);
+
+ if (!kpis || !defects || !waste || !trends) return Loading Analytics...
;
+
+ const pieData = {
+ labels: defects.predictions.map(d => d.defect_type),
+ datasets: [{
+ data: defects.predictions.map(d => d.count),
+ backgroundColor: defects.predictions.map(d => DEFECT_COLORS[d.defect_type] || COLORS.textMuted),
+ borderColor: 'transparent'
+ }]
+ };
+
+ const barData = {
+ labels: waste.waste_by_type.map(w => w.defect_type),
+ datasets: [{
+ label: 'Total Material Waste (Wafers)',
+ data: waste.waste_by_type.map(w => w.total_waste),
+ backgroundColor: waste.waste_by_type.map(w => DEFECT_COLORS[w.defect_type] || COLORS.textMuted)
+ }]
+ };
+
+ const trendData = {
+ labels: trends.dates,
+ datasets: [{
+ label: 'Fail Rate %',
+ data: trends.fail_rate,
+ borderColor: COLORS.danger,
+ backgroundColor: 'rgba(251, 113, 133, 0.4)',
+ fill: true,
+ yAxisID: 'y'
+ }]
+ };
+
+ const wasteTrendData = {
+ labels: trends.dates,
+ datasets: [{
+ label: 'Total Lost Wafers',
+ data: trends.waste,
+ borderColor: COLORS.warning,
+ backgroundColor: 'rgba(251, 191, 36, 0.4)',
+ fill: true,
+ yAxisID: 'y'
+ }]
+ };
+
+ return (
+
+
+
+
+
+
+
+
+
+
+
+
+
YOLOv8 Predicted Distributions
+
+
+
+
Total Material Waste by Predict Defect
+
+
+
+
+
+
Daily Defect Rate Over Time
+
+
+
+
+
+
Daily Material Waste Over Time
+
+
+
+
+
+
+ );
+};
diff --git a/frontend/src/components/KPICard.jsx b/frontend/src/components/KPICard.jsx
new file mode 100644
index 0000000000000000000000000000000000000000..04f6d1b77052595c225f91366824e88230591d2c
--- /dev/null
+++ b/frontend/src/components/KPICard.jsx
@@ -0,0 +1,14 @@
+import React from 'react';
+import '../index.css';
+
+const KPICard = ({ title, value, subtitle, color }) => {
+ return (
+
+
{title}
+
{value}
+ {subtitle &&
{subtitle}
}
+
+ );
+};
+
+export default KPICard;
diff --git a/frontend/src/components/MaterialPredictor.jsx b/frontend/src/components/MaterialPredictor.jsx
new file mode 100644
index 0000000000000000000000000000000000000000..c8e74675e00ed7b2058ab6470db96f3243dc74a6
--- /dev/null
+++ b/frontend/src/components/MaterialPredictor.jsx
@@ -0,0 +1,73 @@
+import React, { useState, useEffect } from 'react';
+import axios from 'axios';
+import KPICard from './KPICard';
+
+import API_BASE_URL from '../apiConfig';
+
+export const MaterialPredictor = () => {
+ const [scans, setScans] = useState(1300);
+ const [failRate, setFailRate] = useState(97);
+ const [prediction, setPrediction] = useState(null);
+ const [modelStatus, setModelStatus] = useState(null);
+
+ useEffect(() => {
+ axios.get(`${API_BASE_URL}/api/model/status`).then(res => {
+ setModelStatus(res.data);
+ }).catch(() => setModelStatus({loaded: false}));
+ }, []);
+
+ const handlePredict = async () => {
+ try {
+ const res = await axios.post(`${API_BASE_URL}/api/predict`, { scans, fail_rate: failRate });
+ setPrediction(res.data);
+ } catch(e) {
+ console.error(e);
+ }
+ };
+
+ if (!modelStatus) return Loading...
;
+
+ return (
+
+
+
+
Prediction Model
+
+ {modelStatus.loaded ? 'Model loaded' : 'No model found'}
+
+
+ {modelStatus.loaded && (
+
+ R² = {modelStatus.metrics.r2} | MAE = {modelStatus.metrics.mae}%
+
+ )}
+
+
+
+
Forecast Parameters
+
+
+
+
+ {prediction && (
+
+ )}
+
+ );
+};
diff --git a/frontend/src/index.css b/frontend/src/index.css
new file mode 100644
index 0000000000000000000000000000000000000000..6c6a07a96543d8aa4928c61c9cd3d61e924383f7
--- /dev/null
+++ b/frontend/src/index.css
@@ -0,0 +1,490 @@
+:root {
+ --bg: #fbbf24; /* Banana Yellow */
+ --card: #ffffff;
+ --card-border: #000000;
+ --accent: #f472b6; /* Bubblegum Pink */
+ --accent2: #38bdf8; /* Sky Blue */
+ --accent3: #4ade80; /* Jungle Green */
+ --danger: #fb7185;
+ --warning: #fbcfe8;
+ --text: #000000;
+ --text-muted: #3f3f46;
+ --font-family: 'Fredoka', sans-serif;
+}
+
+body {
+ margin: 0;
+ padding: 0;
+ background-color: var(--bg);
+ color: var(--text);
+ font-family: var(--font-family);
+ -webkit-font-smoothing: antialiased;
+ -moz-osx-font-smoothing: grayscale;
+}
+
+#root {
+ height: 100vh;
+ padding: 12px 16px;
+ box-sizing: border-box;
+ display: flex;
+ flex-direction: column;
+ overflow: hidden;
+}
+
+.dashboard-header {
+ display: flex;
+ flex-direction: column;
+ margin-bottom: 12px;
+ border-bottom: 4px solid var(--card-border);
+ padding-bottom: 8px;
+ flex-shrink: 0;
+ background-color: var(--card);
+ border-radius: 12px;
+ padding: 8px 16px;
+ box-shadow: 4px 4px 0px #000000;
+ border: 3px solid #000000;
+}
+
+.header-title-container {
+ display: flex;
+ align-items: center;
+ gap: 12px;
+}
+
+.header-dot {
+ width: 12px;
+ height: 12px;
+ border-radius: 50%;
+ background-color: var(--accent3);
+ box-shadow: 0 0 8px var(--accent3);
+ animation: pulse 2s infinite;
+}
+
+@keyframes pulse {
+ 0% { box-shadow: 0 0 8px var(--accent3); }
+ 50% { box-shadow: 0 0 16px var(--accent3); }
+ 100% { box-shadow: 0 0 8px var(--accent3); }
+}
+
+.header-title {
+ margin: 0;
+ font-size: 26px;
+ font-weight: 700;
+ color: var(--text);
+ letter-spacing: 1px;
+}
+
+
+.gorilla-logo {
+ height: 48px;
+ width: auto;
+ border-radius: 8px;
+ object-fit: contain;
+ cursor: pointer;
+ transform-origin: center bottom;
+ transition: transform 0.2s cubic-bezier(0.34, 1.56, 0.64, 1);
+}
+
+.gorilla-logo:hover {
+ transform: scale(1.15);
+}
+
+@keyframes fadeInOverlay {
+ 0% { opacity: 0; }
+ 100% { opacity: 1; }
+}
+
+@keyframes fadeInScale {
+ 0% { opacity: 0; transform: scale(0.5); }
+ 100% { opacity: 1; transform: scale(1); }
+}
+
+.thump-video-overlay {
+ position: fixed;
+ top: 0;
+ left: 0;
+ width: 100vw;
+ height: 100vh;
+ background: rgba(0, 0, 0, 0.5);
+ display: flex;
+ justify-content: center;
+ align-items: center;
+ z-index: 9999;
+ animation: fadeInOverlay 0.4s ease-out forwards;
+}
+
+.thump-video {
+ width: 250px;
+ height: auto;
+ border-radius: 20px;
+ border: 6px solid #000000;
+ box-shadow: 8px 8px 0px #000000;
+ animation: fadeInScale 0.4s cubic-bezier(0.34, 1.56, 0.64, 1) forwards;
+}
+
+.header-subtitle {
+ color: var(--text-muted);
+ margin-top: 4px;
+ font-size: 13px;
+}
+
+/* Tabs */
+.tabs-container {
+ display: flex;
+ gap: 12px;
+ margin-bottom: 16px;
+ flex-shrink: 0;
+}
+
+.tab-btn {
+ background-color: var(--card);
+ color: var(--text);
+ border: 3px solid var(--card-border);
+ border-bottom: none;
+ border-radius: 16px 16px 0 0;
+ padding: 12px 24px;
+ font-weight: 600;
+ font-size: 16px;
+ cursor: pointer;
+ transition: all 0.2s cubic-bezier(0.34, 1.56, 0.64, 1);
+ outline: none;
+ font-family: var(--font-family);
+ box-shadow: inset 0 -4px 0 rgba(0,0,0,0.1);
+}
+
+.tab-btn:hover {
+ background-color: var(--accent);
+ transform: translateY(-2px);
+}
+
+.tab-btn.active {
+ background-color: var(--accent2);
+ color: #000000;
+ border-color: #000000;
+}
+
+/* KPI Container */
+.kpi-container {
+ display: flex;
+ gap: 8px;
+ flex-wrap: nowrap;
+ margin-bottom: 16px;
+ flex-shrink: 0;
+}
+
+/* KPI Card */
+.kpi-card {
+ background: var(--card);
+ border: 3px solid var(--card-border);
+ border-radius: 16px;
+ padding: 10px;
+ text-align: center;
+ flex: 1;
+ min-width: 0;
+ transition: all 0.2s cubic-bezier(0.34, 1.56, 0.64, 1);
+ box-shadow: 4px 4px 0px #000000;
+}
+
+.kpi-card:hover {
+ transform: scale(1.05) rotate(-2deg);
+ box-shadow: 8px 8px 0px #000000;
+ background-color: var(--warning);
+}
+
+.kpi-title {
+ color: var(--text-muted);
+ font-size: 11px;
+ margin-bottom: 2px;
+ font-weight: 500;
+ text-transform: uppercase;
+ letter-spacing: 1px;
+}
+
+.kpi-value {
+ font-size: 20px;
+ font-weight: 700;
+ margin: 4px 0;
+}
+
+.kpi-subtitle {
+ color: var(--text-muted);
+ font-size: 10px;
+ margin-top: 2px;
+}
+
+/* Base Card */
+.glass-card {
+ background: var(--card);
+ border: 3px solid var(--card-border);
+ border-radius: 16px;
+ padding: 12px;
+ margin-bottom: 0;
+ box-shadow: 6px 6px 0px #000000;
+ transition: transform 0.2s cubic-bezier(0.34, 1.56, 0.64, 1);
+}
+.glass-card h3 {
+ font-weight: 700;
+ letter-spacing: 0.5px;
+}
+
+/* Charts Grid */
+.charts-master-grid {
+ display: grid;
+ grid-template-columns: 1fr 1fr;
+ grid-template-rows: minmax(0, 1fr) minmax(0, 1fr);
+ gap: 12px;
+ flex: 1;
+ min-height: 0;
+}
+
+.chart-card {
+ display: flex;
+ flex-direction: column;
+}
+
+.canvas-container {
+ flex: 1;
+ min-height: 0;
+ position: relative;
+}
+
+@media (max-width: 900px) {
+ .charts-master-grid {
+ grid-template-columns: 1fr;
+ grid-template-rows: auto;
+ overflow-y: auto;
+ }
+ #root {
+ height: auto;
+ overflow: visible;
+ }
+}
+
+/* Material Predictor */
+.predictor-header {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+}
+
+.predictor-title {
+ margin: 0 0 4px 0;
+ font-size: 18px;
+}
+
+.predictor-form {
+ display: grid;
+ grid-template-columns: 1fr 1fr;
+ gap: 24px;
+ margin-bottom: 20px;
+}
+
+.slider-container label {
+ display: block;
+ color: var(--text-muted);
+ font-size: 13px;
+ margin-bottom: 8px;
+}
+
+.slider-container input[type="range"] {
+ width: 100%;
+ accent-color: var(--accent2);
+}
+
+.btn-primary {
+ background-color: var(--accent);
+ color: #000000;
+ border: 3px solid #000000;
+ border-radius: 16px;
+ padding: 12px 32px;
+ font-size: 18px;
+ font-weight: 700;
+ cursor: pointer;
+ width: 100%;
+ transition: all 0.2s cubic-bezier(0.34, 1.56, 0.64, 1);
+ box-shadow: 4px 4px 0px #000000;
+ font-family: var(--font-family);
+}
+
+.btn-primary:hover {
+ background-color: var(--accent2);
+ transform: translate(-2px, -2px);
+ box-shadow: 6px 6px 0px #000000;
+}
+.btn-primary:active {
+ transform: translate(2px, 2px);
+ box-shadow: 0px 0px 0px #000000;
+}
+
+.prediction-result {
+ background: var(--accent3);
+}
+
+/* ChatBot Styles */
+.chat-modal-overlay {
+ position: fixed;
+ top: 0;
+ left: 0;
+ width: 100vw;
+ height: 100vh;
+ background: rgba(0, 0, 0, 0.4);
+ display: flex;
+ justify-content: center;
+ align-items: center;
+ z-index: 10000;
+ animation: fadeInOverlay 0.2s ease-out forwards;
+}
+
+.chat-widget-container {
+ position: fixed;
+ width: 360px;
+ height: 600px;
+ min-width: 250px;
+ min-height: 300px;
+ background: var(--card);
+ border: 4px solid #000000;
+ border-radius: 16px;
+ box-shadow: 12px 12px 0px #000000;
+ display: flex;
+ flex-direction: column;
+ overflow: hidden;
+ z-index: 10000;
+ resize: both;
+}
+
+.chat-header {
+ background: var(--accent2);
+ padding: 16px;
+ border-bottom: 4px solid #000000;
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ cursor: move;
+ user-select: none;
+}
+
+.chat-header h3 {
+ margin: 0;
+ font-size: 20px;
+ font-weight: 800;
+}
+
+.chat-close-btn {
+ background: var(--card);
+ border: 3px solid #000000;
+ border-radius: 50%;
+ width: 32px;
+ height: 32px;
+ font-size: 20px;
+ font-weight: bold;
+ cursor: pointer;
+ display: flex;
+ justify-content: center;
+ align-items: center;
+ transition: transform 0.2s;
+}
+
+.chat-close-btn:hover {
+ background: var(--danger);
+ transform: scale(1.1) rotate(90deg);
+}
+
+.chat-messages {
+ flex: 1;
+ padding: 16px;
+ overflow-y: auto;
+ display: flex;
+ flex-direction: column;
+ gap: 12px;
+ background: #e0f2fe;
+}
+
+.chat-message {
+ display: flex;
+ width: 100%;
+}
+
+.chat-message.user {
+ justify-content: flex-end;
+}
+
+.chat-message.model {
+ justify-content: flex-start;
+}
+
+.chat-bubble {
+ max-width: 80%;
+ padding: 12px 16px;
+ border: 3px solid #000000;
+ border-radius: 12px;
+ font-size: 15px;
+ line-height: 1.4;
+ box-shadow: 4px 4px 0px #000000;
+ white-space: pre-wrap;
+}
+
+.chat-message.user .chat-bubble {
+ background: var(--accent);
+ border-bottom-right-radius: 0;
+}
+
+.chat-message.model .chat-bubble {
+ background: var(--card);
+ border-bottom-left-radius: 0;
+}
+
+.chat-bubble.loading {
+ font-style: italic;
+ color: var(--text-muted);
+}
+
+.chat-input-area {
+ display: flex;
+ padding: 12px;
+ background: var(--card);
+ border-top: 4px solid #000000;
+ gap: 8px;
+}
+
+.chat-input {
+ flex: 1;
+ padding: 12px;
+ border: 3px solid #000000;
+ border-radius: 8px;
+ font-size: 16px;
+ font-family: var(--font-family);
+ outline: none;
+}
+
+.chat-input:focus {
+ border-color: var(--accent2);
+}
+
+.chat-send-btn {
+ background: var(--accent3);
+ color: #000000;
+ border: 3px solid #000000;
+ border-radius: 8px;
+ padding: 0 24px;
+ font-weight: 700;
+ font-size: 16px;
+ cursor: pointer;
+ font-family: var(--font-family);
+ box-shadow: 4px 4px 0px #000000;
+ transition: transform 0.1s, box-shadow 0.1s;
+}
+
+.chat-send-btn:hover:not(:disabled) {
+ transform: translate(-2px, -2px);
+ box-shadow: 6px 6px 0px #000000;
+}
+
+.chat-send-btn:active:not(:disabled) {
+ transform: translate(2px, 2px);
+ box-shadow: 0px 0px 0px #000000;
+}
+
+.chat-send-btn:disabled {
+ background: #ccc;
+ cursor: not-allowed;
+}
diff --git a/frontend/src/main.jsx b/frontend/src/main.jsx
new file mode 100644
index 0000000000000000000000000000000000000000..b9a1a6deac8775b5598874b2bc3c7971d82cf211
--- /dev/null
+++ b/frontend/src/main.jsx
@@ -0,0 +1,10 @@
+import { StrictMode } from 'react'
+import { createRoot } from 'react-dom/client'
+import './index.css'
+import App from './App.jsx'
+
+createRoot(document.getElementById('root')).render(
+
+
+ ,
+)
diff --git a/frontend/vite.config.js b/frontend/vite.config.js
new file mode 100644
index 0000000000000000000000000000000000000000..8b0f57b91aeb45c54467e29f983a0893dc83c4d9
--- /dev/null
+++ b/frontend/vite.config.js
@@ -0,0 +1,7 @@
+import { defineConfig } from 'vite'
+import react from '@vitejs/plugin-react'
+
+// https://vite.dev/config/
+export default defineConfig({
+ plugins: [react()],
+})
diff --git a/middleware/EDA_wafer_control_db.ipynb b/middleware/EDA_wafer_control_db.ipynb
new file mode 100644
index 0000000000000000000000000000000000000000..93b15073cf62eb69ce626c8541be3d6d1045586d
--- /dev/null
+++ b/middleware/EDA_wafer_control_db.ipynb
@@ -0,0 +1,604 @@
+{
+ "cells": [
+ {
+ "cell_type": "code",
+ "execution_count": 1,
+ "id": "2d67457f",
+ "metadata": {},
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ " id wafer_id batch_id scan_time status \\\n",
+ "0 1 wafer_0 BATCH_20260317_201406 2026-02-15 20:14:17 FAIL \n",
+ "1 2 wafer_1 BATCH_20260317_201406 2026-02-15 20:14:57 FAIL \n",
+ "2 3 wafer_2 BATCH_20260317_201406 2026-02-15 20:14:59 FAIL \n",
+ "3 4 wafer_3 BATCH_20260317_201406 2026-02-15 20:14:21 FAIL \n",
+ "4 5 wafer_4 BATCH_20260317_201406 2026-02-15 20:14:36 FAIL \n",
+ "\n",
+ " ground_truth defect_type action confidence \\\n",
+ "0 Center+Edge-Loc+Random Edge-Ring MOVE_TO_MICRO_STAGE 0.90 \n",
+ "1 Center+Edge-Loc+Random Center ROUTE_TO_SCRAP 0.96 \n",
+ "2 Center+Edge-Loc+Random Center ROUTE_TO_SCRAP 0.88 \n",
+ "3 Center+Edge-Loc+Random Edge-Ring MOVE_TO_MICRO_STAGE 0.91 \n",
+ "4 Center+Edge-Loc+Random Center ROUTE_TO_SCRAP 0.85 \n",
+ "\n",
+ " roi_coordinates defect_area_px material_wasted_pct \n",
+ "0 [1, 0, 51, 50] 2500 92.46 \n",
+ "1 [1, 0, 51, 50] 2500 92.46 \n",
+ "2 [1, 0, 51, 50] 2500 92.46 \n",
+ "3 [1, 0, 51, 50] 2500 92.46 \n",
+ "4 [1, 0, 52, 51] 2601 96.19 \n"
+ ]
+ }
+ ],
+ "source": [
+ "import pandas as pd\n",
+ "import sqlite3\n",
+ "\n",
+ "# 1. Connect to the database file\n",
+ "conn = sqlite3.connect('/Users/udayan/CHITS_PR_1/middleware/wafer_control.db')\n",
+ "\n",
+ "# 2. Write a query to select the data you want\n",
+ "query = \"SELECT * FROM wafer_logs\"\n",
+ "\n",
+ "# 3. Load the data into a DataFrame\n",
+ "df = pd.read_sql_query(query, conn)\n",
+ "\n",
+ "# 4. Close the connection\n",
+ "conn.close()\n",
+ "\n",
+ "# View the first few rows\n",
+ "print(df.head())"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 3,
+ "id": "b287cad1",
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/html": [
+ "\n",
+ "\n",
+ "
\n",
+ " \n",
+ " \n",
+ " | \n",
+ " id | \n",
+ " wafer_id | \n",
+ " batch_id | \n",
+ " scan_time | \n",
+ " status | \n",
+ " defect_type | \n",
+ " action | \n",
+ " confidence | \n",
+ " roi_coordinates | \n",
+ " defect_area_px | \n",
+ " material_wasted_pct | \n",
+ "
\n",
+ " \n",
+ " \n",
+ " \n",
+ " | 0 | \n",
+ " 1 | \n",
+ " wafer_100574 | \n",
+ " BATCH_20260317_021451 | \n",
+ " 2026-02-15 02:15:48 | \n",
+ " FAIL | \n",
+ " Edge-Ring | \n",
+ " MOVE_TO_MICRO_STAGE | \n",
+ " 0.95 | \n",
+ " [1, 0, 115, 136] | \n",
+ " 15504 | \n",
+ " 97.56 | \n",
+ "
\n",
+ " \n",
+ " | 1 | \n",
+ " 2 | \n",
+ " wafer_101787 | \n",
+ " BATCH_20260317_021451 | \n",
+ " 2026-02-15 02:15:09 | \n",
+ " FAIL | \n",
+ " Center | \n",
+ " ROUTE_TO_SCRAP | \n",
+ " 0.96 | \n",
+ " [0, 0, 43, 43] | \n",
+ " 1849 | \n",
+ " 95.51 | \n",
+ "
\n",
+ " \n",
+ " | 2 | \n",
+ " 3 | \n",
+ " wafer_103333 | \n",
+ " BATCH_20260317_021451 | \n",
+ " 2026-02-15 02:14:59 | \n",
+ " FAIL | \n",
+ " Edge-Ring | \n",
+ " MOVE_TO_MICRO_STAGE | \n",
+ " 0.98 | \n",
+ " [0, 0, 43, 43] | \n",
+ " 1849 | \n",
+ " 95.51 | \n",
+ "
\n",
+ " \n",
+ " | 3 | \n",
+ " 4 | \n",
+ " wafer_106281 | \n",
+ " BATCH_20260317_021451 | \n",
+ " 2026-02-15 02:15:19 | \n",
+ " FAIL | \n",
+ " Loc | \n",
+ " MOVE_TO_MICRO_STAGE | \n",
+ " 0.60 | \n",
+ " [0, 0, 30, 34] | \n",
+ " 1020 | \n",
+ " 94.01 | \n",
+ "
\n",
+ " \n",
+ " | 4 | \n",
+ " 5 | \n",
+ " wafer_106301 | \n",
+ " BATCH_20260317_021451 | \n",
+ " 2026-02-15 02:15:19 | \n",
+ " FAIL | \n",
+ " Loc | \n",
+ " MOVE_TO_MICRO_STAGE | \n",
+ " 0.96 | \n",
+ " [0, 0, 29, 33] | \n",
+ " 957 | \n",
+ " 88.20 | \n",
+ "
\n",
+ " \n",
+ " | ... | \n",
+ " ... | \n",
+ " ... | \n",
+ " ... | \n",
+ " ... | \n",
+ " ... | \n",
+ " ... | \n",
+ " ... | \n",
+ " ... | \n",
+ " ... | \n",
+ " ... | \n",
+ " ... | \n",
+ "
\n",
+ " \n",
+ " | 5099 | \n",
+ " 5100 | \n",
+ " wafer_95994 | \n",
+ " BATCH_20260317_021451 | \n",
+ " 2026-03-16 02:15:15 | \n",
+ " FAIL | \n",
+ " Center | \n",
+ " ROUTE_TO_SCRAP | \n",
+ " 0.96 | \n",
+ " [0, 0, 60, 40] | \n",
+ " 2400 | \n",
+ " 93.68 | \n",
+ "
\n",
+ " \n",
+ " | 5100 | \n",
+ " 5101 | \n",
+ " wafer_96083 | \n",
+ " BATCH_20260317_021451 | \n",
+ " 2026-03-17 02:15:17 | \n",
+ " FAIL | \n",
+ " Center | \n",
+ " ROUTE_TO_SCRAP | \n",
+ " 0.96 | \n",
+ " [0, 0, 60, 40] | \n",
+ " 2400 | \n",
+ " 93.68 | \n",
+ "
\n",
+ " \n",
+ " | 5101 | \n",
+ " 5102 | \n",
+ " wafer_9637 | \n",
+ " BATCH_20260317_021451 | \n",
+ " 2026-03-17 02:14:52 | \n",
+ " FAIL | \n",
+ " Edge-Loc | \n",
+ " MOVE_TO_MICRO_STAGE | \n",
+ " 0.98 | \n",
+ " [0, 0, 28, 31] | \n",
+ " 868 | \n",
+ " 90.70 | \n",
+ "
\n",
+ " \n",
+ " | 5102 | \n",
+ " 5103 | \n",
+ " wafer_96594 | \n",
+ " BATCH_20260317_021451 | \n",
+ " 2026-03-17 02:15:22 | \n",
+ " FAIL | \n",
+ " Loc | \n",
+ " MOVE_TO_MICRO_STAGE | \n",
+ " 0.81 | \n",
+ " [0, 0, 30, 29] | \n",
+ " 870 | \n",
+ " 90.53 | \n",
+ "
\n",
+ " \n",
+ " | 5103 | \n",
+ " 5104 | \n",
+ " wafer_983 | \n",
+ " BATCH_20260317_021451 | \n",
+ " 2026-03-17 02:15:00 | \n",
+ " FAIL | \n",
+ " Edge-Loc | \n",
+ " MOVE_TO_MICRO_STAGE | \n",
+ " 0.69 | \n",
+ " [0, 0, 25, 25] | \n",
+ " 625 | \n",
+ " 92.46 | \n",
+ "
\n",
+ " \n",
+ "
\n",
+ "
5104 rows × 11 columns
\n",
+ "
"
+ ],
+ "text/plain": [
+ " id wafer_id batch_id scan_time status \\\n",
+ "0 1 wafer_100574 BATCH_20260317_021451 2026-02-15 02:15:48 FAIL \n",
+ "1 2 wafer_101787 BATCH_20260317_021451 2026-02-15 02:15:09 FAIL \n",
+ "2 3 wafer_103333 BATCH_20260317_021451 2026-02-15 02:14:59 FAIL \n",
+ "3 4 wafer_106281 BATCH_20260317_021451 2026-02-15 02:15:19 FAIL \n",
+ "4 5 wafer_106301 BATCH_20260317_021451 2026-02-15 02:15:19 FAIL \n",
+ "... ... ... ... ... ... \n",
+ "5099 5100 wafer_95994 BATCH_20260317_021451 2026-03-16 02:15:15 FAIL \n",
+ "5100 5101 wafer_96083 BATCH_20260317_021451 2026-03-17 02:15:17 FAIL \n",
+ "5101 5102 wafer_9637 BATCH_20260317_021451 2026-03-17 02:14:52 FAIL \n",
+ "5102 5103 wafer_96594 BATCH_20260317_021451 2026-03-17 02:15:22 FAIL \n",
+ "5103 5104 wafer_983 BATCH_20260317_021451 2026-03-17 02:15:00 FAIL \n",
+ "\n",
+ " defect_type action confidence roi_coordinates \\\n",
+ "0 Edge-Ring MOVE_TO_MICRO_STAGE 0.95 [1, 0, 115, 136] \n",
+ "1 Center ROUTE_TO_SCRAP 0.96 [0, 0, 43, 43] \n",
+ "2 Edge-Ring MOVE_TO_MICRO_STAGE 0.98 [0, 0, 43, 43] \n",
+ "3 Loc MOVE_TO_MICRO_STAGE 0.60 [0, 0, 30, 34] \n",
+ "4 Loc MOVE_TO_MICRO_STAGE 0.96 [0, 0, 29, 33] \n",
+ "... ... ... ... ... \n",
+ "5099 Center ROUTE_TO_SCRAP 0.96 [0, 0, 60, 40] \n",
+ "5100 Center ROUTE_TO_SCRAP 0.96 [0, 0, 60, 40] \n",
+ "5101 Edge-Loc MOVE_TO_MICRO_STAGE 0.98 [0, 0, 28, 31] \n",
+ "5102 Loc MOVE_TO_MICRO_STAGE 0.81 [0, 0, 30, 29] \n",
+ "5103 Edge-Loc MOVE_TO_MICRO_STAGE 0.69 [0, 0, 25, 25] \n",
+ "\n",
+ " defect_area_px material_wasted_pct \n",
+ "0 15504 97.56 \n",
+ "1 1849 95.51 \n",
+ "2 1849 95.51 \n",
+ "3 1020 94.01 \n",
+ "4 957 88.20 \n",
+ "... ... ... \n",
+ "5099 2400 93.68 \n",
+ "5100 2400 93.68 \n",
+ "5101 868 90.70 \n",
+ "5102 870 90.53 \n",
+ "5103 625 92.46 \n",
+ "\n",
+ "[5104 rows x 11 columns]"
+ ]
+ },
+ "execution_count": 3,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "df"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 2,
+ "id": "299c2be8",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "pass_wafers = df[df['status'] == 'PASS']"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 3,
+ "id": "46fbb2d3",
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/html": [
+ "\n",
+ "\n",
+ "
\n",
+ " \n",
+ " \n",
+ " | \n",
+ " id | \n",
+ " wafer_id | \n",
+ " batch_id | \n",
+ " scan_time | \n",
+ " status | \n",
+ " ground_truth | \n",
+ " defect_type | \n",
+ " action | \n",
+ " confidence | \n",
+ " roi_coordinates | \n",
+ " defect_area_px | \n",
+ " material_wasted_pct | \n",
+ "
\n",
+ " \n",
+ " \n",
+ " \n",
+ " | 33866 | \n",
+ " 33867 | \n",
+ " wafer_33866 | \n",
+ " BATCH_20260317_201406 | \n",
+ " 2026-03-13 20:14:29 | \n",
+ " PASS | \n",
+ " Normal | \n",
+ " None | \n",
+ " ROUTE_TO_ASSEMBLY | \n",
+ " 1.0 | \n",
+ " [] | \n",
+ " 0 | \n",
+ " 0.0 | \n",
+ "
\n",
+ " \n",
+ " | 33867 | \n",
+ " 33868 | \n",
+ " wafer_33867 | \n",
+ " BATCH_20260317_201406 | \n",
+ " 2026-03-13 20:14:08 | \n",
+ " PASS | \n",
+ " Normal | \n",
+ " None | \n",
+ " ROUTE_TO_ASSEMBLY | \n",
+ " 1.0 | \n",
+ " [] | \n",
+ " 0 | \n",
+ " 0.0 | \n",
+ "
\n",
+ " \n",
+ " | 33868 | \n",
+ " 33869 | \n",
+ " wafer_33868 | \n",
+ " BATCH_20260317_201406 | \n",
+ " 2026-03-13 20:14:08 | \n",
+ " PASS | \n",
+ " Normal | \n",
+ " None | \n",
+ " ROUTE_TO_ASSEMBLY | \n",
+ " 1.0 | \n",
+ " [] | \n",
+ " 0 | \n",
+ " 0.0 | \n",
+ "
\n",
+ " \n",
+ " | 33869 | \n",
+ " 33870 | \n",
+ " wafer_33869 | \n",
+ " BATCH_20260317_201406 | \n",
+ " 2026-03-13 20:14:29 | \n",
+ " PASS | \n",
+ " Normal | \n",
+ " None | \n",
+ " ROUTE_TO_ASSEMBLY | \n",
+ " 1.0 | \n",
+ " [] | \n",
+ " 0 | \n",
+ " 0.0 | \n",
+ "
\n",
+ " \n",
+ " | 33870 | \n",
+ " 33871 | \n",
+ " wafer_33870 | \n",
+ " BATCH_20260317_201406 | \n",
+ " 2026-03-13 20:14:31 | \n",
+ " PASS | \n",
+ " Normal | \n",
+ " None | \n",
+ " ROUTE_TO_ASSEMBLY | \n",
+ " 1.0 | \n",
+ " [] | \n",
+ " 0 | \n",
+ " 0.0 | \n",
+ "
\n",
+ " \n",
+ " | ... | \n",
+ " ... | \n",
+ " ... | \n",
+ " ... | \n",
+ " ... | \n",
+ " ... | \n",
+ " ... | \n",
+ " ... | \n",
+ " ... | \n",
+ " ... | \n",
+ " ... | \n",
+ " ... | \n",
+ " ... | \n",
+ "
\n",
+ " \n",
+ " | 823948 | \n",
+ " 823949 | \n",
+ " wm811k_811442 | \n",
+ " BATCH_20260317_201406 | \n",
+ " 2026-03-16 20:14:06 | \n",
+ " PASS | \n",
+ " Normal | \n",
+ " None | \n",
+ " ROUTE_TO_ASSEMBLY | \n",
+ " 1.0 | \n",
+ " [] | \n",
+ " 0 | \n",
+ " 0.0 | \n",
+ "
\n",
+ " \n",
+ " | 823949 | \n",
+ " 823950 | \n",
+ " wm811k_811445 | \n",
+ " BATCH_20260317_201406 | \n",
+ " 2026-03-16 20:14:07 | \n",
+ " PASS | \n",
+ " Normal | \n",
+ " None | \n",
+ " ROUTE_TO_ASSEMBLY | \n",
+ " 1.0 | \n",
+ " [] | \n",
+ " 0 | \n",
+ " 0.0 | \n",
+ "
\n",
+ " \n",
+ " | 823950 | \n",
+ " 823951 | \n",
+ " wm811k_811449 | \n",
+ " BATCH_20260317_201406 | \n",
+ " 2026-03-16 20:14:07 | \n",
+ " PASS | \n",
+ " Normal | \n",
+ " None | \n",
+ " ROUTE_TO_ASSEMBLY | \n",
+ " 1.0 | \n",
+ " [] | \n",
+ " 0 | \n",
+ " 0.0 | \n",
+ "
\n",
+ " \n",
+ " | 823951 | \n",
+ " 823952 | \n",
+ " wm811k_811455 | \n",
+ " BATCH_20260317_201406 | \n",
+ " 2026-03-16 20:14:07 | \n",
+ " PASS | \n",
+ " Normal | \n",
+ " None | \n",
+ " ROUTE_TO_ASSEMBLY | \n",
+ " 1.0 | \n",
+ " [] | \n",
+ " 0 | \n",
+ " 0.0 | \n",
+ "
\n",
+ " \n",
+ " | 823952 | \n",
+ " 823953 | \n",
+ " wm811k_811456 | \n",
+ " BATCH_20260317_201406 | \n",
+ " 2026-03-16 20:14:09 | \n",
+ " PASS | \n",
+ " Normal | \n",
+ " None | \n",
+ " ROUTE_TO_ASSEMBLY | \n",
+ " 1.0 | \n",
+ " [] | \n",
+ " 0 | \n",
+ " 0.0 | \n",
+ "
\n",
+ " \n",
+ "
\n",
+ "
786938 rows × 12 columns
\n",
+ "
"
+ ],
+ "text/plain": [
+ " id wafer_id batch_id scan_time \\\n",
+ "33866 33867 wafer_33866 BATCH_20260317_201406 2026-03-13 20:14:29 \n",
+ "33867 33868 wafer_33867 BATCH_20260317_201406 2026-03-13 20:14:08 \n",
+ "33868 33869 wafer_33868 BATCH_20260317_201406 2026-03-13 20:14:08 \n",
+ "33869 33870 wafer_33869 BATCH_20260317_201406 2026-03-13 20:14:29 \n",
+ "33870 33871 wafer_33870 BATCH_20260317_201406 2026-03-13 20:14:31 \n",
+ "... ... ... ... ... \n",
+ "823948 823949 wm811k_811442 BATCH_20260317_201406 2026-03-16 20:14:06 \n",
+ "823949 823950 wm811k_811445 BATCH_20260317_201406 2026-03-16 20:14:07 \n",
+ "823950 823951 wm811k_811449 BATCH_20260317_201406 2026-03-16 20:14:07 \n",
+ "823951 823952 wm811k_811455 BATCH_20260317_201406 2026-03-16 20:14:07 \n",
+ "823952 823953 wm811k_811456 BATCH_20260317_201406 2026-03-16 20:14:09 \n",
+ "\n",
+ " status ground_truth defect_type action confidence \\\n",
+ "33866 PASS Normal None ROUTE_TO_ASSEMBLY 1.0 \n",
+ "33867 PASS Normal None ROUTE_TO_ASSEMBLY 1.0 \n",
+ "33868 PASS Normal None ROUTE_TO_ASSEMBLY 1.0 \n",
+ "33869 PASS Normal None ROUTE_TO_ASSEMBLY 1.0 \n",
+ "33870 PASS Normal None ROUTE_TO_ASSEMBLY 1.0 \n",
+ "... ... ... ... ... ... \n",
+ "823948 PASS Normal None ROUTE_TO_ASSEMBLY 1.0 \n",
+ "823949 PASS Normal None ROUTE_TO_ASSEMBLY 1.0 \n",
+ "823950 PASS Normal None ROUTE_TO_ASSEMBLY 1.0 \n",
+ "823951 PASS Normal None ROUTE_TO_ASSEMBLY 1.0 \n",
+ "823952 PASS Normal None ROUTE_TO_ASSEMBLY 1.0 \n",
+ "\n",
+ " roi_coordinates defect_area_px material_wasted_pct \n",
+ "33866 [] 0 0.0 \n",
+ "33867 [] 0 0.0 \n",
+ "33868 [] 0 0.0 \n",
+ "33869 [] 0 0.0 \n",
+ "33870 [] 0 0.0 \n",
+ "... ... ... ... \n",
+ "823948 [] 0 0.0 \n",
+ "823949 [] 0 0.0 \n",
+ "823950 [] 0 0.0 \n",
+ "823951 [] 0 0.0 \n",
+ "823952 [] 0 0.0 \n",
+ "\n",
+ "[786938 rows x 12 columns]"
+ ]
+ },
+ "execution_count": 3,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "pass_wafers"
+ ]
+ }
+ ],
+ "metadata": {
+ "kernelspec": {
+ "display_name": "venv",
+ "language": "python",
+ "name": "python3"
+ },
+ "language_info": {
+ "codemirror_mode": {
+ "name": "ipython",
+ "version": 3
+ },
+ "file_extension": ".py",
+ "mimetype": "text/x-python",
+ "name": "python",
+ "nbconvert_exporter": "python",
+ "pygments_lexer": "ipython3",
+ "version": "3.13.5"
+ }
+ },
+ "nbformat": 4,
+ "nbformat_minor": 5
+}
diff --git a/middleware/__init__.py b/middleware/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/middleware/best.pt b/middleware/best.pt
new file mode 100644
index 0000000000000000000000000000000000000000..00db005b908091b4b8ed954b61e88fcd2cd1f165
--- /dev/null
+++ b/middleware/best.pt
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:f3df8caca758c1959cc0f4dfc3190c4887a8d9ad053052a685b0bd2d5fb8f502
+size 6195306
diff --git a/middleware/dashboard.py b/middleware/dashboard.py
new file mode 100644
index 0000000000000000000000000000000000000000..3fe0e786f24447e4266b68a804b552fa80b8e0d5
--- /dev/null
+++ b/middleware/dashboard.py
@@ -0,0 +1,369 @@
+"""
+Wafer Defect Analytics Dashboard — Phase 5
+Plotly Dash web application for semiconductor material waste analysis
+and predictive material requirement estimation.
+Now using the Mixed-type Wafer Defect Dataset (38,015 wafers).
+"""
+
+import os
+import pickle
+import sqlite3
+import numpy as np
+import pandas as pd
+import plotly.express as px
+import plotly.graph_objects as go
+from dash import Dash, html, dcc, callback, Input, Output, State
+
+# --- CONFIGURATION ---
+DB_PATH = os.path.join(os.path.dirname(__file__), 'wafer_control.db')
+MODEL_PATH = os.path.join(os.path.dirname(__file__), 'material_model.pkl')
+
+# Semiconductor-themed color palette
+COLORS = {
+ 'bg': '#0a0e17',
+ 'card': '#131a2e',
+ 'card_border': '#1e2d4a',
+ 'accent': '#00d4ff',
+ 'accent2': '#7c3aed',
+ 'accent3': '#10b981',
+ 'danger': '#ef4444',
+ 'warning': '#f59e0b',
+ 'text': '#e2e8f0',
+ 'text_muted': '#94a3b8',
+}
+
+DEFECT_COLORS = {
+ 'Center': '#ef4444', 'Donut': '#f59e0b', 'Edge-Loc': '#10b981',
+ 'Edge-Ring': '#3b82f6', 'Loc': '#8b5cf6', 'Random': '#ec4899',
+ 'Scratch': '#06b6d4', 'Near-full': '#f97316', 'None': '#6b7280',
+ 'Undetected': '#374151',
+}
+
+
+def load_data():
+ conn = sqlite3.connect(DB_PATH)
+ df = pd.read_sql_query("SELECT * FROM wafer_logs", conn)
+ conn.close()
+ df['scan_time'] = pd.to_datetime(df['scan_time'])
+ df['scan_date'] = df['scan_time'].dt.date
+ return df
+
+
+def load_model():
+ if os.path.exists(MODEL_PATH):
+ with open(MODEL_PATH, 'rb') as f:
+ return pickle.load(f)
+ return None
+
+
+# --- LOAD DATA ---
+df = load_data()
+model_pkg = load_model()
+
+# --- PRE-COMPUTE STATS ---
+total_scans = len(df)
+fail_count = len(df[df['status'] == 'FAIL'])
+pass_count = len(df[df['status'] == 'PASS'])
+pass_rate = round((pass_count / total_scans) * 100, 1)
+scrap_count = len(df[df['action'] == 'ROUTE_TO_SCRAP'])
+total_waste = round(df['material_wasted_pct'].sum(), 1)
+avg_waste = round(df[df['status'] == 'FAIL']['material_wasted_pct'].mean(), 2)
+avg_confidence = round(df[df['status'] == 'FAIL']['confidence'].mean(), 2)
+
+# Daily aggregations
+daily = df.groupby('scan_date').agg(
+ scans=('id', 'count'),
+ fails=('status', lambda x: (x == 'FAIL').sum()),
+ waste=('material_wasted_pct', lambda x: x.sum() / 100.0),
+ avg_waste=('material_wasted_pct', 'mean'),
+).reset_index()
+daily['fail_rate'] = round((daily['fails'] / daily['scans']) * 100, 1)
+daily['scan_date'] = pd.to_datetime(daily['scan_date'])
+
+# ============================================================
+# DASH APP
+# ============================================================
+app = Dash(__name__, suppress_callback_exceptions=True)
+app.title = "Wafer Defect Analytics — Semiconductor QC Dashboard"
+
+card_style = {
+ 'backgroundColor': COLORS['card'], 'border': f"1px solid {COLORS['card_border']}",
+ 'borderRadius': '12px', 'padding': '24px', 'marginBottom': '16px',
+}
+
+kpi_style = {
+ 'backgroundColor': COLORS['card'], 'border': f"1px solid {COLORS['card_border']}",
+ 'borderRadius': '12px', 'padding': '20px', 'textAlign': 'center', 'flex': '1', 'minWidth': '160px',
+}
+
+
+def make_kpi(title, value, subtitle="", color=COLORS['accent']):
+ return html.Div(style=kpi_style, children=[
+ html.P(title, style={'color': COLORS['text_muted'], 'fontSize': '12px', 'marginBottom': '4px', 'fontWeight': '500', 'textTransform': 'uppercase', 'letterSpacing': '1px'}),
+ html.H2(str(value), style={'color': color, 'fontSize': '28px', 'fontWeight': '700', 'margin': '4px 0'}),
+ html.P(subtitle, style={'color': COLORS['text_muted'], 'fontSize': '11px', 'marginTop': '4px'}),
+ ])
+
+
+def chart_layout(title):
+ return dict(
+ template='plotly_dark', paper_bgcolor='rgba(0,0,0,0)', plot_bgcolor='rgba(0,0,0,0)',
+ title=dict(text=title, font=dict(size=16, color=COLORS['text'])),
+ font=dict(color=COLORS['text_muted'], size=12),
+ margin=dict(l=40, r=20, t=50, b=40),
+ legend=dict(bgcolor='rgba(0,0,0,0)'),
+ )
+
+
+# ============================================================
+# FIGURES
+# ============================================================
+
+# 1. Defect type distribution (pie) — YOLO predictions
+defect_counts = df[df['status'] == 'FAIL']['defect_type'].value_counts().reset_index()
+defect_counts.columns = ['defect_type', 'count']
+fig_pie = px.pie(defect_counts, names='defect_type', values='count', color='defect_type',
+ color_discrete_map=DEFECT_COLORS, hole=0.45)
+fig_pie.update_layout(**chart_layout('YOLOv8 Predicted Defect Distribution'))
+fig_pie.update_traces(textinfo='label+percent', textfont_size=11)
+
+# 2. Ground truth distribution (pie) — actual labels
+gt_counts = df[df['status'] == 'FAIL']['ground_truth'].value_counts().reset_index()
+gt_counts.columns = ['ground_truth', 'count']
+fig_gt_pie = px.pie(gt_counts.head(15), names='ground_truth', values='count', hole=0.45)
+fig_gt_pie.update_layout(**chart_layout('Ground Truth Label Distribution (Top 15)'))
+fig_gt_pie.update_traces(textinfo='label+percent', textfont_size=10)
+
+# 3. Material waste by defect type (bar)
+waste_by_type = df[df['status'] == 'FAIL'].groupby('defect_type').agg(
+ total_waste=('material_wasted_pct', lambda x: x.sum() / 100.0), count=('id', 'count'),
+).reset_index().sort_values('total_waste', ascending=True)
+
+fig_waste_bar = go.Figure()
+fig_waste_bar.add_trace(go.Bar(
+ y=waste_by_type['defect_type'], x=waste_by_type['total_waste'], orientation='h',
+ marker_color=[DEFECT_COLORS.get(d, '#6b7280') for d in waste_by_type['defect_type']],
+ text=[f"{v:.1f}" for v in waste_by_type['total_waste']], textposition='outside',
+))
+fig_waste_bar.update_layout(**chart_layout('Total Material Waste by Predicted Defect'))
+fig_waste_bar.update_xaxes(title_text='Equivalent Lost Wafers')
+
+# 4. Daily fail rate trend
+fig_trend = go.Figure()
+fig_trend.add_trace(go.Scatter(
+ x=daily['scan_date'], y=daily['fail_rate'], mode='lines+markers',
+ line=dict(color=COLORS['danger'], width=2), marker=dict(size=5),
+ name='Fail Rate %', fill='tozeroy', fillcolor='rgba(239, 68, 68, 0.1)',
+))
+fig_trend.update_layout(**chart_layout('Daily Defect Rate Over Time'))
+fig_trend.update_yaxes(title_text='Fail Rate %', range=[0, 105])
+
+# 5. Daily waste trend
+fig_waste_trend = go.Figure()
+fig_waste_trend.add_trace(go.Scatter(
+ x=daily['scan_date'], y=daily['waste'], mode='lines+markers',
+ line=dict(color=COLORS['warning'], width=2), marker=dict(size=5),
+ name='Lost Wafers', fill='tozeroy', fillcolor='rgba(245, 158, 11, 0.1)',
+))
+fig_waste_trend.update_layout(**chart_layout('Daily Material Waste Over Time'))
+fig_waste_trend.update_yaxes(title_text='Total Lost Wafers')
+
+# 6. Action breakdown
+action_counts = df['action'].value_counts().reset_index()
+action_counts.columns = ['action', 'count']
+action_colors = {'ROUTE_TO_SCRAP': COLORS['danger'], 'MOVE_TO_MICRO_STAGE': COLORS['warning'], 'ROUTE_TO_ASSEMBLY': COLORS['accent3']}
+fig_action = px.bar(action_counts, x='action', y='count', color='action',
+ color_discrete_map=action_colors, text='count')
+fig_action.update_layout(**chart_layout('Wafer Routing Actions'))
+fig_action.update_traces(textposition='outside')
+
+# 7. Feature importance
+fig_importance = go.Figure()
+if model_pkg:
+ imp = model_pkg['metrics']['importances']
+ imp_df = pd.DataFrame({'feature': list(imp.keys()), 'importance': list(imp.values())})
+ imp_df = imp_df.sort_values('importance', ascending=True).tail(10)
+ fig_importance.add_trace(go.Bar(
+ y=imp_df['feature'], x=imp_df['importance'], orientation='h',
+ marker_color=COLORS['accent2'],
+ text=[f"{v:.3f}" for v in imp_df['importance']], textposition='outside',
+ ))
+fig_importance.update_layout(**chart_layout('Top 10 Prediction Features'))
+
+# ============================================================
+# LAYOUT
+# ============================================================
+app.layout = html.Div(style={
+ 'backgroundColor': COLORS['bg'], 'minHeight': '100vh',
+ 'fontFamily': "'Inter', -apple-system, BlinkMacSystemFont, sans-serif",
+ 'color': COLORS['text'], 'padding': '24px 32px',
+}, children=[
+
+ # HEADER
+ html.Div(style={'marginBottom': '32px', 'borderBottom': f"1px solid {COLORS['card_border']}", 'paddingBottom': '20px'}, children=[
+ html.Div(style={'display': 'flex', 'alignItems': 'center', 'gap': '12px'}, children=[
+ html.Div(style={'width': '12px', 'height': '12px', 'borderRadius': '50%',
+ 'backgroundColor': COLORS['accent3'], 'boxShadow': f"0 0 8px {COLORS['accent3']}"}),
+ html.H1("Wafer Defect Analytics", style={
+ 'margin': '0', 'fontSize': '28px', 'fontWeight': '700',
+ 'background': f"linear-gradient(135deg, {COLORS['accent']}, {COLORS['accent2']})",
+ 'WebkitBackgroundClip': 'text', 'WebkitTextFillColor': 'transparent',
+ }),
+ ]),
+ html.P("Mixed-type Wafer Defect Dataset — Material Waste Dashboard",
+ style={'color': COLORS['text_muted'], 'marginTop': '4px', 'fontSize': '14px'}),
+ ]),
+
+ # TABS
+ dcc.Tabs(id='tabs', value='tab-waste', style={'marginBottom': '24px'}, children=[
+ dcc.Tab(label='📊 Historical Waste Analysis', value='tab-waste', style={
+ 'backgroundColor': COLORS['card'], 'color': COLORS['text_muted'],
+ 'border': f"1px solid {COLORS['card_border']}", 'borderRadius': '8px 8px 0 0',
+ 'padding': '12px 24px', 'fontWeight': '600',
+ }, selected_style={
+ 'backgroundColor': COLORS['accent2'], 'color': '#fff',
+ 'border': f"1px solid {COLORS['accent2']}", 'borderRadius': '8px 8px 0 0',
+ 'padding': '12px 24px', 'fontWeight': '600',
+ }),
+ dcc.Tab(label='🔮 Material Prediction', value='tab-predict', style={
+ 'backgroundColor': COLORS['card'], 'color': COLORS['text_muted'],
+ 'border': f"1px solid {COLORS['card_border']}", 'borderRadius': '8px 8px 0 0',
+ 'padding': '12px 24px', 'fontWeight': '600',
+ }, selected_style={
+ 'backgroundColor': COLORS['accent2'], 'color': '#fff',
+ 'border': f"1px solid {COLORS['accent2']}", 'borderRadius': '8px 8px 0 0',
+ 'padding': '12px 24px', 'fontWeight': '600',
+ }),
+ ]),
+
+ html.Div(id='tab-content'),
+])
+
+
+# ============================================================
+# CALLBACKS
+# ============================================================
+@callback(Output('tab-content', 'children'), Input('tabs', 'value'))
+def render_tab(tab):
+ if tab == 'tab-waste':
+ return html.Div([
+ # KPI Row
+ html.Div(style={'display': 'flex', 'gap': '12px', 'flexWrap': 'wrap', 'marginBottom': '24px'}, children=[
+ make_kpi("Total Scans", f"{total_scans:,}", "wafers inspected", COLORS['accent']),
+ make_kpi("Pass Rate", f"{pass_rate}%", f"{pass_count:,} passed", COLORS['accent3']),
+ make_kpi("Fail Rate", f"{100-pass_rate}%", f"{fail_count:,} defective", COLORS['danger']),
+ make_kpi("Scrapped", f"{scrap_count:,}", "routed to scrap", COLORS['warning']),
+ make_kpi("Avg Waste/Wafer", f"{avg_waste}%", "per defective wafer", COLORS['danger']),
+ make_kpi("Avg Confidence", f"{avg_confidence}", "model certainty", COLORS['accent3']),
+ ]),
+
+ # Charts Row 1: YOLO predictions vs Ground Truth
+ html.Div(style={'display': 'grid', 'gridTemplateColumns': '1fr 1fr', 'gap': '16px', 'marginBottom': '16px'}, children=[
+ html.Div(style=card_style, children=[dcc.Graph(figure=fig_pie, config={'displayModeBar': False})]),
+ html.Div(style=card_style, children=[dcc.Graph(figure=fig_gt_pie, config={'displayModeBar': False})]),
+ ]),
+
+ # Charts Row 2
+ html.Div(style={'display': 'grid', 'gridTemplateColumns': '1fr 1fr', 'gap': '16px', 'marginBottom': '16px'}, children=[
+ html.Div(style=card_style, children=[dcc.Graph(figure=fig_waste_bar, config={'displayModeBar': False})]),
+ html.Div(style=card_style, children=[dcc.Graph(figure=fig_action, config={'displayModeBar': False})]),
+ ]),
+
+ # Trend charts
+ html.Div(style={'display': 'grid', 'gridTemplateColumns': '1fr 1fr', 'gap': '16px'}, children=[
+ html.Div(style=card_style, children=[dcc.Graph(figure=fig_trend, config={'displayModeBar': False})]),
+ html.Div(style=card_style, children=[dcc.Graph(figure=fig_waste_trend, config={'displayModeBar': False})]),
+ ]),
+ ])
+
+ elif tab == 'tab-predict':
+ model_status = "✅ Model loaded" if model_pkg else "❌ No model found"
+ model_metrics = ""
+ if model_pkg:
+ m = model_pkg['metrics']
+ model_metrics = f"R² = {m['r2']:.4f} | MAE = {m['mae']:.2f}%"
+
+ return html.Div([
+ html.Div(style={**card_style, 'display': 'flex', 'justifyContent': 'space-between', 'alignItems': 'center'}, children=[
+ html.Div([
+ html.H3("Prediction Model", style={'margin': '0 0 4px 0', 'fontSize': '18px'}),
+ html.P(model_status, style={'margin': '0', 'color': COLORS['accent3'] if model_pkg else COLORS['danger']}),
+ ]),
+ html.P(model_metrics, style={'color': COLORS['text_muted'], 'fontFamily': 'monospace'}),
+ ]),
+
+ html.Div(style=card_style, children=[
+ html.H3("Forecast Parameters", style={'marginTop': '0', 'fontSize': '18px', 'marginBottom': '20px'}),
+ html.Div(style={'display': 'grid', 'gridTemplateColumns': '1fr 1fr', 'gap': '24px'}, children=[
+ html.Div([
+ html.Label("Expected Daily Production (wafers)", style={'color': COLORS['text_muted'], 'fontSize': '13px'}),
+ dcc.Slider(id='slider-scans', min=100, max=2000, step=50, value=1300,
+ marks={100: '100', 500: '500', 1000: '1000', 1500: '1500', 2000: '2000'},
+ tooltip={"placement": "bottom", "always_visible": True}),
+ ]),
+ html.Div([
+ html.Label("Expected Defect Rate (%)", style={'color': COLORS['text_muted'], 'fontSize': '13px'}),
+ dcc.Slider(id='slider-fail-rate', min=0, max=100, step=5, value=97,
+ marks={0: '0%', 25: '25%', 50: '50%', 75: '75%', 100: '100%'},
+ tooltip={"placement": "bottom", "always_visible": True}),
+ ]),
+ ]),
+ html.Br(),
+ html.Button("🔮 Predict Material Needs", id='btn-predict', n_clicks=0, style={
+ 'backgroundColor': COLORS['accent2'], 'color': '#fff', 'border': 'none',
+ 'borderRadius': '8px', 'padding': '12px 32px', 'fontSize': '15px',
+ 'fontWeight': '600', 'cursor': 'pointer', 'width': '100%',
+ }),
+ ]),
+
+ html.Div(id='prediction-result'),
+
+ html.Div(style=card_style, children=[
+ dcc.Graph(figure=fig_importance, config={'displayModeBar': False}),
+ ]),
+ ])
+
+
+@callback(
+ Output('prediction-result', 'children'),
+ Input('btn-predict', 'n_clicks'),
+ State('slider-scans', 'value'),
+ State('slider-fail-rate', 'value'),
+ prevent_initial_call=True,
+)
+def predict(n_clicks, n_scans, fail_pct):
+ if not model_pkg:
+ return html.Div(style={**card_style, 'borderColor': COLORS['danger']}, children=[
+ html.P("❌ No model loaded.", style={'color': COLORS['danger']}),
+ ])
+
+ from material_predictor import predict_material_needs
+ model = model_pkg['model']
+ feat_cols = model_pkg['feature_cols']
+
+ fail_df = df[df['status'] == 'FAIL']
+ dist = fail_df['defect_type'].value_counts(normalize=True).to_dict()
+
+ pred = predict_material_needs(model, feat_cols, n_scans, fail_pct / 100.0, dist)
+
+ return html.Div(style={
+ **card_style, 'borderColor': COLORS['accent'],
+ 'background': f"linear-gradient(135deg, {COLORS['card']}, #1a1040)",
+ }, children=[
+ html.Div(style={'display': 'flex', 'justifyContent': 'space-around', 'flexWrap': 'wrap', 'gap': '16px'}, children=[
+ make_kpi("Daily Production", f"{n_scans}", "wafers", COLORS['accent']),
+ make_kpi("Expected Defect Rate", f"{fail_pct}%", f"~{int(n_scans * fail_pct / 100)} defective", COLORS['danger']),
+ make_kpi("Avg Waste/Wafer", f"{pred['avg_waste_per_wafer']:.1f}%", "per defective wafer loss", COLORS['warning']),
+ make_kpi("Total Daily Waste", f"{pred['total_daily_waste']:.1f} wafers", "total estimated loss", COLORS['danger']),
+ ]),
+ ])
+
+
+if __name__ == '__main__':
+ print("\n" + "=" * 60)
+ print(" WAFER DEFECT ANALYTICS DASHBOARD")
+ print(f" Data: {total_scans:,} scans | {fail_count:,} defects | {pass_count:,} pass")
+ print(f" Model: {'Loaded ✅' if model_pkg else 'Not found ❌'}")
+ print("=" * 60)
+ print(f"\n 🌐 Open: http://127.0.0.1:8050\n")
+ app.run(debug=True, port=8050)
diff --git a/middleware/database.py b/middleware/database.py
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/middleware/material_model.pkl b/middleware/material_model.pkl
new file mode 100644
index 0000000000000000000000000000000000000000..c4483d7c030195453ad78e6a9c2745a7a6c68bb2
--- /dev/null
+++ b/middleware/material_model.pkl
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:47b26c94a138551231a0bb2a9a7b2dfe918c278224048c4fa12a22feacf6a0b2
+size 240685
diff --git a/middleware/material_predictor.py b/middleware/material_predictor.py
new file mode 100644
index 0000000000000000000000000000000000000000..94dd14543aa7136d1593e5f602a052678826a03a
--- /dev/null
+++ b/middleware/material_predictor.py
@@ -0,0 +1,229 @@
+"""
+Material Predictor — Phase 5
+Trains a Random Forest model on historical wafer scan data to predict
+material waste percentage for future production batches.
+"""
+
+import os
+import pickle
+import sqlite3
+import numpy as np
+import pandas as pd
+from sklearn.ensemble import RandomForestRegressor
+from sklearn.model_selection import train_test_split
+from sklearn.metrics import mean_absolute_error, r2_score
+
+# --- CONFIGURATION ---
+DB_PATH = os.path.join(os.path.dirname(__file__), 'wafer_control.db')
+MODEL_PATH = os.path.join(os.path.dirname(__file__), 'material_model.pkl')
+
+# Defect types for feature engineering
+DEFECT_TYPES = ['Center', 'Donut', 'Edge-Loc', 'Edge-Ring', 'Loc', 'Random', 'Scratch', 'Near-full', 'None', 'Undetected']
+
+
+def load_data():
+ """Load wafer logs from the SQLite database into a DataFrame."""
+ conn = sqlite3.connect(DB_PATH)
+ df = pd.read_sql_query("SELECT * FROM wafer_logs", conn)
+ conn.close()
+ df['scan_time'] = pd.to_datetime(df['scan_time'])
+ return df
+
+
+def engineer_features(df):
+ """
+ Build daily-aggregated features from raw scan logs.
+ Each row = one day of production with aggregated metrics.
+ """
+ df['scan_date'] = df['scan_time'].dt.date
+ df['is_fail'] = (df['status'] == 'FAIL').astype(int)
+ df['is_scrap'] = (df['action'] == 'ROUTE_TO_SCRAP').astype(int)
+
+ # One-hot encode defect types per scan
+ for defect in DEFECT_TYPES:
+ col_name = f'is_{defect.lower().replace("-", "_")}'
+ df[col_name] = (df['defect_type'] == defect).astype(int)
+
+ # --- Aggregate by day ---
+ daily = df.groupby('scan_date').agg(
+ total_scans=('id', 'count'),
+ fail_count=('is_fail', 'sum'),
+ scrap_count=('is_scrap', 'sum'),
+ avg_confidence=('confidence', 'mean'),
+ avg_defect_area=('defect_area_px', 'mean'),
+ max_defect_area=('defect_area_px', 'max'),
+ total_waste_pct=('material_wasted_pct', 'sum'),
+ avg_waste_pct=('material_wasted_pct', 'mean'),
+ # Defect type counts per day
+ center_count=('is_center', 'sum'),
+ donut_count=('is_donut', 'sum'),
+ edge_loc_count=('is_edge_loc', 'sum'),
+ edge_ring_count=('is_edge_ring', 'sum'),
+ loc_count=('is_loc', 'sum'),
+ random_count=('is_random', 'sum'),
+ scratch_count=('is_scratch', 'sum'),
+ near_full_count=('is_near_full', 'sum'),
+ pass_count=('is_none', 'sum'),
+ ).reset_index()
+
+ # --- Compute waste among defective wafers only ---
+ defective_daily = df[df['status'] == 'FAIL'].groupby('scan_date').agg(
+ avg_waste_defective=('material_wasted_pct', 'mean'),
+ avg_defect_area_fail=('defect_area_px', 'mean'),
+ avg_confidence_fail=('confidence', 'mean'),
+ ).reset_index()
+
+ daily = daily.merge(defective_daily, on='scan_date', how='left')
+ daily['avg_waste_defective'] = daily['avg_waste_defective'].fillna(0)
+ daily['avg_defect_area_fail'] = daily['avg_defect_area_fail'].fillna(0)
+ daily['avg_confidence_fail'] = daily['avg_confidence_fail'].fillna(0)
+
+ # Derived ratios
+ daily['fail_rate'] = daily['fail_count'] / daily['total_scans']
+ daily['scrap_rate'] = daily['scrap_count'] / daily['total_scans']
+
+ # Time features
+ daily['scan_date'] = pd.to_datetime(daily['scan_date'])
+ daily['day_of_week'] = daily['scan_date'].dt.dayofweek
+ daily['day_index'] = (daily['scan_date'] - daily['scan_date'].min()).dt.days
+
+ return daily
+
+
+def train_model(daily):
+ """Train a Random Forest to predict avg material waste among defective wafers."""
+ feature_cols = [
+ 'total_scans', 'fail_count', 'scrap_count', 'avg_confidence',
+ 'avg_defect_area', 'max_defect_area', 'fail_rate', 'scrap_rate',
+ 'avg_defect_area_fail', 'avg_confidence_fail',
+ 'center_count', 'donut_count', 'edge_loc_count', 'edge_ring_count',
+ 'loc_count', 'random_count', 'scratch_count', 'near_full_count',
+ 'pass_count', 'day_of_week', 'day_index'
+ ]
+
+ target = 'avg_waste_defective'
+
+ X = daily[feature_cols]
+ y = daily[target]
+
+ X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42)
+
+ model = RandomForestRegressor(
+ n_estimators=100,
+ max_depth=10,
+ random_state=42,
+ n_jobs=-1
+ )
+ model.fit(X_train, y_train)
+
+ # Evaluate
+ y_pred = model.predict(X_test)
+ mae = mean_absolute_error(y_test, y_pred)
+ r2 = r2_score(y_test, y_pred)
+
+ print(f"\n{'=' * 50}")
+ print(f" MODEL EVALUATION (target: avg waste % per wafer)")
+ print(f" Mean Absolute Error: {mae:.2f}%")
+ print(f" R² Score: {r2:.4f}")
+ print(f"{'=' * 50}")
+
+ # Feature importance
+ importance = pd.Series(model.feature_importances_, index=feature_cols).sort_values(ascending=False)
+ print(f"\n Top 5 Feature Importances:")
+ for feat, imp in importance.head(5).items():
+ print(f" {feat:25s} {imp:.4f}")
+
+ return model, feature_cols, {'mae': mae, 'r2': r2, 'importances': importance.to_dict()}
+
+
+def predict_material_needs(model, feature_cols, total_scans, fail_rate, defect_distribution):
+ """
+ Predict material waste for a hypothetical future production day.
+ """
+ fail_count = int(total_scans * fail_rate)
+ pass_count = total_scans - fail_count
+
+ features = {
+ 'total_scans': total_scans,
+ 'fail_count': fail_count,
+ 'scrap_count': int(fail_count * defect_distribution.get('Center', 0) +
+ fail_count * defect_distribution.get('Near-full', 0)),
+ 'avg_confidence': 0.95,
+ 'avg_defect_area': 1500,
+ 'max_defect_area': 2704,
+ 'fail_rate': fail_rate,
+ 'scrap_rate': defect_distribution.get('Center', 0) + defect_distribution.get('Near-full', 0),
+ 'avg_defect_area_fail': 1500,
+ 'avg_confidence_fail': 0.85,
+ 'center_count': int(fail_count * defect_distribution.get('Center', 0)),
+ 'donut_count': int(fail_count * defect_distribution.get('Donut', 0)),
+ 'edge_loc_count': int(fail_count * defect_distribution.get('Edge-Loc', 0)),
+ 'edge_ring_count': int(fail_count * defect_distribution.get('Edge-Ring', 0)),
+ 'loc_count': int(fail_count * defect_distribution.get('Loc', 0)),
+ 'random_count': int(fail_count * defect_distribution.get('Random', 0)),
+ 'scratch_count': int(fail_count * defect_distribution.get('Scratch', 0)),
+ 'near_full_count': int(fail_count * defect_distribution.get('Near-full', 0)),
+ 'pass_count': pass_count,
+ 'day_of_week': 2,
+ 'day_index': 30,
+ }
+
+ X = pd.DataFrame([features])[feature_cols]
+ avg_waste_per_wafer = model.predict(X)[0]
+ total_waste_wafers = (avg_waste_per_wafer / 100.0) * fail_count
+ return {
+ 'avg_waste_per_wafer': round(avg_waste_per_wafer, 2),
+ 'total_daily_waste': round(total_waste_wafers, 1),
+ 'total_scans': total_scans,
+ 'fail_rate': fail_rate,
+ }
+
+
+if __name__ == '__main__':
+ print("=" * 50)
+ print(" MATERIAL WASTE PREDICTOR — Training")
+ print("=" * 50)
+
+ # 1. Load and engineer features
+ print("\nLoading scan data...")
+ raw_df = load_data()
+ print(f" Total records: {len(raw_df)}")
+ print(f" PASS: {len(raw_df[raw_df['status'] == 'PASS'])}")
+ print(f" FAIL: {len(raw_df[raw_df['status'] == 'FAIL'])}")
+
+ print("Engineering daily features...")
+ daily_df = engineer_features(raw_df)
+ print(f" Training days: {len(daily_df)}")
+
+ # 2. Train
+ print("\nTraining Random Forest model...")
+ trained_model, feat_cols, metrics = train_model(daily_df)
+
+ # 3. Save
+ model_package = {
+ 'model': trained_model,
+ 'feature_cols': feat_cols,
+ 'metrics': metrics,
+ }
+ with open(MODEL_PATH, 'wb') as f:
+ pickle.dump(model_package, f)
+ print(f"\nModel saved to: {MODEL_PATH}")
+
+ # 4. Demo prediction
+ print(f"\n{'=' * 50}")
+ print(" DEMO PREDICTION")
+ print(f"{'=' * 50}")
+
+ demo_distribution = {
+ 'Center': 0.15, 'Edge-Ring': 0.37, 'Edge-Loc': 0.06,
+ 'Donut': 0.23, 'Random': 0.03, 'Scratch': 0.03,
+ 'Loc': 0.10, 'Near-full': 0.01
+ }
+
+ pred = predict_material_needs(trained_model, feat_cols,
+ total_scans=1300, fail_rate=0.97,
+ defect_distribution=demo_distribution)
+ print(f" Scenario: 1,300 wafers/day, 97% defect rate")
+ print(f" Predicted avg waste per wafer: {pred['avg_waste_per_wafer']:.2f}%")
+ print(f" Predicted total daily waste: {pred['total_daily_waste']:.1f} equivalent wafers")
+ print(f"{'=' * 50}")
diff --git a/middleware/robot_controller.py b/middleware/robot_controller.py
new file mode 100644
index 0000000000000000000000000000000000000000..9eb0b7dc2ff668d4b46bdbfbd7502739cfda7b2a
--- /dev/null
+++ b/middleware/robot_controller.py
@@ -0,0 +1,278 @@
+"""
+Robotic Control Middleware — Full Production Scan
+Phase 1: Loads Mixed-type Wafer Defect Dataset, runs YOLOv8 on defective wafers.
+Phase 2: Loads ALL passed wafers from WM-811K dataset (direct insert, no YOLO needed).
+"""
+
+import os
+import sys
+import pickle
+import sqlite3
+import random
+import cv2
+import time
+import numpy as np
+from datetime import datetime, timedelta
+from ultralytics import YOLO
+
+# Fix for old Pandas architecture in WM-811K pickle
+import pandas.core.indexes
+sys.modules['pandas.indexes'] = pandas.core.indexes
+import pandas as pd
+
+# --- CONFIGURATION ---
+NPZ_PATH = os.path.expanduser(
+ '~/.cache/kagglehub/datasets/co1d7era/mixedtype-wafer-defect-datasets/versions/4/Wafer_Map_Datasets.npz'
+)
+WM811K_PATH = os.path.expanduser(
+ '~/.cache/kagglehub/datasets/qingyi/wm811k-wafer-map/versions/1/LSWMD.pkl'
+)
+MODEL_PATH = 'middleware/best.pt'
+DB_PATH = os.path.join('middleware', 'wafer_control.db')
+
+# Defect names matching the 8-dim one-hot encoding order in the dataset
+DEFECT_NAMES = ['Center', 'Donut', 'Edge-Loc', 'Edge-Ring', 'Loc', 'Near-full', 'Random', 'Scratch']
+
+
+def setup_database():
+ """Creates a fresh wafer_logs table with ground_truth column."""
+ os.makedirs('middleware', exist_ok=True)
+ conn = sqlite3.connect(DB_PATH)
+ cursor = conn.cursor()
+
+ cursor.execute('DROP TABLE IF EXISTS wafer_logs')
+ cursor.execute('''
+ CREATE TABLE wafer_logs (
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
+ wafer_id TEXT,
+ batch_id TEXT,
+ scan_time TEXT,
+ status TEXT,
+ ground_truth TEXT,
+ defect_type TEXT,
+ action TEXT,
+ confidence REAL,
+ roi_coordinates TEXT,
+ defect_area_px INTEGER,
+ material_wasted_pct REAL
+ )
+ ''')
+ conn.commit()
+ return conn
+
+
+def decode_label(one_hot):
+ """Convert 8-dim one-hot label to human-readable defect string."""
+ active = np.where(one_hot == 1)[0]
+ if len(active) == 0:
+ return 'Normal'
+ return '+'.join([DEFECT_NAMES[i] for i in active])
+
+
+def wafer_to_image(wafer_map):
+ """Convert a 52x52 wafer map array to a 3-channel BGR image for YOLOv8."""
+ img = np.zeros(wafer_map.shape, dtype=np.uint8)
+ img[wafer_map == 1] = 127 # Normal die → gray
+ img[wafer_map == 2] = 255 # Broken die → white
+ img[wafer_map == 3] = 255 # Treat 3 as defect too (rare edge artifact)
+ # YOLOv8 expects 3-channel (BGR) images
+ img_bgr = cv2.cvtColor(img, cv2.COLOR_GRAY2BGR)
+ return img_bgr
+
+
+def compute_defect_area(coords):
+ """Calculate bounding box area in pixels from [x1, y1, x2, y2]."""
+ if not coords or len(coords) != 4:
+ return 0
+ x1, y1, x2, y2 = coords
+ return max(0, (x2 - x1) * (y2 - y1))
+
+
+def run_production_scan(conn, model, wafer_maps, labels, batch_id, start_time):
+ """Process all wafers: YOLO inference on defective, direct insert for normals."""
+ cursor = conn.cursor()
+ total = len(wafer_maps)
+
+ for i in range(total):
+ wafer_id = f"wafer_{i}"
+ ground_truth = decode_label(labels[i])
+
+ # Distribute realistic timestamps with Gaussian noise to create natural defect spikes
+ base_day = (i / total) * 30
+ noisy_day = base_day + random.gauss(0, 4) # High variance for defects
+ days_offset = int(max(0, min(29, noisy_day)))
+ scan_time = start_time + timedelta(
+ days=days_offset,
+ seconds=random.randint(0, 68)
+ )
+ scan_time_str = scan_time.strftime("%Y-%m-%d %H:%M:%S")
+
+ if ground_truth == 'Normal':
+ # PASS wafer — no YOLO needed
+ cursor.execute('''
+ INSERT INTO wafer_logs
+ (wafer_id, batch_id, scan_time, status, ground_truth, defect_type, action, confidence, roi_coordinates, defect_area_px, material_wasted_pct)
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
+ ''', (wafer_id, batch_id, scan_time_str, "PASS", ground_truth, "None", "ROUTE_TO_ASSEMBLY", 1.0, "[]", 0, 0.0))
+ else:
+ # Defective wafer — convert to image and run YOLO
+ img = wafer_to_image(wafer_maps[i])
+ wafer_area_px = img.shape[0] * img.shape[1] # 52*52 = 2704
+
+ results = model.predict(source=img, conf=0.25, verbose=False)
+ boxes = results[0].boxes
+
+ if len(boxes) > 0:
+ box = boxes[0]
+ class_id = int(box.cls[0].item())
+ defect_type = model.names[class_id]
+ confidence = round(box.conf[0].item(), 2)
+
+ x1, y1, x2, y2 = [int(x) for x in box.xyxy[0].tolist()]
+ coords = [x1, y1, x2, y2]
+
+ status = "FAIL"
+ action = "ROUTE_TO_SCRAP" if defect_type in ["Center", "Near-full"] else "MOVE_TO_MICRO_STAGE"
+ else:
+ # YOLO didn't detect anything (could be mixed pattern it can't see)
+ status = "FAIL"
+ defect_type = "Undetected"
+ action = "MOVE_TO_MICRO_STAGE"
+ confidence = 0.0
+ coords = []
+
+ defect_area = compute_defect_area(coords)
+ material_wasted_pct = round((defect_area / wafer_area_px) * 100, 2) if defect_area > 0 else 0.0
+
+ cursor.execute('''
+ INSERT INTO wafer_logs
+ (wafer_id, batch_id, scan_time, status, ground_truth, defect_type, action, confidence, roi_coordinates, defect_area_px, material_wasted_pct)
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
+ ''', (wafer_id, batch_id, scan_time_str, status, ground_truth, defect_type, action, confidence, str(coords), defect_area, material_wasted_pct))
+
+ # Commit in batches
+ if (i + 1) % 500 == 0:
+ conn.commit()
+ print(f" Processed {i + 1}/{total} wafers...")
+
+ conn.commit()
+
+
+def insert_wm811k_passed(conn, batch_id, start_time):
+ """Load ALL passed wafers from WM-811K and insert directly into DB."""
+ print("Loading WM-811K dataset...")
+ with open(WM811K_PATH, 'rb') as f:
+ wm_df = pickle.load(f, encoding='latin1')
+
+ wm_df['failure_class'] = wm_df['failureType'].apply(lambda x: x[0][0] if len(x) > 0 else 'None')
+ passed = wm_df[(wm_df['failure_class'] == 'None') | (wm_df['failure_class'] == 'none')]
+ passed = passed[passed['waferMap'].apply(lambda x: isinstance(x, np.ndarray) and x.size > 0)]
+
+ total = len(passed)
+ print(f" Found {total:,} passed wafers in WM-811K")
+ print(f" Inserting all into database...")
+
+ cursor = conn.cursor()
+ rows = []
+ for i, (index, row) in enumerate(passed.iterrows()):
+ # Stable production schedule with low variance for normal wafers
+ base_day = (i / total) * 30
+ noisy_day = base_day + random.gauss(0, 0.5)
+ days_offset = int(max(0, min(29, noisy_day)))
+ scan_time = start_time + timedelta(
+ days=days_offset,
+ seconds=random.randint(0, 3)
+ )
+ rows.append((
+ f"wm811k_{index}", batch_id, scan_time.strftime("%Y-%m-%d %H:%M:%S"),
+ "PASS", "Normal", "None", "ROUTE_TO_ASSEMBLY", 1.0, "[]", 0, 0.0
+ ))
+
+ if len(rows) >= 10000:
+ cursor.executemany('''
+ INSERT INTO wafer_logs
+ (wafer_id, batch_id, scan_time, status, ground_truth, defect_type, action, confidence, roi_coordinates, defect_area_px, material_wasted_pct)
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
+ ''', rows)
+ conn.commit()
+ rows = []
+ print(f" Inserted {i + 1:,}/{total:,} passed wafers...")
+
+ if rows:
+ cursor.executemany('''
+ INSERT INTO wafer_logs
+ (wafer_id, batch_id, scan_time, status, ground_truth, defect_type, action, confidence, roi_coordinates, defect_area_px, material_wasted_pct)
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
+ ''', rows)
+ conn.commit()
+
+ print(f" Done! Inserted {total:,} passed wafers.")
+ return total
+
+
+if __name__ == '__main__':
+ print("=" * 60)
+ print(" ROBOTIC CONTROL MIDDLEWARE — HYBRID PRODUCTION SCAN")
+ print("=" * 60)
+
+ # 1. Load Mixed-type dataset
+ print("\nPhase 1: Loading Mixed-type Wafer Defect Dataset...")
+ data = np.load(NPZ_PATH)
+ X = data['arr_0']
+ Y = data['arr_1']
+
+ normals_mixed = sum(1 for y in Y if np.sum(y) == 0)
+ defective = len(X) - normals_mixed
+
+ print(f" Mixed-type: {len(X):,} total ({defective:,} defective + {normals_mixed:,} normal)")
+ print(f" WM-811K: ~786K passed wafers")
+
+ # 2. Setup
+ db_connection = setup_database()
+ wafer_model = YOLO(MODEL_PATH)
+
+ batch_id = f"BATCH_{datetime.now().strftime('%Y%m%d_%H%M%S')}"
+ start_time = datetime.now() - timedelta(days=30)
+
+ print(f"\n Batch ID: {batch_id}")
+ print(f" Scan window: {start_time.strftime('%Y-%m-%d')} → {datetime.now().strftime('%Y-%m-%d')}")
+
+ try:
+ # PHASE 1: YOLOv8 on Mixed-type defective wafers
+ print(f"\n{'=' * 60}")
+ print(f" PHASE 1: YOLOv8 Inference ({len(X):,} Mixed-type wafers)")
+ print(f"{'=' * 60}\n")
+
+ t0 = time.time()
+ run_production_scan(db_connection, wafer_model, X, Y, batch_id, start_time)
+ t1 = time.time()
+ print(f"\n Phase 1 complete: {t1 - t0:.1f}s")
+
+ # PHASE 2: All passed wafers from WM-811K
+ print(f"\n{'=' * 60}")
+ print(f" PHASE 2: WM-811K Passed Wafers (all ~786K)")
+ print(f"{'=' * 60}\n")
+
+ passed_count = insert_wm811k_passed(db_connection, batch_id, start_time)
+ t2 = time.time()
+
+ total = len(X) + passed_count
+ print(f"\n{'=' * 60}")
+ print(f" SCAN COMPLETE")
+ print(f" Defective (Mixed-type): {defective:,}")
+ print(f" Normal (Mixed-type): {normals_mixed:,}")
+ print(f" Passed (WM-811K): {passed_count:,}")
+ print(f" Total records: {total:,}")
+ print(f" Pass rate: {(normals_mixed + passed_count) / total * 100:.1f}%")
+ print(f" Time elapsed: {t2 - t0:.1f}s")
+ print(f" Database: {DB_PATH}")
+ print(f"{'=' * 60}")
+
+ except Exception as e:
+ print(f"\nError during scan: {e}")
+ import traceback
+ traceback.print_exc()
+
+ finally:
+ db_connection.close()
+ print("Database connection closed.")
\ No newline at end of file
diff --git a/middleware/wafer_control.db b/middleware/wafer_control.db
new file mode 100644
index 0000000000000000000000000000000000000000..604611b2c3a8998d57d0501b1e0019d9ceffd7c8
--- /dev/null
+++ b/middleware/wafer_control.db
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:f38b7b11d6ac3e7f8292a4bc48c23de272ae29c0c728ef62f62a42a4bc42406b
+size 90316800
diff --git a/notebooks/01_data_exploration.ipynb b/notebooks/01_data_exploration.ipynb
new file mode 100644
index 0000000000000000000000000000000000000000..3e8117b00e02b50ecdeba92d4fc18ce22f58d198
--- /dev/null
+++ b/notebooks/01_data_exploration.ipynb
@@ -0,0 +1,399 @@
+{
+ "cells": [
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "03c79b89",
+ "metadata": {},
+ "outputs": [],
+ "source": []
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 1,
+ "id": "e517de18",
+ "metadata": {},
+ "outputs": [
+ {
+ "ename": "ModuleNotFoundError",
+ "evalue": "No module named 'kagglehub'",
+ "output_type": "error",
+ "traceback": [
+ "\u001b[1;31m---------------------------------------------------------------------------\u001b[0m",
+ "\u001b[1;31mModuleNotFoundError\u001b[0m Traceback (most recent call last)",
+ "Cell \u001b[1;32mIn[1], line 2\u001b[0m\n\u001b[0;32m 1\u001b[0m \u001b[38;5;28;01mimport\u001b[39;00m\u001b[38;5;250m \u001b[39m\u001b[38;5;21;01msys\u001b[39;00m\n\u001b[1;32m----> 2\u001b[0m \u001b[38;5;28;01mimport\u001b[39;00m\u001b[38;5;250m \u001b[39m\u001b[38;5;21;01mkagglehub\u001b[39;00m\n\u001b[0;32m 3\u001b[0m \u001b[38;5;28;01mimport\u001b[39;00m\u001b[38;5;250m \u001b[39m\u001b[38;5;21;01mpandas\u001b[39;00m\u001b[38;5;250m \u001b[39m\u001b[38;5;28;01mas\u001b[39;00m\u001b[38;5;250m \u001b[39m\u001b[38;5;21;01mpd\u001b[39;00m\n\u001b[0;32m 4\u001b[0m \u001b[38;5;28;01mimport\u001b[39;00m\u001b[38;5;250m \u001b[39m\u001b[38;5;21;01mnumpy\u001b[39;00m\u001b[38;5;250m \u001b[39m\u001b[38;5;28;01mas\u001b[39;00m\u001b[38;5;250m \u001b[39m\u001b[38;5;21;01mnp\u001b[39;00m\n",
+ "\u001b[1;31mModuleNotFoundError\u001b[0m: No module named 'kagglehub'"
+ ]
+ }
+ ],
+ "source": [
+ "import sys\n",
+ "import kagglehub\n",
+ "import pandas as pd\n",
+ "import numpy as np\n",
+ "import matplotlib.pyplot as plt\n",
+ "import os\n",
+ "import pickle"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 2,
+ "id": "f7ac0941",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "import pandas.core.indexes\n",
+ "sys.modules['pandas.indexes'] = pandas.core.indexes"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 3,
+ "id": "82aec67e",
+ "metadata": {},
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "Downloading dataset from Kaggle...\n"
+ ]
+ },
+ {
+ "ename": "NameError",
+ "evalue": "name 'kagglehub' is not defined",
+ "output_type": "error",
+ "traceback": [
+ "\u001b[1;31m---------------------------------------------------------------------------\u001b[0m",
+ "\u001b[1;31mNameError\u001b[0m Traceback (most recent call last)",
+ "Cell \u001b[1;32mIn[3], line 2\u001b[0m\n\u001b[0;32m 1\u001b[0m \u001b[38;5;28mprint\u001b[39m(\u001b[38;5;124m\"\u001b[39m\u001b[38;5;124mDownloading dataset from Kaggle...\u001b[39m\u001b[38;5;124m\"\u001b[39m)\n\u001b[1;32m----> 2\u001b[0m path \u001b[38;5;241m=\u001b[39m \u001b[43mkagglehub\u001b[49m\u001b[38;5;241m.\u001b[39mdataset_download(\u001b[38;5;124m\"\u001b[39m\u001b[38;5;124mqingyi/wm811k-wafer-map\u001b[39m\u001b[38;5;124m\"\u001b[39m)\n\u001b[0;32m 3\u001b[0m \u001b[38;5;28mprint\u001b[39m(\u001b[38;5;124mf\u001b[39m\u001b[38;5;124m\"\u001b[39m\u001b[38;5;124mDataset downloaded to: \u001b[39m\u001b[38;5;132;01m{\u001b[39;00mpath\u001b[38;5;132;01m}\u001b[39;00m\u001b[38;5;124m\"\u001b[39m)\n",
+ "\u001b[1;31mNameError\u001b[0m: name 'kagglehub' is not defined"
+ ]
+ }
+ ],
+ "source": [
+ "print(\"Downloading dataset from Kaggle...\")\n",
+ "path = kagglehub.dataset_download(\"qingyi/wm811k-wafer-map\")\n",
+ "print(f\"Dataset downloaded to: {path}\")"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 9,
+ "id": "4dea4d86",
+ "metadata": {},
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "Loading dataset with latin1 encoding (this might take a minute)...\n"
+ ]
+ },
+ {
+ "name": "stderr",
+ "output_type": "stream",
+ "text": [
+ "/var/folders/_b/4pz7ygss6y7c564mcqz3grmw0000gn/T/ipykernel_3677/1757211552.py:6: VisibleDeprecationWarning: dtype(): align should be passed as Python or NumPy boolean but got `align=0`. Did you mean to pass a tuple to create a subarray type? (Deprecated NumPy 2.4)\n",
+ " df = pickle.load(f, encoding='latin1')\n"
+ ]
+ },
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "Success! Total wafers in dataset: 811457\n"
+ ]
+ }
+ ],
+ "source": [
+ "#Making the file path and loading the file in this venv\n",
+ "file_path = os.path.join(path, 'LSWMD.pkl')\n",
+ "\n",
+ "print(\"Loading dataset with latin1 encoding (this might take a minute)...\")\n",
+ "with open(file_path, 'rb') as f:\n",
+ " df = pickle.load(f, encoding='latin1')\n",
+ "print(f\"Success! Total wafers in dataset: {len(df)}\")"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 14,
+ "id": "bd97a8e5",
+ "metadata": {},
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "Data cleaned! Now you can view it.\n"
+ ]
+ }
+ ],
+ "source": [
+ "# 1. Clean the nested failure type column to create 'failure_class'\n",
+ "df['failure_class'] = df['failureType'].apply(lambda x: x[0][0] if len(x) > 0 else 'None')\n",
+ "\n",
+ "# 2. Filter out the perfect wafers to create the 'defective_wafers' subset\n",
+ "defective_wafers = df[(df['failure_class'] != 'None') & (df['failure_class'] != 'none')]\n",
+ "\n",
+ "print(\"Data cleaned! Now you can view it.\")"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "780e3a68",
+ "metadata": {},
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "Top 5 rows of the dataset:\n"
+ ]
+ },
+ {
+ "data": {
+ "text/html": [
+ "\n",
+ "\n",
+ "
\n",
+ " \n",
+ " \n",
+ " | \n",
+ " waferMap | \n",
+ " dieSize | \n",
+ " failure_class | \n",
+ "
\n",
+ " \n",
+ " \n",
+ " \n",
+ " | 0 | \n",
+ " [[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,... | \n",
+ " 1683.0 | \n",
+ " none | \n",
+ "
\n",
+ " \n",
+ " | 1 | \n",
+ " [[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,... | \n",
+ " 1683.0 | \n",
+ " none | \n",
+ "
\n",
+ " \n",
+ " | 2 | \n",
+ " [[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,... | \n",
+ " 1683.0 | \n",
+ " none | \n",
+ "
\n",
+ " \n",
+ " | 3 | \n",
+ " [[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,... | \n",
+ " 1683.0 | \n",
+ " none | \n",
+ "
\n",
+ " \n",
+ " | 4 | \n",
+ " [[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,... | \n",
+ " 1683.0 | \n",
+ " none | \n",
+ "
\n",
+ " \n",
+ "
\n",
+ "
"
+ ],
+ "text/plain": [
+ " waferMap dieSize failure_class\n",
+ "0 [[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,... 1683.0 none\n",
+ "1 [[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,... 1683.0 none\n",
+ "2 [[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,... 1683.0 none\n",
+ "3 [[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,... 1683.0 none\n",
+ "4 [[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,... 1683.0 none"
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ },
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "\n",
+ "Shape of the first defective wafer array: (45, 48)\n",
+ "\n",
+ "The raw 2D array data (Notice the 0s, 1s, and 2s):\n",
+ "[[0 0 0 ... 0 0 0]\n",
+ " [0 0 0 ... 0 0 0]\n",
+ " [0 0 0 ... 0 0 0]\n",
+ " ...\n",
+ " [0 0 0 ... 0 0 0]\n",
+ " [0 0 0 ... 0 0 0]\n",
+ " [0 0 0 ... 0 0 0]]\n"
+ ]
+ }
+ ],
+ "source": [
+ "#first look at the data\n",
+ "print(\"Top 5 rows of the dataset:\")\n",
+ "display(df[['waferMap', 'dieSize', 'failure_class']].head())\n",
+ "\n",
+ "# Look at exactly what the 2D array looks like for the first defective wafer\n",
+ "first_defect_index = defective_wafers.index[0]\n",
+ "first_defect_array = defective_wafers.loc[first_defect_index, 'waferMap']\n",
+ "\n",
+ "print(\"\\nShape of the first defective wafer array:\", first_defect_array.shape)\n",
+ "print(\"\\nThe raw 2D array data (Notice the 0s, 1s, and 2s):\")\n",
+ "print(first_defect_array)"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 17,
+ "id": "899f308a",
+ "metadata": {},
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "The FULL raw 2D array data:\n",
+ "[[0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 2 1 2 1 1 2 0 0 0 0 0 0 0 0 0\n",
+ " 0 0 0 0 0 0 0 0 0 0 0 0]\n",
+ " [0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 2 1 1 1 1 1 1 1 1 1 1 1 1 1 0 0 0 0 0\n",
+ " 0 0 0 0 0 0 0 0 0 0 0 0]\n",
+ " [0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 0 0\n",
+ " 0 0 0 0 0 0 0 0 0 0 0 0]\n",
+ " [0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 2 1 1 1 1 1 1 1 1\n",
+ " 0 0 0 0 0 0 0 0 0 0 0 0]\n",
+ " [0 0 0 0 0 0 0 0 0 0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1\n",
+ " 1 2 0 0 0 0 0 0 0 0 0 0]\n",
+ " [0 0 0 0 0 0 0 0 0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1\n",
+ " 1 1 1 0 0 0 0 0 0 0 0 0]\n",
+ " [0 0 0 0 0 0 0 2 1 1 1 1 1 2 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 2 2\n",
+ " 1 1 1 2 0 0 0 0 0 0 0 0]\n",
+ " [0 0 0 0 0 0 1 1 1 1 1 2 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1\n",
+ " 1 1 1 1 1 0 0 0 0 0 0 0]\n",
+ " [0 0 0 0 0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 2 1 1 2 1 1 1 1 1\n",
+ " 1 2 1 1 1 1 0 0 0 0 0 0]\n",
+ " [0 0 0 0 0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 2 1 1 1 1 1 1 1 1 1 1 1 1 2 1\n",
+ " 1 1 1 1 1 1 1 0 0 0 0 0]\n",
+ " [0 0 0 0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 2\n",
+ " 1 1 1 1 1 1 2 1 0 0 0 0]\n",
+ " [0 0 0 1 1 1 1 1 1 1 1 1 1 1 1 2 1 1 1 1 1 1 1 1 1 1 1 1 2 2 1 1 1 1 1 1\n",
+ " 1 1 1 1 2 2 1 1 0 0 0 0]\n",
+ " [0 0 0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1\n",
+ " 1 2 2 2 2 1 1 2 1 0 0 0]\n",
+ " [0 0 1 2 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 2 1\n",
+ " 2 2 2 2 2 1 1 1 1 2 0 0]\n",
+ " [0 0 1 1 1 1 1 1 1 1 2 1 1 1 1 1 1 1 1 1 1 2 1 1 2 1 1 1 1 1 1 1 1 1 2 2\n",
+ " 2 2 2 2 2 2 1 1 1 2 0 0]\n",
+ " [0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 2 2\n",
+ " 2 2 2 2 2 2 2 2 2 1 0 0]\n",
+ " [0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 2 2 2 2\n",
+ " 2 2 2 2 1 1 1 1 1 1 1 0]\n",
+ " [0 1 1 1 1 1 2 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 2 2 2\n",
+ " 2 2 2 2 2 2 2 2 1 1 1 0]\n",
+ " [1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 2 1\n",
+ " 2 2 2 2 2 2 2 1 2 1 1 0]\n",
+ " [1 1 1 1 2 1 2 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 2 1 1 1 1 1 1 1\n",
+ " 1 1 1 2 1 1 1 1 1 1 1 2]\n",
+ " [1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 2 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1\n",
+ " 2 1 1 1 1 1 1 1 1 1 1 2]\n",
+ " [1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 2 1 1 1 1 1 1 1 2 1 1 1 1 1 1 1 2 1\n",
+ " 1 1 1 1 1 1 1 1 1 1 1 2]\n",
+ " [2 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 2 2 1 1 1 1 1 1 1 1 1 1 1\n",
+ " 1 1 1 2 1 1 1 2 1 1 2 2]\n",
+ " [1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 2 1 1 1 1 1 1 1 1 1 1 1 1 1 1\n",
+ " 1 1 1 1 1 1 1 2 1 1 1 2]\n",
+ " [2 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 2 1 2 1 1 1 1 1 1 1 1 1\n",
+ " 1 1 1 2 1 1 1 1 1 1 1 2]\n",
+ " [1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1\n",
+ " 1 1 2 1 2 1 1 1 1 1 1 2]\n",
+ " [2 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1\n",
+ " 1 1 1 1 1 1 1 1 1 1 1 0]\n",
+ " [2 1 1 2 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1\n",
+ " 1 1 1 1 1 1 1 1 1 1 2 0]\n",
+ " [0 1 1 1 1 1 1 1 1 1 1 1 1 2 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 2\n",
+ " 1 1 1 1 1 1 1 1 1 1 1 0]\n",
+ " [0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 2 1 1 1 1 1 1 1 1 1 1 1 1\n",
+ " 1 1 1 1 1 1 1 1 1 1 1 0]\n",
+ " [0 1 2 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 2 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1\n",
+ " 1 1 1 1 1 1 1 1 1 1 0 0]\n",
+ " [0 0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 2 1 1 1 1 1 1 1 1 1 1 1 1 1 1\n",
+ " 1 1 1 1 1 1 1 1 1 1 0 0]\n",
+ " [0 0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1\n",
+ " 1 1 1 1 1 1 1 1 1 0 0 0]\n",
+ " [0 0 0 1 1 1 1 2 1 1 1 1 1 1 1 1 1 1 2 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1\n",
+ " 1 1 1 1 1 1 1 1 1 0 0 0]\n",
+ " [0 0 0 0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1\n",
+ " 1 1 1 1 1 1 1 1 0 0 0 0]\n",
+ " [0 0 0 0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1\n",
+ " 1 1 1 1 1 1 1 0 0 0 0 0]\n",
+ " [0 0 0 0 0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 2\n",
+ " 1 1 1 1 1 1 1 0 0 0 0 0]\n",
+ " [0 0 0 0 0 0 2 1 2 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1\n",
+ " 1 1 1 2 1 1 0 0 0 0 0 0]\n",
+ " [0 0 0 0 0 0 0 2 1 1 1 2 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 2 1 1 1 1 1\n",
+ " 1 1 2 1 1 0 0 0 0 0 0 0]\n",
+ " [0 0 0 0 0 0 0 0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1\n",
+ " 1 1 1 1 0 0 0 0 0 0 0 0]\n",
+ " [0 0 0 0 0 0 0 0 0 2 1 1 1 1 1 1 1 1 1 1 1 1 1 2 1 1 1 1 1 1 1 2 1 1 1 1\n",
+ " 2 1 0 0 0 0 0 0 0 0 0 0]\n",
+ " [0 0 0 0 0 0 0 0 0 0 0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 2 1 1 1 1 1 1 1\n",
+ " 2 0 0 0 0 0 0 0 0 0 0 0]\n",
+ " [0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 0\n",
+ " 0 0 0 0 0 0 0 0 0 0 0 0]\n",
+ " [0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 0 0 0\n",
+ " 0 0 0 0 0 0 0 0 0 0 0 0]\n",
+ " [0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 1 1 1 1 1 1 1 1 2 0 0 0 0 0 0\n",
+ " 0 0 0 0 0 0 0 0 0 0 0 0]]\n"
+ ]
+ }
+ ],
+ "source": [
+ "# Force NumPy to print the entire array without truncation\n",
+ "np.set_printoptions(threshold=10000)\n",
+ "\n",
+ "print(\"The FULL raw 2D array data:\")\n",
+ "print(first_defect_array)\n",
+ "\n",
+ "# Reset it back to default right after so future arrays don't break your terminal\n",
+ "np.set_printoptions(threshold=1000)"
+ ]
+ }
+ ],
+ "metadata": {
+ "kernelspec": {
+ "display_name": "wafer_gpu",
+ "language": "python",
+ "name": "python3"
+ },
+ "language_info": {
+ "codemirror_mode": {
+ "name": "ipython",
+ "version": 3
+ },
+ "file_extension": ".py",
+ "mimetype": "text/x-python",
+ "name": "python",
+ "nbconvert_exporter": "python",
+ "pygments_lexer": "ipython3",
+ "version": "3.9.25"
+ }
+ },
+ "nbformat": 4,
+ "nbformat_minor": 5
+}
diff --git a/requirements.txt b/requirements.txt
new file mode 100644
index 0000000000000000000000000000000000000000..a5899cd601205fd033c250f779a737b05a2c2c7d
--- /dev/null
+++ b/requirements.txt
@@ -0,0 +1,16 @@
+# Data handling & EDA
+kagglehub
+pandas
+numpy
+matplotlib
+jupyter
+
+# Computer Vision & AI Tools
+opencv-python
+ultralytics
+scikit-learn
+
+# Web Application & AI Chatbot
+fastapi
+uvicorn[standard]
+google-genai
\ No newline at end of file
diff --git a/src/__init__.py b/src/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/src/batch_inference.py b/src/batch_inference.py
new file mode 100644
index 0000000000000000000000000000000000000000..1ce6e051cba6bf1b0750e37308129600a4e2889e
--- /dev/null
+++ b/src/batch_inference.py
@@ -0,0 +1,19 @@
+from ultralytics import YOLO
+
+def test_all_validation_images():
+ print("Loading your custom-trained wafer brain...")
+ # Pointing to your exact model path from earlier
+ model_path = 'middleware/best.pt'
+ model = YOLO(model_path)
+
+ print("Running inference on ALL validation images...")
+ # Instead of one image, we hand it the entire validation folder
+ val_dir = 'data/yolo_dataset/images/val'
+
+ # The AI will automatically loop through all 5,000+ images!
+ results = model.predict(source=val_dir, save=True, conf=0.25)
+
+ print("\nMassive inference complete! Look in the newest 'predict' folder to see thousands of drawn bounding boxes.")
+
+if __name__ == '__main__':
+ test_all_validation_images()
\ No newline at end of file
diff --git a/src/data_prep.py b/src/data_prep.py
new file mode 100644
index 0000000000000000000000000000000000000000..65a551c5254c7fc0585f3a8150ef54e50ea8d10a
--- /dev/null
+++ b/src/data_prep.py
@@ -0,0 +1,98 @@
+import os
+import sys
+import pickle
+import numpy as np
+import pandas as pd
+import cv2 # OpenCV for generating the images
+
+# --- 1. THE FIX for old Pandas architecture ---
+import pandas.core.indexes
+sys.modules['pandas.indexes'] = pandas.core.indexes
+
+# --- 2. CONFIGURATION ---
+# The path where Kaggle downloaded your dataset
+RAW_DATA_PATH = os.path.expanduser('~/.cache/kagglehub/datasets/qingyi/wm811k-wafer-map/versions/1/LSWMD.pkl')
+
+# Our output folders
+IMAGE_DIR = 'data/processed/images'
+LABEL_DIR = 'data/processed/labels'
+
+os.makedirs(IMAGE_DIR, exist_ok=True)
+os.makedirs(LABEL_DIR, exist_ok=True)
+
+# YOLO needs integers (0-7), not text words for classes
+CLASS_MAP = {
+ 'Center': 0, 'Donut': 1, 'Edge-Loc': 2, 'Edge-Ring': 3,
+ 'Loc': 4, 'Random': 5, 'Scratch': 6, 'Near-full': 7
+}
+
+def process_data():
+ print("Loading dataset (this might take a minute)...")
+ with open(RAW_DATA_PATH, 'rb') as f:
+ df = pickle.load(f, encoding='latin1')
+
+ print("Cleaning data...")
+ df['failure_class'] = df['failureType'].apply(lambda x: x[0][0] if len(x) > 0 else 'None')
+ defective_wafers = df[(df['failure_class'] != 'None') & (df['failure_class'] != 'none')]
+
+ print(f"Found {len(defective_wafers)} defective wafers.")
+ print("Starting conversion on a test batch of 500 wafers...")
+
+#processing the whole set now
+ count = 0
+ for index, row in defective_wafers.iterrows():
+ wafer_map = row['waferMap']
+ defect_class_text = row['failure_class']
+
+ # Skip if the class isn't in our dictionary (safety check)
+ if defect_class_text not in CLASS_MAP:
+ continue
+
+ class_id = CLASS_MAP[defect_class_text]
+
+ # --- 3. BOUNDING BOX MATH ---
+ # Find all Y (row) and X (col) coordinates where value is 2 (the defect)
+ defect_y, defect_x = np.where(wafer_map == 2)
+
+ # If there are no 2s for some reason, skip this wafer
+ if len(defect_x) == 0:
+ continue
+
+ # Find the extreme edges
+ xmin, xmax = np.min(defect_x), np.max(defect_x)
+ ymin, ymax = np.min(defect_y), np.max(defect_y)
+
+ # Total array dimensions
+ img_height, img_width = wafer_map.shape
+
+ # Calculate YOLO format (normalized 0.0 to 1.0)
+ x_center = ((xmin + xmax) / 2.0) / img_width
+ y_center = ((ymin + ymax) / 2.0) / img_height
+ w_yolo = (xmax - xmin) / img_width
+ h_yolo = (ymax - ymin) / img_height
+
+ # --- 4. GENERATE IMAGE ---
+ # Map 0 -> Black (0), 1 -> Gray (127), 2 -> White (255)
+ # We use a grayscale format because it gives the AI perfect contrast to see the defects
+ img_array = np.zeros((img_height, img_width), dtype=np.uint8)
+ img_array[wafer_map == 1] = 127
+ img_array[wafer_map == 2] = 255
+
+ # Save the image using OpenCV
+ img_filename = os.path.join(IMAGE_DIR, f"wafer_{index}.jpg")
+ cv2.imwrite(img_filename, img_array)
+
+ # --- 5. GENERATE LABEL FILE ---
+ label_filename = os.path.join(LABEL_DIR, f"wafer_{index}.txt")
+ with open(label_filename, 'w') as f:
+ # YOLO strictly requires: class_id x_center y_center width height
+ f.write(f"{class_id} {x_center:.6f} {y_center:.6f} {w_yolo:.6f} {h_yolo:.6f}\n")
+
+ count += 1
+ if count % 100 == 0:
+ print(f"Processed {count} wafers...")
+
+ print("Pipeline complete! Check your data/processed/ folders.")
+
+if __name__ == "__main__":
+ process_data()
\ No newline at end of file
diff --git a/src/inference.py b/src/inference.py
new file mode 100644
index 0000000000000000000000000000000000000000..aff7fe5861824f7e5db0cd02236485a3d5ba2af7
--- /dev/null
+++ b/src/inference.py
@@ -0,0 +1,31 @@
+import os
+import random
+from ultralytics import YOLO
+
+def test_model():
+ print("Loading your custom-trained wafer brain...")
+ # Updated path to match your exact file explorer structure
+ model_path = 'middleware/best.pt'
+
+ if not os.path.exists(model_path):
+ print(f"Error: Could not find model at {model_path}.")
+ return
+
+ model = YOLO(model_path)
+
+ print("Picking a random unseen wafer from the validation set...")
+ val_dir = 'data/yolo_dataset/images/val'
+ val_images = [f for f in os.listdir(val_dir) if f.endswith('.jpg')]
+
+ # Grab one random image
+ test_img = os.path.join(val_dir, random.choice(val_images))
+ print(f"Running inference on: {test_img}")
+
+ # The magic command: predict()
+ # conf=0.25 means the AI will only draw a box if it is at least 25% confident
+ results = model.predict(source=test_img, save=True, conf=0.25)
+
+ print("\nInference complete! Look in the newly generated 'predict' folder to see the drawn bounding box.")
+
+if __name__ == '__main__':
+ test_model()
\ No newline at end of file
diff --git a/src/model_eval.py b/src/model_eval.py
new file mode 100644
index 0000000000000000000000000000000000000000..8590602e2baba3cdc189f8a2e24825e0057f4aab
--- /dev/null
+++ b/src/model_eval.py
@@ -0,0 +1,16 @@
+from ultralytics import YOLO
+
+def evaluate_model():
+ print("Loading your custom-trained wafer brain for official grading...")
+ # Pointing to your exact model path
+ model_path = 'middleware/best.pt'
+ model = YOLO(model_path)
+
+ print("Running official validation...")
+ # model.val() automatically uses dataset.yaml to find the images AND the answer keys!
+ metrics = model.val()
+
+ print("\nGrading complete! Check the new 'runs/detect/val' folder for your fresh graphs.")
+
+if __name__ == '__main__':
+ evaluate_model()
\ No newline at end of file
diff --git a/src/model_train.py b/src/model_train.py
new file mode 100644
index 0000000000000000000000000000000000000000..06815fbd3c3a74d74a8c1c60e96522a5038b75a9
--- /dev/null
+++ b/src/model_train.py
@@ -0,0 +1,22 @@
+from ultralytics import YOLO
+
+def train_wafer_model():
+ print("Loading pre-trained YOLOv8 Nano model...")
+ # We use the 'nano' version (yolov8n) because it is lightweight and fast to train
+ model = YOLO('yolov8n.pt')
+
+ print("Starting AI training sequence...")
+ # This single command handles the entire neural network training process
+ results = model.train(
+ data='dataset.yaml', # Pointing to the cheat sheet we just made
+ epochs=10, # Number of times it will study all 20,415 images
+ imgsz=128, # Resizing the small wafer maps to a standard AI size
+ batch=32, # How many images it memorizes at the exact same time
+ project='runs/wafer_defects', # The master folder where it saves its brain later
+ name='yolov8_run1' # The specific name of this training session
+ )
+
+ print("Training completely finished! Check the 'runs/' folder for the results.")
+
+if __name__ == '__main__':
+ train_wafer_model()
\ No newline at end of file
diff --git a/src/split_data.py b/src/split_data.py
new file mode 100644
index 0000000000000000000000000000000000000000..a70105b13eb128d174f186706964db30daa867b7
--- /dev/null
+++ b/src/split_data.py
@@ -0,0 +1,47 @@
+import os
+import shutil
+from sklearn.model_selection import train_test_split
+
+# Current raw output folders
+IMAGE_DIR = 'data/processed/images'
+LABEL_DIR = 'data/processed/labels'
+
+# New YOLO-compliant folders
+YOLO_DIR = 'data/yolo_dataset'
+
+def setup_yolo_folders():
+ print("Creating YOLO folder structure...")
+ for split in ['train', 'val']:
+ os.makedirs(os.path.join(YOLO_DIR, 'images', split), exist_ok=True)
+ os.makedirs(os.path.join(YOLO_DIR, 'labels', split), exist_ok=True)
+
+ # Grab all the images we just generated
+ images = [f for f in os.listdir(IMAGE_DIR) if f.endswith('.jpg')]
+
+ print(f"Found {len(images)} total images. Splitting 80/20...")
+
+ # Classic 80/20 split using scikit-learn
+ train_imgs, val_imgs = train_test_split(images, test_size=0.2, random_state=42)
+
+ def copy_files(file_list, split_name):
+ print(f"Copying {len(file_list)} files to {split_name} set...")
+ for img_name in file_list:
+ # Copy Image
+ shutil.copy(
+ os.path.join(IMAGE_DIR, img_name),
+ os.path.join(YOLO_DIR, 'images', split_name, img_name)
+ )
+ # Copy matching Label
+ label_name = img_name.replace('.jpg', '.txt')
+ if os.path.exists(os.path.join(LABEL_DIR, label_name)):
+ shutil.copy(
+ os.path.join(LABEL_DIR, label_name),
+ os.path.join(YOLO_DIR, 'labels', split_name, label_name)
+ )
+
+ copy_files(train_imgs, 'train')
+ copy_files(val_imgs, 'val')
+ print("Done! Dataset is formatted and ready for YOLOv8.")
+
+if __name__ == "__main__":
+ setup_yolo_folders()
\ No newline at end of file
diff --git a/yolov8n.pt b/yolov8n.pt
new file mode 100644
index 0000000000000000000000000000000000000000..719e6f1dbdfe7c560e5933fc8b0c5a7e857d0234
--- /dev/null
+++ b/yolov8n.pt
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:f59b3d833e2ff32e194b5bb8e08d211dc7c5bdf144b90d2c8412c47ccfc83b36
+size 6549796