Files
test_dashboard/CLAUDE.md
T
2026-05-26 14:36:34 -04:00

6.6 KiB

CLAUDE.md — Project Context for AI Assistants

What this project is

A real-time web dashboard that tracks WiFi test execution progress. It compares a target tests directory (.ini files defining every test that must run) against a results directory (folders created when each test completes). The dashboard shows completion rates, elapsed time, estimated time remaining, and a filterable table of every test.


How to run

Backend (Python)

cd server
.\.venv\Scripts\Activate.ps1          # activate venv
python app.py                          # starts Flask on port 3001

Frontend (dev)

cd dashboard
npm run dev                            # Vite dev server; proxies /api → localhost:3001

Frontend (production build)

cd dashboard
npm run build                          # outputs to dashboard/dist/
# Flask serves dist/ automatically when app.py is running

Tech stack

Layer Technology
Frontend React 18 + Vite, TailwindCSS v4 (@tailwindcss/vite), TanStack React Query
Backend Python 3, Flask, flask-cors
Database SQLite via Python stdlib sqlite3 (WAL mode, single persistent connection + RLock)
File watching watchdog Python library
Real-time Server-Sent Events (SSE) — one-way push from backend to browser

Key files

Backend (server/)

File Purpose
app.py Flask app, all route handlers, bootstrap(), __main__
db_py.py SQLite schema init, all query helpers (upsert_test, mark_completed, reset_by_file_id_and_device, get_config, set_config, etc.)
scanner.py full_scan(), scan_targets(), scan_results(), process_result_dir(), parse_deleted_result_dir_name()
parser.py parse_target_filename(), parse_result_filename(), parse_timestamp(), parse_elapsed_time(), parse_tput_rssi()
watcher.py Two watchdog Observer instances — _TargetHandler and _ResultsHandler. start_watching() / stop_watching()
sse_py.py Per-client Queue registry, stream_events() generator, broadcast(data)

Frontend (dashboard/src/)

File Purpose
lib/api.js Thin fetch wrapper; all API calls go through apiFetch('/path')
hooks/useStats.js Fetches /api/stats; owns the SSE EventSource; invalidates ['stats'] and ['tests'] on update events
hooks/useTests.js Fetches /api/tests
hooks/useConfig.js Fetches/saves /api/config
components/ConfigModal.jsx Settings modal: directory paths (always editable) + avg time overrides
components/DirectoryBrowser.jsx Server-side folder picker using /api/browse

Data model

How tests are identified

  • Target files live in subdirectories of the target directory. Format: TC_WIFI_<tags>.ini. Files named GLOBAL.ini are skipped.
  • test_id: segment matching R\d+[A-Z0-9]+ (e.g., R2COERXAX014)
  • Result directories: top-level subdirectories of the results directory. A test is completed when a result directory name contains the same test_id.
  • Result folder name example: COE_CGW453_R2COERXAX014_TPT3E_RSSI70_STA56_2GHZ_CH1_BW20_TCP_MIMOFD_SONFD_MESHFD_LPI_UL

Tags parsed from target filename

interference, device, test_point, rssi, station, band, channel, bandwidth, direction

rotation is parsed from the parent folder name (segment matching ROT\d+).

Database tables

tests — one row per target .ini file:

  • id (TEXT PK) — parent_dir/base_filename
  • test_id, parent_dir, filename, interference, device, rotation, test_point, station, band, channel, bandwidth, rssi, direction
  • completed (0/1), completed_at (ISO timestamp), duration_seconds (REAL)
  • tput_results (JSON array of {station, tput, dlRssi, ulRssi})

config — key/value store:

  • target_dir, results_dir
  • avg_time_coe, avg_time_p2p, avg_time_p3p (seconds as string; NULL = use calculated average)

API routes

Method Path Description
GET /api/tests All tests; supports query filters (completed, interference, device, rotation, testPoint, station, band, channel, bandwidth, rssi, direction)
GET /api/stats Aggregated stats: overall, per-device, timing with estimates
GET /api/events SSE stream; sends data: {"type":"update"}\n\n on any directory change
GET /api/config Current config values
POST /api/config Update config; triggers full rescan + rewatcher if dirs changed
POST /api/config/rescan Force full rescan without changing config
GET /api/browse?path=... List subdirectories at a server path (omit path for drive roots)

Watcher architecture

Two watchdog.Observer instances run in daemon threads:

  1. _TargetHandler — watches target_dir recursively. Any add/delete/move of a TC_WIFI_*.ini file schedules a debounced full scan (1 second timer, cancels and restarts on rapid changes).

  2. _ResultsHandler — watches results_dir recursively. Handles events surgically:

    • Directory created → process_result_dir() + broadcast
    • Directory deleted → reset_by_file_id_and_device() + broadcast
    • Directory moved in/out/renamed → appropriate reset/process + broadcast
    • File created/deleted inside a result dir → process_result_dir() + broadcast

Critical Windows quirk

When a directory is deleted (including Recycle Bin), watchdog calls os.path.isdir() at event-processing time. The directory is already gone, so it returns False, making event.is_directory = False even for directory events. All handlers check _is_direct_child_dir(path) directly — never gate on event.is_directory for delete/move cases.


SSE implementation notes

  • sse_py.py: each connected client gets a queue.Queue. broadcast() calls put_nowait(payload) on all queues. stream_events() is a generator yielding f"data: {payload}\n\n" (real newlines — \n not \\n).
  • useStats.js: creates new EventSource('/api/events') once on mount. On {"type":"update"} message, invalidates React Query keys ['stats'] and ['tests'].
  • Vite dev proxy forwards /api/events to Flask, keeping the SSE connection alive.

Known issues / gotchas

  • config.json in server/ is a legacy file used for a one-time migration to SQLite on first run. It is no longer needed once dashboard.db exists.
  • The backend entrypoint is app.py, not main.py.
  • Flask dev server (app.run) is used directly — no gunicorn/waitress configured yet.
  • Port is 3001 (configurable via PORT env var).