evo.ehs

The natural evolution of your business.

An EHS command center for multi-plant industrial manufacturers — permits, environment, and safety in one place, with an audit trail so compliance is visible, not buried in spreadsheets and email.

Overview

Permits, safety incidents, recurring maintenance, and training records typically live across spreadsheets, email threads, and filing cabinets. That fragmentation causes missed deadlines, unclear ownership, and compliance gaps that only surface during an audit.

evo.ehs replaces that fragmentation with one command center — structured, traceable, and visible to both plant supervisors and corporate leadership.

Getting started is fast: enter a company name and evo.ehs looks up your EPA ID, lists every registered facility with its SIC codes, and lets you select the facilities you want to bring in — or add them all at once. The system auto-generates those facilities and pulls all available EPA and state permits for each one. State coverage varies; not all states publish permit data online, but where data exists it comes in automatically.

From there it runs the full permit lifecycle — owners, due dates, tasks, attachments, and a status history of who changed what and when. EPA integrations surface TRI, GHGRP, inspections, violations, and enforcement history alongside your own records.

Work tasks with recurring cadence rules (daily, weekly, monthly) auto-generate scheduled occurrences so compliance and maintenance work never falls through the cracks. Tasks support subtasks, file attachments, and full lifecycle tracking from assignment through completion.

Safety and OSHA-aligned workflows cover structured incident management from reporting through investigation and closeout — with optional OSHA Form 301-style fields, regulatory notification flags, root-cause and CAPA tracking, weather forensics at incident time, and CSV exports for internal recordkeeping handoff.

Chemical inventory tracks products at each plant with SDS review dates, GHS signal words and hazard summaries, storage locations, and links to related permits and incidents — flagging any SDS that hasn't been reviewed in three years. An incidence rate calculator computes OSHA-style nonfatal rates using the BLS (N × 200,000) / EH formula, with an optional industry benchmark to compare against.

Training deadlines stay visible through a training matrix and reminders. Audit binders collect checklists and evidence so inspections don't require a last-minute scramble. EHS directors get a plant-level health score, environmental dashboards, and a compliance calendar across all plants. Built-in chat, calendaring, and permit and task reports keep corporate and site teams aligned.

Notifications are configurable rather than hard-coded: an action manager lets an administrator watch any field across permits, incidents, tasks, plants, events, and users — or a record's creation, modification, or deletion — and email a chosen set of recipients when it changes, using editable templates with merge tags.

Ask AI answers plain-language questions about a site's own data — permits, incidents, tasks, chemicals, training records, calendar events, EPA compliance, weather logs, and users — and cites the records behind every answer. It is powered by evo-ai, the retrieval platform described separately, running tenant-isolated so one workspace's data can never surface in another's answers.

Teams onboard themselves: self-serve signup creates a workspace with the signer as its administrator, and subscription billing runs through Stripe with plan tiers gating features such as Ask AI. Behind that sit the operational necessities — role-based access, an audit log, active session management, a recycle bin for recovering deleted records, backups, usage analytics, and bulk CSV import.

What you get

  • One command center — permits, tasks, incidents, training, and audit readiness in a single auditable system.
  • Instant facility onboarding — search by company name, get your EPA ID, review all registered facilities with SIC codes, select what you need, and auto-generate facilities with permits pulled in. State coverage varies.
  • Permit discovery plus lifecycle — agency-sourced context from EPA, TCEQ, and others, then ownership, due dates, and renewal tracking with full history.
  • Recurring work tasks — cadence rules (one-time, daily, weekly, monthly) auto-generate occurrences on a timezone-aware schedule; subtasks and attachments included.
  • Safety alongside environment — structured incidents, OSHA 301-style record fields, CAPA tracking, weather forensics, and exports for your internal processes.
  • Chemical inventory — SDS review dates with a three-year overdue flag, GHS signal words and hazard summaries, storage locations, and links to related permits and incidents.
  • Training and audit readiness — training matrix with deadline reminders, audit binders with checklists and evidence attachments.
  • OSHA incidence rates — nonfatal rate calculator using the BLS (N × 200,000) / EH formula, with optional benchmark comparison.
  • Ask AI — plain-language questions answered from your own permits, incidents, tasks, chemicals, training, and compliance records, with every answer citing its sources. Powered by evo-ai, tenant-isolated.
  • Configurable notifications — an action manager watches any field, or record creation, modification, and deletion, and emails chosen recipients using editable merge-tag templates.
  • Leadership and plant views — plant health score, TRI and enforcement dashboards, compliance calendar, permit and task reports, and real-time workload visibility for supervisors.
  • EPA data integrations — TRI, GHGRP, inspections, violations, enforcement history, and facility context surfaced alongside your own records.
  • Collaboration — built-in chat, shared calendar, customizable email templates, and role-based access so every user sees what they need.
  • Self-serve onboarding and billing — sign up to get your own workspace as its administrator, with Stripe subscriptions and plan tiers gating premium features.
  • Operational tooling — audit log, session management, recycle bin for deleted records, backups, usage analytics, and bulk CSV import.

QA engineering in practice

I build this product and I test it. The slice below is three days of QA work against the deployed production application — 1–3 August 2026, builds v0.0.0.1.1461 through v0.0.0.1.1477 — recorded as it happened rather than summarized afterward. Findings came from manual testing of the running system, code inspection during fixes, and production log and database inspection.

Measure Count
Issues raised55 — 45 bugs, 7 features, 3 chores/plans
Pull requests opened51
Deploys16 (build counter increments once per deploy)
Automated tests at close1,012, up from roughly 640
Issues still open at close6

How defects were found

