SQL Ops Dashboard is a read-only retail analytics portfolio project. It uses Python and Flask to validate dashboard filters, query a relational SQLite database, and transfer numeric analytics through JSON APIs. A responsive HTML, CSS, and vanilla JavaScript interface presents recognized revenue, product margin, category performance, monthly trends, and sortable product results.
Live Demo: https://sql-ops-dashboard.vercel.app
All records are generated demonstration data. Local setup instructions are included below.
Introduction, dashboard filters, selected data period, and operational summary metrics.
Monthly recognized revenue and gross-margin trends across the selected period.
Top products by recognized revenue and revenue distribution across product categories.
Highest-performing products ranked by gross-margin amount and margin rate.
Searchable and sortable product results with revenue, gross margin, margin rate, rank, and CSV export.
- Four filtered KPIs with equal-length prior-period comparisons
- Date presets, custom date range, and category filters shared by every metric and chart
- Monthly recognized-revenue and gross-margin SVG chart
- Product and category revenue analysis with value-derived bar lengths
- Product margin analysis and SQL window-function revenue ranking
- Searchable and sortable product performance table
- Browser-generated CSV download using the filtered, searched, and sorted rows
- Loading, empty-data, partial-failure, and controlled API-error states
- Read-only SQLite connections for all application requests
- Deterministic 24-month data generation with integrity validation
- Responsive and keyboard-accessible presentation without a frontend framework
- Ruff, pytest, pytest-cov, and a two-version GitHub Actions workflow
- Python 3.10 or newer
- Flask 3
- SQLite 3
- HTML5
- CSS
- Vanilla JavaScript and accessible SVG
- pytest and pytest-cov
- Ruff
- GitHub Actions
Runtime dependencies are intentionally limited to Flask and its transitive dependencies. The dashboard does not use an ORM, authentication, external APIs, third-party chart packages, analytics tracking, or writable web endpoints.
flowchart LR
G[Deterministic Python generator] -->|schema + catalogue + transactions| D[(SQLite ops.db)]
D -->|read-only parameterized SQL| F[Flask application]
F -->|JSON APIs| J[Vanilla JavaScript]
F -->|Jinja template + public assets| B[Browser dashboard]
J --> S[KPIs, SVG trend, bars, sortable table]
J --> C[Client-side CSV download]
Database creation is separate from the web application. scripts/init_db.py
delegates to the single deterministic generation system, validates SQLite
integrity and foreign keys, and atomically replaces the requested database.
Flask startup and HTTP requests never rebuild or modify the database.
.github/workflows/tests.yml # Python 3.10/3.12 quality workflow
app/
├── __init__.py # Application factory, errors, security headers
├── db.py # Request-scoped read-only SQLite access
├── filters.py # Shared date and category validation
├── routes.py # Page, health, metadata, and analytics APIs
└── templates/
└── index.html # Semantic single-page dashboard
data/
└── ops.db # Ready-to-run generated database
public/
├── app.js # Filters, charts, sorting, CSV, UI states
├── favicon.svg # Original analytics favicon
└── styles.css # Responsive dashboard styling
screenshots/ # Five dashboard presentation images
scripts/
├── generate_demo_data.py # Seeded transaction generator
└── init_db.py # Documented database rebuild entry point
sql/
├── schema.sql # Tables, constraints, keys, and indexes
├── seed.sql # Reference categories and product catalogue
└── views.sql # Inspectable reusable analytics views
tests/
├── conftest.py # Isolated temporary database fixtures
├── test_app.py # Page, API, filters, security, read-only tests
├── test_database.py # Determinism, distribution, integrity tests
└── test_scripts.py # Generator commands and failure paths
index.py # Vercel-compatible Flask application instance
run.py # Local development entry point
pyproject.toml # Ruff and pytest configuration
requirements.txt # Runtime dependency
requirements-dev.txt # Test, coverage, and lint dependencies
.vercelignore # Excludes development-only deployment files
The default database is generated with random seed 42 for 24 complete months,
from 2024-07-01 through 2026-06-30. It contains:
- 5 product categories
- 20 products, including active and inactive catalogue items
- 360 completed orders
- 24 pending orders
- 24 cancelled orders
- 944 order-line records
- Completed sales in every month and every category
Prices, recorded unit costs, quantities, discounts, sales channels, product weights, and seasonal patterns vary deterministically. Pending and cancelled orders have line records but never contribute to recognized revenue, cost of goods, gross margin, rankings, or trends. The dataset contains no customer names, email addresses, postal addresses, telephone numbers, or other personal information.
Running the generator repeatedly with the same database path, seed, ending date, and month count produces the same logical records.
Money is stored as integer cents in transactional tables and returned as numeric US-dollar values by the APIs.
categoriescontains the five reference category records.productsstores SKU, category relationship, unit cost, list price, and active status.ordersstores an ISO order date, constrained status, and generated sales channel.order_itemsrelates orders to products and records quantity, selling price, and unit cost at the time represented by the order.
Foreign keys enforce product/category and order-line relationships. Order-item rows cascade only when an order is deliberately removed by an offline database maintenance operation; the dashboard itself cannot delete records.
idx_orders_status_datesupports the completed-status and date-range predicates used by every analytics query.idx_order_items_order_idandidx_order_items_product_idindex SQLite foreign-key join columns.idx_products_category_activesupports category and active-product lookups.
No index is included without a corresponding join or filter use.
completed_order_linesdefines the recognized transaction grain and calculates revenue, cost, and margin in cents for completed orders.revenue_by_productaggregates recognized revenue by product.revenue_by_categoryaggregates recognized revenue by category.margin_by_productcalculates product revenue, cost, gross margin, and margin rate.product_revenue_rankapplies SQLite'sRANK()window function.monthly_performanceaggregates completed results by calendar month.dashboard_summarysupplies inspectable all-data KPI definitions.
Filtered API queries reuse the same completed-order logic while applying parameterized dates and category values.
- Recognized revenue: completed-order quantity multiplied by the recorded selling price.
- Cost of goods: completed-order quantity multiplied by the recorded unit cost.
- Gross margin: recognized revenue minus cost of goods.
- Gross margin rate: gross margin divided by recognized revenue, multiplied
by 100. A zero revenue denominator returns
null/N/A instead of dividing by zero. - Completed orders: distinct completed orders in the selected period.
- Active products: distinct products marked active in the database that appear in completed order lines for the selected period and category.
The APIs return JSON numbers rather than currency-formatted strings. Financial rounding and US-dollar formatting are applied consistently at the query and presentation boundaries.
The default dashboard period is Latest 12 months. Preset periods are calculated from the latest completed-order date in the dataset rather than the visitor's current date. Available options are Latest 30 days, Latest 90 days, Latest 12 months, All available data, and Custom range. Manual start and end dates are available through Custom range.
The visible Data through notice is populated from the maximum completed-order date returned by the database metadata. Adding newer completed orders automatically moves that notice and the preset periods forward.
Date and category filters update every KPI and chart. Custom dates use
YYYY-MM-DD; invalid dates, repeated values, unsupported parameters, unknown
categories, and reversed ranges return controlled HTTP 400 responses. Valid
filters with no completed records return zero KPIs and empty analytics arrays,
which the interface presents as no-data states.
The first three KPIs compare the selected period with the immediately preceding period of equal inclusive length. Comparisons are N/A when no valid prior value exists, when the prior denominator is zero, or when All available data is selected.
Product search affects only the table. Reset filters returns to Latest 12 months, all categories, empty product search, and Revenue descending sorting.
| Method | Route | Purpose |
|---|---|---|
GET |
/ |
Render the dashboard |
GET |
/health |
Confirm Flask can open and query SQLite |
GET |
/api/meta |
Return dataset bounds, counts, and categories |
GET |
/api/summary |
Return KPIs and previous-period comparisons |
GET |
/api/trends/monthly |
Return ordered monthly revenue and margin |
GET |
/api/revenue/products |
Return descending product revenue |
GET |
/api/revenue/categories |
Return descending category revenue |
GET |
/api/margins/products |
Return product revenue, cost, and margin |
GET |
/api/rankings/products |
Return ranked product performance |
GET |
/styles.css |
Serve the public stylesheet |
GET |
/app.js |
Serve the public dashboard JavaScript |
GET |
/favicon.svg |
Serve the original SVG favicon |
All analytics routes accept optional start_date, end_date, and category
query parameters:
/api/summary?start_date=2025-07-01&end_date=2026-06-30
/api/trends/monthly?start_date=2026-01-01&end_date=2026-06-30&category=Office
/api/rankings/products?category=Fitness
List routes use a consistent envelope containing count, data, filters,
and generated_data. The metadata and summary routes provide named meta and
summary objects.
python3 is commonly used on macOS and Linux. The Windows PowerShell examples
use the Python launcher command py. Python 3.10 or newer is supported.
python3 -m venv .venv
source .venv/bin/activate
python3 -m pip install --upgrade pip
python3 -m pip install -r requirements.txt
python3 scripts/init_db.py
python3 run.pyOpen http://127.0.0.1:5000 after the application starts. Debug mode is disabled by default. Optional Flask development mode can be started with:
python3 -m flask --app app run --debugpy -m venv .venv
.venv\Scripts\Activate.ps1
py -m pip install --upgrade pip
py -m pip install -r requirements.txt
py scripts\init_db.py
py run.pyOpen http://127.0.0.1:5000 after the application starts. Optional Flask development mode can be started with:
py -m flask --app app run --debugIf PowerShell prevents activation scripts from running, use
.venv\Scripts\python.exe directly for the Python commands.
scripts/init_db.py uses the deterministic generator defaults and atomically
replaces data/ops.db after integrity checks:
python3 scripts/init_db.pyGenerate a separate database or change the deterministic arguments:
python3 scripts/generate_demo_data.py data/portfolio-copy.db \
--seed 42 \
--ending-date 2026-06-30 \
--months 24The ending date must be the final day of a month so the generated period contains complete calendar months.
Install development dependencies:
python3 -m pip install -r requirements-dev.txtRun Ruff:
python3 -m ruff check .Run the tests:
python3 -m pytest -qRun the required coverage check:
python3 -m pytest \
--cov=app \
--cov=scripts \
--cov-report=term-missing \
--cov-fail-under=85 \
-qWindows PowerShell uses the equivalent py -m ruff check .,
py -m pytest -q, and py -m pytest --cov=app --cov=scripts --cov-report=term-missing --cov-fail-under=85 -q commands.
Tests create temporary SQLite databases and never modify data/ops.db.
The Download CSV button exports the rows currently displayed in the Product Performance table after global filters, product search, and sorting. The browser creates a UTF-8 CSV with spreadsheet-ready numeric values and correct escaping; no data is sent to another service or written by the Flask server. The filename contains the selected start and end dates.
The dashboard uses one H1, structured headings, labelled controls, a status
aria-live region, keyboard-operable sorting, valid aria-sort states, visible
focus outlines, a table caption, textual comparison directions, and an SVG chart
with a title, description, legend, and screen-reader data summary. Missing chart
months are explicitly identified.
At 768px and above, Performance over time uses the combined Monthly Revenue and Gross Margin line chart. Below 768px, compact revenue and gross-margin summaries replace the chart. Mobile layouts include Jump to section, collapsible filters, Back to top, and isolated horizontal scrolling within the Product Performance table. Responsive behavior was tested at 320px, 375px, 430px, 768px, and 1440px. Reduced-motion preferences minimize loading animation.
The root index.py exports a Flask instance named app, while run.py remains
the local entry point. Flask serves the same public/ assets locally that a
production Vercel deployment uses. The bundled data/ops.db requires no
build-time write, environment secret, external database, or writable deployed
filesystem.
.vercelignore excludes virtual environments, tests, screenshots, caches,
coverage output, and development-only scripts. Application modules, templates,
public assets, data/ops.db, and the SQL documentation remain available. A
vercel.json file is not included because the standard Python entry point and
public directory require no additional routing configuration.
The production dashboard is linked near the top of this README.
- Python application structure with type hints and focused modules
- Flask routing, validation, controlled errors, and JSON APIs
- SQLite relational design, constraints, foreign keys, indexes, and views
- Parameterized SQL and read-only request connections
- Reproducible generated-data processing
- Aggregation, ranking, comparisons, and time-series analytics
- Data retrieval and frontend visualization with no chart dependency
- Accessible table sorting and local CSV data transfer
- Automated testing, coverage measurement, and linting
- Continuous integration across Python 3.10 and 3.12
- Production deployment with a lightweight Flask entry point




