---
title: "Mohamed Dhif | Portfolio"
name: "Mohamed Dhif"
role: "Full-stack developer, AI and automation"
website: https://www.mohameddhif.dev
canonical: https://www.mohameddhif.dev/portfolio.md
format: markdown
language: en
---

# Mohamed Dhif — Portfolio

This is the canonical machine-readable version of Mohamed Dhif’s portfolio for recruiters, search assistants, and AI crawlers.

## Profile

- **Role:** Full-stack developer, AI and automation
- **Website:** https://www.mohameddhif.dev
- **Based in:** Tunisia
- **Availability:** Available for new projects

## Services

- **Full-Stack Web Applications:** Complete products: logins, roles, dashboards, exports, admin tools. Secure by default and ready for a real team on day one.
- **AI and Machine Learning Products:** Real AI features, not demos. Models run on your own servers, so there are no per-use bills and no customer data leaving your systems.
- **Workflow and Business Automation:** Replace repetitive handoffs, spreadsheets, and status chasing with workflows that run automatically and keep your team in control.
- **Integrations and Data Systems:** Connect your tools, APIs, and data into one reliable system, with background jobs that recover cleanly and never duplicate work.

## Technology stack

React, TypeScript, Express 5, PostgreSQL + Prisma, MQTT, ESP32 · C++, node-cron, TanStack Query, Tailwind CSS, React 19, Supabase, PostgreSQL + RLS, Edge Functions, AG Grid, Chart.js, ExcelJS, Vite, Next.js 16, Express, ONNX Runtime, ArcFace + SCRFD, PostgreSQL + pgvector, Prisma, BullMQ + Redis, LabVIEW, Python, Pandas, xlrd, Matplotlib

## Contact and profiles

- **Email:** [dhifmohamed4@gmail.com](mailto:dhifmohamed4@gmail.com)
- **WhatsApp:** https://wa.me/21652158059
- **LinkedIn:** https://www.linkedin.com/in/dhif
- **GitHub:** https://github.com/mohameddhif

# Case studies

## GMAO

- **Category:** Industrial CMMS
- **Year:** 2026
- **Case study:** https://www.mohameddhif.dev/projects/gmao
- **Screenshots:** 4

### Overview

A maintenance system for a factory team keeping 120 machines online. Replaces scattered Excel sheets with one place for equipment, spare parts, work orders, service schedules, downtime, and reliability figures, fed live by current sensors on each machine.

### Challenge

A 120-machine factory ran on spreadsheets: machines broke without warning, stock vanished at the worst moment, and no one knew real availability.

### Solution

One system: an Express and PostgreSQL API, a French React frontend, and ESP32 sensors reporting over MQTT, with stock, scheduling, and downtime kept in sync inside the database.

### Client value

One system instead of a wall of Excel sheets. Machines get serviced before they break, parts are there when a job needs them, and every stoppage is measured.

- **Service machines before they break:** The system schedules maintenance and creates the work orders itself, so servicing happens on time instead of after a breakdown.
- **Never caught without a part:** Stock is tracked as parts are reserved and used, with low-stock alerts, so the last critical part is never promised to two jobs at once.
- **See every machine live:** Sensors report whether each machine is running, idle, or off, and raise an alert the moment one goes quiet.
- **Prove reliability with real numbers:** Availability, MTBF, and MTTR come straight from the records, with monthly trends. No reports to assemble by hand.

### Engineering details

Three services held together by database-level correctness: automations that must never double-book, never take the API down, and never let stock or downtime drift apart.

- **Stock as a ledger, not a number:** Inventory is never overwritten. Every change is a movement with a reason, parts go through reserve, release, and consume, and completion locks the row with SELECT ... FOR UPDATE so two jobs finishing at the same instant cannot drive stock negative.
- **A scheduler that survives everything:** A cron job generates work orders from due plans, with a unique (plan, due-date) constraint for idempotency, a lock row so only one instance runs across servers, and per-plan claims via a conditional updateMany. A missing optional column logs once and skips instead of taking the API down.
- **One status change, many side-effects:** Moving a work order reserves or consumes its parts, opens or closes the machine downtime session, and stamps the plan last-done date, all in one transaction, so the three never disagree.
- **Live sensor data with honest counters:** ESP32 sensors publish current over MQTT. The backend derives on, idle, and off from thresholds, caps the gap between readings so a silent sensor cannot inflate runtime hours, and rolls readings into 1-minute and 1-hour buckets for fast charts. Offline detection emits one self-updating alert instead of spamming every minute.
- **Auth built for leaks and races:** Short-lived access tokens stay in memory. Refresh tokens are hashed in the database, kept in an httpOnly cookie, and rotated near expiry inside a transaction that revokes before it issues, so concurrent refreshes survive.
- **Firmware for months of uptime:** Calibrated ADC reads, flagged clipping, and no String usage to avoid heap fragmentation, plus a watchdog and self-reboot if WiFi stays down, because a sensor bolted to a factory machine has to run untouched.

