01/Signal

I build systemsthat moveteams forward.

I take business problems that live in spreadsheets, chat threads and somebody’s memory, and turn them into systems that a team can actually run — with the rules written down, the decisions reproducible, and an audit trail behind them.

  • AI & AUTOMATION
  • PRODUCT SYSTEMS
  • PROJECT DIRECTION
  • DEVELOPMENT

Bali, Indonesia · WITA (UTC+8)

Selected systems

01/Systems & Platforms

PeopleOS

HR, attendance and statutory payroll for ~300 people

The problem
HR ran on a 291-column master workbook, a legacy attendance app, paper leave forms and a macro-driven payroll spreadsheet. Nobody could answer who reports to whom, on what contract, since when — and no month's payroll could be proved to have come from a specific set of rules.
What I built
A full HR platform: 17 domain modules over a 102-model Postgres schema, an append-only attendance ledger, and separately versioned rule engines that price statutory payroll in exact decimal arithmetic.
My role
Sole engineer and de-facto product owner. Domain model, rule interpretation with Accounting and HR, release process, and production ownership.
What changed
Replaced spreadsheets and paper with one auditable system of record, live on managed cloud infrastructure since the August 2026 cutover.
View case study

2026 · Production

PeopleOS architectureData flows through 12 components: PWA client (Interface), Next.js 16 App Router (Service), CLI: import/reconcile (Ingest), Legacy workbooks (Source), Permission resolver (Decision logic), Cloud Run + Cloud SQL (External), 17 domain modules (Decision logic), Pure rule engines (Decision logic), Field encryption (Decision logic), Document store (Store), Payslips · bank file (Output), PostgreSQL (Store). Connections: PWA client to Next.js 16 App Router; Next.js 16 App Router to Permission resolver; Permission resolver to 17 domain modules; 17 domain modules to Pure rule engines; 17 domain modules to Field encryption; 17 domain modules to PostgreSQL; Field encryption to PostgreSQL; 17 domain modules to Document store; 17 domain modules to Payslips · bank file; Legacy workbooks to CLI: import/reconcile; CLI: import/reconcile to PostgreSQL; Next.js 16 App Router to Cloud Run + Cloud SQL; Cloud Run + Cloud SQL to PostgreSQL.PWA clientAdmin / Manager / Employee …Next.js 16 App RouterRSC + 35 server-action modu…CLI: import/reconcileIntegrity checks, environme…Legacy workbooks291-column master data, tax…Permission resolver158 codes · level + grants …Cloud Run + Cloud SQLNon-destructive, code-only …17 domain modulesAuthorize, scope and audit …Pure rule enginesAttendance day · overtime ·…Field encryptionAES-256-GCM, versioned ciph…Document storeMagic-byte validated, versi…Payslips · bank fileReact-PDF · exceljs · MCM2 …PostgreSQL102 models · 28 migrations …
  • TypeScript
  • Next.js 16
  • React 19
  • PostgreSQL
  • Prisma 6
  • decimal.js
  • Zod 4
  • AES-256-GCM
  • argon2
  • React-PDF

02/AI & Automation

Ads Review Automation

A deterministic rules engine with an LLM allowed only to explain it

The problem
The weekly paid-media review was a manual pass through Ads Manager: read ROAS, CPA, CPM and CPV per ad, decide what to pause, re-type the conclusions into a document. The thresholds lived in someone's head, so the same numbers produced different decisions on different weeks.
What I built
A staged Python pipeline that fetches ad performance, scores every ad against thresholds held in one JSON file, checks that pausing an ad will not leave a product with zero live coverage, and ships the review in four formats.
My role
Sole designer and implementer. Wrote the design specs, the rules engine, four prompt versions, the exporters, and merged the work through reviewed feature branches.
What changed
Turned a remembered judgement into a reproducible one: the thresholds that drive the decision now feed the engine, the spreadsheet templates and the LLM prompt from a single file.
View case study

2026 · Production pipeline

