Awaab’s Law measures damp and mould deadlines in working days. Ten working days to investigate, another five to make safe, three more for the written summary. Dataverse calculated columns cannot do working-day arithmetic, so none of those deadlines can be worked out until the platform knows which days do not count.
That is why the first thing I built in my hazard case management solution was a table of bank holidays and a flow to keep it up to date. Not an app, not a form, not anything you could show someone.
I want to be clear about how I am using AI here, because I think it matters. I am using Claude Code with Microsoft’s Power Platform Skills to do the building. What I am not doing is handing over the understanding with it. If something goes into this solution then I am the one who has to support it, which means explaining it to a colleague, fixing it when it breaks, and extending it six months later, without going back and reading the transcript to work out what it does. I have been working with the Power Platform for a while now and I am still learning it, and I would rather use AI to learn faster than use it to skip the learning entirely. So I wrote that expectation into the repository as a working agreement, and the last thing that happens on every component is that I get asked questions about what was just built.
This is the first component I have done that way. It went better than I expected and I got more wrong than I expected, which I will come back to.
The scenario, and what is real in it
Aldergate Housing Group is fictional. I gave it 14,240 homes across West and South Yorkshire, a C3 consumer grading and two Housing Ombudsman findings of severe maladministration, because I have found that a scenario with some pressure in it produces better decisions than a scenario without.
The regulatory side is not fictional. Awaab’s Law is real and so are the timescales. That is the part I wanted to build against.
This is self-directed practice. Aldergate does not exist and there is no client. It sits in my own Developer environments, but I am running it the way a delivery would run: a solution design register as the source of truth, user stories and design decisions in Azure DevOps, three environments and source control behind them. The scenario exists so I have to make the decisions a consultant would have to make, including the ones where you decide not to build something.
I split the work across two places. Design conversations happen where I can see the whole register, and the build work happens in Claude Code. That stops a design discussion turning into a build before I have finished deciding what I want.
The working agreement
Left to its own devices, an agentic tool will produce something that works and move on to the next thing. That is fine right up until you have to support it or answer a question about it. I have had that experience already with a smaller build earlier this year, where I ended up with a working app and a fairly shaky grasp of half of it.
So the agreement has four parts, and it lives in a CLAUDE.md file in the repository rather than in my head. Claude Code reads that file at the start of every session, so I am not relying on myself to set the expectation out again each time and word it slightly differently:
- A design conversation before anything gets built, covering the options and the trade-offs, ending with a decision that is mine.
- Narration of the substantive actions during the build, so I know what is changing and why.
- A walkthrough afterwards, written from what actually exists rather than from what we talked about.
- A comprehension check, where I answer questions without help.
The last one is what makes the rest of it mean anything. You can nod along to a walkthrough.
If you want the starting point, my first attempt at Claude Code is written up here: Power Platform with Claude Code: First Impressions.
The design conversation
Three options went on the table.
A scheduled cloud flow reading the GOV.UK bank holidays feed, which is what I had already recorded as the likely approach during the design phase. A seed file loaded by pipeline once a year, with an alert when the data started running short. Or a Power Platform dataflow.
The seed file tempted me because it is simpler to support, and simpler to support counts for a lot. I ruled it out because it puts a person back in the loop on something that has to be correct in eighteen months’ time, when nobody is thinking about bank holidays.
The dataflow failed on specifics rather than on preference, which was useful to see. Measured against the seven acceptance criteria on the story, it failed four. It could not protect rows someone had added manually, it could not protect historic years, and it could not raise an alert anywhere I was monitoring. Dataflows are good at loading data. They are less good at reconciling data you only partly control.
I took the scheduled flow and changed three things about how it had been proposed to me.
It runs weekly, early on a Monday, rather than daily. Bank holidays get published years ahead and barely change, so a daily schedule gives you 365 chances a year for something to break in return for information you do not need that often.
Dates that disappear from the feed get deactivated rather than deleted, using the platform’s status reason rather than a column I invented for it. There are two reasons, so I can tell the difference between a date a person withdrew and a date the feed stopped publishing.
And the flow will not remove more than two dates in one run. If it wants to, it writes nothing and holds the run for review. If the feed has genuinely dropped three bank holidays, that is not a data update and I do not want it applied automatically at two in the morning.
The bit that reordered everything else
Part way through the design conversation, something came up that changed how I thought about the rest of it.
The two ways this can be wrong are not equally bad.
If a bank holiday is missing from the calendar, the deadline calculated from it lands a day early. Someone gets chased slightly sooner than they needed to be. Not ideal, but safe.
If there is an extra date in the calendar, the deadline lands a day late, and Aldergate reports a breach as compliant. Against a framework like this one, that is the version that causes real problems.
Once that was written down, most of the remaining decisions answered themselves. The flow reconciles against the feed instead of just adding to the calendar. Automation never overturns something a person decided. Office closure days, which are not statutory non-working days, stay out of the table however convenient it would be to put them in. All of that exists to protect the late direction more carefully than the early one.
What got built
The refresh flow does seven things in order. Two of them are worth explaining.
It writes its run record before it does anything else. If you write the run record at the end, you only get records for runs that reached the end, and a run that died halfway through looks exactly like a run that never started. Writing it first means I can spot silence.
Then it fetches the feed and validates it before trusting any of it: every year I expect is present, each year has a plausible number of dates, no duplicates, and the last date is still in the future. It compares the feed against the rows already in the table, then creates, retitles, reactivates or deactivates as needed.
The second thing worth explaining is the limit on that last step. It only touches rows the feed put there, and only within the feed’s own date range. GOV.UK drops old years off the front of the feed as time goes on, so without that boundary the flow would find a real bank holiday from three years ago, fail to see it in the feed, and switch it off. The older history would gradually disappear.
After that it checks how far ahead the calendar reaches and warns if it is under twelve months, then completes the run record and raises an alert on anything other than a clean success.
Two tables sit behind it. Non Working Day holds the calendar, with a composite alternate key on date and division so the same date cannot get loaded twice. Integration Run is a general-purpose run log, one row per run, built so future integrations can use it rather than each one getting its own.
Eight test scenarios ran in the development environment: first load, no change, the removal guard, deactivation and reactivation, a duplicate key, the coverage warning, per-year validation, and GOV.UK being unavailable. Then the solution went into source control, the documentation got written, and it all merged across three pull requests.
There was some tooling friction in among that. A plugin hung for half an hour and the work moved to the APIs instead. It will probably all be different by the time you read this.
Where it went wrong
My register and my environment disagreed
The register said the Non Working Day table had one composite alternate key across date and division. What had actually been built was two separate single-column keys.
With only one division option, a key on division on its own means the table can hold one row. One bank holiday for the whole organisation.
The root cause was mine. My register listed key columns one per line, and one per line cannot tell you whether that is one composite key or several separate ones. The information was not in there to read in the first place. The second cause was process, in that nothing was checking the built environment against the register before the next thing got built on top of it.
Fixing the table took a few minutes. The more useful outcome came from writing a script to read the environment back and compare it against the register, which found the same key pattern on two other tables, along with ownership mismatches that cannot be changed after a table is created, wrong table names and missing columns. That is now a separate piece of work, and it comes before anything that depends on those tables.
The check that checked nothing
The first version of that read-back script told me there were no mismatches.
It had read zero tables. The used range on my register starts one column in from A, which shifted every column index, and the script came back with nothing at all. Nothing compared cleanly against nothing.
This is the one I will remember. A tool getting something wrong loudly is easy enough to deal with. A verification step reporting success while quietly doing no work is not, and it is exactly the kind of thing you go on to build more work on top of. The script now reads from A1 and errors if it reads nothing.
Documentation describing the design rather than the build
Three things got written up as intended rather than as built.
The flow was described as validating at least eight dates per year, when it was actually checking an average across the years. In that case the design was right and the flow was wrong, so the flow changed.
The permissions were wrong twice. The documentation said only System Administrator could edit the calendar, but System Customizer can too, which matters because that role gets given out more freely. It also said business users could see the run log, when no role gave them access to it at all. That one turned into a story for a view and a form.
Those all got caught by reading the exported solution back rather than writing from the conversation. Documentation written from what someone meant to do is wrong in ways that are hard to spot afterwards, and that applies to me as much as to Claude.
The comprehension check
Fifteen questions at the end, no notes and no help. Four of them are worth repeating here, mostly the ones I did not get right.
Why can this not be all-or-nothing? I asked for this one to be broken down step by step. A Dataverse changeset gives you transactional writes, but a changeset cannot contain an Apply to each loop, and this flow writes a variable number of rows. The design works around it instead: validate everything before writing anything, make every individual write safe to repeat, and guard the dangerous operation, which is removal.
What do the run-after settings actually do? I was partly right and got corrected. I thought a failure would stop the flow. What happens is that a step set to run only on success gets skipped when the step before it fails, and the flow carries on past it. That is not academic. An earlier improvement to the failure alerting had introduced a step that broke, and because everything after it was set to run on success only, one of my test runs failed with no alert and no completed run record. The reporting steps now run whatever happened before them.
Which direction of error is worse? This is the principle the whole design rests on and I still had to come back to it more than once. Not the reasoning, which is straightforward, but which way round it goes when you are asked cold. A day late is the direction with the consequences.
Who can activate a flow? I was told only the connection owner could turn a flow on. I had turned it on myself, as me, so I said so. We tested it, and once the owner has activated a flow for the first time, a System Administrator can disable it, enable it and edit it. Binding the connections, changing the owner and that first activation are the owner’s. The documentation got corrected everywhere it appeared.
Across all fifteen: five I answered cleanly, three partly right and corrected, two I asked to have broken down, and five that took more than one go to land. I find that more useful than a score. It tells me where I am actually weak, which at the moment is licensing and anything transactional.
What I am taking from it
I can explain this component without help, and more to the point I could pick it up and fix it if it started failing. Why a bank holiday table exists before there is an app, why deactivation is bounded by the feed’s date range, why the run record is written first, and what a changeset can and cannot promise. That was the point of working this way, and it held.
The agreement needed one addition, which is now in it: read the built environment back before building anything on top of it, and before writing any documentation. Both of the real problems above were the same mistake, which is trusting a description of a system over the system.
Next up is fixing the mismatches the read-back script found, since the deadline calculation depends on those tables. Then the deadline calculation itself, which has already picked up two extra acceptance criteria from all of the above.
Still learning, still writing it down.
