Initial commit: Sistema Autoparts DB

- Base de datos SQLite con información de vehículos
- Dashboard web con Flask y Bootstrap
- Scripts de web scraping para RockAuto
- Interfaz CLI para consultas
- Documentación completa del proyecto

Incluye:
- 12 marcas de vehículos
- 10,923 modelos
- 10,919 especificaciones de motores
- 12,075 combinaciones modelo-año-motor
This commit is contained in:
2026-01-19 08:45:03 +00:00
commit f395d67136
59 changed files with 10881 additions and 0 deletions

79
dashboard/README.md Normal file
View File

@@ -0,0 +1,79 @@
# Vehicle Database Dashboard
A web-based dashboard for searching and filtering vehicle data from your database.
## Features
- Filter vehicles by brand, model, year, and engine
- Responsive web interface with Bootstrap
- Real-time filtering and search
- Detailed vehicle information display
- Modern UI with cards and badges
## Prerequisites
- Python 3.x
- Flask (installed via `sudo apt-get install python3-flask`)
- SQLite database with vehicle data (created in the vehicle_database directory)
## Setup
1. Make sure you have the vehicle database created in the `../vehicle_database/vehicle_database.db` path
2. Install Flask: `sudo apt-get install python3-flask`
3. Run the dashboard server: `python3 server.py`
## Usage
1. Start the server:
```bash
cd dashboard
python3 server.py
```
2. Open your web browser and navigate to `http://localhost:5000`
3. Use the filters on the left panel to search for vehicles:
- Select a brand from the dropdown
- Select a model (based on the selected brand)
- Select a year
- Select an engine type
- Click "Search Vehicles" to apply filters
4. The results will appear in the right panel with detailed information
## API Endpoints
The dashboard uses the following API endpoints:
- `GET /api/brands` - Get all vehicle brands
- `GET /api/models?brand=[brand]` - Get models for a specific brand
- `GET /api/years` - Get all years
- `GET /api/engines` - Get all engines
- `GET /api/vehicles?[filters]` - Search vehicles with optional filters
## File Structure
```
dashboard/
├── index.html # Main dashboard page
├── dashboard.js # Frontend JavaScript
├── server.py # Flask backend
├── requirements.txt # Python dependencies
├── start_dashboard.sh # Startup script
└── README.md # This file
```
## Customization
You can customize the dashboard by:
- Modifying the CSS styles in index.html
- Adding more filters in the JavaScript
- Changing the layout in index.html
- Adding more vehicle details in the display
## Troubleshooting
- If the server won't start, make sure the vehicle database exists
- If filters don't populate, check that the database has data
- If the page doesn't load, verify that Flask is installed correctly

440
dashboard/dashboard.js Normal file
View File

