Skip to main content

Overview

CoroNet stores all vehicle registrations in a CSV-based database system, providing a lightweight yet robust solution for managing vehicle entry records. The system supports full CRUD operations (Create, Read, Update, Delete) with automatic ID assignment and file-based persistence.

Data Model

Each vehicle registration contains seven fields:
CSV_HEADER = [
    "id",           # Auto-incremented unique identifier
    "fecha_hora",   # Timestamp of registration
    "matricula",    # Detected license plate number
    "propietario",  # Vehicle owner name
    "tipo_vehiculo",# Vehicle type (car, truck, motorcycle, etc.)
    "observacion",  # Optional notes
    "imagen"        # Filename of the uploaded image
]
The CSV storage format (defined at app.py:24) provides human-readable data that can be easily imported into spreadsheet applications or other systems.

Storage Architecture

File System Structure

CoroNet maintains two primary storage locations:
BASE_DIR = os.path.dirname(__file__)
DATA_PATH = os.path.join(BASE_DIR, "data", "registros.csv")
UPLOADS_DIR = os.path.join(BASE_DIR, "uploads")
  • data/registros.csv: Main CSV database file containing all registration records (app.py:18)
  • uploads/: Directory storing all uploaded vehicle images (app.py:19)
Both directories are automatically created at application startup if they don’t exist (app.py:21-22).

CSV File Management

The system implements three core functions for CSV operations:
Guarantees the CSV file exists with proper headers:
def ensure_csv():
    if not os.path.exists(DATA_PATH):
        with open(DATA_PATH, "w", newline="", encoding="utf-8") as f:
            csv.writer(f).writerow(CSV_HEADER)
  • Checks if the CSV file exists (app.py:27)
  • Creates new file with headers if missing (app.py:28-29)
  • Uses UTF-8 encoding for international character support
  • Called before any read/write operation to prevent errors
Loads all registrations from the CSV file:
def read_csv():
    ensure_csv()
    with open(DATA_PATH, "r", encoding="utf-8") as f:
        reader = list(csv.DictReader(f))
    return reader
  • Ensures CSV exists before reading (app.py:32)
  • Uses DictReader for dictionary-based row access (app.py:34)
  • Returns list of dictionaries, each representing one registration
  • Keys correspond to CSV_HEADER field names
Writes complete dataset back to CSV file:
def write_csv(rows):
    with open(DATA_PATH, "w", newline="", encoding="utf-8") as f:
        writer = csv.DictWriter(f, fieldnames=CSV_HEADER)
        writer.writeheader()
        writer.writerows(rows)
  • Overwrites entire CSV file with provided rows (app.py:38-41)
  • Uses DictWriter for consistent field ordering
  • Writes header row before data rows
  • Ensures atomic updates (read-modify-write pattern)

CRUD Operations

Create: Adding New Registrations

New registrations are created through the /guardar endpoint:
@app.route("/guardar", methods=["POST"])
def guardar():
    ensure_csv()
    file = request.files.get("imagen")
    propietario = request.form.get("propietario", "")
    tipo = request.form.get("tipo_vehiculo", "")
    obs = request.form.get("observacion", "")

    # Validate image upload
    if not file:
        flash("⚠️ Debes subir una imagen", "error")
        return redirect(url_for("index"))

    # Save image with timestamp
    filename = f"matricula_{datetime.now().strftime('%Y%m%d_%H%M%S')}.jpg"
    path = os.path.join(UPLOADS_DIR, filename)
    file.save(path)

    # Detect license plate (GPT-4o + Tesseract fallback)
    matricula = extract_plate_from_image(path)
    if matricula == "NO_DETECTADA":
        image = Image.open(path)
        ocr_text = pytesseract.image_to_string(image, lang="eng")
        ocr_text = ocr_text.strip().replace(" ", "").replace("\n", "").upper()
        matricula = "".join([c for c in ocr_text if c.isalnum() or c == "-"])[:10]

    # Load existing records
    rows = read_csv()
    
    # Auto-increment ID
    new_id = str(len(rows) + 1)
    fecha = datetime.now().strftime("%Y-%m-%d %H:%M:%S")

    # Append new record
    rows.append({
        "id": new_id,
        "fecha_hora": fecha,
        "matricula": matricula,
        "propietario": propietario,
        "tipo_vehiculo": tipo,
        "observacion": obs,
        "imagen": filename
    })
    
    # Persist to CSV
    write_csv(rows)

    flash("✅ Registro guardado correctamente", "ok")
    return redirect(url_for("index"))
Key features of the create operation:
  • Auto-incrementing IDs: New ID calculated as len(rows) + 1 (app.py:113)
  • Automatic timestamps: Current date/time in YYYY-MM-DD HH:MM:SS format (app.py:114)
  • Image validation: Requires image upload before processing (app.py:94-96)
  • Dual OCR detection: Attempts GPT-4o first, falls back to Tesseract (app.py:103-110)
  • User feedback: Flash messages confirm successful save (app.py:127)
