Case 02Operations and safety
Kippis
Workplace safety in the pocket of the people on site.
A workplace safety platform that puts risk assessments, work permits, inspections and disciplinary warnings on the technician’s phone and ties them to employees, roles, safety standards, exams and PPE. Every night, five routines sweep each company’s legal deadlines and alert the right person ahead of the due date and again on the day, before the shift starts.
- Client
- Large construction company
- Period
- 2025 — 2026
- Team
- 4-person squad
- Role
- Full stack engineer · lead for the mobile app and safety rules
- Stack
- Go · Fiber · PostgreSQL · Expo · React Native · TypeScript
- Confidentiality
- Client data kept private
Results in numbers
- 5legal deadlines watched every nighttraining, approval number, PPE lifespan, periodic exam and assets
- 180endpoints in the multi-tenant API27 API modules, from HR and health to inventory and comms
- 2,500employees trackedin-house and contracted, across several sites
- 42unhealthy and hazardous conditions mapped24 under NR-15 and 18 under NR-16, with each role’s allowance rate
The business context
- Client
- Large construction company
- Period
- 2025 — 2026
At a large construction company, workplace safety is both a legal requirement and a matter of life and death. Every site mixes in-house and contracted crews; every role carries safety standards, occupational health exams, PPE with a valid approval certificate and hazard or unhealthy-work allowances. Every critical activity starts with a risk assessment and a signed permit. When all of that lives on paper, spreadsheets and chat groups, an expired training shows up during an inspection, an audit or, at worst, after an accident.
The challenge
Records had to happen on site, in minutes, signed by the people who will do the job. HR, occupational health, safety and the warehouse needed to see the same employee without mixing data across companies. And deadlines of very different kinds — training certificates, PPE approval numbers, the lifespan of each unit, periodic medical exams and assets — had to become alerts for the right person, at the right time and never repeated, without burying the safety technician in notifications.
Field first
Risk assessments with a risk matrix, work permits and inspections filled in on the phone, step by step, with on-screen signatures and validation before sending. No permit may exist without a linked risk assessment.
Every standard has its own rule
NR-35, NR-15, NR-16, NR-06 — work at height, unhealthy work, hazardous work, PPE: each regulation demands different things from roles, classes and permits. Adding a new standard or following a revision had to be data entry, never a deploy.
Deadlines that cannot slip
Training, approval numbers, per-unit PPE lifespan, periodic medical exams and assets expire at different paces — and each one has a different person who must be warned.
What I delivered
I built the field app in Expo and React Native and structured the React web dashboard. Behind them, a Go and Fiber API with 180 endpoints across 27 API modules and 32 permission modules scopes every company at the session and treats standards, exams, PPE and allowances as data tied to the role. A Go scheduler runs five routines per company every night, works out who must be warned and records every delivery.
Field app
Five-step risk assessment with a 5 × 4 risk matrix, permits with crew and signatures, checklist inspections and warnings with witnesses and photos.
Multi-tenant API
Go and Fiber with the company taken from the session, three-level RBAC per module and per platform, MFA and an audit trail on every change.
Standards, roles and training
A standards catalog linked to classes and permits; each role defines exams, PPE and allowances from 42 mapped unhealthy and hazardous conditions.
Expiry scheduler
Five routines staggered between 3 and 5 a.m. that alert ahead of the due date and again on the day, with recipients resolved through the hierarchy and a single alert per item per day.
How it works
Follow the work through the system step by step, or click any component on the board to see what it does.
drag to see the flow
Legend
- Synchronous
- Asynchronous
- Secure channel
- External integration
Text description of the flow
- From Field app to Multi-tenant API — Field entries (synchronous).
- From Management dashboard to Multi-tenant API — Management (synchronous).
- From Multi-tenant API to Training and standards — Classes (synchronous).
- From Multi-tenant API to Safety and health — Permits (synchronous).
- From Multi-tenant API to PPE and inventory — PPE, assets (synchronous).
- From Training and standards to Certificates and evidence — Certificate (synchronous).
- From Training and standards to Operational database — Attendance (synchronous).
- From Safety and health to Operational database — Persists (synchronous).
- From PPE and inventory to Operational database — Stock (synchronous).
- From PPE and inventory to CA-EPI service — CA lookup (external integration).
- From Operational database to Expiry scheduler — Due dates (asynchronous).
- From Expiry scheduler to Alerts — Pending items (asynchronous).
- From Alerts to Pending work and action — Alert (asynchronous).
Walk through the flow
←→change step
6 steps
From start to finish
Pick a step or press Play to follow the whole flow. The board highlights where the work goes as you move along.
System components
Field app
- What it solves
- Bring safety records to the place where the work happens.
- How it works
- Eighteen screens on Expo Router. The risk assessment follows five steps — details, risks, activity steps, severity and signature — and each risk gets a frequency and severity on a 5 × 4 matrix that returns a priority from 1 to 5, with an administrative, PPE or engineering control. React Hook Form and Zod block incomplete submissions. The session lives in cookies in SecureStore, and the app only returns to login when the API answers SESSION_EXPIRED: a generic 401 never kills a form in progress.
Expo · React Native · TypeScript · Zod
Multi-tenant API
- What it solves
- Let HR, occupational health, safety and the warehouse share context without mixing companies.
- How it works
- The authentication middleware takes company and user from the session; the authorization middleware compares the level the route requires — view, interact or manage — with the role’s level for that module and platform, cached for 30 seconds and invalidated when the role changes. Login with an MFA code, a 1-hour session renewed by a 30-day refresh token and at most 5 web sessions per user. Sixty-eight audit points record who changed what. Seventy-eight tables across 6 schemas: core, hr, health, safety, stock and comms.
Go · Fiber · PostgreSQL · JWT
Standards, roles and occupational health
- What it solves
- Make the system adapt to each standard without rewriting code.
- How it works
- Each standard is a record with its activity and objective; link tables connect it to the classes that cover it and the permits that require it. The role defines mandatory exams, required PPE and allowances: unhealthy-work allowance under NR-15 at 10, 20 or 40%, hazard pay under NR-16 at a fixed 30%, and strenuous-work conditions. Medical certificates — pre-hire, periodic, return to work, exit and the health program — record fit, unfit or restricted.
Go · PostgreSQL
Training and certificates
- What it solves
- Record who attended, which standards the class covers and what evidence was issued.
- How it works
- Training classes or safety talks, typed as mandatory, specific or refresher, with instructor, workload, attendance and standards. The backend generates an A4 PDF certificate with no external dependency — name, ID, workload, instructor and expiry — and stores it in S3 per class and participant, ready to download at audit time.
Go · PostgreSQL · AWS S3
PPE, inventory and assets
- What it solves
- Know which equipment is with whom, whether its approval is valid and when it must be replaced.
- How it works
- The PPE model looks up the approval number on the CA-EPI service and stores its expiry. Each unit has a serial number, status and purchase date, from which lifespan is computed in days, weeks, months or years. Handouts to employees record the return and the item’s condition; tools and machines get loans with checklists, interventions and inventory. This module alone serves 38 endpoints.
Go · PostgreSQL · CA-EPI
Expiry scheduler
- What it solves
- Turn deadlines scattered across modules into actionable alerts for the right person.
- How it works
- A Go service separate from the API, with five jobs at their own times: training at 3:00, approval numbers at 3:30, PPE lifespan at 4:00, periodic exams at 4:30 and assets at 5:00. All reuse a snapshot of companies cached for 6 hours. Each job resolves the recipient — the class site manager, the manager of the sector whose roles use that PPE, the employee’s direct leader with fallback to the sector and the company — and stores the item, recipients and delivery result. Every deadline triggers one alert ahead of the due date and another on the day itself, and the query skips anything already alerted that day.
Go · robfig/cron · SMTP
Management dashboard
- What it solves
- Give safety management an administrative view of employees, PPE, inspections, training and permits.
- How it works
- React 19, Vite and Tailwind. The menu comes from the session: the backend returns the paths unlocked for the role from a registry of 32 permission modules, so changing a route never means rewriting profiles. Alert screens for approval numbers, lifespan, medical exams and training gather pending items, and the same alerts land in an inbox with an unread counter.
React · Vite · TypeScript · Tailwind CSS
Decisions that hold the system up
Company comes from the session, never the client
The middleware loads the company on every request and every query filters by it. When a permit is created, risk assessments, technician, workers and signers are batch-validated against the same company — no screen can mix in another company’s data, even with a tampered parameter.
A standard is data, not code
A standard is a row in the catalog, linked to classes, permits and roles through relation tables. Supporting a new standard, a revision or a site-specific requirement becomes data entry, and the permit already shows which standards each worker is trained on.
Permission by module, level and platform
The same route can require different permissions on web and mobile: creating a risk assessment in the dashboard needs a management module, on the phone it needs the field module. The technician works on site without gaining admin access.
Alerts recorded, not just fired
Each delivery stores the item, recipients, where the owner came from and success. That prevents repeating the same notification on the same day and proves, at audit time, that the owner was warned in time.
Business results
Training, approval numbers, PPE, medical exams and assets alert their owner ahead of the due date and again on the day, before the shift, instead of surfacing at audit time.
Issuing a work permit on site dropped from about 20 minutes on paper to under 5 in the app.
Every permit is born linked to a risk assessment scored risk by risk, with crew and signers validated inside the same company.
Every alert is stored with its recipient and delivery result: when inspectors arrive, proof that the owner was warned is already there.
My role
- Role
- Full stack engineer · lead for the mobile app and safety rules
- Team
- 4-person squad
I planned and built the mobile app from scratch, from route architecture to session handling, and did the real structuring of the web dashboard. In the API, I modeled the backend to support many safety standards and adapt to each one: standard, role, exam, PPE and allowance became related data, not rules hard-coded in the system. I wrote the business logic for exam alerts, PPE expiry and training notices, and turned the client safety team’s requirements into system validations.
What I owned
- Built the Expo/React Native app: risk assessments, permits, inspections, warnings, MFA and password recovery across 18 screens.
- Modeled standards, roles, exams, PPE and allowances as related data, so a new standard is data entry rather than code.
- Designed RBAC with view, interact and manage levels per module, enforced separately for web and mobile.
- Wrote the five expiry routines and recipient resolution through direct leader, sector manager and site manager.
- Integrated PPE registration with the CA-EPI service and per-unit lifespan with inventory control.
- Generated PDF training certificates in the backend itself, stored in S3 per class and participant.
Stack by layer
Mobile
- Expo
- React Native
- Expo Router
- React Query
- React Hook Form
- Zod
- SecureStore
Web
- React
- Vite
- TypeScript
- Tailwind CSS
- Zustand
Backend
- Go
- Fiber
- sqlx
- JWT
- TOTP
- robfig/cron
Data and integrations
- PostgreSQL
- Goose
- AWS S3
- CA-EPI
- SMTP
Infra
- Docker
- Nginx
- GitHub Actions
What I take from this project
Modeling standard, role and requirement as related data from the start is what let the system follow each standard without a queue of code changes.
A useful alert reaches the right person at the right time, never repeated. Resolving the recipient through the hierarchy and recording every delivery made the technician trust the system instead of muting it.
In the field, an expired session must be an explicit UI state. Telling SESSION_EXPIRED apart from other 401s prevented lost forms and orphaned signatures.