Ads Review Automation architectureData flows through 12 components: Meta Marketing API (External), Connector (Ingest), thresholds.json (Store), Gzip JSON cache (Store), Transform + metrics (Decision logic), Product coverage index (Decision logic), Rules engine (Decision logic), Report orchestrator (Service), Versioned prompts (AI), MD · JSON · HTML · PDF (Output), Sheets + Drive export (Output), LLM narrator (AI). Connections: Meta Marketing API to Connector; Connector to Gzip JSON cache; Connector to Transform + metrics; Gzip JSON cache to Transform + metrics; Transform + metrics to Rules engine; thresholds.json to Rules engine; thresholds.json to Versioned prompts; Transform + metrics to Product coverage index; Rules engine to Report orchestrator; Product coverage index to Report orchestrator; Report orchestrator to Versioned prompts; Versioned prompts to LLM narrator; LLM narrator to Report orchestrator; Report orchestrator to MD · JSON · HTML · PDF; Report orchestrator to Sheets + Drive export.Meta Marketing APIInsights, metadata, budgets…Connector3 fetch modes, chunked look…thresholds.jsonSingle source of truth for …Gzip JSON cachePer stage + range, 6h TTLTransform + metricsStage detection · ROAS/CPA/…Product coverage ind…Remaining live ads per prod…Rules enginePer sub-stage · severity · …Report orchestratorBuckets by ad set × stageVersioned promptsv4–v7 · thresholds injected…MD · JSON · HTML · P…Self-contained HTML; PDF vi…Sheets + Drive exportOpt-in, failure-isolated, =…LLM narratorOne JSON call per bucket · …
  • Python 3
  • pandas
  • Meta Marketing API v25
  • OpenAI API
  • gpt-4o-mini
  • gspread
  • google-api-python-client
  • Playwright
  • markdown-it-py
  • openpyxl

03/Decision Systems

Slow Seller Decision Engine

A recommendation per style, and a spreadsheet it cannot damage

The problem
Merchandisers decided each season what to keep, sell out, mark down or drop — but the inputs were assembled by hand and by a brittle Apps Script. Four sales columns carried end dates from four different days, status grouping was a hard-coded list that broke whenever someone typed a new wording, and two engine inputs were placeholders rather than real data.
What I built
A Next.js app that runs a seven-stage sync — read the sheet, load Shopify products and orders, match style and colour, aggregate two rolling windows, run the engine, write back — streaming progress over Server-Sent Events.
My role
Sole developer. Both integrations, the decision engine, the test suite, and a 1,032-line README written for the operator as much as the developer.
What changed
The same decision, now sourced from live Shopify data, reproducible, and structurally unable to overwrite a formula, a header, or a colleague's edit.
View case study

2025 · Internal production

Slow Seller Decision Engine architectureData flows through 12 components: Shopify Admin GraphQL (External), Shopify client (Ingest), Google Sheets API (External), Write plan + guards (Decision logic), Sheets client (Ingest), Run pipeline (Service), Eligibility + matching (Decision logic), Run store (Store), Sales aggregation (Decision logic), API routes (Service), Decision engine (Decision logic), Dashboard (Interface). Connections: Shopify Admin GraphQL to Shopify client; Google Sheets API to Sheets client; Shopify client to Run pipeline; Sheets client to Run pipeline; Run pipeline to Eligibility + matching; Eligibility + matching to Sales aggregation; Sales aggregation to Decision engine; Decision engine to Write plan + guards; Write plan + guards to Sheets client; Sheets client to Google Sheets API; Run pipeline to Run store; API routes to Run pipeline; Run store to API routes; API routes to Dashboard.Shopify Admin GraphQLProducts, variants, orders,…Shopify clientOAuth refresh · cost thrott…Google Sheets APIService-account batch read/…Write plan + guardsAllowlist · formula guard ·…Sheets clientColumns resolved by header …Run pipeline7 stages; reads all before …Eligibility + matchi…Style + colour index, SKU f…Run storeSingle lock, preview cache,…Sales aggregationNet units, refunds, store-t…API routesZod validation · SSE streamDecision enginePure — no I/O, no clockDashboardLive stages, results, apply…
  • TypeScript
  • Next.js 15
  • React 19
  • Tailwind CSS 3
  • Radix UI
  • Zod
  • Shopify Admin GraphQL
  • Google Sheets API v4
  • Server-Sent Events
  • Vitest

