Context
Positive Duty is a multi-tenant compliance training platform built to support Australia's
Respect at Work legislation. Clients need employees to re-complete key training modules on an
annual basis to stay compliant, but earlier workflows relied on manual exports and
spreadsheets, making it easy to miss overdue reviews as tenant and user counts grew.
Challenges
- Deriving annual review due dates from historical completion data instead of static fields.
- Designing an anniversary rule (11-month reminder, 12-month due date) that is correct and explainable.
- Ensuring reminders are idempotent at scale so users do not receive duplicate emails.
- Surfacing reminders across channels (email and in-portal UI) without overwhelming users.
- Providing diagnostics and admin tools to seed data, test behavior, and debug specific users.
Solution
I implemented an annual review engine that scans training completions daily, computes who is due
for review, and delivers reminders via email and in-product notifications, with guardrails for
idempotency and operational visibility.
-
Extended the Prisma schema with
ReminderDispatch and UINotification
models to track which reminders have been sent and to persist in-app notifications for each
user and module.
-
Built a daily scanner in
anniversaryReminder.js that aggregates the last
completion per user and module from ViewerModuleCertification, computes
11-month reminder and 12-month due dates, and skips users who are not yet due.
-
Implemented composite idempotency keys on
(email, moduleId, completionAt, reminderType) so repeated runs of the job do not
send duplicate reminders.
-
Integrated the scanner with existing
cronJobs.js infrastructure using file-based
locks in a .locks directory to prevent overlapping executions and to log summary
metrics for each run.
-
Added anniversary-specific API endpoints (scan, diagnose, seed, mark-read) so admins can
trigger scans, backfill test data, and inspect why a particular user did or did not receive a
reminder without direct database access.
-
Surfaced the reminder count in the portal via an
AnniversaryContext React
context and a NotificationBanner component that announces when modules are
pending review, updating on navigation, focus, and visibility changes.
Impact
The annual review engine replaced a manual, spreadsheet-based renewal process with a repeatable
and auditable workflow. Compliance teams now have higher confidence that employees due for
review receive timely reminders, while admins and stakeholders can diagnose behavior through
internal tools and persisted reminder records. This reduces operational overhead and helps
Positive Duty tenants maintain ongoing training compliance at scale.
Go to project page
Context
Extended Bandoneon is a research and performance project around the bandoneon instrument. A key
goal of the web app is to publish a curated online soundbank of high-quality bandoneon samples
that musicians, producers, and researchers can browse, preview, and download.
Challenges
- Modeling sounds and soundpacks across MySQL and Cloudinary while keeping metadata in sync.
- Supporting WAV uploads for quality while serving performant MP3 files to users.
- Designing a tagging and filtering system that works across both sounds and soundpacks.
- Building a responsive soundbank UI with waveform playback that scales with many sounds.
- Making the soundbank discoverable with structured data without duplicating business logic.
Solution
I implemented an end-to-end soundbank pipeline that ingests audio into Cloudinary, normalizes
metadata in MySQL, and exposes a filterable, SEO-friendly sound library with waveform playback
and gated downloads.
-
Modeled sounds, soundpacks, and hashtags in a relational schema, with a thin
db
wrapper for queries and mutations so the Next.js API routes could stay focused on behavior
rather than connection management.
-
Built an admin-only upload endpoint that accepts WAV or MP3 files, uploads them to
Cloudinary, converts WAV to MP3 when needed, and stores duration, size, and canonical MP3
URLs in the
sounds table.
-
Implemented soundpack management and tagging so editors can define soundpacks with shared
tags; individual sounds inherit those tags and can add their own, all persisted via a
normalized
hashtags/entity_hashtags model.
-
Exposed a public
/api/sounds endpoint that performs cursor-based pagination,
joins soundpacks and tags, filters to soundbank MP3 assets, and returns a frontend-friendly
shape for the React Query client on the soundbank page.
-
Built the soundbank frontend with infinite scroll, soundpack and tag filters, and a custom
SoundPlayer component using WaveSurfer.js to render waveforms and control
playback without relying on browser-native controls.
-
Added JSON-LD structured data for the soundbank using a
generateSoundbankStructuredData
helper so search engines can index the collection as a set of AudioObject
resources.
Impact
The soundbank pipeline turned a static collection of audio files into a structured, explorable
library. Editors can upload new bandoneon sounds and organize them into soundpacks without
touching code, while visitors get fast waveform playback, meaningful filters, and gated
downloads. This makes the Extended Bandoneon project more useful to performers, producers, and
researchers and provides a solid foundation for future tooling such as datasets or analysis
tools.
Go to project page
Context
As a self-employed professional in Spain, I needed to manage invoices, deductible expenses,
social security contributions, and quarterly and annual tax obligations (IVA and IRPF). My existing
workflow involved spreadsheets and manual calculations before filing official forms like
Modelos 130, 303 and 390, which was error-prone and time-consuming.
Challenges
- Centralizing invoices, expenses, and tax-related payments in a consistent data model.
- Encoding Spanish tax rules (quarters, IVA devengado/deducible, IRPF base and retentions) in code.
- Designing a CLI workflow that was fast to use but still guided and safe for everyday bookkeeping.
- Generating exports and backups that could be shared with an accountant or used for audits.
Solution
I designed and implemented Conta, a Python-based command-line application that
models the accounting domain explicitly and automates the most repetitive parts of my
freelancer workflow.
-
Built a typed domain model with SQLModel and SQLite for entities like issued invoices,
deductible expenses, social security payments, and fractioned IRPF payments.
-
Implemented a Typer-powered CLI with focused commands for recording invoices (
emite),
expenses (gasto), and listing data by year or quarter, using Rich tables for
readable terminal output.
-
Encapsulated IVA and IRPF logic in dedicated services that compute quarterly snapshots based
on real data, mirroring the structure of official Spanish tax forms.
-
Added CSV exports for VAT books and a
backup-db command so the SQLite database
can be safely archived or handed off to an accountant.
Impact
With Conta, I replaced a fragile spreadsheet-based workflow with a repeatable CLI routine.
Recording invoices and expenses is now a matter of running a few commands, and quarterly IVA
and IRPF snapshots can be generated on demand. This reduces manual calculation errors,
shortens the time I spend preparing tax forms, and gives me a clearer view of my financial
position throughout the year.
Go to project page