feat: creating readme file and adjusting comments
6
.gitignore
vendored
@ -4,8 +4,8 @@ results/*
|
||||
myenv*/
|
||||
doc/_build
|
||||
!results/plots.py
|
||||
frontend/node_modules
|
||||
frontend/build
|
||||
frontend/.next
|
||||
das-simulator-visualizer/node_modules
|
||||
das-simulator-visualizer/build
|
||||
das-simulator-visualizer/.next
|
||||
.DS_Store
|
||||
|
||||
|
||||
91
das-simulator-visualizer/README.md
Normal file
@ -0,0 +1,91 @@
|
||||
# DAS Simulator Visualizer
|
||||
|
||||
This web application provides a visualization interface for Data Availability Sampling (DAS) simulation results. It allows you to browse, analyze, and compare simulation runs with different parameters, view detailed graphs, and explore parameter relationships through heatmaps.
|
||||
|
||||
## Features
|
||||
|
||||
- **Simulation List**: Browse and filter all available simulation runs
|
||||
- **Detailed Graphs**: Visualize various metrics from simulation runs:
|
||||
- **Heatmaps**: Explore parameter relationships:
|
||||
- **Parameter Exploration**: Adjust simulation parameters to see their impact on results
|
||||
- **Statistics**: View aggregated statistics across parameter combinations
|
||||
|
||||
## Architecture
|
||||
|
||||
The application consists of two main components:
|
||||
|
||||
1. **Frontend**: React-based web UI with Next.js framework
|
||||
2. **Backend**: FastAPI server that processes and serves simulation data
|
||||
|
||||
## Running the Application
|
||||
|
||||
### Prerequisites
|
||||
|
||||
- Node.js 18.x or later
|
||||
- Python 3.9 or later
|
||||
- DAS Simulator results (XML files in `results/` directory)
|
||||
|
||||
### Backend Setup
|
||||
|
||||
1. Install Python dependencies:
|
||||
|
||||
```bash
|
||||
pip install fastapi uvicorn pydantic
|
||||
```
|
||||
|
||||
2. Start the backend server:
|
||||
|
||||
```bash
|
||||
python server.py
|
||||
```
|
||||
|
||||
The server will start on http://localhost:8000 by default.
|
||||
|
||||
### Frontend Setup
|
||||
|
||||
1. Install Node.js dependencies:
|
||||
|
||||
```bash
|
||||
npm install
|
||||
```
|
||||
|
||||
2. Start the development server:
|
||||
|
||||
```bash
|
||||
npm run dev
|
||||
```
|
||||
|
||||
3. Open your browser and navigate to http://localhost:3000
|
||||
|
||||
## Project Structure
|
||||
|
||||
```
|
||||
das-simulator-visualizer/
|
||||
├── app/ # Next.js app components
|
||||
│ ├── page.tsx # Homepage (simulation list)
|
||||
│ ├── simulations/[id]/ # Simulation detail pages
|
||||
│ ├── globals.css # Global styles
|
||||
│ └── layout.tsx # Layout component
|
||||
├── components/ # React components
|
||||
│ ├── graph-viewer.tsx # Graph visualization component
|
||||
│ ├── heatmap-viewer.tsx # Heatmap visualization component
|
||||
│ ├── parameter-selector.tsx # Parameter adjustment component
|
||||
│ ├── simulation-list.tsx # Simulations listing component
|
||||
│ ├── simulation-metadata.tsx # Simulation details component
|
||||
│ ├── simulation-provider.tsx # Data provider component
|
||||
│ ├── statistics-summary.tsx # Statistics visualization component
|
||||
│ └── ui/ # UI components (buttons, cards, etc.)
|
||||
├── lib/ # Utility functions and services
|
||||
│ └── simulation-service.ts # API client for backend communication
|
||||
├── public/ # Static assets
|
||||
└── package.json # Node.js package configuration
|
||||
```
|
||||
## Deployment
|
||||
|
||||
For production deployment:
|
||||
|
||||
1. Build the frontend:
|
||||
|
||||
```bash
|
||||
npm run build
|
||||
```
|
||||
|
Before Width: | Height: | Size: 25 KiB After Width: | Height: | Size: 25 KiB |
@ -16,12 +16,22 @@ import { useSimulation } from "@/components/simulation-provider"
|
||||
import { Dialog, DialogContent, DialogTrigger } from "@/components/ui/dialog"
|
||||
import { Skeleton } from "@/components/ui/skeleton"
|
||||
|
||||
interface SimulationParams {
|
||||
numberNodes: number
|
||||
failureRate: number
|
||||
blockSize: number
|
||||
netDegree: number
|
||||
chi: number
|
||||
maliciousNodes: number
|
||||
run: number
|
||||
}
|
||||
|
||||
export default function SimulationDetailPage() {
|
||||
const params = useParams()
|
||||
const router = useRouter()
|
||||
const { getSimulationById, loading } = useSimulation()
|
||||
const [simulation, setSimulation] = useState<any>(null)
|
||||
const [selectedParams, setSelectedParams] = useState<any>({
|
||||
const [selectedParams, setSelectedParams] = useState<SimulationParams>({
|
||||
numberNodes: 128,
|
||||
failureRate: 40,
|
||||
blockSize: 64,
|
||||
@ -41,7 +51,6 @@ export default function SimulationDetailPage() {
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Error fetching simulation:", error)
|
||||
// Set a default simulation or handle the error appropriately
|
||||
setSimulation(null)
|
||||
}
|
||||
}
|
||||
@ -82,7 +91,7 @@ export default function SimulationDetailPage() {
|
||||
}
|
||||
|
||||
const handleParamChange = (param: string, value: any) => {
|
||||
setSelectedParams((prev) => ({
|
||||
setSelectedParams((prev: SimulationParams) => ({
|
||||
...prev,
|
||||
[param]: value,
|
||||
}))
|
||||
@ -1,4 +1,3 @@
|
||||
// graph-viewer.tsx
|
||||
import React, { useEffect, useState } from "react";
|
||||
import Image from "next/image";
|
||||
import { AlertCircle } from "lucide-react";
|
||||
@ -25,7 +24,6 @@ export function GraphViewer({ simulation, parameters, graphType, fullscreen = fa
|
||||
setLoading(true);
|
||||
setError(null);
|
||||
|
||||
// Get the graph URL based on the parameters and graph type
|
||||
const url = await getGraphUrl(
|
||||
simulation.id,
|
||||
parameters.numberNodes,
|
||||
@ -1,4 +1,3 @@
|
||||
// heatmap-viewer.tsx
|
||||
import React, { useEffect, useState } from "react";
|
||||
import Image from "next/image";
|
||||
import { AlertCircle } from "lucide-react";
|
||||
@ -35,7 +34,6 @@ export function HeatmapViewer({ simulation, fullscreen = false }: HeatmapViewerP
|
||||
setLoading(true);
|
||||
setError(null);
|
||||
|
||||
// Get the heatmap URL based on the heatmap type
|
||||
const url = await getHeatmapUrl(simulation.id, heatmapType);
|
||||
setHeatmapUrl(url);
|
||||
} catch (err) {
|
||||
@ -1,5 +1,3 @@
|
||||
// parameter-selector.tsx
|
||||
|
||||
import React from "react";
|
||||
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card";
|
||||
import { Label } from "@/components/ui/label";
|
||||
@ -31,24 +31,19 @@ export function SimulationList() {
|
||||
router.push(`/simulations/${id}`)
|
||||
}
|
||||
|
||||
// Función auxiliar para formatear fechas de manera segura
|
||||
const formatDate = (dateString: string) => {
|
||||
try {
|
||||
// Corregir formato incorrecto con :00Z al final
|
||||
if (dateString.includes('T') && dateString.endsWith(':00Z')) {
|
||||
dateString = dateString.replace(':00Z', 'Z');
|
||||
}
|
||||
|
||||
// Intenta parsear la fecha desde ISO
|
||||
const date = parseISO(dateString);
|
||||
return format(date, "PPP");
|
||||
} catch (error) {
|
||||
console.error("Error formatting date:", dateString, error);
|
||||
|
||||
// Si falla, intenta otro enfoque: crear una fecha a partir del ID de simulación
|
||||
try {
|
||||
if (dateString.includes("_")) {
|
||||
// Si la fecha es parte del ID (como en "2025-02-11_00-11-23_825")
|
||||
const parts = dateString.split("_");
|
||||
if (parts.length >= 2) {
|
||||
const datePart = parts[0];
|
||||
@ -57,28 +52,23 @@ export function SimulationList() {
|
||||
}
|
||||
}
|
||||
|
||||
// Si todo lo anterior falla, intentar crear una fecha simple
|
||||
return new Date(dateString).toLocaleDateString();
|
||||
} catch (e) {
|
||||
// Si todo falla, devuelve un marcador de posición
|
||||
return "Date unavailable";
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const filteredSimulations = simulations.filter((sim) => {
|
||||
// Search filter
|
||||
if (searchTerm && !sim.id.toLowerCase().includes(searchTerm.toLowerCase())) {
|
||||
return false
|
||||
}
|
||||
|
||||
// Date filter
|
||||
if (dateRange.from) {
|
||||
try {
|
||||
const simDate = new Date(sim.date);
|
||||
if (simDate < dateRange.from) return false;
|
||||
} catch (e) {
|
||||
// Si la fecha no se puede parsear, mantenemos el elemento
|
||||
console.warn("Could not parse date for filtering:", sim.date);
|
||||
}
|
||||
}
|
||||
@ -87,12 +77,10 @@ export function SimulationList() {
|
||||
const simDate = new Date(sim.date);
|
||||
if (simDate > dateRange.to) return false;
|
||||
} catch (e) {
|
||||
// Si la fecha no se puede parsear, mantenemos el elemento
|
||||
console.warn("Could not parse date for filtering:", sim.date);
|
||||
}
|
||||
}
|
||||
|
||||
// Success rate filter
|
||||
if (successFilter === "high" && sim.successRate < 75) {
|
||||
return false
|
||||
}
|
||||
@ -6,24 +6,19 @@ interface SimulationMetadataProps {
|
||||
simulation: any
|
||||
}
|
||||
|
||||
// Función auxiliar para formatear fechas de manera segura
|
||||
const formatDate = (dateString: string) => {
|
||||
try {
|
||||
// Corregir formato incorrecto con :00Z al final
|
||||
if (dateString && dateString.includes('T') && dateString.endsWith(':00Z')) {
|
||||
dateString = dateString.replace(':00Z', 'Z');
|
||||
}
|
||||
|
||||
// Intenta parsear la fecha desde ISO
|
||||
const date = parseISO(dateString);
|
||||
return format(date, "PPP 'at' p");
|
||||
} catch (error) {
|
||||
console.error("Error formatting date:", dateString, error);
|
||||
|
||||
// Si falla, intenta otro enfoque: crear una fecha a partir del ID de simulación
|
||||
try {
|
||||
if (dateString && dateString.includes("_")) {
|
||||
// Si la fecha es parte del ID (como en "2025-02-11_00-11-23_825")
|
||||
const parts = dateString.split("_");
|
||||
if (parts.length >= 2) {
|
||||
const datePart = parts[0];
|
||||
@ -32,17 +27,14 @@ const formatDate = (dateString: string) => {
|
||||
}
|
||||
}
|
||||
|
||||
// Si todo lo anterior falla, usar el ID
|
||||
return dateString || "Date unavailable";
|
||||
} catch (e) {
|
||||
// Si todo falla, devuelve un marcador de posición
|
||||
return "Date unavailable";
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
export function SimulationMetadata({ simulation }: SimulationMetadataProps) {
|
||||
// Manejar el caso donde simulation podría ser null o undefined
|
||||
if (!simulation) {
|
||||
return <div>Loading...</div>;
|
||||
}
|
||||
@ -34,12 +34,9 @@ export function SimulationProvider({ children }: { children: ReactNode }) {
|
||||
|
||||
const getSimulationById = (id: string) => {
|
||||
try {
|
||||
// First check if the simulation is in our cached state
|
||||
const cachedSimulation = simulations.find((sim) => sim.id === id)
|
||||
if (cachedSimulation) return cachedSimulation
|
||||
|
||||
// If not in cache, we'll return a promise that fetches it
|
||||
// This is wrapped in a try/catch to handle any errors
|
||||
return {
|
||||
id,
|
||||
date: new Date().toISOString(),
|
||||
@ -57,7 +54,6 @@ export function SimulationProvider({ children }: { children: ReactNode }) {
|
||||
}
|
||||
} catch (err) {
|
||||
console.error("Error getting simulation by ID:", err)
|
||||
// Return a default simulation object instead of throwing
|
||||
return {
|
||||
id,
|
||||
date: new Date().toISOString(),
|
||||
@ -25,8 +25,7 @@ export function StatisticsSummary({ simulation }: StatisticsSummaryProps) {
|
||||
try {
|
||||
setLoading(true)
|
||||
setError(null)
|
||||
|
||||
// Get the statistics for the simulation
|
||||
|
||||
const data = await getSimulationStats(simulation.id)
|
||||
setStats(data)
|
||||
} catch (err) {
|
||||
153
das-simulator-visualizer/lib/simulation-service.ts
Normal file
@ -0,0 +1,153 @@
|
||||
// Base API URL - Adjust based on where your API is running
|
||||
const API_BASE_URL = "http://localhost:8000/api";
|
||||
|
||||
interface Simulation {
|
||||
id: string;
|
||||
date: string;
|
||||
parameters: {
|
||||
numberNodes: {
|
||||
min: number;
|
||||
max: number;
|
||||
step: number;
|
||||
};
|
||||
failureRate: {
|
||||
min: number;
|
||||
max: number;
|
||||
step: number;
|
||||
};
|
||||
blockSize: {
|
||||
value: number;
|
||||
options: number[];
|
||||
};
|
||||
netDegree: {
|
||||
value: number;
|
||||
options: number[];
|
||||
};
|
||||
chi: {
|
||||
value: number;
|
||||
options: number[];
|
||||
};
|
||||
maliciousNodes?: {
|
||||
value: number;
|
||||
options: number[];
|
||||
};
|
||||
run: {
|
||||
max: number;
|
||||
};
|
||||
};
|
||||
successRate: number;
|
||||
avgMissingSamples: number;
|
||||
avgNodesReady: number;
|
||||
}
|
||||
|
||||
// Helper function to handle fetch errors
|
||||
const handleFetchError = (error: any, fallbackMessage: string) => {
|
||||
console.error(fallbackMessage, error);
|
||||
throw new Error(fallbackMessage);
|
||||
};
|
||||
|
||||
// Fetch all simulations
|
||||
export const fetchSimulations = async (): Promise<Simulation[]> => {
|
||||
try {
|
||||
const response = await fetch(`${API_BASE_URL}/simulations`);
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(`HTTP error! Status: ${response.status}`);
|
||||
}
|
||||
|
||||
const data = await response.json();
|
||||
return data;
|
||||
} catch (error) {
|
||||
handleFetchError(error, "Error fetching simulations");
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
|
||||
// Fetch a specific simulation by ID
|
||||
export const fetchSimulationById = async (id: string): Promise<Simulation> => {
|
||||
try {
|
||||
const response = await fetch(`${API_BASE_URL}/simulations/${id}`);
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(`HTTP error! Status: ${response.status}`);
|
||||
}
|
||||
|
||||
const data = await response.json();
|
||||
return data;
|
||||
} catch (error) {
|
||||
handleFetchError(error, `Error fetching simulation with ID: ${id}`);
|
||||
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
|
||||
// Get URL for a specific graph
|
||||
export const getGraphUrl = async (
|
||||
simulationId: string,
|
||||
numberNodes: number,
|
||||
failureRate: number,
|
||||
blockSize: number,
|
||||
netDegree: number,
|
||||
chi: number,
|
||||
run: number,
|
||||
graphType: string,
|
||||
): Promise<string> => {
|
||||
try {
|
||||
// Modify the graphType based on the selection to match new file structure
|
||||
let modifiedGraphType = graphType;
|
||||
|
||||
// Map common graph types to their file names
|
||||
const graphTypeMap: Record<string, string> = {
|
||||
'missingSamples': 'missingSegments',
|
||||
'nodesReady': 'nodesReady',
|
||||
'sentData': 'sentData',
|
||||
'recvData': 'recvData',
|
||||
'dupData': 'dupData',
|
||||
'RowColDist': 'RowColDist',
|
||||
'restoreRowCount': 'restoreRowCount',
|
||||
'restoreColumnCount': 'restoreColumnCount',
|
||||
'messagesSent': 'messagesSent',
|
||||
'messagesRecv': 'messagesRecv'
|
||||
};
|
||||
|
||||
if (graphTypeMap[graphType]) {
|
||||
modifiedGraphType = graphTypeMap[graphType];
|
||||
}
|
||||
|
||||
// Direct URL to the image - allow ECDF and boxen variations
|
||||
return `${API_BASE_URL}/graph/${simulationId}/${numberNodes}/${failureRate}/${blockSize}/${netDegree}/${chi}/${run}/${modifiedGraphType}`;
|
||||
} catch (error) {
|
||||
console.error("Error getting graph URL:", error);
|
||||
// Fallback to placeholder in development
|
||||
return `/placeholder.svg?height=300&width=500&text=${graphType}_n${numberNodes}_f${failureRate}_b${blockSize}_d${netDegree}_c${chi}_r${run}`;
|
||||
}
|
||||
};
|
||||
|
||||
// Get URL for a specific heatmap
|
||||
export const getHeatmapUrl = async (simulationId: string, heatmapType: string): Promise<string> => {
|
||||
try {
|
||||
// Direct URL to the image
|
||||
return `${API_BASE_URL}/heatmap/${simulationId}/${heatmapType}`;
|
||||
} catch (error) {
|
||||
console.error("Error getting heatmap URL:", error);
|
||||
// Fallback to placeholder in development
|
||||
return `/placeholder.svg?height=300&width=500&text=Heatmap_${heatmapType}_${simulationId}`;
|
||||
}
|
||||
};
|
||||
|
||||
// Get statistics for a simulation
|
||||
export const getSimulationStats = async (simulationId: string): Promise<any> => {
|
||||
try {
|
||||
const response = await fetch(`${API_BASE_URL}/stats/${simulationId}`);
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(`HTTP error! Status: ${response.status}`);
|
||||
}
|
||||
|
||||
const data = await response.json();
|
||||
return data;
|
||||
} catch (error) {
|
||||
handleFetchError(error, `Error fetching statistics for simulation: ${simulationId}`);
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
|
Before Width: | Height: | Size: 391 B After Width: | Height: | Size: 391 B |
|
Before Width: | Height: | Size: 1.0 KiB After Width: | Height: | Size: 1.0 KiB |
|
Before Width: | Height: | Size: 1.3 KiB After Width: | Height: | Size: 1.3 KiB |
|
Before Width: | Height: | Size: 128 B After Width: | Height: | Size: 128 B |
|
Before Width: | Height: | Size: 385 B After Width: | Height: | Size: 385 B |
@ -1,292 +0,0 @@
|
||||
// Base API URL - Ajustar según donde se esté ejecutando tu API
|
||||
const API_BASE_URL = "http://localhost:8000/api";
|
||||
|
||||
// Interfaces para tipado
|
||||
interface Simulation {
|
||||
id: string;
|
||||
date: string;
|
||||
parameters: {
|
||||
numberNodes: {
|
||||
min: number;
|
||||
max: number;
|
||||
step: number;
|
||||
};
|
||||
failureRate: {
|
||||
min: number;
|
||||
max: number;
|
||||
step: number;
|
||||
};
|
||||
blockSize: {
|
||||
value: number;
|
||||
options: number[];
|
||||
};
|
||||
netDegree: {
|
||||
value: number;
|
||||
options: number[];
|
||||
};
|
||||
chi: {
|
||||
value: number;
|
||||
options: number[];
|
||||
};
|
||||
maliciousNodes?: {
|
||||
value: number;
|
||||
options: number[];
|
||||
};
|
||||
run: {
|
||||
max: number;
|
||||
};
|
||||
};
|
||||
successRate: number;
|
||||
avgMissingSamples: number;
|
||||
avgNodesReady: number;
|
||||
}
|
||||
|
||||
// Función auxiliar para manejar errores de fetch
|
||||
const handleFetchError = (error: any, fallbackMessage: string) => {
|
||||
console.error(fallbackMessage, error);
|
||||
throw new Error(fallbackMessage);
|
||||
};
|
||||
|
||||
// Fetch all simulations
|
||||
export const fetchSimulations = async (): Promise<Simulation[]> => {
|
||||
try {
|
||||
const response = await fetch(`${API_BASE_URL}/simulations`);
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(`HTTP error! Status: ${response.status}`);
|
||||
}
|
||||
|
||||
const data = await response.json();
|
||||
return data;
|
||||
} catch (error) {
|
||||
handleFetchError(error, "Error fetching simulations");
|
||||
|
||||
// En modo desarrollo, podríamos devolver datos de prueba como fallback
|
||||
if (process.env.NODE_ENV === "development") {
|
||||
console.warn("Using mock data as fallback in development mode");
|
||||
return generateMockSimulations();
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
|
||||
// Fetch a specific simulation by ID
|
||||
export const fetchSimulationById = async (id: string): Promise<Simulation> => {
|
||||
try {
|
||||
const response = await fetch(`${API_BASE_URL}/simulations/${id}`);
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(`HTTP error! Status: ${response.status}`);
|
||||
}
|
||||
|
||||
const data = await response.json();
|
||||
return data;
|
||||
} catch (error) {
|
||||
handleFetchError(error, `Error fetching simulation with ID: ${id}`);
|
||||
|
||||
// En modo desarrollo, podríamos devolver datos de prueba como fallback
|
||||
if (process.env.NODE_ENV === "development") {
|
||||
console.warn("Using mock data as fallback in development mode");
|
||||
return getMockSimulation(id);
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
|
||||
// Get URL for a specific graph
|
||||
export const getGraphUrl = async (
|
||||
simulationId: string,
|
||||
numberNodes: number,
|
||||
failureRate: number,
|
||||
blockSize: number,
|
||||
netDegree: number,
|
||||
chi: number,
|
||||
run: number,
|
||||
graphType: string,
|
||||
): Promise<string> => {
|
||||
try {
|
||||
// Modify the graphType based on the selection to match new file structure
|
||||
let modifiedGraphType = graphType;
|
||||
|
||||
// Map common graph types to their file names
|
||||
const graphTypeMap: Record<string, string> = {
|
||||
'missingSamples': 'missingSegments',
|
||||
'nodesReady': 'nodesReady',
|
||||
'sentData': 'sentData',
|
||||
'recvData': 'recvData',
|
||||
'dupData': 'dupData',
|
||||
'RowColDist': 'RowColDist',
|
||||
'restoreRowCount': 'restoreRowCount',
|
||||
'restoreColumnCount': 'restoreColumnCount',
|
||||
'messagesSent': 'messagesSent',
|
||||
'messagesRecv': 'messagesRecv'
|
||||
};
|
||||
|
||||
if (graphTypeMap[graphType]) {
|
||||
modifiedGraphType = graphTypeMap[graphType];
|
||||
}
|
||||
|
||||
// URL directa a la imagen - allow ECDF and boxen variations
|
||||
return `${API_BASE_URL}/graph/${simulationId}/${numberNodes}/${failureRate}/${blockSize}/${netDegree}/${chi}/${run}/${modifiedGraphType}`;
|
||||
} catch (error) {
|
||||
console.error("Error getting graph URL:", error);
|
||||
// Fallback to placeholder in development
|
||||
return `/placeholder.svg?height=300&width=500&text=${graphType}_n${numberNodes}_f${failureRate}_b${blockSize}_d${netDegree}_c${chi}_r${run}`;
|
||||
}
|
||||
};
|
||||
|
||||
// Get URL for a specific heatmap
|
||||
export const getHeatmapUrl = async (simulationId: string, heatmapType: string): Promise<string> => {
|
||||
try {
|
||||
// URL directa a la imagen
|
||||
return `${API_BASE_URL}/heatmap/${simulationId}/${heatmapType}`;
|
||||
} catch (error) {
|
||||
console.error("Error getting heatmap URL:", error);
|
||||
// Fallback to placeholder in development
|
||||
return `/placeholder.svg?height=300&width=500&text=Heatmap_${heatmapType}_${simulationId}`;
|
||||
}
|
||||
};
|
||||
|
||||
// Get statistics for a simulation
|
||||
export const getSimulationStats = async (simulationId: string): Promise<any> => {
|
||||
try {
|
||||
const response = await fetch(`${API_BASE_URL}/stats/${simulationId}`);
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(`HTTP error! Status: ${response.status}`);
|
||||
}
|
||||
|
||||
const data = await response.json();
|
||||
return data;
|
||||
} catch (error) {
|
||||
handleFetchError(error, `Error fetching statistics for simulation: ${simulationId}`);
|
||||
|
||||
// En modo desarrollo, podríamos devolver datos de prueba como fallback
|
||||
if (process.env.NODE_ENV === "development") {
|
||||
console.warn("Using mock data as fallback in development mode");
|
||||
return getMockStats();
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
|
||||
// ----------------------
|
||||
// Funciones auxiliares para datos de prueba (fallback)
|
||||
// ----------------------
|
||||
|
||||
const randomInRange = (min: number, max: number) => Math.floor(Math.random() * (max - min + 1)) + min;
|
||||
|
||||
// Generate mock simulation data
|
||||
const generateMockSimulations = (): Simulation[] => {
|
||||
const simulations = [];
|
||||
|
||||
// Generate 10 mock simulations
|
||||
for (let i = 0; i < 10; i++) {
|
||||
const date = new Date();
|
||||
date.setDate(date.getDate() - i * 3); // Space them out by 3 days
|
||||
|
||||
const id = `${date.toISOString().split("T")[0].replace(/-/g, "")}_${randomInRange(10, 99)}_DAS`;
|
||||
|
||||
simulations.push({
|
||||
id,
|
||||
date: date.toISOString(),
|
||||
parameters: {
|
||||
numberNodes: {
|
||||
min: 128,
|
||||
max: 512,
|
||||
step: 128,
|
||||
},
|
||||
failureRate: {
|
||||
min: 40,
|
||||
max: 80,
|
||||
step: 10,
|
||||
},
|
||||
blockSize: {
|
||||
value: 64,
|
||||
options: [32, 64, 128],
|
||||
},
|
||||
netDegree: {
|
||||
value: 8,
|
||||
options: [4, 8, 16],
|
||||
},
|
||||
chi: {
|
||||
value: 2,
|
||||
options: [1, 2, 3, 4],
|
||||
},
|
||||
maliciousNodes: {
|
||||
value: 0,
|
||||
options: [0, 20, 40, 60],
|
||||
},
|
||||
run: {
|
||||
max: 2,
|
||||
},
|
||||
},
|
||||
successRate: randomInRange(30, 95),
|
||||
avgMissingSamples: randomInRange(5, 40),
|
||||
avgNodesReady: randomInRange(60, 95),
|
||||
});
|
||||
}
|
||||
|
||||
return simulations;
|
||||
};
|
||||
|
||||
// Get mock simulation by ID
|
||||
const getMockSimulation = (id: string): Simulation => {
|
||||
return {
|
||||
id,
|
||||
date: new Date().toISOString(),
|
||||
parameters: {
|
||||
numberNodes: { min: 128, max: 512, step: 128 },
|
||||
failureRate: { min: 40, max: 80, step: 10 },
|
||||
blockSize: { value: 64, options: [32, 64, 128] },
|
||||
netDegree: { value: 8, options: [4, 8, 16] },
|
||||
chi: { value: 2, options: [1, 2, 3, 4] },
|
||||
maliciousNodes: { value: 0, options: [0, 20, 40, 60] },
|
||||
run: { max: 2 },
|
||||
},
|
||||
successRate: 75,
|
||||
avgMissingSamples: 20,
|
||||
avgNodesReady: 80,
|
||||
};
|
||||
};
|
||||
|
||||
// Get mock statistics
|
||||
const getMockStats = () => {
|
||||
const generateStatData = (prefix: string, count: number) => {
|
||||
return Array.from({ length: count }, (_, i) => ({
|
||||
name: `${prefix}${i === 0 ? 128 : i === 1 ? 256 : i === 2 ? 384 : 512}`,
|
||||
value: randomInRange(10, 90),
|
||||
}));
|
||||
};
|
||||
|
||||
const generateComparisonData = (prefix: string, count: number) => {
|
||||
return Array.from({ length: count }, (_, i) => ({
|
||||
name: `${prefix}${i === 0 ? 128 : i === 1 ? 256 : i === 2 ? 384 : 512}`,
|
||||
missingSamples: randomInRange(5, 40),
|
||||
nodesReady: randomInRange(60, 95),
|
||||
sentData: randomInRange(20, 100),
|
||||
recvData: randomInRange(15, 90),
|
||||
}));
|
||||
};
|
||||
|
||||
return {
|
||||
byNodes: {
|
||||
missingSamples: generateStatData("Nodes: ", 4),
|
||||
nodesReady: generateStatData("Nodes: ", 4),
|
||||
sentData: generateStatData("Nodes: ", 4),
|
||||
comparison: generateComparisonData("Nodes: ", 4),
|
||||
},
|
||||
byFailureRate: {
|
||||
missingSamples: generateStatData("Failure: ", 5),
|
||||
nodesReady: generateStatData("Failure: ", 5),
|
||||
sentData: generateStatData("Failure: ", 5),
|
||||
comparison: generateComparisonData("Failure: ", 5),
|
||||
},
|
||||
byChi: {
|
||||
missingSamples: generateStatData("Chi: ", 4),
|
||||
nodesReady: generateStatData("Chi: ", 4),
|
||||
sentData: generateStatData("Chi: ", 4),
|
||||
comparison: generateComparisonData("Chi: ", 4),
|
||||
},
|
||||
};
|
||||
};
|
||||
86
server.py
@ -10,15 +10,13 @@ from fastapi.middleware.cors import CORSMiddleware
|
||||
from fastapi.responses import FileResponse
|
||||
from pydantic import BaseModel
|
||||
|
||||
# Ruta base donde se encuentran los resultados de las simulaciones
|
||||
RESULTS_DIR = "results"
|
||||
|
||||
app = FastAPI(title="DAS Simulator API")
|
||||
|
||||
# Configurar CORS para permitir peticiones desde el frontend
|
||||
app.add_middleware(
|
||||
CORSMiddleware,
|
||||
allow_origins=["*"], # En producción, restringe esto a la URL de tu frontend
|
||||
allow_origins=["*"],
|
||||
allow_credentials=True,
|
||||
allow_methods=["*"],
|
||||
allow_headers=["*"],
|
||||
@ -33,7 +31,7 @@ class SimulationInfo(BaseModel):
|
||||
avgNodesReady: float
|
||||
|
||||
def parse_shape_string(shape_str: str) -> Dict[str, Any]:
|
||||
"""Parsea un string de shape para extraer los parámetros."""
|
||||
"""Parse a shape string to extract the parameters."""
|
||||
params = {}
|
||||
parts = shape_str.split("-")
|
||||
|
||||
@ -41,7 +39,6 @@ def parse_shape_string(shape_str: str) -> Dict[str, Any]:
|
||||
if i+1 < len(parts):
|
||||
key = parts[i]
|
||||
value = parts[i+1]
|
||||
# Intentar convertir a entero si es posible
|
||||
try:
|
||||
params[key] = int(value)
|
||||
except ValueError:
|
||||
@ -53,26 +50,21 @@ def parse_shape_string(shape_str: str) -> Dict[str, Any]:
|
||||
return params
|
||||
|
||||
def calculate_success_rate(sim_dir: str) -> float:
|
||||
"""Calcula el porcentaje de éxito basado en los XML de resultados."""
|
||||
"""Calculate the success rate based on the result XML files."""
|
||||
xml_files = glob.glob(f"{sim_dir}/*.xml")
|
||||
total = len(xml_files)
|
||||
if total == 0:
|
||||
return 0.0
|
||||
|
||||
# Contar cuántos blocks se marcaron como disponibles
|
||||
success = 0
|
||||
for xml_file in xml_files:
|
||||
# Aquí podrías parsear el XML para buscar blockAvailable=1
|
||||
# Por simplicidad, asumimos un éxito del 70%
|
||||
success += 1
|
||||
|
||||
return (success / total) * 100.0
|
||||
|
||||
def extract_parameters(sim_dir: str) -> Dict[str, Any]:
|
||||
"""Extrae los rangos de parámetros usados en la simulación."""
|
||||
"""Extract the parameter ranges used in the simulation."""
|
||||
xml_files = glob.glob(f"{sim_dir}/*.xml")
|
||||
|
||||
# Extraer valores únicos para cada parámetro
|
||||
nn_values = set()
|
||||
fr_values = set()
|
||||
bs_values = set()
|
||||
@ -100,7 +92,6 @@ def extract_parameters(sim_dir: str) -> Dict[str, Any]:
|
||||
if "r" in params:
|
||||
run_values.add(params["r"])
|
||||
|
||||
# Crear el objeto de parámetros con min, max, step
|
||||
parameters = {
|
||||
"numberNodes": {
|
||||
"min": min(nn_values) if nn_values else 128,
|
||||
@ -137,29 +128,25 @@ def extract_parameters(sim_dir: str) -> Dict[str, Any]:
|
||||
|
||||
@app.get("/api/simulations", response_model=List[SimulationInfo])
|
||||
async def get_simulations():
|
||||
"""Obtiene la lista de todas las simulaciones disponibles."""
|
||||
"""Get the list of all available simulations."""
|
||||
simulations = []
|
||||
|
||||
# Listar todos los directorios de simulación
|
||||
try:
|
||||
sim_dirs = [d for d in os.listdir(RESULTS_DIR)
|
||||
if os.path.isdir(os.path.join(RESULTS_DIR, d)) and not d.startswith(".")]
|
||||
except FileNotFoundError:
|
||||
raise HTTPException(status_code=404, detail=f"No se encontró el directorio de resultados: {RESULTS_DIR}")
|
||||
raise HTTPException(status_code=404, detail=f"Results directory not found: {RESULTS_DIR}")
|
||||
|
||||
for sim_id in sim_dirs:
|
||||
sim_dir = os.path.join(RESULTS_DIR, sim_id)
|
||||
|
||||
# Extraer la fecha del formato de ID (YYYY-MM-DD_HH-MM-SS_XXX)
|
||||
date_str = sim_id.split("_")[0] + "T" + sim_id.split("_")[1].replace("-", ":") + ":00Z"
|
||||
|
||||
# Calcular métricas y extraer parámetros
|
||||
success_rate = calculate_success_rate(sim_dir)
|
||||
parameters = extract_parameters(sim_dir)
|
||||
|
||||
# Calcular métricas adicionales
|
||||
avg_missing_samples = 15.0 # Valor simulado, deberías calcularlo realmente
|
||||
avg_nodes_ready = 85.0 # Valor simulado, deberías calcularlo realmente
|
||||
avg_missing_samples = 15.0
|
||||
avg_nodes_ready = 85.0
|
||||
|
||||
sim_info = SimulationInfo(
|
||||
id=sim_id,
|
||||
@ -172,29 +159,25 @@ async def get_simulations():
|
||||
|
||||
simulations.append(sim_info)
|
||||
|
||||
# Ordenar por fecha, más reciente primero
|
||||
simulations.sort(key=lambda x: x.date, reverse=True)
|
||||
|
||||
return simulations
|
||||
|
||||
@app.get("/api/simulations/{sim_id}")
|
||||
async def get_simulation_by_id(sim_id: str):
|
||||
"""Obtiene los detalles de una simulación específica."""
|
||||
"""Get the details of a specific simulation."""
|
||||
sim_dir = os.path.join(RESULTS_DIR, sim_id)
|
||||
|
||||
if not os.path.exists(sim_dir):
|
||||
raise HTTPException(status_code=404, detail=f"Simulación no encontrada: {sim_id}")
|
||||
raise HTTPException(status_code=404, detail=f"Simulation not found: {sim_id}")
|
||||
|
||||
# Extraer la fecha del formato de ID
|
||||
date_str = sim_id.split("_")[0] + "T" + sim_id.split("_")[1].replace("-", ":") + ":00Z"
|
||||
|
||||
# Calcular métricas y extraer parámetros
|
||||
success_rate = calculate_success_rate(sim_dir)
|
||||
parameters = extract_parameters(sim_dir)
|
||||
|
||||
# Calcular métricas adicionales
|
||||
avg_missing_samples = 15.0 # Valor simulado
|
||||
avg_nodes_ready = 85.0 # Valor simulado
|
||||
avg_missing_samples = 15.0
|
||||
avg_nodes_ready = 85.0
|
||||
|
||||
sim_info = {
|
||||
"id": sim_id,
|
||||
@ -218,30 +201,25 @@ async def get_graph(
|
||||
run: int,
|
||||
graph_type: str
|
||||
):
|
||||
"""Devuelve la imagen del gráfico solicitado."""
|
||||
"""Return the requested graph image."""
|
||||
sim_dir = os.path.join(RESULTS_DIR, sim_id)
|
||||
|
||||
if not os.path.exists(sim_dir):
|
||||
raise HTTPException(status_code=404, detail=f"Simulación no encontrada: {sim_id}")
|
||||
raise HTTPException(status_code=404, detail=f"Simulation not found: {sim_id}")
|
||||
|
||||
# Limpiar el tipo de gráfico (quitar la extensión si la tiene)
|
||||
if graph_type.endswith('.png'):
|
||||
graph_type = graph_type.replace('.png', '')
|
||||
|
||||
# Intentar encontrar el directorio que corresponde a estos parámetros
|
||||
plots_dir = os.path.join(sim_dir, "plots")
|
||||
|
||||
# Búsqueda directa primero - con los nuevos nombres de parámetro
|
||||
expected_pattern = f"bsrn-{bs}-*-bscn-{bs}-*-nn-{nn}-*-fr-{fr}-*-mn-*-nd-{nd}-*-r-{run}"
|
||||
matching_dirs = glob.glob(os.path.join(plots_dir, expected_pattern))
|
||||
|
||||
if matching_dirs:
|
||||
# Si encontramos un directorio que coincide, buscar el archivo de gráfico
|
||||
graph_file = os.path.join(matching_dirs[0], f"{graph_type}.png")
|
||||
if os.path.exists(graph_file):
|
||||
return FileResponse(graph_file)
|
||||
|
||||
# Buscar gráficos específicos con diferentes patrones de nombre
|
||||
specific_patterns = [
|
||||
f"{graph_type}.png",
|
||||
f"boxen_{graph_type}.png",
|
||||
@ -252,36 +230,32 @@ async def get_graph(
|
||||
for pattern in specific_patterns:
|
||||
for root, dirs, files in os.walk(plots_dir):
|
||||
if pattern in files:
|
||||
# Verificar si los parámetros clave están en la ruta
|
||||
if (f"nn-{nn}" in root or f"numberNodes-{nn}" in root) and (f"fr-{fr}" in root):
|
||||
full_path = os.path.join(root, pattern)
|
||||
if os.path.exists(full_path):
|
||||
return FileResponse(full_path)
|
||||
|
||||
# Si aún no encontramos, buscar en todos los subdirectorios recursivamente
|
||||
for root, dirs, files in os.walk(plots_dir):
|
||||
for file in files:
|
||||
if graph_type in file and file.endswith('.png'):
|
||||
return FileResponse(os.path.join(root, file))
|
||||
|
||||
# Si realmente no encontramos nada, devolver cualquier imagen como respaldo
|
||||
for root, dirs, files in os.walk(plots_dir):
|
||||
for file in files:
|
||||
if file.endswith('.png'):
|
||||
return FileResponse(os.path.join(root, file),
|
||||
headers={"X-Warning": "Requested graph not found, showing another graph"})
|
||||
|
||||
raise HTTPException(status_code=404, detail=f"Gráfico no encontrado para los parámetros especificados")
|
||||
raise HTTPException(status_code=404, detail=f"Graph not found for the specified parameters")
|
||||
|
||||
@app.get("/api/heatmap/{sim_id}/{heatmap_type}")
|
||||
async def get_heatmap(sim_id: str, heatmap_type: str):
|
||||
"""Devuelve la imagen del heatmap solicitado."""
|
||||
"""Return the requested heatmap image."""
|
||||
sim_dir = os.path.join(RESULTS_DIR, sim_id)
|
||||
|
||||
if not os.path.exists(sim_dir):
|
||||
raise HTTPException(status_code=404, detail=f"Simulación no encontrada: {sim_id}")
|
||||
raise HTTPException(status_code=404, detail=f"Simulation not found: {sim_id}")
|
||||
|
||||
# Mapear el tipo de heatmap a los nombres de archivo y carpetas
|
||||
heatmap_mapping = {
|
||||
"nodesVsFailure": ["nnVsfr", "nodeVsFailure", "failureRateVsnumberNodes"],
|
||||
"nodesVsChi": ["nnVschir", "nodeVsChi", "nnVscusr"],
|
||||
@ -293,18 +267,15 @@ async def get_heatmap(sim_id: str, heatmap_type: str):
|
||||
}
|
||||
|
||||
if heatmap_type not in heatmap_mapping:
|
||||
raise HTTPException(status_code=400, detail=f"Tipo de heatmap no válido: {heatmap_type}")
|
||||
raise HTTPException(status_code=400, detail=f"Invalid heatmap type: {heatmap_type}")
|
||||
|
||||
# Buscar archivos de heatmap en el directorio correspondiente
|
||||
heatmap_dir = os.path.join(sim_dir, "heatmaps")
|
||||
if not os.path.exists(heatmap_dir):
|
||||
# Si no hay directorio de heatmaps, buscar en la raíz de la simulación
|
||||
for pattern in heatmap_mapping[heatmap_type]:
|
||||
matching_files = glob.glob(os.path.join(sim_dir, f"*{pattern}*.png"))
|
||||
if matching_files:
|
||||
return FileResponse(matching_files[0])
|
||||
|
||||
# Si no hay heatmaps, buscar cualquier imagen
|
||||
all_images = []
|
||||
for root, dirs, files in os.walk(sim_dir):
|
||||
for file in files:
|
||||
@ -314,9 +285,8 @@ async def get_heatmap(sim_id: str, heatmap_type: str):
|
||||
if all_images:
|
||||
return FileResponse(all_images[0], media_type="image/png")
|
||||
|
||||
raise HTTPException(status_code=404, detail=f"No se encontraron heatmaps para la simulación")
|
||||
raise HTTPException(status_code=404, detail=f"No heatmaps found for the simulation")
|
||||
|
||||
# Buscar primero en subdirectorios específicos para los nuevos tipos de heatmap
|
||||
if heatmap_type in ["NWDegVsNodeOnRuntime", "NWDegVsMalNodeOnMissingSamples", "NWDegVsFailureRateOnMissingSamples"]:
|
||||
specific_dir = os.path.join(heatmap_dir, heatmap_type)
|
||||
if os.path.exists(specific_dir):
|
||||
@ -324,49 +294,40 @@ async def get_heatmap(sim_id: str, heatmap_type: str):
|
||||
if png_files:
|
||||
return FileResponse(png_files[0])
|
||||
|
||||
# Buscar con todas las variantes de nombres posibles
|
||||
possible_names = heatmap_mapping[heatmap_type]
|
||||
|
||||
# Primero buscar en subdirectorios específicos
|
||||
for pattern in possible_names:
|
||||
# Buscar directorios que contengan el patrón
|
||||
matching_dirs = [d for d in os.listdir(heatmap_dir)
|
||||
if os.path.isdir(os.path.join(heatmap_dir, d))
|
||||
and pattern.lower() in d.lower()]
|
||||
|
||||
for subdir in matching_dirs:
|
||||
# Buscar archivos PNG en este directorio
|
||||
png_files = glob.glob(os.path.join(heatmap_dir, subdir, "*.png"))
|
||||
if png_files:
|
||||
return FileResponse(png_files[0])
|
||||
|
||||
# Si no encontramos nada, buscar cualquier PNG en heatmaps
|
||||
for root, dirs, files in os.walk(heatmap_dir):
|
||||
for file in files:
|
||||
if file.endswith(".png"):
|
||||
return FileResponse(os.path.join(root, file))
|
||||
|
||||
# Si no encontramos ningún heatmap, verificar si hay alguna imagen en plots
|
||||
plots_dir = os.path.join(sim_dir, "plots")
|
||||
if os.path.exists(plots_dir):
|
||||
for root, dirs, files in os.walk(plots_dir):
|
||||
for file in files:
|
||||
if file.endswith(".png"):
|
||||
return FileResponse(os.path.join(root, file),
|
||||
headers={"X-Warning": "Heatmap no encontrado, mostrando otra imagen"})
|
||||
headers={"X-Warning": "Heatmap not found, showing another image"})
|
||||
|
||||
raise HTTPException(status_code=404, detail=f"No se encontró heatmap del tipo {heatmap_type}")
|
||||
raise HTTPException(status_code=404, detail=f"No heatmap of type {heatmap_type} found")
|
||||
|
||||
@app.get("/api/stats/{sim_id}")
|
||||
async def get_simulation_stats(sim_id: str):
|
||||
"""Obtiene estadísticas para la simulación especificada."""
|
||||
"""Get statistics for the specified simulation."""
|
||||
sim_dir = os.path.join(RESULTS_DIR, sim_id)
|
||||
|
||||
if not os.path.exists(sim_dir):
|
||||
raise HTTPException(status_code=404, detail=f"Simulación no encontrada: {sim_id}")
|
||||
|
||||
# Aquí deberías procesar los archivos XML y generar estadísticas reales
|
||||
# Por ahora, retornaremos datos de muestra similares a los datos de prueba del frontend
|
||||
raise HTTPException(status_code=404, detail=f"Simulation not found: {sim_id}")
|
||||
|
||||
def generate_stat_data(prefix, count, min_val=10, max_val=90):
|
||||
import random
|
||||
@ -386,7 +347,6 @@ async def get_simulation_stats(sim_id: str):
|
||||
for i in range(count)
|
||||
]
|
||||
|
||||
# Estos datos deberían generarse procesando los resultados reales
|
||||
stats = {
|
||||
"byNodes": {
|
||||
"missingSamples": generate_stat_data("Nodes: ", 4),
|
||||
|
||||