04/Internal Tools

Order Export for Production Planning

Removing the developer from a routine data request

The problem
Production planners needed one row per line item, each tagged with the warehouse that will actually ship it. Shopify's native export does not produce that shape, and Shopify exposes several non-equivalent 'location' values — so 'which warehouse' is a decision problem, not a field lookup.
What I built
A stateless FastAPI service where the planning team picks a date range, previews the orders in-browser, and downloads Excel or CSV — behind company Google sign-in, with nothing customer-related written to disk.
My role
Sole developer: requirements analysis, Shopify schema investigation, architecture, implementation, tests, containerisation and the cloud deployment.
What changed
A non-technical team now self-serves data that previously required a developer to run local scripts on request.
View case study

2026 · Internal production

Order Export for Production Planning architectureData flows through 11 components: Planning team browser (Interface), TLS + Google sign-in (Service), FastAPI export service (Service), Preview cache (Store), Warehouse resolver (Decision logic), Secret Manager (Store), Structured logging (Output), Stock matcher (Decision logic), Shopify Admin GraphQL (External), Excel / CSV builders (Output), Stock workbooks ×3 (External). Connections: Planning team browser to TLS + Google sign-in; TLS + Google sign-in to FastAPI export service; FastAPI export service to Preview cache; FastAPI export service to Warehouse resolver; Warehouse resolver to Shopify Admin GraphQL; Warehouse resolver to Stock matcher; Stock matcher to Stock workbooks ×3; FastAPI export service to Secret Manager; Warehouse resolver to Excel / CSV builders; Excel / CSV builders to Planning team browser; FastAPI export service to Structured logging.Planning team browserServer-rendered page, no bu…TLS + Google sign-inoauth2-proxy · Workspace do…FastAPI export servi…Stateless, non-root contain…Preview cache10 min TTL, keyed by reques…Warehouse resolverfulfilled → assigned → Unkn…Secret ManagerClient secret → ~24h tokenStructured loggingPII-redacted by constructionStock matcherstyle + colour + size; cons…Shopify Admin GraphQLCost-aware pacing, read-onl…Excel / CSV buildersGenerated fully in memoryStock workbooks ×3Read-only service account, …
  • Python 3.12
  • FastAPI
  • Pydantic v2
  • httpx
  • tenacity
  • Jinja2
  • XlsxWriter
  • gspread
  • Docker
  • Cloud Run

05/AI & Automation

Seasonal Catalog Media Pipeline

~1,000 images, 165 products, and no click-by-click afternoon

The problem
A seasonal drop meant getting roughly 1,000 images into WordPress under a strict naming convention, then telling each of 165 products which images are its own, in which order, with which colour label. Source filenames come from a design export and do not match the catalog's colourway names — colours appear in a different order, get abbreviated, and style names contain typos, so a naive string match silently binds the wrong swatch to the wrong colourway.
What I built
Seven standalone Python CLI tools, standard library only, forming a staged pipeline from a design export to live collection pages — every writing tool dry-run by default.
My role
Sole developer and operator. Designed the naming convention, wrote the matching heuristics and the WordPress client, and ran the pipeline against production.
What changed
A drop that would otherwise be a click-by-click afternoon in the media library and ACF editor became a reviewable plan you approve and apply.
View case study

2026 · Internal tooling