Every defect is filed under the activity that surfaced it, which keeps the coverage question honest — it shows which techniques are earning their keep and which areas depend on one person looking.

  • Exploratory — unscripted passes through onboarding, permit discovery, and import. Several defects reproduce only in sequence, not in isolation: one needs the plants to already exist, another a completed discovery run.
  • Usability and workflow — cases where the code did what it was written to do but told the user nothing. Pressing Ask with an empty input, for instance, returned neither output nor message.
  • Functional — cases producing an incorrect result.
  • Integration — the third-party boundaries: Stripe, SMTP, evo-ai, and the EPA/TCEQ/MPCA data sources.
  • Regression — both defects that reappeared elsewhere and defects introduced by fixes made during the period.
  • Cross-platform — macOS-specific browser behavior.
  • Roles and permissions, and security.
  • Data and content, configuration and environment, and visual and layout.
  • Deployment verification — confirming merged code is actually running, by inspecting the container rather than trusting a deploy script's exit status.

Representative findings

  • A webhook that failed silently. The Stripe endpoint returned HTTP 200 when signature verification failed. Stripe reads 200 as success, so it never retried — a broken integration that reported itself as healthy.
  • Any user could cancel the subscription. The tenant Stripe billing portal was reachable by any authenticated user, regardless of role.
  • A date bug with a measurable blast radius. Annual due dates were computed in the current calendar year, so 43 of 53 permits marked Done displayed a due date in the past. Found by reconciling the UI against the production database rather than by reading code.
  • Timeouts multiplied by row count. Mail was sent synchronously inside the request handler with a 15-second SMTP timeout; a bulk import multiplied that by the number of permits until the websocket dropped.
  • A CSS rule with a non-obvious side effect. A filter value other than none — including a no-op blur(0px) — makes an element the containing block for its position: fixed descendants, moving elements that appeared unrelated.
  • One root cause, two platforms. A native <input type="date"> gives value ownership to the browser, and the picker wrote a value the Vue model never received. The macOS report and a second issue turned out to be the same defect; one fix closed both.
  • Secrets in version control. 36 full-application archive zips were tracked in git containing credential-shaped DATABASE_URL, SECRET_KEY, and GLOBAL_ADMIN_PASS values — and the admin password was 9 characters, below the 12-character NIST SP 800-63B / OWASP minimum the application itself enforces on users.
  • Content drift after a rename. 20 of 20 email template bodies and 18 of 20 subjects, across both tenants, still contained the pre-rename product name. Fixing the seed templates only helps tenants created afterward, so the fix required a data migration for existing ones.
  • Verifying a deploy is harder than it looks. Two failure classes appeared: deploys made from a checkout on a non-default branch, and commits pushed to a branch after its pull request merged, which therefore never reached main. Squash merges make git log origin/main..branch useless for detecting the second case, since it lists commits whose content is already on main under a different SHA — so the check is a content comparison against the running container.

What the automated suite caught — and what it didn't

The suite grew from roughly 640 tests to 1,012 during the period. Being specific about its limits matters more than the headline number:

  • Caught before production — a construction-order NameError, by an AST check added in response to an earlier regression; and three call sites converted without their import, by an import-verification check. Both checks exist because a defect got through first.
  • Not caught — every exploratory, usability, cross-platform, and visual finding above. Automation does not notice that a button gives no feedback.
  • The instructive miss — one module imported cleanly, so both the test suite and an import check passed, while every request to that page returned HTTP 500. A passing import is not a passing page; the check was strengthened to exercise the route.

The same defect discipline applied to the AI layer is written up separately in QA Engineering for AI Systems.

Technology stack

The following reflects the current production application stack. (A separate target architecture for a future extraction is documented elsewhere and is not shown here.)

Application

Layer Technology Notes
Language Python 3.11 Aligned with the production Docker image base.
Web UI / server NiceGUI Single-process app; Quasar/Vue on the client; page-based routing.
Entry Python module Application starts via python -m <package>.main after database migrations in the container.

Data & persistence

Layer Technology Notes
Database PostgreSQL Production-style relational store.
Driver psycopg (binary extras) Connection string uses postgresql+psycopg:// style in deployment configs.
ORM SQLAlchemy 2.x Domain models drive reads and writes.
Migrations Alembic Schema revisions are versioned and applied in order.
SQLite (ancillary) aiosqlite Available for lightweight or async SQLite scenarios where used.

Security & configuration

Layer Technology Notes
Password hashing passlib + bcrypt Includes legacy-compatible hashes where applicable.
Crypto cryptography Used for integrity and encryption helpers in sensitive flows.
Config python-dotenv Local .env; production relies on environment variables.

Integrations & libraries

Concern Library Notes
HTTP client httpx, requests External APIs — EPA, weather, agency discovery, and similar integrations.
Payments stripe Billing and webhooks as implemented in the product.
PDF reports fpdf2 Server-generated PDFs for permits, environmental views, and exports.

Testing & quality

Tool Role
pytest Unit and integration tests — 1,012 in the suite as of August 2026.
pytest-mock Mocking helpers for isolated tests.
AST and import checks Static guards added in response to specific escaped defects — construction-order errors and converted call sites missing their imports. See QA engineering in practice.

Deployment & operations

Item Details
Docker Application image plus PostgreSQL via compose for repeatable environments.
Volumes Uploads and database persistence via named volumes.
Scripts PowerShell / CMD helpers for deploy, restart, and database workflows on Windows and cloud hosts.

Frontend assets

Item Details
Static files Packaged static assets, uploads, logos, and rich text (for example TinyMCE) where enabled.
Styling Utility-style classes, Quasar components, and shared theme tokens for a consistent UI.

Dependency versions are tracked in the project requirements.txt; production Python should stay aligned with the application Docker image when debugging parity issues.

Back to Work · Contact