@@ -0,0 +1,440 @@
// Vehicle Dashboard JavaScript - Navegación por tarjetas
class VehicleDashboard {
constructor() {
this.currentView = 'brands'; // brands, models, vehicles
this.selectedBrand = null;
this.selectedModel = null;
this.allVehicles = [];
this.filteredVehicles = [];
this.stats = { brands: 0, models: 0, vehicles: 0 };
this.init();
}
async init() {
await this.loadStats();
await this.showBrands();
this.bindFilterEvents();
}
async loadStats() {
try {
const [brandsRes, vehiclesRes] = await Promise.all([
fetch('/api/brands'),
fetch('/api/vehicles')
]);
if (brandsRes.ok && vehiclesRes.ok) {
const brands = await brandsRes.json();
const vehicles = await vehiclesRes.json();
// Contar modelos únicos
const uniqueModels = new Set(vehicles.map(v => `${v.brand}-${v.model}`));
this.stats.brands = brands.length;
this.stats.models = uniqueModels.size;
this.stats.vehicles = vehicles.length;
document.getElementById('totalBrands').textContent = this.stats.brands;
document.getElementById('totalModels').textContent = this.stats.models;
document.getElementById('totalVehicles').textContent = this.stats.vehicles;
}
} catch (error) {
console.error('Error loading stats:', error);
}
}
updateBreadcrumb() {
const breadcrumb = document.getElementById('breadcrumb');
let html = '';
if (this.currentView === 'brands') {
html = `<li class="breadcrumb-item active"><i class="fas fa-home"></i> Marcas</li>`;
} else if (this.currentView === 'models') {
html = `
<li class="breadcrumb-item">
<a href="#" onclick="dashboard.goToBrands(); return false;">
<i class="fas fa-home"></i> Marcas
</a>
</li>
<li class="breadcrumb-item active">${this.selectedBrand}</li>
`;
} else if (this.currentView === 'vehicles') {
html = `
<li class="breadcrumb-item">
<a href="#" onclick="dashboard.goToBrands(); return false;">
<i class="fas fa-home"></i> Marcas
</a>
</li>
<li class="breadcrumb-item">
<a href="#" onclick="dashboard.goToModels('${this.selectedBrand}'); return false;">
${this.selectedBrand}
</a>
</li>
<li class="breadcrumb-item active">${this.selectedModel}</li>
`;
}
breadcrumb.innerHTML = html;
}
async showBrands() {
this.currentView = 'brands';
this.selectedBrand = null;
this.selectedModel = null;
this.updateBreadcrumb();
this.hideFilters();
const container = document.getElementById('mainContent');
container.innerHTML = `
<div class="loading-state">
<i class="fas fa-spinner fa-spin"></i>
<h4>Cargando marcas...</h4>
</div>
`;
try {
const [brandsRes, vehiclesRes] = await Promise.all([
fetch('/api/brands'),
fetch('/api/vehicles')
]);
if (!brandsRes.ok || !vehiclesRes.ok) {
throw new Error('Error al cargar datos');
}
const brands = await brandsRes.json();
const vehicles = await vehiclesRes.json();
// Contar modelos y vehículos por marca
const brandStats = {};
brands.forEach(brand => {
brandStats[brand] = { models: new Set(), vehicles: 0 };
});
vehicles.forEach(v => {
if (brandStats[v.brand]) {
brandStats[v.brand].models.add(v.model);
brandStats[v.brand].vehicles++;
}
});
if (brands.length === 0) {
container.innerHTML = `
<div class="empty-state">
<i class="fas fa-car"></i>
<h4>No hay marcas disponibles</h4>
<p>Agrega algunas marcas a la base de datos</p>
</div>
`;
return;
}
container.innerHTML = `<div class="content-grid brands-grid">
${brands.map(brand => `
<div class="brand-card" onclick="dashboard.goToModels('${brand}')">
<div class="brand-icon">
<i class="fas fa-car"></i>
</div>
<div class="brand-name">${brand}</div>
<div class="brand-count">
${brandStats[brand].models.size} modelos
</div>
<div class="brand-count">
${brandStats[brand].vehicles} vehículos
</div>
</div>
`).join('')}
</div>`;
} catch (error) {
console.error('Error:', error);
container.innerHTML = `
<div class="empty-state">
<i class="fas fa-exclamation-triangle"></i>
<h4>Error al cargar marcas</h4>
<p>${error.message}</p>
</div>
`;
}
}
async goToModels(brand) {
this.currentView = 'models';
this.selectedBrand = brand;
this.selectedModel = null;
this.updateBreadcrumb();
this.hideFilters();
const container = document.getElementById('mainContent');
container.innerHTML = `
<div class="loading-state">
<i class="fas fa-spinner fa-spin"></i>
<h4>Cargando modelos de ${brand}...</h4>
</div>
`;
try {
const [modelsRes, vehiclesRes] = await Promise.all([
fetch(`/api/models?brand=${encodeURIComponent(brand)}`),
fetch(`/api/vehicles?brand=${encodeURIComponent(brand)}`)
]);
if (!modelsRes.ok || !vehiclesRes.ok) {
throw new Error('Error al cargar datos');
}
const models = await modelsRes.json();
const vehicles = await vehiclesRes.json();
// Contar vehículos y años por modelo
const modelStats = {};
models.forEach(model => {
modelStats[model] = { years: new Set(), vehicles: 0, engines: new Set() };
});
vehicles.forEach(v => {
if (modelStats[v.model]) {
modelStats[v.model].years.add(v.year);
modelStats[v.model].vehicles++;
modelStats[v.model].engines.add(v.engine);
}
});
if (models.length === 0) {
container.innerHTML = `
<div class="empty-state">
<i class="fas fa-car-side"></i>
<h4>No hay modelos para ${brand}</h4>
<p>Esta marca no tiene modelos registrados</p>
<button class="btn btn-back mt-3" onclick="dashboard.goToBrands()">
<i class="fas fa-arrow-left"></i> Volver a marcas
</button>
</div>
`;
return;
}
container.innerHTML = `<div class="content-grid models-grid">
${models.map(model => {
const stats = modelStats[model];
const yearsArray = Array.from(stats.years).sort((a, b) => b - a);
const yearRange = yearsArray.length > 0
? (yearsArray.length > 1
? `${yearsArray[yearsArray.length - 1]} - ${yearsArray[0]}`
: `${yearsArray[0]}`)
: 'N/A';
return `
<div class="model-card" onclick="dashboard.goToVehicles('${brand}', '${model}')">
<div class="model-name">${model}</div>
<div class="model-info">
<i class="fas fa-calendar-alt"></i> ${yearRange}
</div>
<div class="model-info">
<i class="fas fa-cogs"></i> ${stats.engines.size} motores
</div>
<div class="model-info">
<i class="fas fa-list"></i> ${stats.vehicles} variantes
</div>
</div>
`;
}).join('')}
</div>`;
} catch (error) {
console.error('Error:', error);
container.innerHTML = `
<div class="empty-state">
<i class="fas fa-exclamation-triangle"></i>
<h4>Error al cargar modelos</h4>
<p>${error.message}</p>
<button class="btn btn-back mt-3" onclick="dashboard.goToBrands()">
<i class="fas fa-arrow-left"></i> Volver a marcas
</button>
</div>
`;
}
}
async goToVehicles(brand, model) {
this.currentView = 'vehicles';
this.selectedBrand = brand;
this.selectedModel = model;
this.updateBreadcrumb();
this.showFilters();
const container = document.getElementById('mainContent');
container.innerHTML = `
<div class="loading-state">
<i class="fas fa-spinner fa-spin"></i>
<h4>Cargando vehículos...</h4>
</div>
`;
try {
const response = await fetch(
`/api/vehicles?brand=${encodeURIComponent(brand)}&model=${encodeURIComponent(model)}`
);
if (!response.ok) {
throw new Error('Error al cargar vehículos');
}
this.allVehicles = await response.json();
this.filteredVehicles = [...this.allVehicles];
// Poblar filtros
await this.populateFilters(brand, model);
this.displayVehicles();
} catch (error) {
console.error('Error:', error);
container.innerHTML = `
<div class="empty-state">
<i class="fas fa-exclamation-triangle"></i>
<h4>Error al cargar vehículos</h4>
<p>${error.message}</p>
<button class="btn btn-back mt-3" onclick="dashboard.goToModels('${brand}')">
<i class="fas fa-arrow-left"></i> Volver a modelos
</button>
</div>
`;
}
}
async populateFilters(brand, model) {
try {
const [yearsRes, enginesRes] = await Promise.all([
fetch(`/api/years?brand=${encodeURIComponent(brand)}&model=${encodeURIComponent(model)}`),
fetch(`/api/engines?brand=${encodeURIComponent(brand)}&model=${encodeURIComponent(model)}`)
]);
if (yearsRes.ok) {
const years = await yearsRes.json();
const yearFilter = document.getElementById('yearFilter');
yearFilter.innerHTML = '<option value="">Todos los años</option>';
years.forEach(year => {
yearFilter.innerHTML += `<option value="${year}">${year}</option>`;
});
}
if (enginesRes.ok) {
const engines = await enginesRes.json();
const engineFilter = document.getElementById('engineFilter');
engineFilter.innerHTML = '<option value="">Todos los motores</option>';
engines.forEach(engine => {
engineFilter.innerHTML += `<option value="${engine}">${engine}</option>`;
});
}
} catch (error) {
console.error('Error populating filters:', error);
}
}
bindFilterEvents() {
document.getElementById('yearFilter').addEventListener('change', () => {
this.applyFilters();
});
document.getElementById('engineFilter').addEventListener('change', () => {
this.applyFilters();
});
}
applyFilters() {
const year = document.getElementById('yearFilter').value;
const engine = document.getElementById('engineFilter').value;
this.filteredVehicles = this.allVehicles.filter(v => {
return (!year || v.year.toString() === year) &&
(!engine || v.engine === engine);
});
this.displayVehicles();
}
displayVehicles() {
const container = document.getElementById('mainContent');
const resultCount = document.getElementById('resultCount');
resultCount.textContent = `${this.filteredVehicles.length} resultado${this.filteredVehicles.length !== 1 ? 's' : ''}`;
if (this.filteredVehicles.length === 0) {
container.innerHTML = `
<div class="empty-state">
<i class="fas fa-car"></i>
<h4>No se encontraron vehículos</h4>
<p>Intenta ajustar los filtros</p>
</div>
`;
return;
}
container.innerHTML = `<div class="content-grid vehicles-grid">
${this.filteredVehicles.map(v => `
<div class="vehicle-card">
<div class="vehicle-header">
<div class="vehicle-title">${v.year} ${v.brand} ${v.model}</div>
<div class="vehicle-engine">${v.engine}</div>
</div>
<div class="vehicle-body">
<div class="vehicle-specs">
<div class="spec-item">
<i class="fas fa-gas-pump"></i>
<div class="spec-value">${v.fuel_type || 'N/A'}</div>
</div>
<div class="spec-item">
<i class="fas fa-tachometer-alt"></i>
<div class="spec-value">${v.power_hp || 0} HP</div>
</div>
<div class="spec-item">
<i class="fas fa-cogs"></i>
<div class="spec-value">${v.transmission || 'N/A'}</div>
</div>
<div class="spec-item">
<i class="fas fa-road"></i>
<div class="spec-value">${v.drivetrain || 'N/A'}</div>
</div>
<div class="spec-item">
<i class="fas fa-cube"></i>
<div class="spec-value">${v.cylinders || 0} cil.</div>
</div>
<div class="spec-item">
<i class="fas fa-oil-can"></i>
<div class="spec-value">${v.displacement_cc || 0} cc</div>
</div>
</div>
${v.trim_level && v.trim_level !== 'unknown' ? `
<div class="mt-2 text-center">
<span class="badge bg-primary">${v.trim_level}</span>
</div>
` : ''}
</div>
</div>
`).join('')}
</div>`;
}
goToBrands() {
this.showBrands();
}
showFilters() {
document.getElementById('filtersBar').style.display = 'block';
// Reset filters
document.getElementById('yearFilter').value = '';
document.getElementById('engineFilter').value = '';
}
hideFilters() {
document.getElementById('filtersBar').style.display = 'none';
}
}
// Initialize dashboard globally
let dashboard;
document.addEventListener('DOMContentLoaded', () => {
dashboard = new VehicleDashboard();
});