Seasonal Catalog Media Pipeline architectureData flows through 13 components: Design export (Source), Catalog CSV (Source), Filename matcher (Decision logic), rename_swatches (Decision logic), Reconciliation tools (Decision logic), photos_sync (Service), media_refresh (Service), ws_link (Service), Collection pages (Interface), WordPress REST (External), Audit reports + log (Output), Media library (Store), Product ACF fields (Store). Connections: Design export to rename_swatches; Design export to photos_sync; Design export to media_refresh; Catalog CSV to Filename matcher; Filename matcher to photos_sync; Filename matcher to media_refresh; rename_swatches to media_refresh; Catalog CSV to ws_link; photos_sync to WordPress REST; media_refresh to WordPress REST; ws_link to WordPress REST; Reconciliation tools to WordPress REST; WordPress REST to Media library; WordPress REST to Product ACF fields; Media library to ws_link; Product ACF fields to Collection pages; Media library to Collection pages; Reconciliation tools to Audit reports + log; media_refresh to Audit reports + log.Design export~527 swatches + ~520 photosCatalog CSV165 products · 517 colourwa…Filename matcherToken matching, typo-tolera…rename_swatchesExport naming → site conven…Reconciliation toolsRead-only; opt-in dupe dele…photos_syncUploads only what is missingmedia_refreshSize diff → delete + re-upl…ws_linkBitmask-DP swatch assignmentCollection pagesRenders swatches by field o…WordPress RESTApp password · throttle + b…Audit reports + logMedia libraryProduct ACF fieldsswatch_1..10 image/name pai…
  • Python 3.14
  • Standard library only
  • WordPress REST API
  • Advanced Custom Fields
  • Application Passwords
  • CSV
  • WebP / PNG

06/Developer Tooling

Agent Session Observability

Watching a dozen AI coding sessions without tabbing through windows

The problem
Running several AI coding sessions at once leaves no way to know which one is working, which is silently blocked on a permission prompt, which errored and which finished — short of tabbing through every terminal and editor window. There is no status API. The only signal is the session registry, the process table, and multi-megabyte JSONL transcripts on disk.
What I built
A zero-dependency Electron dashboard that derives full session state purely by observing files — an incremental transcript digester, an 11-rule state machine, per-model cost arithmetic, and a probabilistic ETA with a confidence band.
My role
Sole author and architect. Wrote the build contract that pinned the module shapes, then integrated the modules implemented against it.
What changed
Answers at a glance what previously required opening every window: what is each session doing, how far along, roughly how much longer, and what the token usage would have cost.
View case study

2026 · Working tool

Agent Session Observability architectureData flows through 13 components: Session files on disk (Source), Process table (Source), Sidecar agent transcripts (Source), fs.watch + safety poll (Ingest), Incremental digest (Ingest), Liveness + activity (Ingest), Agent scanner (Ingest), Digest cache (Store), Assembly + hash (Service), State · ETA · cost (Decision logic), HTTP + SSE on loopback (Service), Vanilla JS dashboard (Interface), Tray · badge · notifications (Output). Connections: Session files on disk to Incremental digest; Session files on disk to Liveness + activity; Process table to Liveness + activity; Sidecar agent transcripts to Agent scanner; Incremental digest to Digest cache; Incremental digest to Assembly + hash; Liveness + activity to Assembly + hash; Agent scanner to Assembly + hash; Assembly + hash to State · ETA · cost; State · ETA · cost to Assembly + hash; fs.watch + safety poll to Assembly + hash; Assembly + hash to HTTP + SSE on loopback; HTTP + SSE on loopback to Vanilla JS dashboard; HTTP + SSE on loopback to Tray · badge · notifications.Session files on diskRegistry, JSONL transcripts…Process tableLiveness + child processesSidecar agent transc…Background work after the m…fs.watch + safety po…150ms debounce, self-rearmi…Incremental digestStreams from stored byte of…Liveness + activityGuards against recycled pidsAgent scannermtime walk, then bounded he…Digest cacheAtomic write + rename; mtim…Assembly + hashBroadcasts only on real cha…State · ETA · cost11-rule machine, confidence…HTTP + SSE on loopba…6 JSON routes, never routab…Vanilla JS dashboardInline-SVG charts, no build…Tray · badge · notif…Consumes its own loopback S…
  • Node.js 22 (ESM)
  • Electron 34
  • Server-Sent Events
  • node:fs streams
  • JSONL stream parsing
  • Vanilla JavaScript
  • Inline SVG
  • Bash

End of sequence

Six systems. One pattern: find the manual step, write down the rule, and make the decision reproducible.

All work

03/How I Work

A system is a decision
you can repeat.

