[{"data":1,"prerenderedAt":213},["ShallowReactive",2],{"case-study-en-property-management-platform":3,"case-study-related-en-property-management-platform":189},{"id":4,"title":5,"card":6,"diagrams":12,"extension":28,"eyebrow":29,"facts":30,"intro":40,"layout":41,"locale":60,"meta":61,"order":59,"results":62,"slug":75,"stem":76,"storySections":77,"techGroups":89,"__hash__":188},"caseStudies\u002Fcase-studies\u002Fen\u002Fproperty-management-platform.json","AWS architecture for a multi-tenant property management platform with AI workflows",{"title":7,"badges":8,"thumb":11},"Multi-tenant property platform",[9,10],"Multi-tenancy","Amazon Bedrock","\u002Fcase-studies\u002Fproperty-management-platform\u002Fsystem-architecture.webp",[13,16,20,24],{"src":11,"alt":14,"caption":15},"System architecture: Web app, API and AI agents in one AWS account","System architecture.",{"src":17,"alt":18,"caption":19},"\u002Fcase-studies\u002Fproperty-management-platform\u002Fai-workflow.webp","AI workflow: A damage report, from message to scheduled repair","The damage report workflow. The same pattern handles questions about statements, documents and appointments.",{"src":21,"alt":22,"caption":23},"\u002Fcase-studies\u002Fproperty-management-platform\u002Faccess-model.webp","Access model: Five roles, one set of rules, four layers of enforcement","Roles and permissions per organisation.",{"src":25,"alt":26,"caption":27},"\u002Fcase-studies\u002Fproperty-management-platform\u002Fcore-stack.webp","Core stack","","json","Case study · Builders & Creatives",[31,34,37],{"label":32,"value":33},"Industry","Real estate, property management",{"label":35,"value":36},"My role","Architecture, full-stack development, AI workflows",{"label":38,"value":39},"Focus","Multi-tenancy, LLM integration, AWS","Software for property management companies, landlords, tenants and caretakers. One web app, a hosted LLM inside the AWS account, and AI agents that take over the routine work.",[42,44,46,49,51,53,54,56,58],{"type":43},"header",{"type":45},"results",{"type":47,"index":48},"section",0,{"type":50,"index":48},"diagram",{"type":50,"index":52},1,{"type":47,"index":52},{"type":50,"index":55},2,{"type":57},"technology",{"type":50,"index":59},3,"en",{},[63,67,71],{"tag":64,"title":65,"body":66},"AI workflows","Routine requests handled by AI","Tenant requests are triaged, answered and routed by agents. A person approves anything that matters.",{"tag":68,"title":69,"body":70},"Security","Strict isolation between organisations","Every user sees exactly their data, enforced in the token, the API, the database and storage.",{"tag":72,"title":73,"body":74},"Privacy","The LLM stays in the AWS account","Models run on Bedrock with guardrails. Property data is not shared with third parties.","property-management-platform","case-studies\u002Fen\u002Fproperty-management-platform",[78,85],{"heading":79,"paragraphs":80},"The story",[81,82,83,84],"Property management runs on messages: a broken heater, a question about the service charge statement, a lease document someone needs by Friday. Managers, landlords, tenants and caretakers all need the same information, but each should only see their part of it.","I designed the platform as a multi-tenant SaaS on AWS. A Nuxt web app and a Python API run in Docker on EC2 inside a private VPC, behind CloudFront and a load balancer. Cognito issues tokens with a role and an organisation ID, and that ID follows every request down to PostgreSQL row-level security and per-organisation S3 prefixes.","On top sits the AI layer. The API puts requests on an SQS queue, a Lambda agent calls Bedrock and works through a set of tools: look up the unit, search the documents, read the photos, draft the ticket. Step Functions run the longer workflows, including the approval step where a property manager confirms before anything is sent.","Everything is observable in CloudWatch, from API latency to every prompt, tool call and token cost, and the whole stack is shipped through GitHub Actions.",{"heading":9,"paragraphs":86},[87,88],"Each property management company is its own organisation. Within it, five roles see different slices of the same data, and the AI assistant only ever sees what the current user is allowed to see.","Access is enforced in four layers rather than trusted to one, so a bug in the app alone cannot leak another organisation's data.",[90,119,139,171],{"title":91,"items":92},"Application",[93,99,105,111,115],{"name":94,"role":95,"icon":96,"icons":97},"Nuxt, Vue","web app for all roles","\u002Ficons\u002Ftech\u002Fnuxt.svg",[96,98],"\u002Ficons\u002Ftech\u002Fvuejs.svg",{"name":100,"role":101,"icon":102,"icons":103},"Python, FastAPI","API, business logic, AI tools","\u002Ficons\u002Ftech\u002Fpython.svg",[102,104],"\u002Ficons\u002Ftech\u002Ffastapi.svg",{"name":106,"role":107,"icon":108,"icons":109},"PostgreSQL on Amazon RDS","multi-tenant data, row-level security","\u002Ficons\u002Ftech\u002Fpostgresql.svg",[108,110],"\u002Ficons\u002Faws\u002FAmazon-RDS.svg",{"name":112,"role":113,"icon":114},"Amazon EC2","app servers","\u002Ficons\u002Faws\u002FAmazon-EC2.svg",{"name":116,"role":117,"icon":118},"Amazon S3","documents and photos","\u002Ficons\u002Faws\u002FAmazon-S3.svg",{"title":120,"items":121},"AI",[122,125,129,133],{"name":10,"role":123,"icon":124},"hosted LLM, knowledge base, guardrails","\u002Ficons\u002Faws\u002FAmazon-Bedrock.svg",{"name":126,"role":127,"icon":128},"AWS Lambda","AI agent with tools","\u002Ficons\u002Faws\u002FAWS-Lambda.svg",{"name":130,"role":131,"icon":132},"AWS Step Functions","multi-step AI workflows","\u002Ficons\u002Faws\u002FAWS-Step-Functions.svg",{"name":134,"role":135,"icon":136,"icons":137},"Amazon SQS, Amazon SNS","job queue, notifications","\u002Ficons\u002Faws\u002FAmazon-SQS.svg",[136,138],"\u002Ficons\u002Faws\u002FAmazon-SNS.svg",{"title":140,"items":141},"Security and networking",[142,146,150,154,161,165],{"name":143,"role":144,"icon":145},"Amazon Cognito","users, roles, organisation claim","\u002Ficons\u002Faws\u002FAmazon-Cognito.svg",{"name":147,"role":148,"icon":149},"AWS IAM","least-privilege roles","\u002Ficons\u002Faws\u002FAWS-IAM.svg",{"name":151,"role":152,"icon":153},"Amazon VPC","private subnets, security groups","\u002Ficons\u002Faws\u002FAmazon-VPC.svg",{"name":155,"role":156,"icon":157,"icons":158},"CloudFront, Route 53, WAF","edge, DNS, protection","\u002Ficons\u002Faws\u002FAmazon-CloudFront.svg",[157,159,160],"\u002Ficons\u002Faws\u002FAmazon-Route-53.svg","\u002Ficons\u002Faws\u002FAWS-WAF.svg",{"name":162,"role":163,"icon":164},"Load Balancer","TLS, routing","\u002Ficons\u002Faws\u002FElastic-Load-Balancing.svg",{"name":166,"role":167,"icon":168,"icons":169},"Secrets Manager, KMS","credentials, encryption","\u002Ficons\u002Faws\u002FAWS-Secrets-Manager.svg",[168,170],"\u002Ficons\u002Faws\u002FAWS-KMS.svg",{"title":172,"items":173},"Delivery and operations",[174,180,184],{"name":175,"role":176,"icon":177,"icons":178},"Docker, Amazon ECR","containers, image registry","\u002Ficons\u002Ftech\u002Fdocker.svg",[177,179],"\u002Ficons\u002Faws\u002FAmazon-ECR.svg",{"name":181,"role":182,"icon":183},"GitHub Actions","CI\u002FCD","\u002Ficons\u002Ftech\u002Fgithubactions.svg",{"name":185,"role":186,"icon":187},"Amazon CloudWatch","logs, metrics, traces, alarms","\u002Ficons\u002Faws\u002FAmazon-CloudWatch.svg","CbTPeCLDA8Vns_VSBa-Uhm5Oz8HQ8rBiq0Mw7VWZ4xk",[190,197,205],{"slug":191,"card":192},"ale-business-bookkeeping",{"title":193,"badges":194,"thumb":196},"Chat-driven bookkeeping",[195,10],"LLM harness","\u002Fcase-studies\u002Fale-business-bookkeeping\u002Fsystem-architecture.webp",{"slug":198,"card":199},"kv-cache-storage-tiering",{"title":200,"badges":201,"thumb":204},"KV cache storage tiering",[202,203],"vLLM","LMCache","\u002Fcase-studies\u002Fkv-cache-storage-tiering\u002Ftier-hierarchy.webp",{"slug":206,"card":207},"live-product-data-pipeline",{"title":208,"badges":209,"thumb":212},"Live product data pipeline",[210,211],"Kafka Streams","Amazon MSK","\u002Fcase-studies\u002Flive-product-data-pipeline\u002Fafter.webp",1790607906264]