Case Study
VisionIQ — People-counting Analytics
Laravel 10 multi-tenant operational platform: session-backed dashboard and token-authenticated REST API with layered services, repositories, policies, queues, and tagged caching—built for production reporting and integrations, not demo CRUD.

Impact
Cut ad-hoc reporting cycles by giving stakeholders branch-level traffic answers in seconds — same definitions every week, fewer spreadsheet exports, and a dashboard the ops team actually trusts for period-over-period reviews. Built on a 36-migration schema, served through 74 API endpoints with tiered rate limiting, 38 passing tests, and PHPStan level 6 static analysis enforced across the codebase.
Problem
Organizations run many brands and branches from one installation: stakeholders need trustworthy people-count and traffic analytics by branch and time window, dependable violation workflows, and stable integrations—without spreadsheets, one-off SQL, or inconsistent JSON from the API.
Solution
Contributed to a layered Laravel codebase: dashboard routes under `/dashboard/{brandId}` for analytics, reports, and branch statistics; parallel `/api` resources for mobile and third parties; Form Request validation and policy checks before services touch repositories; queues and notifications where heavy work belongs off the request thread.
Highlighted implementation
FormRequest validation (analytics endpoint)
phpclass BranchAnalyticsReportRequest extends FormRequest
{
public function rules(): array
{
return [
'period' => ['required', 'string', 'in:day,week,month,custom'],
'branch_id' => ['required', 'integer', 'exists:branches,id'],
'starts_at' => ['required', 'date', 'before_or_equal:ends_at'],
'ends_at' => ['required', 'date', 'after_or_equal:starts_at'],
];
}
}Testing & quality
Core aggregation services and reporting endpoints covered by PHPUnit feature tests. Policy-level access rules verified per role before deployment.
Architecture
API Structure
Two surfaces: a session-based admin dashboard under `/dashboard/{brandId}` (Blade, `Dashboard\*` controllers) with middleware for locale, `admin:web`, and `brand.access`, plus a parallel `/api` REST layer (`routes/api.php`) secured with `auth.token`, tiered rate limits (auth, strict, standard), and `apiResource` routes for brands, branches, AI devices, people counters, violations, facial recognition, roles, and notification settings. Period-based endpoints anchor every comparison to consistent day/week/month/custom windows; branch segmentation keeps traffic and KPI slices aligned to the same branch hierarchy leadership expects in the panel. Dedicated reporting services own exports, scheduled summaries, and dashboard JSON helpers behind the same `ResponseHelper` envelope so mobile, integrations, and Blade charts share one contract shape instead of divergent ad hoc payloads.
Data Flow
Operators pick a brand, then work within that tenant: home and comparison flows delegate to `HomeDashboardPageService` (branch switch, `getBranchData`, `getChartData` as JSON for charts). Counting events stream into ingestion paths that roll up into time-series aggregations in MySQL; precomputed views and summary tables materialize the heaviest rollups so live dashboards query stable aggregates instead of re-scanning raw counters on every refresh. Heavier reads and writes still flow Controller → Service → Repository → MySQL (people-counting statistics, reports/exports, violations, notification settings). People-counting views use branch statistics routes (`traffic-visitors`, `demographics`, `group-behavior`, `time-analysis`); async paths use queue jobs where the README’s notification and export patterns apply.
Backend Decisions
Brand and branch context live in session and route parameters, enforced before dashboard routes run. Mutations use dedicated FormRequests and policies per domain—not inline `$request->validate()` scattered in controllers. Service layer owns transactions; controllers orchestrate validate → authorize → service → `successResponse` / `errorResponse`. Read-optimized pipelines pair staged aggregation (raw ingest → periodic rollups → dashboard-facing queries) with Redis caching and cache tags for hot branch/period slices; the API keeps explicit `by-brand` / `by-branch` / `by-ai-device` endpoints and stricter limits on mutating routes. Eager loading and indexing complete the performance story documented in-repo.
Challenges
Multi-tenant scoping has to stay correct on every path (web and API). Analytics questions are unforgiving on timezones, branch filters, and period boundaries when leadership compares weeks. Keeping dashboard and API authorization rules aligned—as policies and Form Requests evolve—takes discipline so integrations never drift from what operators see in the panel. At scale, keeping high-volume counting data queryable for live dashboards without degrading reliability means guarding queue depth, cache freshness, and aggregation jobs so spikes in foot traffic never starve core dashboard and API paths.
Technical Decisions
Stayed aligned with the project’s documented standards: inspectable SQL aggregations for dashboard ranges, repository boundaries for data access, Spatie-oriented RBAC where the stack uses it, and PHPStan-backed static analysis so refactors in analytics and API modules stay reviewable. Prefer boring Laravel primitives (queues, cache tags, rate limiters) over speculative microservices for this domain.