The same loop runs whether the output is a payroll platform or a seven-script CLI. Most of it happens before any code exists, and the last third is what decides whether anyone else can run the thing.

  1. 01

    Observe

    Find the manual step nobody has questioned.

    Every one of these systems started as a recurring human action: re-typing sales figures, recalling a threshold, cross-checking three spreadsheets, tabbing through windows. The work begins by naming that step precisely.

  2. 02

    Understand

    Learn the rules from the people who own them.

    Statutory payroll, merchandising policy, warehouse attribution — the logic lives with Accounting, merchandisers and planners, not in a spec. Getting it wrong quietly is worse than not building it.

  3. 03

    Decide

    Write down what will not be built.

    Design documents state locked decisions, component boundaries, and the alternatives rejected. A doc that records what was deliberately excluded is worth more than one that only describes what was built.

  4. 04

    Architect

    Put the decision in a pure function.

    The part that decides gets no I/O and no clock. That single constraint is what makes a payroll run reproducible, a merchandising rule testable, and a threshold experiment safe to run.

  5. 05

    Guard

    Make the destructive step something you can inspect.

    Write plans asserted against a column allowlist. Dry-run by default. Two flags for a delete. Idempotency checks per cell. The safety is in the shape of the code, not in remembering to be careful.

  6. 06

    Build

    Parallel lanes against a pinned contract.

    A spec that pins exact shapes, rule ordering and performance budgets is what lets implementation run in parallel — whether the implementers are people or agents — and still integrate.

  7. 07

    Verify

    A fixed gate, in a fixed order.

    Reset, typecheck, lint, unit, integration, templates, end-to-end, build. Plus guard scripts: i18n coverage, a hard-coded-text budget that may only shrink, clean-state proofs, production integrity checks.

  8. 08

    Ship

    Record what changed — and what did not.

    Numbered releases, tags matching the deployed image, and notes stating which files are byte-identical to the previous version, verified by diff. Non-destructive by written policy.

  9. 09

    Hand over

    Done means someone else can run it.

    Runbooks written in second person, troubleshooting matrices mapping each error to its fix, and a 'where to change X' table. A tool only its author can operate is not finished.

04/Web & Commerce

Leading webdevelopment fora live storefront.

I lead web development for wooden-ships.com, a knitwear brand’s Shopify storefront, and the internal systems that read from it. Several of the projects in this portfolio exist because of that vantage point: when you maintain the store, you can see exactly which manual step happens every week.

This is professional involvement in a team’s work, not sole authorship of a company. The storefront is cited as public evidence; the internal systems around it are not shown.

Storefront
Theme and front-end development against a live commercial Shopify store — the kind where a bad deploy is a revenue incident, not a rollback.
Integrations
The store is an upstream dependency for planning, merchandising and catalog tooling. Those integrations are read-only, rate-limit aware, and fail soft.
Production QA
Release checks on a storefront that cannot go down: verify against real orders, keep the destructive paths behind confirmation, and record what was not changed.
Coordination
Work spans merchandising, production planning and operations. Most of the job is agreeing what a rule actually is before encoding it.

05/Experience

Four roles,
one employer.

