From Discovery Notes to an ADO Import Pack
Discovery backlogs often die as sprawling spreadsheets. This week I turned a kickoff architecture, an estimating sheet, and a gap analysis into an Azure DevOps import pack — so a multi-app modernization engagement can load phased work items instead of debating a static file.
What happened
Planned architecture (discussion-grade)
Married the system design with the Azure architecture into a living trajectory the team can share:
- API-first, location-driven platform — one Azure SQL system of record, document binaries in Azure Blob (SQL keeps metadata /
blob_uri), shared .NET API behind API Management - Three clients, one API — internal React SPA, offline-capable field app (.NET MAUI + SQLite sync), and an external React portal (Entra External ID + accessibility expectations)
- Scriptable Azure baseline — PowerShell orchestrating Azure CLI against a JSON resource manifest (Key Vault, App Insights, SQL, App Service, Functions, Static Web Apps stubs, Communication Services) in a single primary resource group
- Auth split — Entra ID for internal users; Entra External ID for portal users
- Integrations later in the sequence — payments, HR feed, insurer file drop, addressing, notifications — after core domain and apps stand up
- Migration + hardening as late phases — cutover from the legacy permitting platform, then security/docs/go-live
Nothing here claims a finished production tenant; it is the kickoff architecture the backlog is sequenced against.
Delivery phases
Sequencing is P0–P10 (~16-month build). Phases are delivery order labels — not ADO Epics:
| Code | Phase | Intent |
|---|---|---|
| P0 | Discovery & UX/data design | Requirements depth, mocks, SQL + Blob layout |
| P1 | Azure platform & IaC | Scriptable baseline resources |
| P2 | Data model & API core | Location-first anchors, OpenAPI, auth |
| P3 | Main app shell | Internal React Static Web App framework |
| P4 | Domain capabilities | Licensing, plan review, inspections, complaints, fiscal |
| P5 | Field / inspectors app | Offline MAUI + sync |
| P6 | External portal | Public React + External ID |
| P7 | Integrations | Payments, HR, file drops, notifications, addressing |
| P8 | Migration | Legacy platform → new system |
| P9 | Reporting & BI | Analytics (decision pending) |
| P10 | Hardening & cutover | Security, runbooks, go-live |
flowchart TD
P0[P0 Discovery] --> P1[P1 Azure_IaC] --> P2[P2 Data_API] --> P3[P3 Main_shell] --> P4[P4 Domain]
P2 --> P5[P5 Field_app]
P2 --> P6[P6 Portal]
P4 --> P7[P7 Integrations]
P5 --> P7
P6 --> P7
P4 --> P8[P8 Migration]
P7 --> P9[P9 Reporting]
P8 --> P10[P10 Cutover]
P9 --> P10
From estimate sheet to picklist
Started from the original estimating backlog (archived workbook — not reproduced in detail here). Ran a gap analysis against the architecture so platform, IaC, auth, observability, and other design-driven work that never appeared as estimating line items still landed as stories.
Final picklist: 238 user stories — 214 from the estimating backlog and 24 gap stories.
How Azure DevOps sees it
Hierarchy is Epic → Feature → User Story under one existing endeavor Epic (client project names stay private):
- Features = durable solution layers (Infrastructure, API, Internal SPA, External SPA, MAUI, Integrations, Migration, Reporting, Program & delivery, …) — not one Feature per phase
- Tags = delivery sequence (
Phase-P0…Phase-P10), layer markers, which app slice owns the story, andSource-BacklogvsSource-Gap - A generator script regenerates the CSV pack when discovery notes change; the import is a single hierarchical CSV for Boards → Import Work Items
Obfuscated picklist sample
A thin slice of Infrastructure / early-platform stories (titles lightly generalized; tags show the real shape):
Feature | User Story | Tags (abbrev.)
Infrastructure | Set up technical work environments (Azure, DevOps, …) | Phase-P1; Layer-Infrastructure; Source-Backlog
Infrastructure | Scaffold PowerShell + Azure CLI + JSON resource manifest| Phase-P1; Layer-Infrastructure; Source-Gap
Infrastructure | Provision Key Vault and wire secret references | Phase-P1; Layer-Infrastructure; Source-Gap
Infrastructure | Provision Application Insights / Monitor baseline | Phase-P1; Layer-Infrastructure; Source-Gap
Infrastructure | Configure Entra ID for internal app + API | Phase-P1; Layer-Infrastructure; Source-Gap
Infrastructure | Configure Entra External ID for portal users | Phase-P1; Layer-Infrastructure; Source-Gap
Infrastructure | Provision Azure SQL (dev) via IaC scripts | Phase-P1; Layer-Infrastructure; Source-Gap
Infrastructure | Provision APIM + App Service for .NET API | Phase-P1; Layer-Infrastructure; Source-Gap
Infrastructure | Provision Static Web Apps stubs (main + portal) | Phase-P1; Layer-Infrastructure; Source-Gap
Infrastructure | Add GitHub Actions CI skeleton for API and web apps | Phase-P1; Layer-Infrastructure; Source-Gap
Gap items (like the IaC scaffold row) are the architecture calling things the estimate sheet never named — exactly why the gap pass matters before import.