feat: creating readme file and adjusting comments

This commit is contained in:
DiegoB1911 2025-03-12 21:13:23 -06:00
parent 04010a04d0
commit 2885af92a2
No known key found for this signature in database
GPG Key ID: 4F405C71255D3527
46 changed files with 283 additions and 393 deletions

6
.gitignore vendored
View File

@ -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

View 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
```

View File

Before

Width:  |  Height:  |  Size: 25 KiB

After

Width:  |  Height:  |  Size: 25 KiB

View File

@ -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,
}))

View File

@ -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,

View File

@ -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) {

View File

@ -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";

View File

@ -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
}

View File

@ -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>;
}

View File

@ -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(),

View File

@ -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) {

View 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;
}
};

View File

Before

Width:  |  Height:  |  Size: 391 B

After

Width:  |  Height:  |  Size: 391 B

View File

Before

Width:  |  Height:  |  Size: 1.0 KiB

After

Width:  |  Height:  |  Size: 1.0 KiB

View File

Before

Width:  |  Height:  |  Size: 1.3 KiB

After

Width:  |  Height:  |  Size: 1.3 KiB

View File

Before

Width:  |  Height:  |  Size: 128 B

After

Width:  |  Height:  |  Size: 128 B

View File

Before

Width:  |  Height:  |  Size: 385 B

After

Width:  |  Height:  |  Size: 385 B

View File

@ -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),
},
};
};

View File

@ -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),