Storefront admin to systems engineering in three years. What sits under each role is dated from the repositories themselves — commit ranges, release tags, file timestamps.

  1. Nov 2025 – Present

    AI & Automation Engineer

    PT In Fashion — formal title: AI Project Manager

    Building the internal systems the company runs on. An HR and statutory payroll platform taken from a 291-column spreadsheet to production, and an ads-review pipeline that puts a constrained LLM downstream of a deterministic engine.

    • Designed a 102-model domain schema and an append-only attendance ledger, and wrote every one of the 165 commits
    • Interpreted Indonesian statutory payroll rules with Accounting and HR, then encoded them as separately versioned engines in exact decimal arithmetic
    • Ran a production cutover with the legacy workbook retained as the reconciliation reference
    • Established a non-destructive deployment invariant and 53 tagged releases, each gated by a written verification runbook
    • Moved ad pause thresholds out of a reviewer's head into one config file feeding engine, templates and prompt — then designed the guardrails that stop the model contradicting the engine
    • Directed AI coding agents against written specifications while owning the architecture, the rule interpretation and the release decision
    Next.js 16PostgreSQLPrismadecimal.jsPythonOpenAIGCP Cloud Run
  2. Sep 2024 – Nov 2025

    Lead Web Developer

    PT In Fashion — wooden-ships.com

    Leading web development for a live Shopify storefront and the systems around it, and building the internal tools that each removed a recurring manual step from merchandising, planning and catalog operations.

    • Built a slow-seller decision engine that writes into a live merchandising sheet without being able to damage it
    • Gave a production planning team self-serve order exports behind company sign-on, removing a developer-in-the-loop dependency
    • Automated a ~1,000-image seasonal catalog drop across 165 products with dry-run-by-default tooling
    • Theme and storefront development, integrations, and release QA on a store that cannot go down
    • Wrote operator-facing runbooks so each tool could be handed over rather than owned by its author
    • Coordinated across a 14-hour gap with US stakeholders and third-party vendors, and ran technical screening for engineering hires
    Shopify Admin GraphQLLiquidGoogle Sheets APIFastAPINext.jsPython
  3. Mar 2024 – Sep 2024

    Lead Graphic Designer

    PT In Fashion

    Directed the storefront and campaign visual system, and stayed design lead alongside the web role through mid-2025.

    • Owned the e-commerce visual system across storefront, campaign and product imagery
    • The habit that carried forward: the interface work on every system since is mine as well
    Adobe PhotoshopShopify
  4. Jul 2023 – Mar 2024

    Shopify Admin

    PT In Fashion

    Where the code started. Storefront administration, then theme customization, then writing Liquid and JavaScript to change how the store behaved.

    • Storefront content and theme configuration on a live commercial store
    • First production Liquid, HTML, CSS and JavaScript — the beginning of everything above
    ShopifyLiquidHTMLCSSJavaScript

06/Capability Map

What I can
actually do.

Grouped by what they are for, not ranked by a number nobody can verify. Everything listed appears in at least one of the audited repositories — nothing aspirational.

  • AI & Automation

    Putting models where they add judgement, and keeping them out of where they would add risk.

    • LLM pipelines
    • Prompt versioning
    • Deterministic rule engines
    • Model output guardrails
    • Agent orchestration
    • Spec-driven AI delivery
    • API orchestration
    • Data pipelines
    • Scheduled automation
  • Product Systems

    Domain modelling for systems that have to be correct months later, not just today.

    • Domain modelling
    • Event ledgers
    • Versioned rule engines
    • Audit trails
    • Permission models
    • Field-level encryption
    • Migration & reconciliation
    • Exact decimal arithmetic
  • Development

    Full-stack, with a bias toward the boring parts that keep working.

    • TypeScript
    • Next.js / React
    • Python
    • PostgreSQL
    • Prisma / Drizzle
    • FastAPI
    • GraphQL clients
    • Vitest / pytest
    • Playwright
    • Docker
    • GCP Cloud Run
  • Web & Commerce

    Storefronts, the APIs behind them, and the operations they feed.

    • Shopify Admin GraphQL
    • Shopify themes
    • WordPress REST / ACF
    • Google Sheets & Drive APIs
    • Meta Marketing API
    • OAuth & service accounts
    • Rate-limit-aware clients
    • Performance & SEO
  • Direction & Delivery

    The part that decides whether any of the above reaches production and survives there.

    • Requirements analysis
    • System architecture
    • Release management
    • Non-destructive deployment policy
    • Verification gates
    • Technical documentation
    • Operator runbooks
    • Stakeholder coordination
    • Production QA
    • Handover discipline

07/Off Duty

I also make
things to watch.

Content creation is the secondary track, and it feeds the primary one more than it looks: framing, pacing, and the discipline of cutting something until only the part that matters is left are the same instincts that make an interface or a report readable.

@jdenandro

@jdenandro

Creator

FEED OFFLINE

Live feed not connected — showing a placeholder. See docs/instagram-setup.md.

Portrait of J. Denandro

08/After the system shuts down

Tennis in the morning. Football with people who take it far too seriously. The gym, most days. And films — which is probably obvious by now.

There is a person behind the systems. He is from Bali, works in WITA, and is generally easier to reach than his infrastructure.

09/Contact

Have a problemthat should be a system?