[{"data":1,"prerenderedAt":598},["ShallowReactive",2],{"project-voice-ai-enterprise-telephony-en":3,"portfolio-en-all-all":167,"testimonials-yml":583},{"_path":4,"_dir":5,"_draft":6,"_partial":6,"_locale":7,"title":8,"description":7,"slug":9,"year":10,"stack":11,"tags":22,"cover":28,"summary":29,"featured":30,"order":31,"body":32,"_type":161,"_id":162,"_source":163,"_file":164,"_stem":165,"_extension":166},"\u002Fportfolio\u002Fvoice-ai-enterprise-telephony","portfolio",false,"","Voice-AI agent for enterprise telephony","voice-ai-enterprise-telephony","2026 - present",[12,13,14,15,16,17,18,19,20,21],"TypeScript","Fastify","WebSockets","Telnyx","Azure","Vue","PostgreSQL","pgvector","RAG","Prisma",[23,24,25,26,27],"AI","Voice","SaaS","Multi-tenant","Enterprise","\u002Fimages\u002Fportfolio\u002Fvoice-ai-enterprise-telephony\u002Fcover.jpg","A multi-tenant voice AI platform deployed for a European software company serving 20,000+ trade businesses - handling inbound calls end-to-end with GDPR-compliant infrastructure.",true,2,{"type":33,"children":34,"toc":156},"root",[35,44,50,55,60,65,71,76,87,97,115,125,135,141,146,151],{"type":36,"tag":37,"props":38,"children":40},"element","h2",{"id":39},"the-problem",[41],{"type":42,"value":43},"text","The problem",{"type":36,"tag":45,"props":46,"children":47},"p",{},[48],{"type":42,"value":49},"The client is a European software company serving 20,000+ trade businesses internationally - painters, plumbers, electricians, decorators. The kind of operators whose owners are usually the ones doing the work.",{"type":36,"tag":45,"props":51,"children":52},{},[53],{"type":42,"value":54},"That's the constraint: every call they answer is time they're not on a job. Every call they miss is a quote that goes to a competitor. Generic call centres and IVR menus haven't solved it - trade callers ask specific questions (\"can you fit a new boiler before Friday?\"), expect a real conversation, and won't sit through phone-tree menus.",{"type":36,"tag":45,"props":56,"children":57},{},[58],{"type":42,"value":59},"The client wanted an AI agent that could answer these calls 24\u002F7, hold a natural conversation, capture quote and callback requests, and stay strictly on-topic - across thousands of independent trade businesses, each with their own services, pricing, and knowledge base.",{"type":36,"tag":45,"props":61,"children":62},{},[63],{"type":42,"value":64},"The brief was multi-tenant by definition. The execution had to be GDPR-clean, enterprise-grade, and operable by non-technical end users.",{"type":36,"tag":37,"props":66,"children":68},{"id":67},"the-approach",[69],{"type":42,"value":70},"The approach",{"type":36,"tag":45,"props":72,"children":73},{},[74],{"type":42,"value":75},"We specced the full architecture and proposed a four-stage build, with each stage compounding on the last - database, models, and abstractions defined in Stage 1 so Stages 2-4 wouldn't require re-architecture. The proposal was accepted within 24 hours of the pitch call.",{"type":36,"tag":45,"props":77,"children":78},{},[79,85],{"type":36,"tag":80,"props":81,"children":82},"strong",{},[83],{"type":42,"value":84},"GDPR as the architectural driver.",{"type":42,"value":86}," Every external dependency was evaluated against data-residency and minimal-third-party principles. Telnyx for telephony (developer-friendly, EU-region routing). Azure OpenAI for STT, LLM, and TTS - using Data Zone Standard to keep all model inference inside the EU, even as Azure shifts load across regions. Mailgun's EU region for transactional email of call summaries. No data leaves the EU at any layer of the stack.",{"type":36,"tag":45,"props":88,"children":89},{},[90,95],{"type":36,"tag":80,"props":91,"children":92},{},[93],{"type":42,"value":94},"Real-time voice pipeline.",{"type":42,"value":96}," A Fastify backend on WebSockets handles the live audio stream - incoming caller audio is transcribed, sent to the LLM with the relevant tenant context, and the response streamed back through TTS, all under conversational latency budgets. Voice activity detection (VAD), barge-in handling, dual-audio prevention, and \"is the caller pausing to think or done talking?\" boundary detection were all calibrated empirically - the kind of work that decides whether the agent feels conversational or robotic.",{"type":36,"tag":45,"props":98,"children":99},{},[100,105,107,113],{"type":36,"tag":80,"props":101,"children":102},{},[103],{"type":42,"value":104},"Tenant-scoped RAG over a knowledge base.",{"type":42,"value":106}," Each tenant uploads their own knowledge - PDFs, URLs, service descriptions. The content is chunked and embedded into a ",{"type":36,"tag":108,"props":109,"children":111},"code",{"className":110},[],[112],{"type":42,"value":19},{"type":42,"value":114},"-backed index. At call time, the caller's question is matched against the tenant's chunks via cosine similarity; only the most relevant context is sent to the LLM. This keeps prompts small (cost), responses on-topic (quality), and inputs strictly tenant-scoped (no cross-tenant leakage). The similarity threshold was tuned for the cost\u002Fquality boundary specifically.",{"type":36,"tag":45,"props":116,"children":117},{},[118,123],{"type":36,"tag":80,"props":119,"children":120},{},[121],{"type":42,"value":122},"A full multi-tenant SaaS to operate it.",{"type":42,"value":124}," Two dashboards - a super-admin view for the client's brand\u002Ftenant management, and a tenant-facing view for the trade businesses themselves. 4-role RBAC (super admin, brand admin, tenant admin, tenant user). Per-tenant configuration of the AI greeting, GDPR disclosure, and CTA text - the three things the agent says immediately after answering. Working hours configurable per tenant and fed to the AI as context. Pricing model with included minutes plus per-minute overage, calculated and visible per tenant. Call logs with full transcripts, AI-generated summaries, classifications (quote request, callback, general question), and configurable email notifications.",{"type":36,"tag":45,"props":126,"children":127},{},[128,133],{"type":36,"tag":80,"props":129,"children":130},{},[131],{"type":42,"value":132},"Built for what comes next.",{"type":42,"value":134}," The architecture is adapter-style throughout - swap one LLM for another, one STT\u002FTTS provider for another, plug in any number of ERPs. The system is already wired for ERP integration in a later stage, enabling status-update calls (\"when is my work happening?\") and automated work-ticket creation. Interactive setup scripts automate Azure provisioning end-to-end.",{"type":36,"tag":37,"props":136,"children":138},{"id":137},"the-outcome",[139],{"type":42,"value":140},"The outcome",{"type":36,"tag":45,"props":142,"children":143},{},[144],{"type":42,"value":145},"Stage 1 shipped to production with the AI agent handling real inbound calls in two languages at launch. By the first demo, the client had 20 beta tenants lined up. After Stage 1 closed, the client immediately accelerated Stage 2 - months ahead of the original roadmap.",{"type":36,"tag":45,"props":147,"children":148},{},[149],{"type":42,"value":150},"The agent answers calls 24\u002F7, stays strictly on-topic to each tenant's business, doesn't hallucinate beyond the tenant's knowledge base, captures quote and callback requests, and routes call summaries to the right people.",{"type":36,"tag":45,"props":152,"children":153},{},[154],{"type":42,"value":155},"Next stages: expansion to additional countries, ERP integration for live job-status calls, and expanded multilingual coverage.",{"title":7,"searchDepth":31,"depth":31,"links":157},[158,159,160],{"id":39,"depth":31,"text":43},{"id":67,"depth":31,"text":70},{"id":137,"depth":31,"text":140},"markdown","content:portfolio:voice-ai-enterprise-telephony.md","content","portfolio\u002Fvoice-ai-enterprise-telephony.md","portfolio\u002Fvoice-ai-enterprise-telephony","md",[168,299,399],{"_path":169,"_dir":5,"_draft":6,"_partial":6,"_locale":7,"title":170,"description":7,"slug":171,"year":172,"stack":173,"tags":179,"cover":184,"summary":185,"featured":30,"order":186,"body":187,"_type":161,"_id":296,"_source":163,"_file":297,"_stem":298,"_extension":166},"\u002Fportfolio\u002Foffline-first-pos","Offline-first POS for hospitality","offline-first-pos","2024 - present",[174,17,175,176,18,177,21,178],"Kotlin","Node","Express","SQLite","Stripe",[180,25,181,182,183],"POS","Mobile","Hospitality","Offline-first","\u002Fimages\u002Fportfolio\u002Foffline-first-pos\u002Fcover.jpg","A mobile-first POS platform built for SMBs in hospitality and retail - running offline by default, fiscally compliant across multiple countries.",1,{"type":33,"children":188,"toc":291},[189,193,198,203,208,212,222,232,242,247,257,267,272,276,281,286],{"type":36,"tag":37,"props":190,"children":191},{"id":39},[192],{"type":42,"value":43},{"type":36,"tag":45,"props":194,"children":195},{},[196],{"type":42,"value":197},"Most POS systems sold to small businesses share a few familiar flaws. They require a web dashboard to manage anything that matters - implying a laptop in the back office, not the phone in your pocket. They lock operators into the vendor's own payment processor. They charge premium pricing and tie that pricing to long contracts. They ship with broken or missing features (inventory that doesn't reconcile, reports that don't add up). And almost universally, they require an internet connection to do their actual job: take payments.",{"type":36,"tag":45,"props":199,"children":200},{},[201],{"type":42,"value":202},"For the small operator - a café, a bar, a barber, a corner shop - these are real costs. A dropped wifi connection becomes a stalled sale. A change to product pricing requires going home to a laptop. A payment processor lock-in becomes a permanent tax on every transaction.",{"type":36,"tag":45,"props":204,"children":205},{},[206],{"type":42,"value":207},"This product was built to fix all of that, for SMBs who can't afford friction at the till.",{"type":36,"tag":37,"props":209,"children":210},{"id":67},[211],{"type":42,"value":70},{"type":36,"tag":45,"props":213,"children":214},{},[215,217],{"type":42,"value":216},"The product is built around three deliberate choices: ",{"type":36,"tag":80,"props":218,"children":219},{},[220],{"type":42,"value":221},"mobile-first management, offline-first operation, and provider-agnostic payments.",{"type":36,"tag":45,"props":223,"children":224},{},[225,230],{"type":36,"tag":80,"props":226,"children":227},{},[228],{"type":42,"value":229},"Native Android for the cash register and management.",{"type":42,"value":231}," The till and the management dashboard live in the same native Kotlin app. Operators can run a register, manage inventory, adjust pricing, view analytics, and onboard staff - all from a phone, on the floor, between customers. The web dashboard exists as an optional add-on for desk work, not a dependency.",{"type":36,"tag":45,"props":233,"children":234},{},[235,240],{"type":36,"tag":80,"props":236,"children":237},{},[238],{"type":42,"value":239},"Offline-first by design.",{"type":42,"value":241}," The cash register works without an internet connection. Setup requires connectivity: an admin signs in, assigns a device to a cash register, and the device pulls down everything it needs - products, prices, taxes, tables, staff, fiscal configuration. After that, the device operates indefinitely offline. Receipts, sales, stock movements all persist locally in SQLite (via Room) and sync to the server the moment connectivity returns.",{"type":36,"tag":45,"props":243,"children":244},{},[245],{"type":42,"value":246},"The sync layer is the unglamorous engineering core of the product. It handles conflict resolution between devices, fiscal rules that allow delayed fiscalisation (Croatia requires every receipt to be fiscalised \"as soon as possible\" - a non-trivial constraint), and the boundary between online-when-possible and offline-when-necessary without losing data or duplicating writes.",{"type":36,"tag":45,"props":248,"children":249},{},[250,255],{"type":36,"tag":80,"props":251,"children":252},{},[253],{"type":42,"value":254},"Bring your own payment processor.",{"type":42,"value":256}," Most POS vendors force their own card processor on the operator. This one integrates Stripe, Square, Teya, and SumUp out of the box, with more in the roadmap. Operators keep their existing merchant relationships.",{"type":36,"tag":45,"props":258,"children":259},{},[260,265],{"type":36,"tag":80,"props":261,"children":262},{},[263],{"type":42,"value":264},"Multi-location and multi-country, by architecture.",{"type":42,"value":266}," Branches, warehouses, cash registers, and inventory all model real-world hierarchy. One company can run multiple branches, each with their own warehouse and tills. Inventory is tracked per warehouse. Fiscal compliance is pluggable per country - currently supporting Croatian and UK fiscal regimes, with new regions added as fiscal-module additions rather than re-architecture.",{"type":36,"tag":45,"props":268,"children":269},{},[270],{"type":42,"value":271},"Building the Croatian fiscalisation flow first was deliberate. Croatian fiscal law is among the strictest known: every receipt must be cryptographically signed, transmitted to the tax authority, and reconciled - including offline-deferred receipts. Once Croatia was solved, adding the UK (a simpler regime) was straightforward. The architecture inverts the usual fiscal pain curve.",{"type":36,"tag":37,"props":273,"children":274},{"id":137},[275],{"type":42,"value":140},{"type":36,"tag":45,"props":277,"children":278},{},[279],{"type":42,"value":280},"The platform is in final QA with five pilot operators across hospitality and retail. It supports two countries (Croatia, UK) with the multi-country architecture proven on the harder one first. The app has run in pilot for three months without a single crash. Onboarding is a two-step process inside the app - no sales call required.",{"type":36,"tag":45,"props":282,"children":283},{},[284],{"type":42,"value":285},"The first sales partnership is in motion, with the partner planning to operate their own venue on the same platform - a credibility signal we'd take over any number of marketing pages.",{"type":36,"tag":45,"props":287,"children":288},{},[289],{"type":42,"value":290},"Next: shipping to general availability, expanding fiscal coverage to the next EU regions, and rolling out the planned payment processor additions.",{"title":7,"searchDepth":31,"depth":31,"links":292},[293,294,295],{"id":39,"depth":31,"text":43},{"id":67,"depth":31,"text":70},{"id":137,"depth":31,"text":140},"content:portfolio:offline-first-pos.md","portfolio\u002Foffline-first-pos.md","portfolio\u002Foffline-first-pos",{"_path":4,"_dir":5,"_draft":6,"_partial":6,"_locale":7,"title":8,"description":7,"slug":9,"year":10,"stack":300,"tags":301,"cover":28,"summary":29,"featured":30,"order":31,"body":302,"_type":161,"_id":162,"_source":163,"_file":164,"_stem":165,"_extension":166},[12,13,14,15,16,17,18,19,20,21],[23,24,25,26,27],{"type":33,"children":303,"toc":394},[304,308,312,316,320,324,328,332,340,348,362,370,378,382,386,390],{"type":36,"tag":37,"props":305,"children":306},{"id":39},[307],{"type":42,"value":43},{"type":36,"tag":45,"props":309,"children":310},{},[311],{"type":42,"value":49},{"type":36,"tag":45,"props":313,"children":314},{},[315],{"type":42,"value":54},{"type":36,"tag":45,"props":317,"children":318},{},[319],{"type":42,"value":59},{"type":36,"tag":45,"props":321,"children":322},{},[323],{"type":42,"value":64},{"type":36,"tag":37,"props":325,"children":326},{"id":67},[327],{"type":42,"value":70},{"type":36,"tag":45,"props":329,"children":330},{},[331],{"type":42,"value":75},{"type":36,"tag":45,"props":333,"children":334},{},[335,339],{"type":36,"tag":80,"props":336,"children":337},{},[338],{"type":42,"value":84},{"type":42,"value":86},{"type":36,"tag":45,"props":341,"children":342},{},[343,347],{"type":36,"tag":80,"props":344,"children":345},{},[346],{"type":42,"value":94},{"type":42,"value":96},{"type":36,"tag":45,"props":349,"children":350},{},[351,355,356,361],{"type":36,"tag":80,"props":352,"children":353},{},[354],{"type":42,"value":104},{"type":42,"value":106},{"type":36,"tag":108,"props":357,"children":359},{"className":358},[],[360],{"type":42,"value":19},{"type":42,"value":114},{"type":36,"tag":45,"props":363,"children":364},{},[365,369],{"type":36,"tag":80,"props":366,"children":367},{},[368],{"type":42,"value":122},{"type":42,"value":124},{"type":36,"tag":45,"props":371,"children":372},{},[373,377],{"type":36,"tag":80,"props":374,"children":375},{},[376],{"type":42,"value":132},{"type":42,"value":134},{"type":36,"tag":37,"props":379,"children":380},{"id":137},[381],{"type":42,"value":140},{"type":36,"tag":45,"props":383,"children":384},{},[385],{"type":42,"value":145},{"type":36,"tag":45,"props":387,"children":388},{},[389],{"type":42,"value":150},{"type":36,"tag":45,"props":391,"children":392},{},[393],{"type":42,"value":155},{"title":7,"searchDepth":31,"depth":31,"links":395},[396,397,398],{"id":39,"depth":31,"text":43},{"id":67,"depth":31,"text":70},{"id":137,"depth":31,"text":140},{"_path":400,"_dir":5,"_draft":6,"_partial":6,"_locale":7,"title":401,"description":7,"slug":402,"year":403,"stack":404,"tags":411,"cover":415,"summary":416,"featured":30,"order":417,"body":418,"_type":161,"_id":580,"_source":163,"_file":581,"_stem":582,"_extension":166},"\u002Fportfolio\u002Fsentinel-trading-strategy-platform","Sentinel - trading strategy platform","sentinel-trading-strategy-platform","2025 - present",[405,406,18,407,408,17,409,410],"Python","FastAPI","TimescaleDB","SQLAlchemy","Claude","Alpaca API",[412,413,25,23,414],"Quant","Trading","Time-series","\u002Fimages\u002Fportfolio\u002Fsentinel-trading-strategy-platform\u002Fcover.jpg","A platform for systematic traders to prototype, backtest, and live-execute trading strategies - with multi-source composite scoring and the same code path from research to production.",3,{"type":33,"children":419,"toc":575},[420,424,429,434,439,443,448,458,492,518,538,551,555,560,565,570],{"type":36,"tag":37,"props":421,"children":422},{"id":39},[423],{"type":42,"value":43},{"type":36,"tag":45,"props":425,"children":426},{},[427],{"type":42,"value":428},"Most retail trading tools force a choice. Use a backtesting framework - your strategy lives in one mental model, designed for replaying historical bars. Use a live execution platform - rewrite the strategy in a different mental model, with different fee assumptions, different order types, different state machines. Each rewrite is an opportunity for the live behaviour to silently diverge from what the backtest promised.",{"type":36,"tag":45,"props":430,"children":431},{},[432],{"type":42,"value":433},"The platforms that do try to unify both - QuantConnect, MetaTrader, the bigger institutional tools - are either prohibitive on cost, locked to a specific broker, or built around opinionated strategy DSLs that can't express the multi-source signal aggregation that real systematic strategies need.",{"type":36,"tag":45,"props":435,"children":436},{},[437],{"type":42,"value":438},"Sentinel was built to remove the rewrite. From \"idea\" to \"validated against four years of historical data\" should take one CLI command. From validated to live paper trading should take a config flip. And the code path between those two states should be the same code path.",{"type":36,"tag":37,"props":440,"children":441},{"id":67},[442],{"type":42,"value":70},{"type":36,"tag":45,"props":444,"children":445},{},[446],{"type":42,"value":447},"Sentinel is a Python + FastAPI backend with a Vue 3 dashboard, running on TimescaleDB. Three architectural decisions carry the platform.",{"type":36,"tag":45,"props":449,"children":450},{},[451,456],{"type":36,"tag":80,"props":452,"children":453},{},[454],{"type":42,"value":455},"One execution engine, two modes.",{"type":42,"value":457}," A shared execution module models fees, slippage, partial fills, bracket orders (parent + take-profit + stop-loss as one atomic submission), and exit ladders. Both the live runner and the backtest replay engine call into the same module. The only difference between modes is the source of truth - Alpaca's API for live, OHLCV bar data for backtest. Same fee model, same lot-splitting, same exit logic. No drift.",{"type":36,"tag":45,"props":459,"children":460},{},[461,466,468,474,476,482,484,490],{"type":36,"tag":80,"props":462,"children":463},{},[464],{"type":42,"value":465},"One signal store, two query patterns.",{"type":42,"value":467}," Every signal - from every source, for every asset, at every timestamp - lives in a single TimescaleDB hypertable keyed by ",{"type":36,"tag":108,"props":469,"children":471},{"className":470},[],[472],{"type":42,"value":473},"(asset, source_id, ts)",{"type":42,"value":475},". Backtest queries it with ",{"type":36,"tag":108,"props":477,"children":479},{"className":478},[],[480],{"type":42,"value":481},"ts \u003C= X",{"type":42,"value":483}," to enforce point-in-time correctness - no future data leakage into historical decisions. Live queries it with ",{"type":36,"tag":108,"props":485,"children":487},{"className":486},[],[488],{"type":42,"value":489},"latest",{"type":42,"value":491},". One data model serves both modes; backtest accuracy and live decision-making share the same source of truth.",{"type":36,"tag":45,"props":493,"children":494},{},[495,500,502,508,510,516],{"type":36,"tag":80,"props":496,"children":497},{},[498],{"type":42,"value":499},"Pure-function scorers, composable signals.",{"type":42,"value":501}," Each signal source (RSI, MACD, fear-greed, Claude-calibrated news sentiment, etc.) is a pure function: given a context, return a ",{"type":36,"tag":108,"props":503,"children":505},{"className":504},[],[506],{"type":42,"value":507},"(score in [-100, +100], confidence)",{"type":42,"value":509}," tuple. The composite is ",{"type":36,"tag":108,"props":511,"children":513},{"className":512},[],[514],{"type":42,"value":515},"Σ (source_score × weight × confidence\u002F100)",{"type":42,"value":517},", normalised against per-asset thresholds. Adding a new source is one scorer function plus one config entry - and it's immediately available in both backtest and live modes.",{"type":36,"tag":45,"props":519,"children":520},{},[521,523,528,530,536],{"type":42,"value":522},"The Claude integration is worth calling out. Most LLM-driven sentiment work is classification (\"bullish\u002Fbearish\u002Fneutral\") - coarse, hard to weight, hard to debug. Sentinel uses Claude for ",{"type":36,"tag":80,"props":524,"children":525},{},[526],{"type":42,"value":527},"calibrated numeric scoring",{"type":42,"value":529},": given a batch of news headlines for an asset, return a structured ",{"type":36,"tag":108,"props":531,"children":533},{"className":532},[],[534],{"type":42,"value":535},"{sentiment: -100..+100, confidence: 0..100, reasoning: string}",{"type":42,"value":537},". The reasoning string is stored alongside every score for auditability - when a strategy fires on news sentiment, you can later read why the model scored it that way.",{"type":36,"tag":45,"props":539,"children":540},{},[541,543,549],{"type":42,"value":542},"Operationally: GitHub Actions deploy to Hetzner in ~90 seconds via an idempotent deploy script. Postgres backed up daily to S3. HetrixTools monitoring with Slack alerts. A ",{"type":36,"tag":108,"props":544,"children":546},{"className":545},[],[547],{"type":42,"value":548},"source_health",{"type":42,"value":550}," table tracks every external integration's last-success time and rolling error rate, surfaced in the dashboard so a missing news feed or RPC outage is visible at a glance.",{"type":36,"tag":37,"props":552,"children":553},{"id":137},[554],{"type":42,"value":140},{"type":36,"tag":45,"props":556,"children":557},{},[558],{"type":42,"value":559},"Sentinel is in active development and running live in paper-trading mode against the Alpaca API. The first strategy validated through walk-forward analysis - an Opening Range Breakout strategy - is executing real bracket orders against current market data. Composite multi-source scoring runs across four crypto and four equity tickers, with 8 weighted sources per asset.",{"type":36,"tag":45,"props":561,"children":562},{},[563],{"type":42,"value":564},"Three strategies have been built and evaluated to date: the ORB strategy (validated, live), Mean Reversion (rejected by walk-forward and shelved), and a Composite-stocks variant (validated with MACD-only and shelved pending source-mix tuning). The rejected results are kept in the engineering record - negative results are as informative as positive ones for strategy iteration.",{"type":36,"tag":45,"props":566,"children":567},{},[568],{"type":42,"value":569},"Walk-forward validation across a four-year window with twelve splits and a full threshold grid runs in roughly five minutes. Roughly 38,000 signal rows are generated every 30 days. Nine external integrations are tracked live (FRED, Polygon, Etherscan, Helius RPC, news feeds, Reddit, Alpaca, Solscan, LunarCrush) with health visible in the dashboard.",{"type":36,"tag":45,"props":571,"children":572},{},[573],{"type":42,"value":574},"Next: additional broker integrations, expanded asset classes, and an early-access program for systematic traders.",{"title":7,"searchDepth":31,"depth":31,"links":576},[577,578,579],{"id":39,"depth":31,"text":43},{"id":67,"depth":31,"text":70},{"id":137,"depth":31,"text":140},"content:portfolio:sentinel-trading-strategy-platform.md","portfolio\u002Fsentinel-trading-strategy-platform.md","portfolio\u002Fsentinel-trading-strategy-platform",[584,591],{"id":585,"name":586,"role":587,"company":588,"quote":589,"projectSlug":590,"featured":6},"jordan-pace-northwind","Jordan Pace","CTO","Northwind Logistics","They shipped in six weeks what our previous agency couldn't in six months. Clear-eyed, senior, no fluff.","northwind-routing",{"id":592,"name":593,"role":594,"company":595,"quote":596,"projectSlug":597,"featured":6},"mira-sato-ledgerline","Mira Sato","Head of Product","Ledgerline","Spectral Byte rebuilt our onboarding and conversion jumped almost overnight. They understood the business, not just the code.","ledgerline-onboarding",1780863208630]