The system uses a read-modify-write pattern: load all records, append new record, write all records back. This ensures data integrity but may have performance implications for very large datasets.

Read: Viewing Registrations

The /registros endpoint displays all stored registrations:
@app.route("/registros")
def registros():
    data = read_csv()
    return render_template("registros.html", registros=data)
  • Loads entire dataset using read_csv() (app.py:132)
  • Passes data to template for rendering (app.py:133)
  • Returns list of dictionaries, one per registration
Image access endpoint:
@app.route("/uploads/<filename>")
def uploaded_file(filename):
    return send_from_directory(UPLOADS_DIR, filename)
Serves uploaded images directly from the uploads directory (app.py:149-151), allowing the web interface to display vehicle photos alongside registration data.

Update: Modifying Registrations

The current implementation does not include an explicit update endpoint. To modify a registration, you would need to delete the existing record and create a new one, or implement a custom update route.
To add update functionality, you could implement:
@app.route("/editar/<id>", methods=["GET", "POST"])
def editar(id):
    rows = read_csv()
    
    if request.method == "POST":
        # Update the matching record
        for row in rows:
            if row["id"] == id:
                row["propietario"] = request.form.get("propietario")
                row["tipo_vehiculo"] = request.form.get("tipo_vehiculo")
                row["observacion"] = request.form.get("observacion")
                break
        
        write_csv(rows)
        flash("Registro actualizado", "ok")
        return redirect(url_for("registros"))
    
    # GET: Find and display record for editing
    registro = next((r for r in rows if r["id"] == id), None)
    return render_template("editar.html", registro=registro)

Delete: Removing Registrations

The /eliminar/<id> endpoint handles record deletion:
@app.route("/eliminar/<id>")
def eliminar(id):
    rows = read_csv()
    
    # Filter out the record to delete
    filtered = [r for r in rows if r["id"] != id]
    
    # Find the deleted record to remove its image
    deleted = [r for r in rows if r["id"] == id]
    if deleted:
        img = deleted[0]["imagen"]
        img_path = os.path.join(UPLOADS_DIR, img)
        if os.path.exists(img_path):
            os.remove(img_path)
    
    # Write updated records
    write_csv(filtered)
    
    flash("Registro eliminado correctamente", "ok")
    return redirect(url_for("registros"))
Delete operation features:
  • List filtering: Creates new list excluding the target ID (app.py:138)
  • Image cleanup: Removes associated image file from uploads directory (app.py:139-144)
  • Safe deletion: Checks if image file exists before attempting removal (app.py:143)
  • Cascading delete: Both database record and physical image file are removed
  • User feedback: Confirms successful deletion (app.py:146)
Records are deleted by ID, which is unique and auto-incremented. The ID is passed as a URL parameter:
/eliminar/5  # Deletes registration with ID "5"

Data Integrity

Automatic File Creation

The system automatically creates required directories and files:
os.makedirs(os.path.dirname(DATA_PATH), exist_ok=True)  # Creates data/
os.makedirs(UPLOADS_DIR, exist_ok=True)                  # Creates uploads/
Executed at application startup (app.py:21-22), ensuring the storage infrastructure exists.

UTF-8 Encoding

All CSV operations use UTF-8 encoding (app.py:28, 33, 38), supporting international characters in:
  • Vehicle owner names
  • License plates
  • Observations/notes

File Path Handling

The system uses os.path.join() for cross-platform compatibility:
DATA_PATH = os.path.join(BASE_DIR, "data", "registros.csv")
UPLOADS_DIR = os.path.join(BASE_DIR, "uploads")
path = os.path.join(UPLOADS_DIR, filename)
This ensures correct path separators on Windows (\) and Unix-based systems (/).

Performance Considerations

Every create and delete operation:
  1. Reads entire CSV into memory
  2. Modifies the in-memory list
  3. Writes entire list back to disk
This approach is simple and maintains data integrity, but may become slow with thousands of records.
For larger deployments, consider:
  • Migrating to SQLite or PostgreSQL for indexed queries
  • Implementing pagination for the registrations list
  • Using a proper ORM like SQLAlchemy
  • Adding database connection pooling
  • Implementing caching for frequently accessed records

User Feedback System

CoroNet uses Flask’s flash message system for user notifications:
flash("✅ Registro guardado correctamente", "ok")
flash("⚠️ Debes subir una imagen", "error")
flash("Registro eliminado correctamente", "ok")
Messages are displayed on the next page load and automatically dismissed, providing clear feedback about operation success or failure.

Build docs developers (and LLMs) love