### Technologies

React, TypeScript, Express 5, PostgreSQL + Prisma, MQTT, ESP32 · C++, node-cron, TanStack Query, Tailwind CSS

## Production Management App

- **Category:** Manufacturing ERP
- **Year:** 2026
- **Case study:** https://www.mohameddhif.dev/projects/philbert-prod
- **Screenshots:** 4

### Overview

Built for Philbert Tunisie: a dashboard where contracts flow through production as individual pieces, welding is recorded and measured, and supplier orders stay on schedule. Every change is logged; each role sees only what it can do. One source of truth replaces the chaos of scattered spreadsheets.

### Challenge

Dozens of contracts running in parallel, hundreds of pieces scattered between them, tracked in scattered spreadsheets. Office and shop floor saw different versions, so delays went unnoticed until pieces were late.

### Solution

A React and Supabase dashboard where the database is the source of truth: row-level security, Postgres views behind the grids, a tested state machine for piece progression, and a full audit trail.

### Client value

The whole workshop in one live dashboard, so the office and the shop floor finally work from the same numbers.

- **Track every job down to the piece:** Each contract breaks into its individual pieces, and a drag-and-drop board shows exactly where each one is, from design to delivery.
- **Measure welding output:** Welding is recorded per piece and charted week by week and year by year, so productivity is something you can see rather than estimate.
- **Keep purchasing on schedule:** Supplier orders are tracked against each project, and alerts flag blocked pieces or late materials before they stall a job.
- **Full accountability, by role:** Every change is logged with who, what, and when. Each team sees only what its role allows, and Excel or PDF reports are one click away.

### Engineering details

A React SPA on Supabase where the interesting decisions all push work into the database: authorization, joins, audit trails, and live updates, so the client stays a thin view it cannot bypass.

- **Security enforced by the database:** Row-Level Security policies gate every row in Postgres, so permissions hold even if someone works around the frontend. The UI reads the same permission data only to hide buttons.
- **Privileged actions behind Edge Functions:** Because Row-Level Security deliberately blocks the client from admin work, creating users and assigning roles run in Supabase Edge Functions with the service role, never from the browser.
- **Permissions computed once:** Capabilities are derived from a chain of tables into flags like update\_piece\_etat or manage\_achats. One hook computes them, the UI consumes them, and the database enforces them.
- **Joins pushed into Postgres views:** Heavy read screens are backed by database views instead of client-side joins, so AG Grid renders enriched rows without the frontend stitching data together on every load.
- **An audit trail with field-level diffs:** Every create, update, and delete is recorded with an old-value to new-value diff, timestamped and linked to the user and entity, with SQL triggers so nothing slips past.
- **A tested state machine:** Piece progression rules live in pure functions covered by Vitest, and every Supabase call goes through one service layer, so the data flow stays predictable and easy to change.

### Technologies

React 19, Supabase, PostgreSQL + RLS, Edge Functions, AG Grid, Chart.js, ExcelJS, Vite, Tailwind CSS

## Mirrorball

- **Category:** AI Photo Platform
- **Year:** 2026
- **Case study:** https://www.mohameddhif.dev/projects/mirrorball
- **Screenshots:** 4

### Overview

Guests upload a selfie and get back every photo they appear in. Face matching runs on my servers, so guest faces are never sent to a cloud API, never stored, and never sold.

### Challenge

Matching faces across thousands of event photos normally means sending guest biometric data to a cloud API, with costs that climb per image and a pipeline that loses work if it crashes.

### Solution

A self-hosted recognition pipeline: face detection into embeddings, vector search inside Postgres, a crash-safe processing queue, and thresholds calibrated on labelled data.

### Client value

Every guest gets every photo they are actually in, in about five seconds. No shared drives, no tagging, no spreadsheets.

- **Photos in seconds:** One selfie and guests see every picture they appear in, with no scrolling through a folder of two thousand photos.
- **Private by design:** Faces are matched on my own servers, and each selfie is deleted the moment it is used. Nothing is sold, shared, or handed to a big-tech API.
- **No surprise cloud bills:** Nothing is billed per photo, so the cost stays the same whether the event has 50 guests or 500.
- **Nothing for you to manage:** Upload the shoot once and everyone is grouped automatically. No tagging, no albums, just a link to share.

