Caregivers are assigned daily shifts to visit clients at their homes. As part of Electronic Visit Verification (EVV) compliance, each caregiver must log their visit by capturing timestamps and real-time location (latitude & longitude).
- Features
- Technical Architecture
- API Endpoints
- Testing
- CI/CD
- Postman Collection
- Development
- Limitations
- Next
- Shift scheduling: plan and assign shifts for caregivers.
- Clock-in / clock-out tracking: log when caregivers start and end their shift.
- Task Progress Tracking: caregivers can mark tasks as completed or not completed (with reason)
- Geolocation Integration: Uses browser's Geolocation API for capture user location, with fallback handling
This project involves creating a backend system for a caregiver shift tracker where users can manage their shifts, track their visits, and monitor task progress.
Hertz is a high-performance HTTP framework for Go. It is designed to be fast, lightweight, and easy to use. It provides a simple and intuitive API for building web applications and APIs.
Benchmark:
The database uses Postgres. The schema includes the following tables:
Table for storing user information.
Table for storing schedule information. It defines the template or rules for occurrences (e.g., timing, recurrence, timezone, grace period).
- One-time: Represents a single, non-recurring event that occurs at a specific date and time.
- Periodic: Represents a recurring event that generates multiple occurrences based on defined recurrence rules (e.g., daily, weekly).
Schedule status vs Occurrence status
schedules.status: The status column in the schedules table indicates the overall lifecycle state of the schedule, reflecting whether it is active, paused, completed, or terminated.
schedule_occurrences.status: The status column in the schedule_occurrences table indicates the state of an individual occurrence of a schedule, whether it’s planned, ongoing, completed, or missed.
Table for storing task information.
Table tracks individual occurrences of schedules, with occurrence_start and occurrence_end defining the time window for each occurrence.
- For
one-timeschedules: Creates a single occurrence withstatus = 'scheduled'. - For
periodicschedules: Creates multiple occurrences based on recurrence rules, each withstatus = 'scheduled'.
Example:
-
A
one-timeschedule withstart_date = '2025-09-21'andstart_time = '14:00:00+08'generates one occurrence with matchingoccurrence_start_dateandoccurrence_start_time. -
A
periodicschedule withinterval = 'day',interval_unit = 1, andstart_date = '2025-09-21'generates daily occurrences (e.g., 2025-09-21, 2025-09-22, etc.) up to 30 days orschedule_expiry_date.
Table tracks individual occurrences of schedule events. Logs specific check-in/out events for an occurrence
Table tracks for schedule/task changes. For example, Logs "Update progress on scheduled care activities" (task status changes).
To track geolocation data, I use the browser's navigator.geolocation API. But it have limitations, browser geolocation can fail due to user denial, no GPS hardware, or privacy settings. So to handle fallback mechanism, I use IP geolocation services like ipstack or ipinfo. IP fallback provides a server-side estimate based on IP address, which is less precise but always available (no user permission needed).
(@see: /ui/lib/geolocation.ts)
A Task Status Transition with FSM is a design pattern where each task has a well-defined state (e.g., not_started, completed, cancelled), and only certain valid transitions are allowed between these states.
The FSM maps events (like complete or cancel) to transitions that move a task from one state to another.
Any attempt to perform an invalid transition is rejected, ensuring state consistency.
FSM enforces strict, predictable, and maintainable task status transitions, reducing bugs and making your workflow logic transparent and future-proof.
(@see: /internal/tasks/state_machine/task.go)
Background jobs monitor and update the status of schedules and occurrences. For instance, they check for missed occurrences (e.g., when the current time, 05:21 PM WIB on 21 Sep 2025, exceeds occurrence_end_date plus grace_period) and update their status to cancelled with a reason like "missed schedule."
They can also cancel periodic schedules if max_missed_occurrences is exceeded, ensuring the system responds to repeated failures.
Jobs trigger at specified intervals: every 5 minutes
Supporting Use Cases:
-
For the
one-timeschedule (e.g., 15 Jan 2025, 09:00–10:00 WIB), a background job can check for missed execution and update the status. -
For the
periodicschedule (e.g., weekly on Wednesdays), jobs generate occurrences (e.g., 15 Jan, 22 Jan 2025) and manage missed occurrences, ensuring continuity.
(@see: /internal/background_jobs/jobs/mark_missed_occurance.go)
Row locking is a database mechanism used to ensure data consistency when multiple users or processes (e.g., concurrent API requests) try to update the same row simultaneously. Usecase: When a caregiver updates task status (Completed/Not Completed), locking prevents race conditions that could misreport progress on the Schedule Details page.
(@see: /schema/postgres/migrations/20250918055231_task_function.sql#L18)
Zap is a structured, fast, leveled logger for Go.
(@see: pkg/logger)
if postgres.IsNoRows(err) {
return nil, apperrs.NewAppError("Task not found", errs.ErrDataNotFound, consts.StatusBadRequest)
}(@see: pkg/apperrs)
I use sentry for error monitoring. Sentry is great for catching errors, stack traces, and context (user, request, tags, etc.).
(@see: pkg/sentryutil)
- GET all schedule
/v1/schedules - GET today schedules
/v1/schedules?filter[today]=true - GET schedule By ID
/v1/schedules/:id/occurrences/:occurrence_id - GET schedule stats/metrics
/v1/schedules/stats - POST clock-in
/v1/schedules/:id/occurrences/:occurrence_id/start - POST cancel clock-in
/v1/schedules/:id/occurrences/:occurrence_id/cancel - POST clock-out
/v1/schedules/:id/occurrences/:occurrence_id/end - POST update task progress
/v1/tasks/:id/update
Testing is critical to ensure reliability, The current setup includes:
- Unit tests using Vitest (frontend) and Go. geolocation.test.ts
- Integration tests using Dockertest update_task_test
You can run the tests locally by executing the following command:
For backend:
make testFor frontend:
pnpm test(@see: ci/cd pipeline )
@see: api collection
Setup environment:
cp .env.example .envInstall required packages:
go mod tidy #backend
pnpm install #frontendCreate Database: mini_evv
Migrate Database:
make migrate-up #or
goose -s -v -dir schema/postgres/migrations postgres "postgresql://USERNAME:PASSWORD@localhost:5432/mini_evv" upmake devdocker build --build-arg GITHUB_TOKEN= \
--build-arg SERVICE_HOST=localhost \
--build-arg SERVICE_PORT=3002 \
--build-arg SERVICE_NAME=core \
-t natserract/mini_evv . && docker run --env-file .env -p 3002:3002 -t natserract/mini_evv- Can't load Google Maps Iframe. Must enable billing to properly load a Google Maps iFrame. This is because the API requires an API key, which is created and managed through Google Cloud Console.
- Periodic schedules

