diff --git a/Claude outputs/case-study-fuel-station.png b/Claude outputs/case-study-fuel-station.png
new file mode 100644
index 0000000..8ad3630
Binary files /dev/null and b/Claude outputs/case-study-fuel-station.png differ
diff --git a/public/sitemap.xml b/public/sitemap.xml
index 20cd850..933d24f 100644
--- a/public/sitemap.xml
+++ b/public/sitemap.xml
@@ -7,6 +7,7 @@
https://dzanan.net/contacthttps://dzanan.net/work/secure-ai-integration-gatewayhttps://dzanan.net/work/field-sales-mobile-platform
+ https://dzanan.net/work/fuel-station-management-systemhttps://dzanan.net/work/travel-operations-platformhttps://dzanan.net/work/service-operations-platformhttps://dzanan.net/work/biodiversity-data-platform
diff --git a/src/app/app.routes.server.ts b/src/app/app.routes.server.ts
index 08f9f83..cfe4a16 100755
--- a/src/app/app.routes.server.ts
+++ b/src/app/app.routes.server.ts
@@ -8,6 +8,7 @@ export const serverRoutes: ServerRoute[] = [
{ path: 'contact', renderMode: RenderMode.Prerender },
{ path: 'work/secure-ai-integration-gateway', renderMode: RenderMode.Prerender },
{ path: 'work/field-sales-mobile-platform', renderMode: RenderMode.Prerender },
+ { path: 'work/fuel-station-management-system', renderMode: RenderMode.Prerender },
{ path: 'work/travel-operations-platform', renderMode: RenderMode.Prerender },
{ path: 'work/service-operations-platform', renderMode: RenderMode.Prerender },
{ path: 'work/biodiversity-data-platform', renderMode: RenderMode.Prerender },
diff --git a/src/app/app.routes.spec.ts b/src/app/app.routes.spec.ts
index 6526661..ec9582b 100755
--- a/src/app/app.routes.spec.ts
+++ b/src/app/app.routes.spec.ts
@@ -10,6 +10,7 @@ describe('app routes', () => {
'contact',
'work/secure-ai-integration-gateway',
'work/field-sales-mobile-platform',
+ 'work/fuel-station-management-system',
'work/travel-operations-platform',
'work/service-operations-platform',
'work/biodiversity-data-platform',
@@ -33,6 +34,7 @@ describe('app routes', () => {
expect(projectIds).toEqual([
'ai-integration',
'field-sales',
+ 'fuel-management',
'travel-operations',
'service-operations',
'biodiversity-data'
diff --git a/src/app/app.routes.ts b/src/app/app.routes.ts
index 12c0a9e..ac8ff38 100755
--- a/src/app/app.routes.ts
+++ b/src/app/app.routes.ts
@@ -32,6 +32,11 @@ export const routes: Routes = [
data: { projectId: 'field-sales' },
loadComponent: () => import('./features/portfolio/portfolio-page.component').then(m => m.PortfolioPageComponent)
},
+ {
+ path: 'work/fuel-station-management-system',
+ data: { projectId: 'fuel-management' },
+ loadComponent: () => import('./features/portfolio/portfolio-page.component').then(m => m.PortfolioPageComponent)
+ },
{
path: 'work/travel-operations-platform',
data: { projectId: 'travel-operations' },
diff --git a/src/app/features/work/portfolio-project.model.ts b/src/app/features/work/portfolio-project.model.ts
index 9a89dac..18e3882 100644
--- a/src/app/features/work/portfolio-project.model.ts
+++ b/src/app/features/work/portfolio-project.model.ts
@@ -1,6 +1,7 @@
export type PortfolioProjectId =
| 'ai-integration'
| 'field-sales'
+ | 'fuel-management'
| 'travel-operations'
| 'service-operations'
| 'biodiversity-data';
@@ -8,6 +9,7 @@ export type PortfolioProjectId =
export type ProjectDiagramKind =
| 'trust-boundaries'
| 'field-sales-platform'
+ | 'fuel-station-platform'
| 'travel-platform'
| 'multitenant-platform'
| 'research-platform';
diff --git a/src/app/features/work/portfolio-projects.data.spec.ts b/src/app/features/work/portfolio-projects.data.spec.ts
index 0b463ab..32e3747 100644
--- a/src/app/features/work/portfolio-projects.data.spec.ts
+++ b/src/app/features/work/portfolio-projects.data.spec.ts
@@ -2,10 +2,10 @@ import { getPortfolioProject, PORTFOLIO_PROJECTS } from './portfolio-projects.da
describe('portfolio project registry', () => {
it('contains four complete, uniquely addressable case studies', () => {
- expect(PORTFOLIO_PROJECTS.length).toBe(5);
- expect(new Set(PORTFOLIO_PROJECTS.map(project => project.id)).size).toBe(5);
- expect(new Set(PORTFOLIO_PROJECTS.map(project => project.slug)).size).toBe(5);
- expect(PORTFOLIO_PROJECTS.map(project => project.index)).toEqual(['01', '02', '03', '04', '05']);
+ expect(PORTFOLIO_PROJECTS.length).toBe(6);
+ expect(new Set(PORTFOLIO_PROJECTS.map(project => project.id)).size).toBe(6);
+ expect(new Set(PORTFOLIO_PROJECTS.map(project => project.slug)).size).toBe(6);
+ expect(PORTFOLIO_PROJECTS.map(project => project.index)).toEqual(['01', '02', '03', '04', '05', '06']);
for (const project of PORTFOLIO_PROJECTS) {
expect(project.contributions.length).toBeGreaterThan(2);
@@ -23,6 +23,17 @@ describe('portfolio project registry', () => {
'WorkLogManager',
'OrderCom',
'ordc',
+ 'Delta Petrol',
+ 'DeltaPetrol',
+ 'Delta-Petrol',
+ 'Trgosped',
+ 'E-Line',
+ 'ELine',
+ 'DOMS',
+ 'Hesel',
+ 'Avera',
+ 'POSInterfejs',
+ 'InterfejsCentral',
'intrix-mcp',
'Unija',
'git.dzanan.net',
diff --git a/src/app/features/work/portfolio-projects.data.ts b/src/app/features/work/portfolio-projects.data.ts
index 22d0f96..32219a5 100644
--- a/src/app/features/work/portfolio-projects.data.ts
+++ b/src/app/features/work/portfolio-projects.data.ts
@@ -101,8 +101,59 @@ export const PORTFOLIO_PROJECTS: readonly PortfolioProject[] = [
'Case study: an offline-first Ionic field sales app with a local ERP replica, order and credit-note sync, barcode and GPS, and a SignalR realtime tracking backend.'
},
{
- id: 'travel-operations',
+ id: 'fuel-management',
index: '03',
+ slug: 'fuel-station-management-system',
+ title: 'Fuel Station Management System',
+ category: 'Ground-up system · Desktop, web & realtime',
+ period: '2026',
+ role: 'Architect and sole engineer',
+ tagline: 'Forecourt & payment-terminal middleware, multi-tenant fleet limits',
+ summary:
+ 'A complete fuel-management system for gas stations, built from the ground up: a Windows desktop middleware that drives forecourt controllers and outdoor payment terminals and hands every sale to the store POS, plus a multi-tenant central platform with an Angular admin app that enforces fleet-card fuel limits at the pump in real time.',
+ challenge:
+ 'Fuel dispensers, an outdoor payment terminal, bank pre-authorizations, the store POS and the operator’s ERP all had to agree on every litre sold — including at night, unattended, when the terminal is the only cashier. Fleet customers needed monthly fuel limits enforced at the pump even though the ERP’s balances lag, and nothing could ever release money or lose a transaction because a network link or the application went down.',
+ contributions: [
+ 'Built the station desktop middleware in VB.NET: forecourt controller integration — pump status, money and volume presets, transaction lock and clear for multi-POS sites, emergency stop, price pushes — over both a serial driver and the vendor’s COM API.',
+ 'Implemented the outdoor payment terminal as a screen-by-screen state machine: pump selection, barcode, amount, PIN, bank pre-authorization and completion, fuelling and receipt printing, with reconnect timers and recovery of uncompleted card transactions after a restart.',
+ 'Defined the file-based POS contract — one transaction file per sale, written temp-then-atomic-rename, with encrypted per-pump recovery files — so the existing POS and ERP agent consume sales unchanged.',
+ 'Designed the central platform on .NET 10: a multi-tenant minimal API with a master database plus one database per tenant, hashed station API keys, JWT roles, EF Core, a worker service syncing companies and vehicles from the ERP, and an Angular 22 zoneless admin app.',
+ 'Wrote the fleet-limit engine: available = monthly limit − fuel this business month − active reservations, with an idempotent create → authorize → complete/cancel protocol, SQL Server application locks per company, and a maintenance worker that moves unresolved reservations to review instead of releasing money.',
+ 'Extracted a pure, unit-tested reliability core — journal state machine, ordered outbox with exponential retry, recovery planner — and wired it into the desktop so fleet fuelling is fail-closed while card and manual sales keep working when the backend is unreachable.',
+ 'Ran the infrastructure end to end: VPN for stations, Caddy TLS in front of IIS, Gitea CI/CD with staged deploys and rollback, a private wiki, and a security hardening pass (secrets out of the repo, fixed-time PIN checks, log redaction, firewall audit).'
+ ],
+ decisions: [
+ {
+ title: 'Reserve before you authorize',
+ detail:
+ 'The ERP balance lags, so the platform reserves limit first and only then authorizes the pump preset — the dispenser pumps against money that is already accounted for.'
+ },
+ {
+ title: 'Never release money automatically',
+ detail:
+ 'Authorized reservations that go unresolved are moved to review with an incident, not cancelled. A person decides; the system only makes the state visible.'
+ },
+ {
+ title: 'Fail closed for fleet cards, open for everything else',
+ detail:
+ 'If the central platform is unreachable, fleet-card fuelling stops but card and manual sales continue — a station never goes dark because a backend did.'
+ },
+ {
+ title: 'Keep the legacy contract',
+ detail:
+ 'The store POS reads the same one-line transaction files it always did; atomic writes and recovery files sit behind that contract instead of replacing it.'
+ }
+ ],
+ outcome:
+ 'The operator runs the system in production across its stations with a second company onboarding: unattended night fuelling against prepayment, fleet limits enforced at the pump in real time, remote diagnostics and pump-incident dashboards for the back office, and a codebase with a tested reliability core rather than defensive patches.',
+ stack: ['VB.NET', '.NET 10', 'ASP.NET Core', 'SQL Server', 'EF Core', 'SignalR', 'Angular', 'Caddy', 'Gitea CI/CD'],
+ diagram: 'fuel-station-platform',
+ seoDescription:
+ 'Case study: a ground-up fuel-management system for gas stations — forecourt and payment-terminal middleware, a file-based POS contract, and a multi-tenant .NET 10 platform enforcing fleet fuel limits in real time.'
+ },
+ {
+ id: 'travel-operations',
+ index: '04',
slug: 'travel-operations-platform',
title: 'Travel Operations & Automation Platform',
category: 'Enterprise platform · Full-stack delivery',
@@ -146,7 +197,7 @@ export const PORTFOLIO_PROJECTS: readonly PortfolioProject[] = [
},
{
id: 'service-operations',
- index: '04',
+ index: '05',
slug: 'service-operations-platform',
title: 'Multitenant Service Operations Platform',
category: 'Business platform · Product evolution',
@@ -190,7 +241,7 @@ export const PORTFOLIO_PROJECTS: readonly PortfolioProject[] = [
},
{
id: 'biodiversity-data',
- index: '05',
+ index: '06',
slug: 'biodiversity-data-platform',
title: 'Biodiversity Data & Field Research Platform',
category: 'Scientific system · Platform modernization',
diff --git a/src/app/features/work/system-diagram.component.ts b/src/app/features/work/system-diagram.component.ts
index 7d7936f..6aa34dd 100644
--- a/src/app/features/work/system-diagram.component.ts
+++ b/src/app/features/work/system-diagram.component.ts
@@ -30,6 +30,17 @@ import { ProjectDiagramKind } from './portfolio-project.model';
OFFLINE QUEUE · RETRYGPS · VISITSLATEST LOCATION PER USER
}
+ @case ('fuel-station-platform') {
+
+ }
@case ('travel-platform') {