### Engineering details

The interesting decisions here are all about privacy, cost, and failure. The stack stays deliberately plain where it can, and gets clever only where that buys something real.

- **Self-hosted face pipeline instead of a cloud API:** SCRFD detection and alignment feed ArcFace 512-dimension embeddings through ONNX Runtime, all running locally. Biometric data stays in-house, per-image costs disappear, and match quality becomes something I can calibrate rather than rent.
- **pgvector over a separate vector database:** Embeddings are stored as vector(512) columns in Postgres and matched with cosine nearest-neighbour search. One fewer service to operate, and all vector I/O goes through raw SQL so the round-trip stays exact.
- **An idempotent, crash-safe worker:** A BullMQ worker processes uploads in per-photo delete-then-insert transactions. Kill it mid-batch and a restart resumes with zero duplicate faces, verified by crashing a 39-photo batch and asserting the stored face count matches exactly.
- **Selfies never touch disk:** The match endpoint embeds the incoming selfie in memory, unions the clusters within the threshold, and discards the image immediately. The most sensitive input in the system is ephemeral by architecture, not by a policy someone has to remember.
- **Automatic clustering with stable centroids:** Each face joins the nearest per-space centroid within a threshold or seeds a new one, and centroids are recomputed as the L2-normalized mean of their members, so reprocessing a space is safe and clusters do not drift. A per-space advisory lock keeps concurrent workers from racing.
- **Thresholds calibrated from data, not vibes:** A calibration run on labelled identities set the cluster and match thresholds from a measured 0.29 separation gap between same-person and different-person distances. Held-out selfies then scored 1.00 precision and recall.

### Technologies

Next.js 16, TypeScript, Express, ONNX Runtime, ArcFace + SCRFD, PostgreSQL + pgvector, Prisma, BullMQ + Redis, Tailwind CSS

## Production KPI Monitor

- **Category:** Industrial Analytics
- **Year:** 2024
- **Case study:** https://www.mohameddhif.dev/projects/eleonetech-production-monitor
- **Screenshots:** 1

### Overview

Built during my internship at Eleonetech (Onetech Group). The test machines drop a fresh Excel report after every cycle. This picks it up, works out the figures for the shift, and puts them in front of operators on a live dashboard, with no manual Excel work.

### Challenge

Operators had no live read on the line. Every figure had to be pieced together by hand from raw Excel reports, so problems surfaced hours after they happened.

### Solution

LabVIEW watches the output folder and owns the operator display, while embedded Python reads each report with pandas, buckets it into the right shift, and rolls it into the running figures.

### Client value

Operators see how the line is performing right now instead of building a spreadsheet after the fact, so problems get caught during the shift.

- **Live view of the shift:** The dashboard updates the moment a test report lands, so operators know how the current shift is going, not just yesterday.
- **No more manual Excel work:** Pass rates, batch breakdowns, and yield used to be worked out by hand. Now they update themselves as production runs.
- **Downtime caught automatically:** Long gaps between tests are flagged as likely stoppages, so unplanned downtime shows up instead of going unnoticed.
- **Know if the line is on pace:** Actual output is compared against the hourly target in real time, so falling behind is visible well before the shift ends.

### Engineering details

The core decision was splitting the work cleanly: LabVIEW for the operator display and file watching, Python for anything data-shaped.

- **Python running inside LabVIEW:** Rather than building the KPI maths in the graphical language, Python scripts run through LabVIEW Python Nodes. Pandas handles the tabular work LabVIEW is awkward at, while LabVIEW keeps the UI and hardware-facing parts.
- **Shift-aware, not just file-aware:** Shifts run 06:00-14:00, 14:00-22:00, and 22:00-06:00, so every incoming file is bucketed into its real production shift first. A report from 21:58 and one from 22:02 belong to different sets even though they are minutes apart.
- **Downtime worked out, not measured:** There was no separate downtime signal from the machine, so stoppages are derived by looking for abnormally long gaps between consecutive test timestamps, turning an existing export into a free metric.
- **First pass yield from serial numbers:** Yield tracks each product serial number across the report and checks whether it passed on the first attempt or needed a retest, which is a stricter signal than counting overall pass and fail.
- **Legacy .xls parsing:** The test machines export old binary .xls, which the default pandas engine does not read, so xlrd was pulled in to parse it reliably every cycle without manual conversion.

### Technologies

LabVIEW, Python, Pandas, xlrd, Matplotlib