367
dashboard/index.html Normal file
View File

@@ -0,0 +1,367 @@
<!DOCTYPE html>
<html lang="es">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Base de Datos de Vehículos</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0/css/all.min.css">
<style>
:root {
--primary-color: #2c3e50;
--secondary-color: #3498db;
--accent-color: #e74c3c;
--bg-color: #ecf0f1;
}
body {
background-color: var(--bg-color);
min-height: 100vh;
}
.dashboard-header {
background: linear-gradient(135deg, var(--primary-color), #1a252f);
color: white;
padding: 2rem 0;
margin-bottom: 2rem;
}
.breadcrumb-nav {
background: white;
padding: 1rem 1.5rem;
border-radius: 10px;
margin-bottom: 1.5rem;
box-shadow: 0 2px 10px rgba(0,0,0,0.1);
}
.breadcrumb-nav .breadcrumb {
margin-bottom: 0;
}
.breadcrumb-nav .breadcrumb-item a {
color: var(--secondary-color);
text-decoration: none;
font-weight: 500;
}
.breadcrumb-nav .breadcrumb-item a:hover {
color: var(--primary-color);
}
.breadcrumb-nav .breadcrumb-item.active {
color: var(--primary-color);
font-weight: 600;
}
/* Tarjetas de marcas */
.brand-card {
background: white;
border-radius: 15px;
padding: 2rem;
text-align: center;
transition: all 0.3s ease;
cursor: pointer;
border: 2px solid transparent;
height: 100%;
}
.brand-card:hover {
transform: translateY(-10px);
box-shadow: 0 15px 30px rgba(0,0,0,0.15);
border-color: var(--secondary-color);
}
.brand-card .brand-icon {
width: 80px;
height: 80px;
background: linear-gradient(135deg, var(--secondary-color), #2980b9);
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
margin: 0 auto 1rem;
font-size: 2rem;
color: white;
}
.brand-card .brand-name {
font-size: 1.3rem;
font-weight: 700;
color: var(--primary-color);
margin-bottom: 0.5rem;
}
.brand-card .brand-count {
color: #7f8c8d;
font-size: 0.9rem;
}
.brand-card .brand-country {
font-size: 0.85rem;
color: #95a5a6;
margin-top: 0.5rem;
}
/* Tarjetas de modelos */
.model-card {
background: white;
border-radius: 12px;
padding: 1.5rem;
transition: all 0.3s ease;
cursor: pointer;
border: 2px solid transparent;
height: 100%;
}
.model-card:hover {
transform: translateY(-5px);
box-shadow: 0 10px 25px rgba(0,0,0,0.12);
border-color: var(--secondary-color);
}
.model-card .model-name {
font-size: 1.2rem;
font-weight: 600;
color: var(--primary-color);
margin-bottom: 0.5rem;
}
.model-card .model-info {
color: #7f8c8d;
font-size: 0.9rem;
}
/* Tarjetas de vehículos */
.vehicle-card {
background: white;
border-radius: 12px;
overflow: hidden;
transition: all 0.3s ease;
height: 100%;
}
.vehicle-card:hover {
transform: translateY(-5px);
box-shadow: 0 10px 25px rgba(0,0,0,0.12);
}
.vehicle-header {
background: linear-gradient(135deg, var(--primary-color), #1a252f);
color: white;
padding: 1.2rem;
}
.vehicle-header .vehicle-title {
font-size: 1.1rem;
font-weight: 600;
margin-bottom: 0.3rem;
}
.vehicle-header .vehicle-engine {
font-size: 0.9rem;
opacity: 0.9;
}
.vehicle-body {
padding: 1rem;
}
.vehicle-specs {
display: grid;
grid-template-columns: repeat(2, 1fr);
gap: 0.8rem;
}
.spec-item {
background: var(--bg-color);
padding: 0.6rem;
border-radius: 8px;
text-align: center;
font-size: 0.85rem;
}
.spec-item i {
color: var(--secondary-color);
margin-bottom: 0.3rem;
display: block;
}
.spec-item .spec-value {
font-weight: 600;
color: var(--primary-color);
}
/* Filtros en vista de vehículos */
.filters-bar {
background: white;
padding: 1rem 1.5rem;
border-radius: 10px;
margin-bottom: 1.5rem;
box-shadow: 0 2px 10px rgba(0,0,0,0.1);
}
.filters-bar .form-select {
border-radius: 8px;
}
/* Grid de contenido */
.content-grid {
display: grid;
gap: 1.5rem;
}
.content-grid.brands-grid {
grid-template-columns: repeat(auto-fill, minmax(220px, 1fr));
}
.content-grid.models-grid {
grid-template-columns: repeat(auto-fill, minmax(250px, 1fr));
}
.content-grid.vehicles-grid {
grid-template-columns: repeat(auto-fill, minmax(320px, 1fr));
}
/* Estados */
.loading-state, .empty-state {
text-align: center;
padding: 4rem 2rem;
color: #7f8c8d;
}
.loading-state i, .empty-state i {
font-size: 4rem;
margin-bottom: 1rem;
color: #bdc3c7;
}
/* Botón volver */
.btn-back {
background: var(--secondary-color);
color: white;
border: none;
padding: 0.5rem 1.5rem;
border-radius: 8px;
font-weight: 500;
transition: all 0.3s;
}
.btn-back:hover {
background: #2980b9;
color: white;
}
/* Estadísticas */
.stats-bar {
display: flex;
gap: 2rem;
flex-wrap: wrap;
}
.stat-item {
text-align: center;
}
.stat-item .stat-number {
font-size: 2rem;
font-weight: 700;
color: white;
}
.stat-item .stat-label {
font-size: 0.9rem;
opacity: 0.8;
}
@media (max-width: 768px) {
.content-grid.brands-grid {
grid-template-columns: repeat(2, 1fr);
}
.stats-bar {
justify-content: center;
}
}
@media (max-width: 480px) {
.content-grid.brands-grid {
grid-template-columns: 1fr;
}
}
</style>
</head>
<body>
<div class="dashboard-header">
<div class="container">
<div class="row align-items-center">
<div class="col-md-6">
<h1><i class="fas fa-car-side"></i> Base de Datos de Vehículos</h1>
<p class="lead mb-0">Explora vehículos por marca, modelo y especificaciones</p>
</div>
<div class="col-md-6">
<div class="stats-bar justify-content-md-end">
<div class="stat-item">
<div class="stat-number" id="totalBrands">0</div>
<div class="stat-label">Marcas</div>
</div>
<div class="stat-item">
<div class="stat-number" id="totalModels">0</div>
<div class="stat-label">Modelos</div>
</div>
<div class="stat-item">
<div class="stat-number" id="totalVehicles">0</div>
<div class="stat-label">Vehículos</div>
</div>
</div>
</div>
</div>
</div>
</div>
<div class="container">
<!-- Breadcrumb navegación -->
<div class="breadcrumb-nav" id="breadcrumbNav">
<nav aria-label="breadcrumb">
<ol class="breadcrumb" id="breadcrumb">
<li class="breadcrumb-item active">
<i class="fas fa-home"></i> Marcas
</li>
</ol>
</nav>
</div>
<!-- Barra de filtros (solo visible en vista de vehículos) -->
<div class="filters-bar" id="filtersBar" style="display: none;">
<div class="row g-3 align-items-center">
<div class="col-auto">
<label class="col-form-label fw-bold">Filtrar por:</label>
</div>
<div class="col-md-3">
<select id="yearFilter" class="form-select">
<option value="">Todos los años</option>
</select>
</div>
<div class="col-md-3">
<select id="engineFilter" class="form-select">
<option value="">Todos los motores</option>
</select>
</div>
<div class="col-auto">
<span id="resultCount" class="badge bg-secondary fs-6">0 resultados</span>
</div>
</div>
</div>
<!-- Contenedor principal -->
<div id="mainContent">
<div class="loading-state">
<i class="fas fa-spinner fa-spin"></i>
<h4>Cargando...</h4>
</div>
</div>
</div>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>
<script src="dashboard.js"></script>
</body>
</html>

View File

@@ -0,0 +1 @@
Flask==2.3.3

248
dashboard/server.py Normal file
View File

@@ -0,0 +1,248 @@
from flask import Flask, render_template, jsonify, request, send_from_directory
import sqlite3
import os
app = Flask(__name__, static_folder='.')
# Database path - adjust as needed
DATABASE_PATH = '../vehicle_database/vehicle_database.db'
def get_db_connection():
"""Get a connection to the vehicle database"""
conn = sqlite3.connect(DATABASE_PATH)
conn.row_factory = sqlite3.Row # This enables column access by name
return conn
def get_all_brands():
"""Get all unique brands from the database"""
conn = get_db_connection()
cursor = conn.cursor()
cursor.execute("SELECT DISTINCT name FROM brands ORDER BY name")
brands = [row['name'] for row in cursor.fetchall()]
conn.close()
return brands
def get_all_years():
"""Get all unique years from the database"""
conn = get_db_connection()
cursor = conn.cursor()
cursor.execute("SELECT DISTINCT year FROM years ORDER BY year DESC")
years = [row['year'] for row in cursor.fetchall()]
conn.close()
return years
def get_all_engines():
"""Get all unique engines from the database"""
conn = get_db_connection()
cursor = conn.cursor()
cursor.execute("SELECT DISTINCT name FROM engines ORDER BY name")
engines = [row['name'] for row in cursor.fetchall()]
conn.close()
return engines
def get_models_by_brand(brand_name=None):
"""Get all models, optionally filtered by brand"""
conn = get_db_connection()
cursor = conn.cursor()
if brand_name:
cursor.execute("""
SELECT DISTINCT m.name
FROM models m
JOIN brands b ON m.brand_id = b.id
WHERE b.name = ?
ORDER BY m.name
""", (brand_name,))
else:
cursor.execute("SELECT DISTINCT name FROM models ORDER BY name")
models = [row['name'] for row in cursor.fetchall()]
conn.close()
return models
def search_vehicles(brand=None, model=None, year=None, engine=None):
"""Search for vehicles based on filters"""
conn = get_db_connection()
cursor = conn.cursor()
query = """
SELECT
b.name AS brand,
m.name AS model,
y.year,
e.name AS engine,
e.power_hp,
e.displacement_cc,
e.cylinders,
e.fuel_type,
mye.trim_level,
mye.drivetrain,
mye.transmission
FROM model_year_engine mye
JOIN models m ON mye.model_id = m.id
JOIN brands b ON m.brand_id = b.id
JOIN years y ON mye.year_id = y.id
JOIN engines e ON mye.engine_id = e.id
WHERE 1=1
"""
params = []
if brand:
query += " AND b.name = ?"
params.append(brand)
if model:
query += " AND m.name = ?"
params.append(model)
if year:
query += " AND y.year = ?"
params.append(int(year))
if engine:
query += " AND e.name = ?"
params.append(engine)
query += " ORDER BY b.name, m.name, y.year"
cursor.execute(query, params)
results = cursor.fetchall()
conn.close()
# Convert to list of dictionaries
vehicles = []
for row in results:
vehicle = {
'brand': row['brand'],
'model': row['model'],
'year': row['year'],
'engine': row['engine'],
'power_hp': row['power_hp'] or 0,
'displacement_cc': row['displacement_cc'] or 0,
'cylinders': row['cylinders'] or 0,
'fuel_type': row['fuel_type'] or 'unknown',
'trim_level': row['trim_level'] or 'unknown',
'drivetrain': row['drivetrain'] or 'unknown',
'transmission': row['transmission'] or 'unknown'
}
vehicles.append(vehicle)
return vehicles
@app.route('/')
def index():
"""Serve the main dashboard page"""
return send_from_directory('.', 'index.html')
@app.route('/<path:path>')
def static_files(path):
"""Serve static files"""
return send_from_directory('.', path)
@app.route('/api/brands')
def api_brands():
"""API endpoint to get all brands"""
brands = get_all_brands()
return jsonify(brands)
@app.route('/api/years')
def api_years():
"""API endpoint to get years, optionally filtered by brand and/or model"""
brand = request.args.get('brand')
model = request.args.get('model')
conn = get_db_connection()
cursor = conn.cursor()
query = """
SELECT DISTINCT y.year
FROM years y
JOIN model_year_engine mye ON y.id = mye.year_id
JOIN models m ON mye.model_id = m.id
JOIN brands b ON m.brand_id = b.id
WHERE 1=1
"""
params = []
if brand:
query += " AND b.name = ?"
params.append(brand)
if model:
query += " AND m.name = ?"
params.append(model)
query += " ORDER BY y.year DESC"
cursor.execute(query, params)
results = cursor.fetchall()
conn.close()
years = [row['year'] for row in results]
return jsonify(years)
@app.route('/api/engines')
def api_engines():
"""API endpoint to get engines, optionally filtered by brand, model, and/or year"""
brand = request.args.get('brand')
model = request.args.get('model')
year = request.args.get('year')
conn = get_db_connection()
cursor = conn.cursor()
query = """
SELECT DISTINCT e.name
FROM engines e
JOIN model_year_engine mye ON e.id = mye.engine_id
JOIN models m ON mye.model_id = m.id
JOIN brands b ON m.brand_id = b.id
JOIN years y ON mye.year_id = y.id
WHERE 1=1
"""
params = []
if brand:
query += " AND b.name = ?"
params.append(brand)
if model:
query += " AND m.name = ?"
params.append(model)
if year:
query += " AND y.year = ?"
params.append(int(year))
query += " ORDER BY e.name"
cursor.execute(query, params)
results = cursor.fetchall()
conn.close()
engines = [row['name'] for row in results]
return jsonify(engines)
@app.route('/api/models')
def api_models():
"""API endpoint to get models, optionally filtered by brand"""
brand = request.args.get('brand')
models = get_models_by_brand(brand)
return jsonify(models)
@app.route('/api/vehicles')
def api_vehicles():
"""API endpoint to search for vehicles"""
brand = request.args.get('brand')
model = request.args.get('model')
year = request.args.get('year')
engine = request.args.get('engine')
vehicles = search_vehicles(brand, model, year, engine)
return jsonify(vehicles)
if __name__ == '__main__':
# Check if database exists
if not os.path.exists(DATABASE_PATH):
print(f"Database not found at {DATABASE_PATH}")
print("Please make sure the vehicle database is created first.")
exit(1)
print("Starting Vehicle Dashboard Server...")
print("Visit http://localhost:5000 to access the dashboard locally")
print("Visit http://192.168.10.198:5000 to access the dashboard from other computers on the network")
app.run(debug=True, host='0.0.0.0', port=5000)

30
dashboard/start_dashboard.sh Executable file
View File

@@ -0,0 +1,30 @@
#!/bin/bash
# Startup script for Vehicle Dashboard
echo "Vehicle Dashboard Startup Script"
echo "================================"
# Check if the vehicle database exists
if [ ! -f "../vehicle_database/vehicle_database.db" ]; then
echo "Error: Vehicle database not found!"
echo "Please make sure you have created the vehicle database first."
exit 1
fi
echo "Vehicle database found."
# Check if Flask is available
if python3 -c "import flask" &> /dev/null; then
echo "Flask is available."
else
echo "Installing Flask..."
sudo apt-get update
sudo apt-get install -y python3-flask
fi
echo "Starting Vehicle Dashboard Server..."
echo "Access the dashboard at: http://localhost:5000"
echo "Press Ctrl+C to stop the server."
cd /home/Autopartes/dashboard
python3 server.py