<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Guido Tapia</title>
    <description>The latest articles on DEV Community by Guido Tapia (@gatapia).</description>
    <link>https://dev.to/gatapia</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4063183%2Fa21b9f42-c4f1-4abf-8442-c06a35468320.jpg</url>
      <title>DEV Community: Guido Tapia</title>
      <link>https://dev.to/gatapia</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/gatapia"/>
    <language>en</language>
    <item>
      <title>An AI blog pipeline with a human editor: how this post was made</title>
      <dc:creator>Guido Tapia</dc:creator>
      <pubDate>Wed, 26 Aug 2026 02:00:47 +0000</pubDate>
      <link>https://dev.to/gatapia/an-ai-blog-pipeline-with-a-human-editor-how-this-post-was-made-5bmf</link>
      <guid>https://dev.to/gatapia/an-ai-blog-pipeline-with-a-human-editor-how-this-post-was-made-5bmf</guid>
      <description>&lt;p&gt;This post was drafted by a machine. A person read it, changed things, and merged it before you saw it. Since we spend this series describing how other organisations should apply AI to marketing work, it seemed reasonable to open up the pipeline that produces the series itself. This is part of our &lt;a href="https://picnet.com.au/blog/practical-ai-in-marketing-the-series/" rel="noopener noreferrer"&gt;Practical AI in Marketing&lt;/a&gt; series, and it is the only post in it about us.&lt;/p&gt;

&lt;h2&gt;
  
  
  The backlog is a JSON file
&lt;/h2&gt;

&lt;p&gt;There is no content calendar in a spreadsheet and no monthly planning meeting. Topics live in a JSON file in the same repository as the site, and each entry carries enough context for an agent to write from it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"slug"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"an-ai-blog-pipeline-with-a-human-editor-how-this-post-was-made"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"series"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"practical-ai-in-marketing"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"title"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"An AI blog pipeline with a human editor: how this post was made"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"notes"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Transparency meta-post. Backlog JSON, weekly research, humanised draft on a review branch, LinkedIn copy alongside, human edits and merges. Human gate is the point. Costs per post vs agency."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"status"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"drafted"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The notes field does most of the work. It is written by a person who knows what we actually do, which is why the pipeline can produce a post about our own process without me having to write the process down twice. Anyone at PicNet can add an entry. Adding one takes about two minutes, which matters more than it sounds: the old bottleneck was not writing, it was that good topic ideas evaporated between the conversation where they came up and the next time anyone opened the blog folder.&lt;/p&gt;

&lt;h2&gt;
  
  
  What runs each week
&lt;/h2&gt;

&lt;p&gt;The weekly job is deliberately boring:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Pick the next backlog item that is ready.&lt;/li&gt;
&lt;li&gt;Run web searches on the topic, scrape the promising results, and write everything into a research file next to the draft. Sources, notes, dates, full text where we can get it.&lt;/li&gt;
&lt;li&gt;Draft the post against a system prompt holding the house rules: word count, Australian English, byline, what may and may not be claimed, which client names are cleared for public use and which are not.&lt;/li&gt;
&lt;li&gt;Run a humanising pass that strips the stock AI phrasing.&lt;/li&gt;
&lt;li&gt;Draft the LinkedIn copy from the same research, in the same run, so the social post and the article do not drift apart.&lt;/li&gt;
&lt;li&gt;Commit the draft, the research file and the social copy to a review branch and open it for review.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Nothing in that list touches the live site. The last step is a person reading the branch and merging it, or not.&lt;/p&gt;

&lt;p&gt;Putting content through git rather than a CMS was the single best decision in the build. Every post has a diff, a branch and a merge, and every claim in it can be traced back to the research file sitting beside it in the same commit. Teams are already reworking their software lifecycle around agent output (&lt;a href="https://news.ycombinator.com/item?id=49275494" rel="noopener noreferrer"&gt;Ask HN, August 2026&lt;/a&gt;), and the review discipline transfers to a content repository without modification. The editor is doing a code review on prose.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why the human gate is the point
&lt;/h2&gt;

&lt;p&gt;The clearest statement of the problem I have read comes from someone who built a marketing AI system for a 60-person team: "generation is free now... that sounds like good news, and for about three weeks it was." Their conclusion after the build was that the most valuable layer in the system is &lt;a href="https://www.reddit.com/r/Maestrix/comments/1vm6a7i/i_built_a_marketing_ai_operating_system_for_a/" rel="noopener noreferrer"&gt;the one that refuses to write&lt;/a&gt;. Once anyone can produce a competent blog post in ninety seconds, the constraint moves to the part of the process nobody had staffed, which is review and editorial judgement.&lt;/p&gt;

&lt;p&gt;Engineering hit the same wall first. Sylvain Kalache's framing, &lt;a href="https://www.sylvainkalache.com/blog/ai-writes-the-code-but-humans-cant-review-it-all" rel="noopener noreferrer"&gt;AI writes the code, but humans can't review it all&lt;/a&gt;, describes teams generating ten times faster while reviewing at human speed, and cites survey data showing high-adoption teams merging 98% more pull requests while review time rose 91%. Content has the same shape and worse consequences, because a bad merge in a repository fails in staging while a bad merge on a public blog is read by your clients.&lt;/p&gt;

&lt;p&gt;So we bounded the volume rather than the generation. The pipeline is capped at what one editor can genuinely read in a week, and the editor's job is not to tidy sentences. It is to answer three questions: is every statistic in here actually in the research file, is every client reference one we are allowed to make, and would I say this out loud to a client. Posts that fail the third question get deleted rather than fixed. That happens more often than the volume-obsessed version of this pipeline would tolerate, which is the point.&lt;/p&gt;

&lt;p&gt;The prompt also carries a list of things the model must never write: client names that are not cleared for publication, any claim we cannot source, any statistic without a link. The editor checks the list on every branch. Prompts are guidance, not controls, and treating them as controls is how organisations end up apologising publicly.&lt;/p&gt;

&lt;h2&gt;
  
  
  Labelling what the machine wrote
&lt;/h2&gt;

&lt;p&gt;Beau Cronin's &lt;a href="https://beaucronin.com/tail/" rel="noopener noreferrer"&gt;TAIL scale&lt;/a&gt;, published on 18 August 2026, gives a 0 to 5 rating for how much of a piece AI wrote. TAIL 4 is "AI substantially authored, with my comprehension, review, and editing", and Cronin describes it as the last level that is meaningfully human in the loop. That is where this post sits, and where the series sits. It is a more honest label than a generic "AI-assisted" footer, because it tells you exactly which half of the work a person did.&lt;/p&gt;

&lt;p&gt;Publishing the mechanics is becoming its own small genre, including &lt;a href="https://treefortsystems.com/blog/is-this-blog-post-ai-generated/" rel="noopener noreferrer"&gt;technical blogs disclosing their own generation process&lt;/a&gt;. We would rather be in that group than pretend the drafts are typed by hand.&lt;/p&gt;

&lt;p&gt;One thing the humanising step is not for: beating detectors. AI writing detectors such as Pangram are now deployed everywhere and, as the Washington Post reported on 25 August 2026, &lt;a href="https://www.washingtonpost.com/technology/interactive/2026/08/25/ai-detectors-like-pangram-are-everywhere-arent-always-accurate/" rel="noopener noreferrer"&gt;they aren't always accurate&lt;/a&gt;. Their reporting notes a papal encyclical was flagged as partly AI-generated. A detector score is not a quality signal, and optimising a draft to pass one produces text that is neither honest nor better. We run the humanising pass because unedited model prose is full of sales phrasing and stock triads that no engineer would say in a meeting.&lt;/p&gt;

&lt;h2&gt;
  
  
  What it costs
&lt;/h2&gt;

&lt;p&gt;Per post, the machine spend is search API calls plus model tokens, and it rounds to nothing against an hour of senior time. The real cost is the editor: roughly an hour per post to read the draft against the research file, cut what cannot be supported, and fix the parts where the model wrote something plausible and wrong.&lt;/p&gt;

&lt;p&gt;Compare that with an agency retainer, where you pay for both the writing and the research, and where the person writing about your systems has never seen them. The pipeline flips the ratio. We pay almost nothing for the drafting and spend the money on the judgement, which is the part an agency cannot do for us anyway because it depends on knowing what we have actually built.&lt;/p&gt;

&lt;p&gt;Volume is not the win, and it is worth being sceptical of pipelines sold on volume. A &lt;a href="https://www.reddit.com/r/AIGrowthHacks/comments/1vho2we/i_used_ai_content_platforms_for_12_months_my/" rel="noopener noreferrer"&gt;twelve-month practitioner review of AI content platforms&lt;/a&gt; starts from a B2B SaaS blog with 12 published articles and basically zero traffic, which is a fair reminder that publishing more of nothing produces more nothing.&lt;/p&gt;

&lt;h2&gt;
  
  
  Honest limitations
&lt;/h2&gt;

&lt;p&gt;The pipeline cannot interview anyone. It cannot visit a client site, sit through a workshop, or notice the thing a client said offhand that turns into a good post. Everything original in this series comes from a person putting it in the backlog notes first, and the quality of a post is capped by the quality of those notes.&lt;/p&gt;

&lt;p&gt;It also gets things wrong in a specific way: it will produce a confident sentence built on a source that does not quite say that. That is exactly what the research file and the branch review exist to catch, and it is why the editor's hour is not negotiable.&lt;/p&gt;

&lt;p&gt;If you want to build something similar, start with the review step. Decide who reads every piece, how long they get, and what they are allowed to reject. Then automate backwards from there. The generation was never the hard part.&lt;/p&gt;

&lt;p&gt;PicNet builds production AI systems for Australian organisations. &lt;a href="https://picnet.com.au/ai-services/" rel="noopener noreferrer"&gt;Talk to us&lt;/a&gt; about what a first project could look like.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Originally published at &lt;a href="https://picnet.com.au/blog/an-ai-blog-pipeline-with-a-human-editor-how-this-post-was-made/" rel="noopener noreferrer"&gt;picnet.com.au&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>marketing</category>
      <category>aicontent</category>
      <category>contentmarketing</category>
    </item>
    <item>
      <title>The robot that chases people: automated reminders, stale quotes and follow-ups</title>
      <dc:creator>Guido Tapia</dc:creator>
      <pubDate>Mon, 24 Aug 2026 00:51:03 +0000</pubDate>
      <link>https://dev.to/gatapia/the-robot-that-chases-people-automated-reminders-stale-quotes-and-follow-ups-3d79</link>
      <guid>https://dev.to/gatapia/the-robot-that-chases-people-automated-reminders-stale-quotes-and-follow-ups-3d79</guid>
      <description>&lt;p&gt;Most operations teams have a version of the same list. Quotes sent three weeks ago with no answer. A job waiting on a client document since before the last public holiday. A renewal that needed a conversation in June and is now awkward. Nobody decided to drop these. They fell through a crack, and nobody was paid to watch the cracks.&lt;/p&gt;

&lt;p&gt;The usual fix is a person: an ops coordinator with a spreadsheet and a Monday morning ritual. That works until they take leave. The better fix is a scheduled task with an opinionated data model behind it, plus a language model doing the one part that genuinely needs judgment, which is writing a chase message that does not read like it came from a machine.&lt;/p&gt;

&lt;p&gt;This is part of our &lt;a href="https://picnet.com.au/blog/practical-ai-in-business-operations-the-series/" rel="noopener noreferrer"&gt;Practical AI in Business Operations&lt;/a&gt; series, and it is deliberately the least glamorous entry. There is very little AI in it. That is the point: the value comes from the scheduler, and the model earns its place at the last step.&lt;/p&gt;

&lt;h2&gt;
  
  
  The table is most of the trick
&lt;/h2&gt;

&lt;p&gt;A working reminder system is smaller than people expect. An open design discussion on a tender tracking project sketches &lt;a href="https://github.com/Mikepeerawit-com/tender-tracker/issues/9" rel="noopener noreferrer"&gt;a &lt;code&gt;reminders&lt;/code&gt; table keyed on the record id plus &lt;code&gt;days_before&lt;/code&gt;, &lt;code&gt;sent&lt;/code&gt; and &lt;code&gt;sent_at&lt;/code&gt;, swept by a daily cron&lt;/a&gt;. The state columns are what stop the same nag firing twice. That is the whole mechanism.&lt;/p&gt;

&lt;p&gt;Ours look roughly like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;create&lt;/span&gt; &lt;span class="k"&gt;table&lt;/span&gt; &lt;span class="n"&gt;reminders&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="n"&gt;id&lt;/span&gt;                &lt;span class="n"&gt;uuid&lt;/span&gt; &lt;span class="k"&gt;primary&lt;/span&gt; &lt;span class="k"&gt;key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;entity_type&lt;/span&gt;       &lt;span class="nb"&gt;text&lt;/span&gt; &lt;span class="k"&gt;not&lt;/span&gt; &lt;span class="k"&gt;null&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;          &lt;span class="c1"&gt;-- quote | job | invoice | renewal&lt;/span&gt;
  &lt;span class="n"&gt;entity_id&lt;/span&gt;         &lt;span class="n"&gt;uuid&lt;/span&gt; &lt;span class="k"&gt;not&lt;/span&gt; &lt;span class="k"&gt;null&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;milestone&lt;/span&gt;         &lt;span class="nb"&gt;text&lt;/span&gt; &lt;span class="k"&gt;not&lt;/span&gt; &lt;span class="k"&gt;null&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;          &lt;span class="c1"&gt;-- first_follow_up | stale_quote | decision_chase&lt;/span&gt;
  &lt;span class="n"&gt;days_before&lt;/span&gt;       &lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;                    &lt;span class="c1"&gt;-- exactly one of these two is set&lt;/span&gt;
  &lt;span class="n"&gt;remind_on&lt;/span&gt;         &lt;span class="nb"&gt;date&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;due_date&lt;/span&gt;          &lt;span class="nb"&gt;date&lt;/span&gt; &lt;span class="k"&gt;not&lt;/span&gt; &lt;span class="k"&gt;null&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;          &lt;span class="c1"&gt;-- computed; the only thing the sweep looks at&lt;/span&gt;
  &lt;span class="n"&gt;assignee_id&lt;/span&gt;       &lt;span class="n"&gt;uuid&lt;/span&gt; &lt;span class="k"&gt;not&lt;/span&gt; &lt;span class="k"&gt;null&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;snoozed_until&lt;/span&gt;     &lt;span class="nb"&gt;date&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;suppressed_reason&lt;/span&gt; &lt;span class="nb"&gt;text&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;                   &lt;span class="c1"&gt;-- an honest reason to stop chasing&lt;/span&gt;
  &lt;span class="n"&gt;sent&lt;/span&gt;              &lt;span class="nb"&gt;boolean&lt;/span&gt; &lt;span class="k"&gt;not&lt;/span&gt; &lt;span class="k"&gt;null&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="k"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;sent_at&lt;/span&gt;           &lt;span class="n"&gt;timestamptz&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The sweep is one query, run daily:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;select&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="n"&gt;reminders&lt;/span&gt;
&lt;span class="k"&gt;where&lt;/span&gt; &lt;span class="n"&gt;due_date&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="k"&gt;current_date&lt;/span&gt;
  &lt;span class="k"&gt;and&lt;/span&gt; &lt;span class="k"&gt;not&lt;/span&gt; &lt;span class="n"&gt;sent&lt;/span&gt;
  &lt;span class="k"&gt;and&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;snoozed_until&lt;/span&gt; &lt;span class="k"&gt;is&lt;/span&gt; &lt;span class="k"&gt;null&lt;/span&gt; &lt;span class="k"&gt;or&lt;/span&gt; &lt;span class="n"&gt;snoozed_until&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="k"&gt;current_date&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;and&lt;/span&gt; &lt;span class="n"&gt;suppressed_reason&lt;/span&gt; &lt;span class="k"&gt;is&lt;/span&gt; &lt;span class="k"&gt;null&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Note &lt;code&gt;due_date &amp;lt;= current_date&lt;/code&gt;, not &lt;code&gt;= current_date&lt;/code&gt;. Exact date equality means one bad deploy day silently drops every reminder that fell on it. The same ticket works through that and two related traps, and lands on catch-up rather than skip, with reminders whose milestone has already passed suppressed in favour of a louder "this was missed" state. It also resets &lt;code&gt;sent&lt;/code&gt; when a deadline moves: a write-once boolean means pushing a due date back permanently silences the record.&lt;/p&gt;

&lt;p&gt;Timezones matter more here than they look. An organisation with staff in Perth and Sydney has a two or three hour window where "today" is two different dates. Pick an org-level timezone, compute &lt;code&gt;due_date&lt;/code&gt; in it, and stop treating server time as the answer.&lt;/p&gt;

&lt;h2&gt;
  
  
  Deciding when it fires is the same decision as deciding how someone hears about it
&lt;/h2&gt;

&lt;p&gt;The tender ticket treats notification delivery and the reminder model as &lt;a href="https://github.com/Mikepeerawit-com/tender-tracker/issues/9" rel="noopener noreferrer"&gt;one design question, not two&lt;/a&gt;, and that is right. A reminder that broadcasts to a shared channel has different semantics from one that reaches an individual. If your only channel posts to a group, then every reminder is public, and that constrains the content: no pricing, no margin, and a hard look at whether client names belong in a channel that may include contractors. Under the Australian Privacy Principles, "who can see this notification" is a design input, not a compliance review item at the end.&lt;/p&gt;

&lt;p&gt;Targeting also decides whether the system survives. Reminders that ping people who have already done the work train the whole team to mute the robot within weeks. Chase the assignees with nothing recorded against the milestone, not everyone attached to the record.&lt;/p&gt;

&lt;h2&gt;
  
  
  Escalation is a schedule, not a mood
&lt;/h2&gt;

&lt;p&gt;Escalation should be boring and configurable. For a client submission deadline, defaults of seven days, three days, one day and the morning of the due date work well. Two rules make it useful rather than noisy:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A missed item does not drop off the list. It stays on the dashboard in a loud state until someone resolves it, and it posts once to the team channel when it is missed.&lt;/li&gt;
&lt;li&gt;Escalation changes audience, not just frequency. The third reminder goes to the assignee's manager. If your escalation only shouts louder at the same person, it is not escalation.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For stale quotes, the trigger is different but the shape is the same: days since sent with no client response, days until the quote's validity expires, and a hard stop at 30 June for anything priced against a financial year.&lt;/p&gt;

&lt;h2&gt;
  
  
  Snooze, and the re-arm problem
&lt;/h2&gt;

&lt;p&gt;Snooze is where these systems go wrong, and the failure is not the trigger. It is when the system decides to re-arm after a human acts.&lt;/p&gt;

&lt;p&gt;There is a neat illustration of this from an unrelated field. A robot's heading-hold controller was counter-steering after a manual turn: the operator would turn, recentre, and the robot would swing back the other way. The hold released during the turn and &lt;a href="https://github.com/innate-inc/innate-os/pull/644" rel="noopener noreferrer"&gt;re-latched at the wrong point, against a target the machine had already rotated past&lt;/a&gt;. The fix was to shorten the hold's memory so the target caught up with reality faster.&lt;/p&gt;

&lt;p&gt;Reminders have the identical bug. Someone rings the client on Tuesday and snoozes for a week. On Wednesday the client replies and the quote status changes. If the snooze re-arms against the old &lt;code&gt;due_date&lt;/code&gt;, the reminder fires next Tuesday about a conversation that is already finished, and the person who did the right thing gets nagged for it. Recompute &lt;code&gt;due_date&lt;/code&gt; on every state change to the underlying record, and let the snooze expire against the recomputed value. The rule is simple: the reminder chases the current state of the record, never the state it had when the row was written.&lt;/p&gt;

&lt;h2&gt;
  
  
  Give people an honest way to stop the nag
&lt;/h2&gt;

&lt;p&gt;The most valuable column in that table is &lt;code&gt;suppressed_reason&lt;/code&gt;. Without it, "hasn't got to it" and "this cannot be actioned" look identical to the scheduler, and it chases people forever for work that is impossible. The tender ticket adds an explicit "no supplier found" record for exactly this reason. Ours are things like "client asked us to hold until the new financial year" or "waiting on their legal, no date". Each one silences the reminder and leaves a written trace of why, which is far more useful in a Monday meeting than a snoozed row.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where the model actually goes
&lt;/h2&gt;

&lt;p&gt;Two places, both narrow.&lt;/p&gt;

&lt;p&gt;The first is classification. A rule that decides whether a quote is stale by looking at a status field alone will get it wrong, because status fields lag reality. Reading the actual email thread and the file notes is a better signal, and that is a job a model does well. One caution: read the whole record, not a sample. A scheduled job in another project fetched a partial version of its source text for years, and the correction was explicit, to &lt;a href="https://github.com/tamnd/kvant-solver/pull/58" rel="noopener noreferrer"&gt;pull all of the text rather than a sample of it&lt;/a&gt;. Reminder rules that peek at only the last two emails will confidently call a dead quote fresh.&lt;/p&gt;

&lt;p&gt;The second is drafting the chase message. The model gets the client name, what was sent, when, what has happened since, and two or three examples of how that account manager actually writes. It produces a draft. A person reads it, changes a line, and sends it from their own mailbox.&lt;/p&gt;

&lt;p&gt;We do not auto-send these, for two reasons. Clients can tell, and an unsupervised bot emailing your customers about commercial matters is a Spam Act 2003 problem waiting to happen rather than a productivity gain. The point of the automation was never to write the email. It was to make sure someone was asked to.&lt;/p&gt;

&lt;h2&gt;
  
  
  Design every state before you build
&lt;/h2&gt;

&lt;p&gt;Alert preferences deserve real screens, not a config file. One mobile build we looked at gives alerts &lt;a href="https://github.com/lucasturcuato-afk/breakingalpha/pull/624" rel="noopener noreferrer"&gt;a dedicated &lt;code&gt;/settings/alerts&lt;/code&gt; route alongside profile and saved items, and enumerates every screen at every lifecycle state&lt;/a&gt; for review. Copy that discipline. Pending, due, sent, snoozed, escalated, missed, suppressed and dismissed are eight states somebody has to design, and the ones teams skip are always suppressed and missed, which are the two that decide whether people trust the thing.&lt;/p&gt;

&lt;h2&gt;
  
  
  Costs and honest limits
&lt;/h2&gt;

&lt;p&gt;The model calls are the cheapest part. A few hundred short drafts a month is a rounding error next to the engineering time, and most of that time goes into failure semantics rather than prompts. Budget for a small build with a fortnight of tuning the thresholds afterwards, because the first set of defaults will be wrong.&lt;/p&gt;

&lt;p&gt;The real limit is consumption. Scheduled automation whose output nobody acts on is worse than none, because it costs attention and buys nothing. That same project found an extraction job that had run against roughly twelve articles a year, was compared against a model's reading, &lt;a href="https://github.com/tamnd/kvant-solver/pull/58" rel="noopener noreferrer"&gt;"and then used for nothing else"&lt;/a&gt;. The author's verdict was blunt: that is backwards. Before you build the chaser, decide who reads its output and what they do next. If the answer is thin, build the daily digest instead and stop there.&lt;/p&gt;

&lt;p&gt;PicNet builds production AI systems for Australian organisations. &lt;a href="https://picnet.com.au/ai-services/" rel="noopener noreferrer"&gt;Talk to us&lt;/a&gt; about what a first project could look like.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Originally published at &lt;a href="https://picnet.com.au/blog/the-robot-that-chases-people-automated-reminders-stale-quotes-and-follow-ups/" rel="noopener noreferrer"&gt;picnet.com.au&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>businessoperations</category>
      <category>automation</category>
      <category>workflowautomation</category>
    </item>
    <item>
      <title>Cutting vulnerability noise: using AI and public data to demote CVEs that don't matter</title>
      <dc:creator>Guido Tapia</dc:creator>
      <pubDate>Fri, 21 Aug 2026 00:56:05 +0000</pubDate>
      <link>https://dev.to/gatapia/cutting-vulnerability-noise-using-ai-and-public-data-to-demote-cves-that-dont-matter-38n1</link>
      <guid>https://dev.to/gatapia/cutting-vulnerability-noise-using-ai-and-public-data-to-demote-cves-that-dont-matter-38n1</guid>
      <description>&lt;p&gt;A container image scan finishes and hands back a few hundred findings. A dozen are critical. Most of the rest are base image packages with a CVE ID, a CVSS score, and no patched version anywhere in the world. The team has one afternoon a fortnight for this work. Where do they start?&lt;/p&gt;

&lt;p&gt;Usually they start by scrolling, and that is the whole problem. When a report is 90% unactionable, people stop reading it, and the one finding that did have a fix available gets the same amount of attention as the 200 that did not. Noise is not just annoying. It is the mechanism by which real vulnerabilities get missed.&lt;/p&gt;

&lt;p&gt;This post is part of our &lt;a href="https://picnet.com.au/blog/practical-ai-in-cyber-security-the-series/" rel="noopener noreferrer"&gt;Practical AI in Cyber Security&lt;/a&gt; series, and it covers something we run for clients rather than something we think would be nice: cross-referencing scanner output against public vulnerability data so that findings with no available fix get demoted automatically, and findings with a released fix stay loud.&lt;/p&gt;

&lt;h2&gt;
  
  
  What "no fix available" actually means
&lt;/h2&gt;

&lt;p&gt;Scanners report what a package version is vulnerable to. They are much less consistent about whether anyone has shipped a repaired version.&lt;/p&gt;

&lt;p&gt;The distinction matters because the two cases need completely different responses. If Debian, Alpine, Ubuntu or Red Hat has published a fixed package, the answer is to bump the version and rebuild, which is an hour of work. If the upstream maintainer has marked the issue as won't fix, disputed, or minor, or the affected code path is not compiled into the distro build at all, there is nothing to install. The only available responses are compensating controls, removing the package, or accepting the risk and writing it down.&lt;/p&gt;

&lt;p&gt;Both arrive in the report looking identical. A CVSS 7.5 with a fix and a CVSS 7.5 without one sit next to each other, sorted by severity, as though they are the same kind of work item.&lt;/p&gt;

&lt;h2&gt;
  
  
  The pipeline
&lt;/h2&gt;

&lt;p&gt;The approach is deliberately boring. Deterministic data does the demotion, and AI is confined to the parts that are genuinely unstructured.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Normalise scanner output into one record per finding: CVE ID, package name, installed version, ecosystem, image or host, and where it runs.&lt;/li&gt;
&lt;li&gt;Query &lt;a href="https://osv.dev/" rel="noopener noreferrer"&gt;OSV.dev&lt;/a&gt; for each package and version. OSV aggregates advisories across language ecosystems and Linux distributions and returns affected version ranges, including whether a fixed version exists for that specific distro build.&lt;/li&gt;
&lt;li&gt;Apply the rule. A finding with a fixed version in the ecosystem the package actually came from stays at its original priority. A finding with no fixed version anywhere, and no runtime exposure flag, drops to a demoted queue that gets reviewed monthly rather than fortnightly.&lt;/li&gt;
&lt;li&gt;Everything demoted keeps its full record, with the reason, the advisory that justified it, and the timestamp of the data the decision was made on.&lt;/li&gt;
&lt;li&gt;Feed the promoted findings into the ticketing system the team already uses, one ticket per rebuild rather than one per CVE.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For clients with several environments, the fetch and reconciliation runs as a scheduled integration job. We use Centazio, our open source data integration platform, for this sort of thing, because the hard part is not the API call, it is keeping the state consistent across repeated runs and knowing which records are stale.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where AI helps, and where we keep it out
&lt;/h2&gt;

&lt;p&gt;The demotion decision itself is not an AI decision. Version comparison is a solved problem and it should stay deterministic, because a language model that occasionally hallucinates a fixed version is worse than no enrichment at all.&lt;/p&gt;

&lt;p&gt;Three things in this pipeline are messy enough that a model earns its place. The first is advisory prose. Distro advisories and upstream issue trackers explain in free text why something is not being fixed, and a model summarising twenty of those into "these six are all the same unfixed glibc locale issue" saves real reading time. The second is clustering: grouping 180 findings into the eight base image rebuilds that would resolve them. The third is drafting the risk acceptance note, which a human then edits and signs.&lt;/p&gt;

&lt;p&gt;Note the pattern. The model reads, groups and drafts. It never decides on its own that something is safe to stop looking at. Practitioners are still asking each other what tooling exists for reviewing AI-assisted work in engineering pipelines, with no settled answer as of &lt;a href="https://news.ycombinator.com/item?id=49321400" rel="noopener noreferrer"&gt;August 2026&lt;/a&gt;, so designing around a verification step rather than trust is the sensible default. It is also worth being explicit with stakeholders about where the model sits, because audiences in 2026 &lt;a href="https://www.reddit.com/r/nerdfighters/comments/1vc37aw/hanks_comment_about_his_ai_use_posted_here_as_its/" rel="noopener noreferrer"&gt;scrutinise disclosed AI use closely&lt;/a&gt;, and a security control nobody trusts does not get used.&lt;/p&gt;

&lt;h2&gt;
  
  
  Fail noisy, not fail quiet
&lt;/h2&gt;

&lt;p&gt;The dangerous failure mode is obvious once you name it: the enrichment source is unreachable, every lookup returns nothing, "no fixed version found" is indistinguishable from "no answer received", and the whole report quietly demotes itself to zero. The dashboard goes green on the day it should go red.&lt;/p&gt;

&lt;p&gt;So the pipeline distinguishes the two cases at the type level and treats an unavailable source as a check failure:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;triage&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;finding&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;osv&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;advisory&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;osv&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;lookup&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;finding&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;cve&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;finding&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;package&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;finding&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ecosystem&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nf"&gt;except &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;TimeoutError&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;HTTPError&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="nc"&gt;EnrichmentUnavailable&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;finding&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;cve&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;   &lt;span class="c1"&gt;# alerts, does not demote
&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;advisory&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;age&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;MAX_CACHE_AGE&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="nc"&gt;EnrichmentStale&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;finding&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;cve&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;advisory&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;age&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;advisory&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;fixed_versions&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;Priority&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ORIGINAL&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;finding&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;runtime_exposed&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;Priority&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;REVIEW&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;Priority&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;DEMOTED&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Two rules follow from that. Findings that could not be enriched keep their original severity and are flagged as unenriched, so a broken run produces a louder report than a healthy one. And the cached advisory data carries a maximum age, because silently triaging today's images against a three week old snapshot is the same bug wearing a disguise.&lt;/p&gt;

&lt;h2&gt;
  
  
  The economics for a lean team
&lt;/h2&gt;

&lt;p&gt;Most Australian organisations we work with have two or three people covering infrastructure, patching and everything else. Their real constraint is rebuild cycles, not knowledge. They know the base image is old.&lt;/p&gt;

&lt;p&gt;The value of demotion is that it makes the remaining queue small enough to finish, which is what turns patching into a habit instead of a quarterly panic. The ACSC's &lt;a href="https://www.cyber.gov.au/resources-business-and-government/essential-cyber-security/essential-eight" rel="noopener noreferrer"&gt;Essential Eight&lt;/a&gt; expects patches for applications and operating systems to be applied within defined windows and expects you to demonstrate it. A queue of fifteen fixable findings can be closed and evidenced. A queue of three hundred, mostly unfixable, cannot, and the auditor sees the same wall of red the engineers gave up on.&lt;/p&gt;

&lt;p&gt;Honest limitations. Version matching across distros is fiddly, because backported patches leave the version string looking unfixed when the vulnerability is already gone, so expect a tuning period and some manual overrides. Demoted is not deleted: the monthly review exists because a package with no fix today may get one next Tuesday, and a re-query catches that. Runtime reachability, which would let you demote far more aggressively, needs instrumentation most teams do not have, so we treat exposure as a coarse flag rather than a proof. And the LLM step costs a few dollars a month at typical volumes, which is nothing next to the engineering time to set the pipeline up and keep the version comparison honest. That build effort is the real cost, and it is worth it only if someone is going to work the shortened queue.&lt;/p&gt;

&lt;p&gt;PicNet builds production AI systems for Australian organisations. &lt;a href="https://picnet.com.au/ai-services/" rel="noopener noreferrer"&gt;Talk to us&lt;/a&gt; about what a first project could look like.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Originally published at &lt;a href="https://picnet.com.au/blog/cutting-vulnerability-noise-using-ai-and-public-data-to-demote-cves-that-don-t-matter/" rel="noopener noreferrer"&gt;picnet.com.au&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>cybersecurity</category>
      <category>vulnerabilitymanagement</category>
      <category>cve</category>
    </item>
    <item>
      <title>Is ChatGPT recommending you? Measuring your visibility to AI assistants</title>
      <dc:creator>Guido Tapia</dc:creator>
      <pubDate>Wed, 19 Aug 2026 00:46:05 +0000</pubDate>
      <link>https://dev.to/gatapia/is-chatgpt-recommending-you-measuring-your-visibility-to-ai-assistants-3i63</link>
      <guid>https://dev.to/gatapia/is-chatgpt-recommending-you-measuring-your-visibility-to-ai-assistants-3i63</guid>
      <description>&lt;p&gt;Someone looking for a Sydney software partner used to open Google. A fair number of them now open ChatGPT, Claude or Perplexity and type something close to "best custom software company in Sydney", then work from whatever list comes back. OpenAI's own study of ChatGPT Enterprise usage, &lt;a href="https://cdn.openai.com/pdf/how-organizations-use-chatgpt.pdf" rel="noopener noreferrer"&gt;How Organizations Use AI&lt;/a&gt;, found use spread across job functions and seniority levels, with research and information synthesis among the common tasks. Vendor shortlisting sits squarely in that category.&lt;/p&gt;

&lt;p&gt;So we asked the obvious question about ourselves: when a prospect asks an assistant that question, does PicNet come up? We could not answer it, which is a bad position for a company that builds measurement systems for other people. This post is part of our &lt;a href="https://picnet.com.au/blog/practical-ai-in-marketing-the-series/" rel="noopener noreferrer"&gt;Practical AI in Marketing&lt;/a&gt; series, and it describes what we built, what it costs, and the fairly large part of the problem it does not solve.&lt;/p&gt;

&lt;h2&gt;
  
  
  The measurement unit is assistant x question x date
&lt;/h2&gt;

&lt;p&gt;The first design decision matters more than the rest. There is no single number called "AI visibility".&lt;/p&gt;

&lt;p&gt;The team behind one of the tracking tools compared how the major assistants answer identical buying questions and found &lt;a href="https://www.reddit.com/r/ai_tools/comments/1vrs35n/do_chatgpt_gemini_and_perplexity_recommend_the/" rel="noopener noreferrer"&gt;the models do not return the same brand lists&lt;/a&gt;. The top slot is often the same obvious market leader across all of them, but positions two through five diverge quickly. If you check ChatGPT once and see yourself in fourth place, you have learned something about ChatGPT that week, and nothing about Gemini.&lt;/p&gt;

&lt;p&gt;That pushed us to store one row per assistant, per question, per run, and to resist averaging them into a headline figure. Multi-model coverage is also the thing buyers of commercial tools argue about most when they &lt;a href="https://www.reddit.com/r/geotoolsreview/comments/1v57q5h/9_top_profound_ai_alternatives_in_2026_for_ai/" rel="noopener noreferrer"&gt;compare products&lt;/a&gt;, which tells you breadth across assistants is the hard part, not depth in one.&lt;/p&gt;

&lt;h2&gt;
  
  
  What we actually run
&lt;/h2&gt;

&lt;p&gt;It is a scheduled task, not a platform. The pieces:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A fixed question set in a config file, written as buyers phrase things rather than as we would: "best custom software company in Sydney", "who can integrate NetSuite with Salesforce in Australia", "open source data integration platform".&lt;/li&gt;
&lt;li&gt;A runner that sends each question to each assistant in a clean session, with memory and personalisation off, so results are not contaminated by our own account history.&lt;/li&gt;
&lt;li&gt;Raw storage of the full answer text, the cited URLs, the model version and the timestamp. The raw text is the evidence. Everything else is derived and can be recomputed.&lt;/li&gt;
&lt;li&gt;A classifier pass over each answer: not mentioned, mentioned in passing, or named in a recommendation list with a position.&lt;/li&gt;
&lt;li&gt;A trend view over runs, not a live dashboard.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The question set is deliberately frozen. If you keep rewriting the prompts you can never tell whether a change in the answers came from the market or from you.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;questions&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;sydney-custom-software&lt;/span&gt;
    &lt;span class="na"&gt;text&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Best&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;custom&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;software&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;development&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;company&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;in&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;Sydney"&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;au-integration-partner&lt;/span&gt;
    &lt;span class="na"&gt;text&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Who&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;can&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;build&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;a&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;data&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;integration&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;between&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;NetSuite&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;and&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;Salesforce&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;in&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;Australia?"&lt;/span&gt;
&lt;span class="na"&gt;assistants&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;chatgpt&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;claude&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;gemini&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;perplexity&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
&lt;span class="na"&gt;schedule&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;0&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;6&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;*&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;*&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;1"&lt;/span&gt;   &lt;span class="c1"&gt;# weekly, Monday morning&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;q&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;questions&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;assistants&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;answer&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;ask&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;q&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;fresh_session&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="nf"&gt;store&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;run_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;q&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nb"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;answer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;answer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;citations&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nf"&gt;utc_now&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Weekly is enough. We tried daily and it produced motion without information.&lt;/p&gt;

&lt;h2&gt;
  
  
  Single runs are noise
&lt;/h2&gt;

&lt;p&gt;Ask the same assistant the same question three times and you will get three different lists. That is normal behaviour, not a bug in your harness, and it is the main reason a screenshot of one good answer is worthless as evidence.&lt;/p&gt;

&lt;p&gt;We read the data as a rolling count: in how many of the last twelve runs did PicNet appear for this question on this assistant, and where. A move from two runs in twelve to seven in twelve is a signal. A move from four to five is weather. Direction over a quarter is the only view we take seriously.&lt;/p&gt;

&lt;p&gt;The more useful column turned out to be the citations. When an assistant names a Sydney software firm, it usually names its sources, and those sources are mostly other people's comparison posts and directory listings. That tells you where the answer is actually coming from.&lt;/p&gt;

&lt;h2&gt;
  
  
  What moves the answers, and how it differs from SEO
&lt;/h2&gt;

&lt;p&gt;The tactics practitioners credit with getting quoted by AI engines &lt;a href="https://www.reddit.com/r/Shopify_AEO/comments/1vnjvbc/how_to_increase_ai_traffic_to_your_ecommerce/" rel="noopener noreferrer"&gt;look familiar but are weighted differently&lt;/a&gt;: complete product and service JSON-LD on every page, content rewritten to answer real buyer questions in plain language, FAQ sections, content kept fresh and visibly dated, and earned mentions in third-party comparison posts and listicles.&lt;/p&gt;

&lt;p&gt;The last item is the awkward one. Assistants lean heavily on the comparison articles and lists that already exist about your category, so off-site mentions you do not control can matter more than the on-page work you do control. Classic SEO rewards a well structured site. Answer engines reward being talked about accurately by other sites, then having a site that confirms it in machine-readable form.&lt;/p&gt;

&lt;p&gt;None of this is exotic. The local-intent version is already mainstream, with people asking assistants for the &lt;a href="https://www.reddit.com/r/geotoolsreview/comments/1v9rthp/10_best_ai_search_visibility_tools_for_local_businesses/" rel="noopener noreferrer"&gt;best dentist near me or top restaurant in my area&lt;/a&gt;, and that answer layer now sitting on top of the Google local pack most Australian businesses already watch. "Best custom software company in Sydney" is the same query shape with a longer sales cycle.&lt;/p&gt;

&lt;h2&gt;
  
  
  Cost, and the case for building it
&lt;/h2&gt;

&lt;p&gt;Commercial monitoring tools run from about &lt;a href="https://www.reddit.com/r/geotoolsreview/comments/1v38gas/top_8_promptwatch_alternatives_for_ai_visibility/" rel="noopener noreferrer"&gt;$29 a month for Radarkit AI to $85 a month for GetMentions AI&lt;/a&gt;. That is not a lot of money, and if you want a maintained product you should buy one.&lt;/p&gt;

&lt;p&gt;We built instead for two reasons. The recurring cost of our task is the API tokens for a few dozen questions a week, which is negligible, and we wanted the raw answer text in our own store to query later. The second reason is the complaint that keeps recurring in those same threads: the platforms can be an expensive dashboard that tells you what you already know, that you are not being cited. A daily share-of-voice figure across four models is not the same as knowing what to change.&lt;/p&gt;

&lt;p&gt;Be clear about the scope. GEO tooling splits into two jobs: prompt tracking and citation monitoring on one side, and content briefs, crawler data, site audits and reporting on the other, as one &lt;a href="https://www.reddit.com/r/geotoolsreview/comments/1vmbs3i/8_best_bluefish_ai_alternatives_for_monitoring/" rel="noopener noreferrer"&gt;tool comparison sets out&lt;/a&gt;. An in-house scheduled task honestly covers the first half. The work of getting mentioned still has to be done by people.&lt;/p&gt;

&lt;p&gt;Two caveats on the evidence itself. Most of the GEO tooling threads above sit on a subreddit with disclosed vendor self-promotion in the comments, so they are fair for market shape and pricing and not much else. And we have not found any Australian regulatory, government or industry source with a dated figure on assistant-driven vendor discovery here. If someone quotes you an Australian percentage, ask where it came from. Your own measurements are the only numbers about your own business you can defend.&lt;/p&gt;

&lt;h2&gt;
  
  
  One reason not to wait
&lt;/h2&gt;

&lt;p&gt;OpenAI now publishes an &lt;a href="https://ads.openai.com/" rel="noopener noreferrer"&gt;advertising product page for ChatGPT&lt;/a&gt;, offering campaigns aimed at people comparing options inside the assistant. Paid placement is arriving on the answer surface. Whatever that does to organic recommendations, you will want a baseline of how you appeared before it landed, and a baseline can only be collected in the past.&lt;/p&gt;

&lt;p&gt;Start with ten questions, four assistants and a weekly cron job. Keep the raw answers. Look at it once a quarter.&lt;/p&gt;

&lt;p&gt;PicNet builds production AI systems for Australian organisations. &lt;a href="https://picnet.com.au/ai-services/" rel="noopener noreferrer"&gt;Talk to us&lt;/a&gt; about what a first project could look like.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Originally published at &lt;a href="https://picnet.com.au/blog/is-chatgpt-recommending-you-measuring-your-visibility-to-ai-assistants/" rel="noopener noreferrer"&gt;picnet.com.au&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>marketing</category>
      <category>generativeengineoptimisation</category>
      <category>answerengineoptimisation</category>
    </item>
    <item>
      <title>Reconciling timesheets, leave and payroll automatically</title>
      <dc:creator>Guido Tapia</dc:creator>
      <pubDate>Mon, 17 Aug 2026 01:06:06 +0000</pubDate>
      <link>https://dev.to/gatapia/reconciling-timesheets-leave-and-payroll-automatically-14m0</link>
      <guid>https://dev.to/gatapia/reconciling-timesheets-leave-and-payroll-automatically-14m0</guid>
      <description>&lt;p&gt;Most Australian businesses under a few hundred staff run payroll across at least three systems that never talk properly: a form or email where leave gets requested, a timesheet system where hours get captured, and an accounting package like Xero where the pay run actually happens. The glue between them is usually a person with a spreadsheet. Practitioners in hospitality, retail, healthcare and manufacturing describe exactly this setup, a &lt;a href="https://www.reddit.com/r/ChoiceHouse/comments/1v26pso/shiftbase_vs_diy_options_for_payroll_integrations/" rel="noopener noreferrer"&gt;patchwork of spreadsheets, manual timesheets and CSV exports&lt;/a&gt; where the software looks free but the real cost turns up as hidden hours fixing the output.&lt;/p&gt;

&lt;p&gt;This post is part of our &lt;a href="https://picnet.com.au/blog/practical-ai-in-business-operations-the-series/" rel="noopener noreferrer"&gt;Practical AI in Business Operations&lt;/a&gt; series, and it covers one of the least glamorous things we build for ourselves and for clients: scheduled checks that compare two systems which should agree, and send an email when they don't.&lt;/p&gt;

&lt;h2&gt;
  
  
  The pattern
&lt;/h2&gt;

&lt;p&gt;Everything below is the same three moving parts.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A source of truth that a human approved: a signed leave form, an approved timesheet, a contracted monthly hour cap.&lt;/li&gt;
&lt;li&gt;A scheduled task, usually daily or on a payroll cycle, that pulls the corresponding records out of the operational system (Xero, the time capture tool, the project register) and compares them.&lt;/li&gt;
&lt;li&gt;An email to a named person listing only the rows that disagree, with enough context to act on without opening three tabs.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The important detail is the first one. Reconciliation checks are worth far more when they are anchored to a stored reference record rather than to the second system's own output. In an open-source HR system, checking a front end against the &lt;a href="https://github.com/yeyint0304/AI_HR_Syetems_FE/pull/16" rel="noopener noreferrer"&gt;saved API examples for roles, project assignments and project lists&lt;/a&gt; surfaced three integration bugs nobody had reported, including assignee names rendering as raw GUIDs and a paginated response shape silently producing an empty project list. The saved examples were the reference. The payroll equivalent is the approved leave form, not whatever the timesheet system decided to export.&lt;/p&gt;

&lt;h2&gt;
  
  
  Three checks worth building first
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Leave forms received versus leave entered in payroll.&lt;/strong&gt; Someone submits a leave request. It gets approved. Then it has to be keyed into Xero as a leave application against the right leave type, for the right dates, at the right hours. The check reads approved requests for the period, reads leave applications from the Xero API, matches on employee and date range, and reports anything present on one side and missing on the other, plus anything where the hours differ.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;A daily timesheet summary email.&lt;/strong&gt; Not a dashboard. An email, each morning, listing who logged hours yesterday, who didn't, and the totals per person. This is a cheap way to catch the failure mode where someone stops entering time for a fortnight and nobody notices until invoicing.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Monthly client-hour caps.&lt;/strong&gt; Where a retainer or a contract caps hours per month, the check joins time entries to project and client assignments and flags anyone tracking toward the cap. That join is its own source of error: who is assigned to which project or client lives in a different system from time capture, and &lt;a href="https://github.com/yeyint0304/AI_HR_Syetems_FE/pull/16" rel="noopener noreferrer"&gt;mismatches between assignment data and time data are a known bug class&lt;/a&gt;. If the assignment table is stale, the cap report is wrong in a way that looks perfectly plausible.&lt;/p&gt;

&lt;h2&gt;
  
  
  Most of this is a join, not AI
&lt;/h2&gt;

&lt;p&gt;Be honest about how much of the work needs a model. For structured comparison on clean keys, a plain query is better in every dimension that matters: it is deterministic, it is testable, it costs nothing to run, and when it produces a wrong answer you can read the code and see why.&lt;/p&gt;

&lt;p&gt;A leave reconciliation in C# against a Xero pull looks roughly like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;missingInPayroll&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;approvedRequests&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Where&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;r&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;StartDate&lt;/span&gt; &lt;span class="p"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="n"&gt;periodStart&lt;/span&gt; &lt;span class="p"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;EndDate&lt;/span&gt; &lt;span class="p"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="n"&gt;periodEnd&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Where&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;r&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;!&lt;/span&gt;&lt;span class="n"&gt;xeroLeave&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Any&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;
        &lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;EmployeeId&lt;/span&gt; &lt;span class="p"&gt;==&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;EmployeeId&lt;/span&gt; &lt;span class="p"&gt;&amp;amp;&amp;amp;&lt;/span&gt;
        &lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;LeaveTypeId&lt;/span&gt; &lt;span class="p"&gt;==&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;LeaveTypeId&lt;/span&gt; &lt;span class="p"&gt;&amp;amp;&amp;amp;&lt;/span&gt;
        &lt;span class="nf"&gt;DatesOverlap&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;)))&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ToList&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;hoursMismatch&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;approvedRequests&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;xeroLeave&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Where&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Abs&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Hours&lt;/span&gt; &lt;span class="p"&gt;-&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Hours&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="m"&gt;0.01m&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ToList&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;missingInPayroll&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Any&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;||&lt;/span&gt; &lt;span class="n"&gt;hoursMismatch&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Any&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;email&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;SendVarianceReport&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;missingInPayroll&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;hoursMismatch&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That is the whole idea. A scheduled job, two queries, a comparison, a conditional email. We run this style of check on our own internal systems, and the pattern transfers to any pair of systems that should agree: bank feed versus invoice ledger, roster versus timesheet, CRM versus billing.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where an LLM actually earns its place
&lt;/h2&gt;

&lt;p&gt;Two spots, both narrow.&lt;/p&gt;

&lt;p&gt;The first is free-text leave reasons. People write "carers day for my son", "sick - flu", "medical appt Thursday am", or nothing at all. Payroll needs a leave type. A model is genuinely good at mapping unstructured text to a fixed set of categories, and it costs almost nothing because you only send the handful of rows that failed the deterministic match. We treat the output as a suggestion attached to the variance email, never as an automatic write into payroll. If the classification is clinically adjacent, personal or carer's leave attached to a medical reason, the design requirement is that a human approves the categorisation before it touches a record, and the model output is stored as a suggestion with the approver's name against the final value.&lt;/p&gt;

&lt;p&gt;The second is name and identifier variants. "Rob Nguyen" in the timesheet tool, "Robert Nguyen" in Xero, "R. Nguyen (contractor)" in the project register. Fuzzy string matching handles a lot of this, but it fails on nicknames, married names, transliterations and the person who joined as a contractor and later became an employee with a new ID. A model with the candidate list in context resolves these well, and you can cache the resolved mapping so you pay for each variant once.&lt;/p&gt;

&lt;p&gt;What we do not do is ask a model to decide whether the numbers agree. Arithmetic and set comparison belong in code.&lt;/p&gt;

&lt;h2&gt;
  
  
  Variances are usually definitional
&lt;/h2&gt;

&lt;p&gt;Once these checks run, the first month produces a pile of mismatches, and the instinct is to treat every one as a data entry error. Usually it isn't. When two sources measure the same population and disagree, the difference is often in the definitions rather than the data. The New York Fed's &lt;a href="https://libertystreeteconomics.newyorkfed.org/2026/08/how-distressed-are-consumers-reconciling-diverging-credit-card-delinquency-measures/" rel="noopener noreferrer"&gt;August 2026 analysis of diverging credit card delinquency measures&lt;/a&gt; is a good illustration: two delinquency series told opposite stories about American households, and the gap came down to whether charged-off debts stayed in the numerator, not to bad reporting by anyone.&lt;/p&gt;

&lt;p&gt;Payroll has the same shape. A public holiday inside a leave period, a half day counted as 4 hours in one system and 3.8 in another, leave accrued on a roster cycle versus a calendar month. Each of those produces a permanent variance that a rule can express once. Budget real time in the first cycle to classify variances into "genuine error" and "definitional", then encode the definitional ones as tolerances or exclusions. If you skip that step, the daily email becomes noise and people stop reading it, which is worse than not having the check at all.&lt;/p&gt;

&lt;h2&gt;
  
  
  Costs and limitations
&lt;/h2&gt;

&lt;p&gt;Build cost for a single check is small, typically a few days including the API plumbing and the first round of variance triage. Running cost is a scheduled job and a mailbox. Where the money actually goes is maintenance: API contracts change, leave types get added, someone renames a project. The build versus buy decision in payroll integration is &lt;a href="https://www.reddit.com/r/ChoiceHouse/comments/1v26pso/shiftbase_vs_diy_options_for_payroll_integrations/" rel="noopener noreferrer"&gt;usually decided by ongoing maintenance and error correction effort rather than licence cost&lt;/a&gt;, and that applies just as much to checks you write yourself.&lt;/p&gt;

&lt;p&gt;The limitations are worth stating plainly. These checks detect disagreement; they do not fix it, and we deliberately keep them read-only against payroll. They cannot catch an error that both systems share, such as an incorrect award rate applied consistently everywhere. And they need an owner. An unowned variance email is a filtered folder within a month.&lt;/p&gt;

&lt;p&gt;None of this is exciting work. It is, however, the kind of automation that pays for itself in the first quarter, keeps your records defensible under Fair Work obligations, and stops errors flowing into Single Touch Payroll reporting where they are much more annoying to correct.&lt;/p&gt;

&lt;p&gt;PicNet builds production AI systems for Australian organisations. &lt;a href="https://picnet.com.au/ai-services/" rel="noopener noreferrer"&gt;Talk to us&lt;/a&gt; about what a first project could look like.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Originally published at &lt;a href="https://picnet.com.au/blog/reconciling-timesheets-leave-and-payroll-automatically/" rel="noopener noreferrer"&gt;picnet.com.au&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>businessoperations</category>
      <category>payroll</category>
      <category>xero</category>
    </item>
    <item>
      <title>Ransomware early warning: watching leak sites for your name, your clients and your suppliers</title>
      <dc:creator>Guido Tapia</dc:creator>
      <pubDate>Fri, 14 Aug 2026 00:34:51 +0000</pubDate>
      <link>https://dev.to/gatapia/ransomware-early-warning-watching-leak-sites-for-your-name-your-clients-and-your-suppliers-244g</link>
      <guid>https://dev.to/gatapia/ransomware-early-warning-watching-leak-sites-for-your-name-your-clients-and-your-suppliers-244g</guid>
      <description>&lt;p&gt;On 13 August the Clop ransomware group posted claims naming Shell and Philips, and both companies confirmed they had experienced a security incident once the claims were reported (&lt;a href="https://nltimes.nl/2026/08/13/russian-ransomware-group-clop-claims-cyberattacks-shell-philips" rel="noopener noreferrer"&gt;NL Times&lt;/a&gt;). Philips called it an attempted attack on a specific server holding internal data, since brought under control, with no impact on customer environments. The security platform that spotted the listing also warned that the numbers come straight from the attackers and are unverified.&lt;/p&gt;

&lt;p&gt;If you run IT for an Australian hospital, imaging practice or aged care group, Philips is not a name from the news. It is on your asset register and in your maintenance contracts. That is the useful part of this story, not the severity of the claim itself. A leak site published a named victim before any customer notification existed. Whoever inside your organisation happened to read that page was your early warning system.&lt;/p&gt;

&lt;p&gt;In most organisations, nobody has that job.&lt;/p&gt;

&lt;h2&gt;
  
  
  The check we run
&lt;/h2&gt;

&lt;p&gt;We run mention monitoring as one of the automated checks in our managed services work. It is not glamorous and it is not a threat intelligence platform. It is a scheduled pipeline with an LLM doing one specific job in the middle.&lt;/p&gt;

&lt;p&gt;The shape of it:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A watchlist of entities: our own legal and trading names and domains, the clients we hold data for, and the suppliers whose failure would stop work. Each entity gets aliases, former names, subsidiary names and email domains, because "Philips Healthcare ANZ" and "Philips" rarely appear the same way twice.&lt;/li&gt;
&lt;li&gt;Collection from public sources on a schedule. Leak site listings from aggregators that mirror them, breach chatter, paste sites, vendor advisories and security press. We read public sources. We do not buy forum access or stolen credentials, and neither should your provider.&lt;/li&gt;
&lt;li&gt;Normalisation into one record shape: source, timestamp, claimed victim string, claimed gang, raw text, URL.&lt;/li&gt;
&lt;li&gt;A cheap prefilter. Plain string and fuzzy matching against the alias list, which is deliberately over-inclusive.&lt;/li&gt;
&lt;li&gt;An LLM classification pass over everything the prefilter caught.&lt;/li&gt;
&lt;li&gt;Deduplication against what we have already alerted on, then routing.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The prefilter is where naive systems stop, and where they generate enough noise that people mute them by week three. In a single four-week window this winter, publicly reported named-victim ransomware events included Shell, Philips, Coca-Cola's Fairlife dairy and a US bank (&lt;a href="https://nltimes.nl/2026/08/13/russian-ransomware-group-clop-claims-cyberattacks-shell-philips" rel="noopener noreferrer"&gt;NL Times&lt;/a&gt;). Now imagine your watchlist contains a supplier called "Shell", or a client whose trading name is two common English words. Every hit is a coin toss.&lt;/p&gt;

&lt;h2&gt;
  
  
  What the model is actually asked
&lt;/h2&gt;

&lt;p&gt;The classifier gets one job: decide whether this text refers to the entity on our watchlist, or to something that merely shares a name. It does not decide severity, it does not write the notification, and it does not talk to anyone.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="err"&gt;You&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;are&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;classifying&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;a&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;security&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;mention.&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;Given&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;the&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;WATCHLIST_ENTITY&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="err"&gt;(with&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;aliases,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;domains,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;industry,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;country)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;and&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;the&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;MENTION&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;text,&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="err"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;JSON&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;only:&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"match"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"yes"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"no"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"unsure"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"match_basis"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"domain"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"exact_name"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"alias"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"context_only"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"claimed_gang"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;string&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"claim_type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"leak_site_listing"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"news_report"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="w"&gt;
                &lt;/span&gt;&lt;span class="s2"&gt;"vendor_advisory"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"chatter"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"other"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"reasoning"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"one sentence"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"distinguishing_evidence"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"the text that decided it"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="err"&gt;Rules:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="err"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;A&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;shared&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;name&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;with&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;a&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;different&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;industry&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;or&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;country&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;is&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"no"&lt;/span&gt;&lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="err"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;Absence&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;of&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;a&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;domain&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;or&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;industry&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;match&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;is&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;not&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;evidence&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;of&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"no"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="err"&gt;when&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;the&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;text&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;is&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;short;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"unsure"&lt;/span&gt;&lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="err"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;Never&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;infer&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;facts&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;not&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;present&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;in&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;MENTION.&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Two design decisions matter more than the prompt wording. First, "unsure" is a valid answer and it routes to a human rather than to the bin. A model forced to choose between yes and no on a forty word leak site entry will guess, and the guess costs more than the review. Second, the model must quote the text that decided it. If the evidence field does not contain the phrase that drove the classification, the record is treated as unsure. That single rule kills most confident nonsense.&lt;/p&gt;

&lt;p&gt;Nothing leaves the building automatically. An alert reaches a person, and a person decides whether a client or supplier gets contacted. For any mention involving health providers or the data they hold, that human sign-off is a design requirement of the pipeline, not a policy we hope people follow.&lt;/p&gt;

&lt;h2&gt;
  
  
  Keep the source list alive
&lt;/h2&gt;

&lt;p&gt;The creator of Ransom Cartel was sentenced to 16 years in prison this month (&lt;a href="https://www.bleepingcomputer.com/news/security/ransom-cartel-ransomware-creator-sentenced-to-16-years-in-prison/" rel="noopener noreferrer"&gt;BleepingComputer&lt;/a&gt;). Brands get taken down, affiliates scatter, and the same people reappear under a new name with a new onion address. A pipeline keyed to a fixed list of gang names and mirror URLs quietly stops finding anything, and a monitoring system that has gone silent looks exactly like a monitoring system with nothing to report.&lt;/p&gt;

&lt;p&gt;We review sources monthly and treat a source that has returned zero results for a fortnight as broken until proven otherwise. Add a synthetic canary entity to the watchlist so you can confirm the pipeline still fires end to end.&lt;/p&gt;

&lt;h2&gt;
  
  
  The first hour when a supplier appears
&lt;/h2&gt;

&lt;p&gt;The listing itself tells you almost nothing reliable. Assume the claim might be exaggerated, and act on the consequences you can control.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Verify the listing exists as described. Screenshot it, record the URL and time in AEST, and note exactly which entity name was published. A subsidiary listing is not the same as the parent.&lt;/li&gt;
&lt;li&gt;Work out your exposure to that supplier by data and by dependency. What of yours do they hold, what integrations do they have into your environment, and what stops if they go dark for a fortnight? Ransomware at Coca-Cola's Fairlife dairy suspended production across its US sites (&lt;a href="https://techcrunch.com/2026/07/16/coca-cola-suspended-production-at-its-fairlife-dairy-after-a-ransomware-attack/" rel="noopener noreferrer"&gt;TechCrunch&lt;/a&gt;). The equivalent here is a booking, pathology or maintenance service simply not being available on Monday.&lt;/li&gt;
&lt;li&gt;Cut the machine-to-machine trust you can afford to cut. Rotate shared credentials and API keys, review any supplier VPN or remote support access, and check what their accounts can reach in your tenancy.&lt;/li&gt;
&lt;li&gt;Contact the supplier through a channel you already had, not one from an email that arrives afterwards. Ask three things: is the claim accurate, what data of ours is in scope, and when will you confirm in writing.&lt;/li&gt;
&lt;li&gt;Open your breach assessment file now, on the assumption it may become a notifiable data breach. Under the OAIC's Notifiable Data Breaches scheme you have 30 days to assess a suspected eligible breach, and that clock is much easier to defend when the timeline starts at the leak site listing rather than at the supplier's letter three weeks later. Report to ReportCyber if it becomes an incident.&lt;/li&gt;
&lt;li&gt;Route the alert to more than one person, and out of band. Zscaler's research on one month-long campaign found nearly two thirds of victims held manager-level titles or above, with the average victim a 46 year old (&lt;a href="https://www.theregister.com/security/2026/08/09/ransomware-gangs-skip-the-ceo-head-straight-for-the-40-something-it-manager/5284499" rel="noopener noreferrer"&gt;The Register&lt;/a&gt;). That profile is the person who owns the alerting inbox. If the alert only ever lands in one mailbox, and that mailbox is a target, you have built a single point of failure.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Keep monitoring after the incident closes. One US bank told regulators it had obtained representations from the attackers that they had deleted the stolen data (&lt;a href="https://www.theregister.com/cyber-crime/2026/07/31/us-bank-places-trust-in-ransomware-crew-that-promised-to-delete-its-data/5281888" rel="noopener noreferrer"&gt;The Register&lt;/a&gt;). A listing coming down means someone decided to take it down. It is not evidence that anything was destroyed.&lt;/p&gt;

&lt;h2&gt;
  
  
  Honest limits and costs
&lt;/h2&gt;

&lt;p&gt;This tells you what criminals have published, which is a partial, self-serving and sometimes fabricated view. It will not catch a quiet compromise nobody brags about, and it is not a substitute for logging, backups, MFA or patching. Expect false positives on generic company names and false negatives when a victim is listed only by an abbreviation or a domain you never recorded as an alias.&lt;/p&gt;

&lt;p&gt;The model spend is the least interesting line item. A few hundred prefiltered items a day through a small model costs a few dollars a month. The real cost is human: roughly half a day a month keeping aliases and sources current, plus the time to triage what the classifier flags as unsure. Skip that maintenance and you have an expensive way of feeling covered.&lt;/p&gt;

&lt;p&gt;This post is part of our &lt;a href="https://picnet.com.au/blog/practical-ai-in-cyber-security-the-series/" rel="noopener noreferrer"&gt;Practical AI in Cyber Security&lt;/a&gt; series, which looks at small AI components doing narrow jobs inside systems that were already working. Mention monitoring is a good first one, because the pipeline is a weekend of work and the failure mode is embarrassing rather than dangerous.&lt;/p&gt;

&lt;p&gt;PicNet builds production AI systems for Australian organisations. &lt;a href="https://picnet.com.au/ai-services/" rel="noopener noreferrer"&gt;Talk to us&lt;/a&gt; about what a first project could look like.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Originally published at &lt;a href="https://picnet.com.au/blog/ransomware-early-warning-monitoring-leak-sites-and-dark-web-mentions-with-ai/" rel="noopener noreferrer"&gt;picnet.com.au&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>cybersecurity</category>
      <category>ransomware</category>
      <category>threatintelligence</category>
    </item>
    <item>
      <title>An LLM-triaged SIEM: reviewing thousands of security events a day with a small team</title>
      <dc:creator>Guido Tapia</dc:creator>
      <pubDate>Wed, 12 Aug 2026 00:50:43 +0000</pubDate>
      <link>https://dev.to/gatapia/an-llm-triaged-siem-reviewing-thousands-of-security-events-a-day-with-a-small-team-3n42</link>
      <guid>https://dev.to/gatapia/an-llm-triaged-siem-reviewing-thousands-of-security-events-a-day-with-a-small-team-3n42</guid>
      <description>&lt;p&gt;Most health providers we talk to have the same shape of problem. Log sources everywhere, a security budget that funds part of one person's week, and an alert console nobody has opened since the fortnight after it was installed. The logs are being collected, which satisfies the auditor. Nobody is reading them, which satisfies nobody.&lt;/p&gt;

&lt;p&gt;We have that problem too. PicNet is a small senior team, and we run our own infrastructure, so somebody has to look at the firewall and endpoint logs. For about a year now that somebody has been a language model doing a first pass, with a human reading whatever it escalates. This post describes how the pipeline actually works, what it costs, and where it is weak. It is part of our &lt;a href="https://picnet.com.au/blog/practical-ai-in-cyber-security-the-series/" rel="noopener noreferrer"&gt;Practical AI in Cyber Security&lt;/a&gt; series.&lt;/p&gt;

&lt;h2&gt;
  
  
  What goes in
&lt;/h2&gt;

&lt;p&gt;Collectors pull from the sources we already had, on a schedule, into flat files in an object store partitioned by day and source:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;FortiGate firewall traffic and IPS events&lt;/li&gt;
&lt;li&gt;Microsoft 365 unified audit log (mail rules, sharing, admin activity)&lt;/li&gt;
&lt;li&gt;Entra ID sign-in logs and directory change events&lt;/li&gt;
&lt;li&gt;AWS CloudTrail&lt;/li&gt;
&lt;li&gt;SentinelOne endpoint detections and agent health&lt;/li&gt;
&lt;li&gt;Windows event logs from servers, shipped with a lightweight agent&lt;/li&gt;
&lt;li&gt;Remote-access logs from the VPN and RDP gateway&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Nothing clever here, and deliberately so. The collectors are dumb, they retry, and they never drop a source silently. A missing source is itself an escalation, because the most common reason a log stops arriving is that someone turned something off.&lt;/p&gt;

&lt;h2&gt;
  
  
  One pass over everything, then selective deep dives
&lt;/h2&gt;

&lt;p&gt;The design decision that made this work was refusing to do per-alert triage. Feeding a model one alert at a time gives it no way to know what normal looks like, so it either flags everything or nothing.&lt;/p&gt;

&lt;p&gt;Instead there is a single global pass. For each window, the pipeline builds a compact per-host, per-source summary: counts by event type, new-this-window values (source IPs, user agents, service principals, parent processes), failed-versus-successful ratios, and anything that deviates from that host's own trailing baseline. The whole fleet's summary goes into one prompt. The model's job is comparative, not forensic. Which of these hosts looks unlike itself yesterday, and unlike its peers today?&lt;/p&gt;

&lt;p&gt;That output is a ranked list, and only the top of it earns a second, expensive call with the raw log lines attached. This is the pattern &lt;a href="https://gwern.net/guardian-angel" rel="noopener noreferrer"&gt;Gwern describes as a "guardian angel"&lt;/a&gt;: a model watching a whole activity stream continuously and surfacing only the parts that look off, rather than being wired into every individual rule.&lt;/p&gt;

&lt;p&gt;The triage call returns structured JSON, one object per host, and anything malformed is treated as a flag rather than a pass:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"host"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"app-prd-03"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"verdict"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"deep_dive"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"confidence"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;0.72&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"reason"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"First observed successful RDP from an ASN not seen in 30d baseline, followed by service install event 7045."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"sources"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"windows_event"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"remote_access"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"suggested_window_hours"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;48&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The deep-dive pass gets the raw events for that host and window and produces a written incident note: what happened, in what order, what would confirm or rule it out. That note lands in a human inbox. It does not close anything, page anyone, or touch a firewall rule.&lt;/p&gt;

&lt;h2&gt;
  
  
  Cost control
&lt;/h2&gt;

&lt;p&gt;We serialise the LLM calls. One at a time, in a loop, with the deep dives running only after triage has finished and ranked. That is not an engineering constraint, it is a spend and rate-limit choice: a serial loop has a predictable ceiling, and a runaway parallel job on a noisy day does not. If our event volume grows enough to matter, the throughput is sitting there to reclaim, because modern inference servers get their speed from continuous batching and paged KV-cache scheduling rather than from anything we would have to redesign (&lt;a href="https://www.aleksagordic.com/blog/vllm" rel="noopener noreferrer"&gt;a good walkthrough of how vLLM does it here&lt;/a&gt;).&lt;/p&gt;

&lt;p&gt;The two-tier split is where the money is saved. ISGroup published costs for an LLM-driven security review of the GlobaLeaks codebase: about US$3,140 of model spend for 41 confirmed findings, roughly US$77 each before human validation. The detail worth stealing is the cost distribution, where &lt;a href="https://www.isgroup.biz/en/cyber-security/llm-based-code-security-review-costs-findings-methodology.html" rel="noopener noreferrer"&gt;the strongest reasoning model consumed 62% of the budget while processing only 7% of the tokens&lt;/a&gt;. Broad coverage is cheap. Deep reasoning is not. Any pipeline that sends every event to the expensive model is paying reasoning prices for volume work.&lt;/p&gt;

&lt;p&gt;The floor on the cheap tier keeps dropping. Cactus released &lt;a href="https://cactuscompute.com/needle" rel="noopener noreferrer"&gt;Needle2, a 14MB agentic model aimed at phones and smart-home hardware&lt;/a&gt;, and someone has demonstrated &lt;a href="https://github.com/slvDev/esp32-ai" rel="noopener noreferrer"&gt;a 28.9M-parameter model running on an $8 ESP32&lt;/a&gt;. For a health client who cannot send raw Entra ID or CloudTrail records to an offshore API, a local classification tier in front of a hosted deep-dive tier is now a realistic build, particularly since &lt;a href="https://github.com/trycua/cua/blob/main/blog/gpu-passthrough-macos-vms.md" rel="noopener noreferrer"&gt;GPU passthrough into isolated macOS VMs for llama.cpp is documented and working&lt;/a&gt;. We are not running that configuration ourselves yet. We have costed it for clients where the data cannot leave the building, and it prices better than most people assume.&lt;/p&gt;

&lt;h2&gt;
  
  
  Logs are attacker-controlled input
&lt;/h2&gt;

&lt;p&gt;Anything a model reads from a log is text an attacker may have written. Hostnames, user agents, filenames, email subjects, HTTP paths, process command lines. All of it is user-supplied, and all of it lands in your prompt.&lt;/p&gt;

&lt;p&gt;We assume the triage model will eventually be told, by a log line, to ignore its instructions and report all clear. The defences are structural rather than clever. Log content is passed as data inside delimited fields, never concatenated into the instruction block. The model has no tools, no network access and no write permissions of any kind, so the worst case is a bad verdict rather than a bad action. Output is validated against a schema and anything unexpected escalates. Most importantly, the deterministic rules run independently of the model: impossible-travel sign-ins, MFA being disabled, a new global administrator, a stopped collector. Those fire on their own logic and cannot be talked out of it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Every escalation lands with a person
&lt;/h2&gt;

&lt;p&gt;No model verdict closes an incident. A named human reads the note and records the decision. That is a hard rule, and the reason is not squeamishness about AI. It is that an unreviewed automated verdict is not evidence you can put in front of a board, an auditor, or the OAIC after a notifiable breach, where assessment timeframes leave no room to reconstruct why nobody looked. This kind of disclosure-and-review requirement is becoming normal practice elsewhere too: Debian spent mid-2026 &lt;a href="https://www.debian.org/vote/2026/vote_002" rel="noopener noreferrer"&gt;voting on competing proposals for how LLM-generated contributions must be disclosed and reviewed before acceptance&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;For health organisations specifically, keep the boundary obvious. This pipeline reads infrastructure and identity logs. It does not touch clinical records, it makes no clinical inference, and any use that drifts towards patient data needs the human sign-off designed into the workflow from the start rather than bolted on as a disclaimer.&lt;/p&gt;

&lt;h2&gt;
  
  
  Honest about what it misses
&lt;/h2&gt;

&lt;p&gt;The reduction in reading volume is large. Thousands of events a day compress into a page of summaries and, on a typical day, no escalations at all. That is the win, and it is a real one: the logs get read every day now instead of never.&lt;/p&gt;

&lt;p&gt;What we cannot tell you is our false-negative rate, and neither can anyone else selling you this. We know what the model escalated. We do not know what it quietly passed over. Two things make that less uncomfortable. The deterministic rules catch the known-bad patterns regardless of what the model thinks. And once a week someone reads a random sample of "no action" verdicts against the underlying logs, which is slow and boring and the only honest check we have found.&lt;/p&gt;

&lt;p&gt;The model is good at "this host is behaving unlike itself". It is poor at slow campaigns that stay inside the baseline, because it only sees a window at a time. If your threat model is a patient attacker, this pipeline is one layer, not the answer. Also worth publishing your prompts and review method internally rather than just the findings, the way that GlobaLeaks review did. A security committee can audit a documented process. It cannot audit a black box that says everything is fine.&lt;/p&gt;

&lt;p&gt;PicNet builds production AI systems for Australian organisations. &lt;a href="https://picnet.com.au/ai-services/" rel="noopener noreferrer"&gt;Talk to us&lt;/a&gt; about what a first project could look like.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Originally published at &lt;a href="https://picnet.com.au/blog/an-llm-triaged-siem-reviewing-thousands-of-security-events-a-day-with-a-small-team/" rel="noopener noreferrer"&gt;picnet.com.au&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>healthcare</category>
      <category>siem</category>
      <category>securityoperations</category>
    </item>
    <item>
      <title>Governing AI in Australian health: privacy, PHI and safe adoption</title>
      <dc:creator>Guido Tapia</dc:creator>
      <pubDate>Mon, 10 Aug 2026 01:08:25 +0000</pubDate>
      <link>https://dev.to/gatapia/governing-ai-in-australian-health-privacy-phi-and-safe-adoption-5h8i</link>
      <guid>https://dev.to/gatapia/governing-ai-in-australian-health-privacy-phi-and-safe-adoption-5h8i</guid>
      <description>&lt;p&gt;Most AI conversations we have with health organisations stall at the same point. Not "does it work", but "where does the patient data go, who is accountable when it goes somewhere it shouldn't, and who signs the output". Those are the right questions, and "the vendor says it's encrypted" is not an answer that survives an audit or a board paper. This post, part of our &lt;a href="https://picnet.com.au/blog/practical-ai-in-health-the-series/" rel="noopener noreferrer"&gt;Practical AI in Health&lt;/a&gt; series, sets out how we approach governance on projects that handle protected health information, including the parts that are genuinely awkward.&lt;/p&gt;

&lt;h2&gt;
  
  
  Start with the data, not the model
&lt;/h2&gt;

&lt;p&gt;Before anyone compares models, write down what the system will actually send. Which fields, from which source, about whom, and to which processor. We do this as a table on a whiteboard, and it usually shrinks the problem.&lt;/p&gt;

&lt;p&gt;A surprising share of health AI work is administrative and needs far less identifiable data than the first design assumes. Sorting a referrals inbox, drafting a discharge summary skeleton from structured fields, matching duplicate patient records, extracting values from a scanned form, coding free text against a reference list. Several of these can run on a de-identified payload, with identifiers rejoined inside your own network after the model returns.&lt;/p&gt;

&lt;p&gt;Under the Privacy Act and the Australian Privacy Principles, health information is sensitive information, and sending it to a model provider is a disclosure, not merely storage. If that provider processes offshore, APP 8 and the cross-border rules come into play and you remain accountable for what the recipient does with it. Ask your privacy officer to write the organisation's position down before the proof of concept starts. Retro-fitting a privacy impact assessment onto a pilot that clinicians already like is the worst possible sequence, and we have watched it happen.&lt;/p&gt;

&lt;h2&gt;
  
  
  Residency and model choice
&lt;/h2&gt;

&lt;p&gt;Three broad options, with honest trade-offs.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Hosted model API in an Australian region. Cheapest per unit of work, best quality, fastest to build. The catch is that your account being in Sydney does not guarantee inference runs in Sydney. Ask which region serves the model, whether prompts and outputs are logged, for how long, and whether zero retention is contractual or a toggle someone can flip back.&lt;/li&gt;
&lt;li&gt;Hosted API offshore. Sometimes the only way to get a specific model. Requires an explicit APP 8 position, disclosure to patients where relevant, and a clear view of sub-processors. We would not put identified clinical records through this without legal sign-off in writing.&lt;/li&gt;
&lt;li&gt;Open weight models on infrastructure you control, in your cloud tenancy or on premises. Full control of residency and logging. You pay for it with a fixed GPU bill whether the system is busy or idle, plus the ops work of running inference yourself, plus more prompt engineering and more human review because smaller models need it. In our experience this only makes financial sense at high, steady volume, or when the residency requirement is absolute.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The middle path we use most often is the de-identification boundary: strip identifiers before the call, keep a mapping table in your own database, rejoin afterwards. It is cheap and it works well for structured data. It is not a silver bullet, because clinical free text is full of names, addresses and dates that no regex will reliably catch. Where free text is involved, either accept that the payload is PHI and govern it accordingly, or keep the model inside your boundary.&lt;/p&gt;

&lt;h2&gt;
  
  
  The disclosure nobody puts in the risk register
&lt;/h2&gt;

&lt;p&gt;The model call is not the only place data leaves the building. A worked example from an open source medication tracking app is instructive: alongside rewriting its privacy policy to cover the Australian Privacy Act, GDPR Article 9 health data, PIPEDA and others, and to call out international AI data transfers as a separate disclosure item, the same change &lt;a href="https://github.com/dizzafizza/Meditrax/pull/15" rel="noopener noreferrer"&gt;removed fonts loaded from Google's CDN&lt;/a&gt;, which had been sending every visitor's IP address to a third party without disclosure.&lt;/p&gt;

&lt;p&gt;Fonts. In a health app. Nobody puts that on a risk register, and yet it was a real undisclosed transfer that contradicted the app's own privacy policy. The lesson generalises: your privacy impact assessment should inventory every outbound network call the application makes, including analytics, error reporting, session replay, map tiles and CDN assets, not just the AI endpoint. That same project also treats international AI data transfers as a distinct thing patients are told about, which is a reasonable pattern for Australian providers to copy given APP 8.&lt;/p&gt;

&lt;h2&gt;
  
  
  Human sign-off is an architectural requirement
&lt;/h2&gt;

&lt;p&gt;We keep our health AI work administrative and operational. It does not diagnose, triage clinical urgency, or produce anything a patient sees without a person in between.&lt;/p&gt;

&lt;p&gt;Where output is clinically adjacent, for example a drafted summary that will eventually sit in a record, the review step has to be built into the system rather than written into a policy document. In practice that means the model output is created in a draft state that cannot be transmitted, filed or actioned until a named person approves it. The approver's identity and timestamp are stored with the record. Rejecting or editing is as easy as accepting, and the prompt, the input and the raw output are logged so an incident can be reconstructed months later.&lt;/p&gt;

&lt;p&gt;Then measure the review. If your audit log shows reviewers approving in under two seconds with a zero per cent edit rate, you do not have oversight, you have a click-through. We treat edit rate as a live quality metric, and a sudden drop is a signal to go and talk to the users.&lt;/p&gt;

&lt;h2&gt;
  
  
  What to ask a vendor before you sign
&lt;/h2&gt;

&lt;p&gt;These are the questions we get asked, and the ones we ask on our clients' behalf when we are reviewing someone else's product:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;In which country is our data processed, and in which country is it stored at rest? Not "where is the company", where does inference physically run?&lt;/li&gt;
&lt;li&gt;Is our data, including prompts and outputs, used to train or improve any model? Is that a contractual commitment or a configuration setting?&lt;/li&gt;
&lt;li&gt;What is the retention period for prompts, outputs and logs, and how do we trigger deletion?&lt;/li&gt;
&lt;li&gt;Who are your sub-processors, and how are we notified when the list changes?&lt;/li&gt;
&lt;li&gt;How does the product enforce human review, and can that enforcement be turned off by an administrator?&lt;/li&gt;
&lt;li&gt;What is logged for audit, how long is it kept, and can we export it into our own SIEM?&lt;/li&gt;
&lt;li&gt;What is your notification timeframe and process for a suspected breach, and does it meet our obligations under the notifiable data breaches scheme?&lt;/li&gt;
&lt;li&gt;If the tool influences clinical decisions, what is your regulatory position on medical device classification? Worth raising with your regulatory advisor early, because it changes the project shape.&lt;/li&gt;
&lt;li&gt;What happens to our data if we terminate, and what does the exit look like?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A vendor who cannot answer the first three in a sentence each is not ready for health data.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where PicNet sits
&lt;/h2&gt;

&lt;p&gt;We are a small senior team in Sydney, and every manager here is an engineer, which means the person answering these questions has usually built the thing. We have delivered large traditional systems for some government contracts under strict constraints, including work where AI was excluded entirely, so we are comfortable building inside tight boundaries rather than treating them as an obstacle. Our open source integration platform, Centazio, handles the unglamorous plumbing that most of these projects turn out to be: moving data between systems reliably, with an audit trail.&lt;/p&gt;

&lt;p&gt;The honest limitations. A contract clause does not make an offshore API compliant, it only allocates blame. De-identification reduces risk, it does not eliminate it. Human review costs clinician time, and if the workflow does not save more time than the review consumes, the project is not worth doing. We would rather tell you that in week one than in month six. Start with one narrow administrative process, measure it, and decide from evidence.&lt;/p&gt;

&lt;p&gt;PicNet builds production AI systems for Australian organisations. &lt;a href="https://picnet.com.au/ai-services/" rel="noopener noreferrer"&gt;Talk to us&lt;/a&gt; about what a first project could look like.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Originally published at &lt;a href="https://picnet.com.au/blog/governing-ai-in-australian-health-privacy-phi-and-safe-adoption/" rel="noopener noreferrer"&gt;picnet.com.au&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>healthcare</category>
      <category>privacy</category>
      <category>governance</category>
    </item>
    <item>
      <title>Scanning public health signals: outbreak and drug safety news monitoring</title>
      <dc:creator>Guido Tapia</dc:creator>
      <pubDate>Fri, 07 Aug 2026 00:37:33 +0000</pubDate>
      <link>https://dev.to/gatapia/scanning-public-health-signals-outbreak-and-drug-safety-news-monitoring-56ia</link>
      <guid>https://dev.to/gatapia/scanning-public-health-signals-outbreak-and-drug-safety-news-monitoring-56ia</guid>
      <description>&lt;p&gt;The first post in this series described a daily digest: a scheduled job that reads a wide set of sources, summarises them, and emails one message to one group. It works because nobody is relying on it for anything time critical. If it misses something, a person notices next week.&lt;/p&gt;

&lt;p&gt;Surveillance is a different job with the same plumbing. You are watching a small number of official feeds where almost nothing happens, and the item that matters is buried among a hundred routine ones. Miss the recall notice for a product your pharmacy stocks and the cost is real. Send twelve false alarms a week and people stop reading, which produces the same outcome by a slower route. This post is part of our &lt;a href="https://picnet.com.au/blog/practical-ai-in-health-the-series/" rel="noopener noreferrer"&gt;Practical AI in Health&lt;/a&gt; series, and it covers the version of the digest we build when the output has to be trusted.&lt;/p&gt;

&lt;h2&gt;
  
  
  The feed list is the hard part
&lt;/h2&gt;

&lt;p&gt;Most of the effort in these projects goes into sources, not models. A typical starting list for an Australian health service or aged care provider:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;WHO Disease Outbreak News, for international events that may affect returning travellers or supply chains.&lt;/li&gt;
&lt;li&gt;TGA safety alerts, recalls and medicine shortage notices.&lt;/li&gt;
&lt;li&gt;The TGA's Database of Adverse Event Notifications, if you want to watch specific products over time rather than react to published alerts.&lt;/li&gt;
&lt;li&gt;Communicable disease and health alert pages published by state and territory health departments.&lt;/li&gt;
&lt;li&gt;Sponsor and manufacturer recall notices for the products you actually stock.&lt;/li&gt;
&lt;li&gt;Professional college and peak body bulletins, which often carry practice advice before it reaches a government page.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Some of these publish RSS. Some publish an HTML list that changes structure once a year without warning. A few give you nothing but a page you have to scrape. Budget for that: feed breakage is the main ongoing maintenance cost of a monitor like this, well ahead of anything to do with the model. We use &lt;a href="https://github.com/PicNet/Centazio" rel="noopener noreferrer"&gt;Centazio&lt;/a&gt;, our open source integration platform, for the fetch and normalise layer, mostly so that each source is an independently testable reader rather than a script that quietly dies.&lt;/p&gt;

&lt;h2&gt;
  
  
  Pipeline
&lt;/h2&gt;

&lt;p&gt;The shape is deliberately boring:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Fetch&lt;/strong&gt; each source on a schedule. Hourly is fine for almost everything here. Nothing in this domain moves fast enough to justify polling every minute.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Normalise&lt;/strong&gt; to a common record: source, url, published date, title, body text, retrieved date.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Deduplicate&lt;/strong&gt;. Hash on url, then fuzzy match titles within a rolling window, because the same recall appears on three sites in slightly different wording.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Pre-filter&lt;/strong&gt; cheaply. Keyword and embedding similarity against your own product list, service list and site locations. This throws away the clearly irrelevant before you spend a model call on it.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Classify&lt;/strong&gt; what survives, with a structured output schema.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Route&lt;/strong&gt; to a named owner queue.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Log&lt;/strong&gt; everything, including the items you discarded, with the reason.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That last point matters more than it sounds. The discard log is what lets you answer "why didn't we hear about this?" three months later, and it is the only way to measure the failure mode you cannot see.&lt;/p&gt;

&lt;p&gt;The classifier returns something like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"category"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"medicine_recall"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"relevance"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"act | review | log"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"confidence"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;0.0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"affected_products"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"..."&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"affected_sites"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"..."&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"evidence_quote"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"verbatim sentence from the source"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"source_url"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"https://..."&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;evidence_quote&lt;/code&gt; field is not decoration. Every alert that reaches a human carries the sentence it was based on and a link to the original page, so the reviewer can confirm or dismiss in about ten seconds without opening a browser tab. Summaries that cannot be traced back to a source sentence do not get sent.&lt;/p&gt;

&lt;h2&gt;
  
  
  Two thresholds, not one
&lt;/h2&gt;

&lt;p&gt;The instinct is to tune one threshold until the alerts feel about right. That never settles, because the two errors have different owners. A missed recall is a clinical governance problem. A noisy inbox is an operational one, and the people who suffer are not the people who set the threshold.&lt;/p&gt;

&lt;p&gt;So we grade into three buckets rather than two. Items above the high bar page the owner. Items in the middle land in a review queue that somebody works through once a day. Everything else is logged and searchable. GitHub took the same approach with npm publish time malware scanning, where packages are &lt;a href="https://github.blog/changelog/2026-07-28-npm-publish-time-malware-scanning-and-dual-use-metadata/" rel="noopener noreferrer"&gt;either published, held for manual review, or blocked&lt;/a&gt;, with a separate metadata category for legitimate code that looks malicious to a scanner. Ambiguous hits get labelled and routed instead of being forced into a yes or a no.&lt;/p&gt;

&lt;p&gt;Run the recall side high and you will get odd results. Apple's on device iMessage classifier, tuned to catch nudity, &lt;a href="https://www.404media.co/apples-imessage-scanning-flagged-a-video-of-my-friends-dog-as-nudity/" rel="noopener noreferrer"&gt;flagged a video of a dog lying on its back&lt;/a&gt; as sensitive content. A classifier looking for medicine names in news text will do the equivalent: flag an article about a share price, a sponsor's marketing announcement, an overseas alert for a product with the same trade name and a different formulation. None of those are bugs you can fix with a better prompt. They are what high recall costs, and the review queue is where you pay it.&lt;/p&gt;

&lt;p&gt;Where the thresholds sit is an operational decision, not a technical one. Practitioners tuning scanners in build pipelines end up in the same argument about &lt;a href="https://news.ycombinator.com/item?id=48944189" rel="noopener noreferrer"&gt;how much noise a team will tolerate&lt;/a&gt;. Ask who is on the receiving end and how many items a day they can genuinely triage, then set the numbers to fit that person's capacity.&lt;/p&gt;

&lt;h2&gt;
  
  
  Routing and sign off
&lt;/h2&gt;

&lt;p&gt;Category maps to owner. Medicine recalls to pharmacy. Device recalls to biomedical engineering or procurement. Communicable disease alerts to infection prevention. Anything unmatched goes to a named fallback owner, never to a shared mailbox that belongs to nobody.&lt;/p&gt;

&lt;p&gt;Two design requirements we hold to on this kind of build. First, the system never contacts a patient, a clinician or a ward directly. It drafts and it routes. A named person reads the source, decides, and sends. Second, nothing the model produces is treated as a clinical judgement. This is administrative monitoring: has something we hold, dispense or use been the subject of an official notice. Whether that changes anyone's care is a decision for the clinician who signs off, and the workflow is built so it cannot be skipped rather than warned against in a policy document.&lt;/p&gt;

&lt;p&gt;One useful consequence of the design: the inputs are all public web pages. No patient information enters this pipeline, which removes most of the privacy work that dominates other health AI projects and makes it a reasonable first build for an organisation that has not run one before.&lt;/p&gt;

&lt;h2&gt;
  
  
  Measuring it
&lt;/h2&gt;

&lt;p&gt;Build a small gold set before you go live. Twenty or thirty past notices your team should have acted on, plus a hundred or so items they should have ignored, all with the correct answer recorded. Replay it whenever you change a prompt, a threshold or a model version. It takes a couple of days to assemble and it is the only thing standing between you and a monitor whose accuracy nobody can describe.&lt;/p&gt;

&lt;p&gt;Then sample the discard log weekly. Twenty random items, read by a human, looking for anything that should have surfaced. False positives announce themselves. False negatives only appear if you go looking.&lt;/p&gt;

&lt;h2&gt;
  
  
  Costs and limits
&lt;/h2&gt;

&lt;p&gt;Inference is the cheap part. A pre-filter that drops most items before the model sees them keeps volume in the tens or low hundreds of classifications a day, and the model bill sits well below the cost of the engineer maintaining the source readers. Expect the real budget to go on the initial source mapping, the gold set, and the ongoing review time you are asking somebody to spend.&lt;/p&gt;

&lt;p&gt;The limits are worth stating plainly. This monitors published notices, so it is always downstream of whoever publishes. It cannot tell you about an emerging problem nobody has written up. It will misread ambiguous product names, and it degrades quietly when a source changes its page layout, which is why every reader needs a heartbeat check that alerts when a feed goes silent for longer than usual. Silence from a feed is not the same as good news.&lt;/p&gt;

&lt;p&gt;PicNet builds production AI systems for Australian organisations. &lt;a href="https://picnet.com.au/ai-services/" rel="noopener noreferrer"&gt;Talk to us&lt;/a&gt; about what a first project could look like.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Originally published at &lt;a href="https://picnet.com.au/blog/scanning-public-health-signals-outbreak-and-drug-safety-news-monitoring/" rel="noopener noreferrer"&gt;picnet.com.au&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>healthcare</category>
      <category>publichealthsurveillance</category>
      <category>drugsafety</category>
    </item>
    <item>
      <title>A policy and procedure chat assistant for health organisations</title>
      <dc:creator>Guido Tapia</dc:creator>
      <pubDate>Wed, 05 Aug 2026 04:45:38 +0000</pubDate>
      <link>https://dev.to/gatapia/a-policy-and-procedure-chat-assistant-for-health-organisations-1549</link>
      <guid>https://dev.to/gatapia/a-policy-and-procedure-chat-assistant-for-health-organisations-1549</guid>
      <description>&lt;p&gt;Most health organisations have somewhere between a few hundred and a few thousand internal documents that tell staff how to do things. Consent forms. Incident escalation. Rostering rules. Infection control procedures. Which form goes to which committee, and by when. The documents exist, they are mostly current, and almost nobody can find the right one in under five minutes. So people ring the ward clerk, or the quality manager, or whoever has been there longest, and that person answers the same twelve questions every week.&lt;/p&gt;

&lt;p&gt;This is the least glamorous AI use case in health and probably the best first one. It sits entirely on the administrative side, the source material is text you already own and control, and when it goes wrong the failure is visible rather than silent. This post is part of our &lt;a href="https://picnet.com.au/blog/practical-ai-in-health-the-series/" rel="noopener noreferrer"&gt;Practical AI in Health&lt;/a&gt; series, and it covers what we actually build for this: retrieval-augmented generation over an internal document set, with citations, hard refusals, and an evaluation you run before anyone outside the project team touches it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Scope it narrowly, on purpose
&lt;/h2&gt;

&lt;p&gt;The first design decision is what the assistant will not answer, and it is a compliance decision rather than a product one.&lt;/p&gt;

&lt;p&gt;The TGA's &lt;a href="https://www.tga.gov.au/resources/guidance/understanding-clinical-decision-support-system-software-regulation" rel="noopener noreferrer"&gt;guidance on clinical decision support software&lt;/a&gt; sets a three-part exemption test in Schedule 4 Part 2 of the Therapeutic Goods (Medical Devices) Regulations 2002, in force since 25 February 2021. Software has to only support recommendations to a health professional, must not process medical images or signals from other devices, and must not replace clinical judgment. All three have to be met, and the TGA is explicit that calling something "decision support" does not make it exempt. Even where the exemption applies, sponsors must notify the TGA within 30 working days of supply, meet the Essential Principles, and report adverse events.&lt;/p&gt;

&lt;p&gt;A chatbot that answers "what is our procedure for documenting a medication error" is an administrative tool. The same chatbot, if it starts answering "what dose should I give," has moved into a different regulatory conversation. So the refusal behaviour is not politeness. It is the control that keeps the system inside the scope you scoped it for. Where an answer touches anything clinically adjacent, the design requirement is that a named human signs off before the answer is acted on, and the interface says so at the point of use rather than in a footer.&lt;/p&gt;

&lt;p&gt;On the broader regulatory picture, the Commonwealth's &lt;a href="https://www.health.gov.au/resources/publications/safe-and-responsible-artificial-intelligence-in-health-care-legislation-and-regulation-review-final-report" rel="noopener noreferrer"&gt;Safe and Responsible AI in Health Care review&lt;/a&gt; (final report, 2025, drawing on 69 written submissions) concluded that existing health portfolio legislation can largely accommodate AI with minor and technical amendments. There is no new AI act to comply with. Your obligations are the ones you already have around privacy, records and clinical governance.&lt;/p&gt;

&lt;h2&gt;
  
  
  The architecture
&lt;/h2&gt;

&lt;p&gt;The shape of the system is unremarkable, which is the point:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Ingestion.&lt;/strong&gt; Pull documents from wherever they live: SharePoint, the intranet, a policy management system. Keep the document ID, version, owner, approval date and review date as metadata on every chunk.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Chunking.&lt;/strong&gt; Split on document structure, not fixed token counts. Policy documents have numbered clauses and headings, and those boundaries are what a person will want to be pointed at.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Embedding and index.&lt;/strong&gt; A vector store with metadata filters, so you can restrict retrieval to current versions and to the document sets a given role is allowed to see.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Retrieval.&lt;/strong&gt; Hybrid search (dense vectors plus keyword) usually beats either alone on policy text, because staff use exact form numbers and internal acronyms.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Generation.&lt;/strong&gt; A prompt that instructs the model to answer only from retrieved chunks, to quote the clause, and to say it does not know when the retrieved text does not contain the answer.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Citation rendering.&lt;/strong&gt; Every claim in the answer links to a document, a version and a section, so the reader can open the source in one click.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Logging.&lt;/strong&gt; Question, retrieved chunk IDs, answer, citations, user feedback. This is your audit trail and your evaluation data.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A chunk record looks roughly like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"chunk_id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"POL-CG-014-v3.2#4.1.2"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"doc_id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"POL-CG-014"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"title"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Open Disclosure Policy"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"version"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"3.2"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"approved"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2025-11-04"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"review_due"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2027-11-04"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"section"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"4.1.2 Notifying the patient"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"audience"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"clinical"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"admin"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"text"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"..."&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Carrying &lt;code&gt;review_due&lt;/code&gt; through to the answer matters more than it looks. An assistant that confidently quotes a policy which expired eight months ago is worse than a search box, because it has removed the moment where the reader would have noticed the date on the cover page.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why RAG rather than fine-tuning
&lt;/h2&gt;

&lt;p&gt;We get asked this on nearly every engagement, usually phrased as "can we just train the model on our policies."&lt;/p&gt;

&lt;p&gt;Ovadia et al. tested exactly that comparison at EMNLP 2024, putting a base model, unsupervised fine-tuning, RAG, and fine-tuning plus RAG against knowledge-intensive tasks. &lt;a href="https://aclanthology.org/2024.emnlp-main.15/" rel="noopener noreferrer"&gt;RAG consistently outperformed fine-tuning&lt;/a&gt; for both existing and entirely new knowledge, and combining the two did not reliably beat RAG on its own.&lt;/p&gt;

&lt;p&gt;The practical arguments are stronger than the benchmark one. Fine-tuning bakes knowledge into weights, so it cannot tell you which document an answer came from, and you cannot cite what you cannot locate. When a policy is revised, RAG needs a re-index and fine-tuning needs a retraining cycle. And when a policy is withdrawn, RAG deletes the chunk while fine-tuning leaves the old text somewhere in the weights with no reliable way to remove it.&lt;/p&gt;

&lt;p&gt;Retrieval quality is where accuracy is won. A published RAG chatbot over hospital EMR manuals, the closest analogue to an internal policy assistant we have seen in the literature, built a &lt;a href="https://pmc.ncbi.nlm.nih.gov/articles/PMC12370418/" rel="noopener noreferrer"&gt;5,931 question-document evaluation set and raised top-k retrieval accuracy to 97.6% by fine-tuning the embedding model&lt;/a&gt; rather than changing the LLM.&lt;/p&gt;

&lt;h2&gt;
  
  
  Citations are the safety mechanism, and they can also lie
&lt;/h2&gt;

&lt;p&gt;"We use RAG" is not a safety claim. Stanford RegLab's preregistered study of commercial legal research tools, published in the Journal of Empirical Legal Studies in 2025, hand-scored 202 queries and &lt;a href="https://reglab.stanford.edu/publications/hallucination-free-assessing-the-reliability-of-leading-ai-legal-research-tools/" rel="noopener noreferrer"&gt;found hallucination rates of roughly 17% for Lexis+ AI and 33% for Westlaw AI-Assisted Research, against 43% for GPT-4&lt;/a&gt;. RAG helped. It did not eliminate the problem, despite vendors advertising "hallucination-free" citations.&lt;/p&gt;

&lt;p&gt;The finding that should change your design is the one about citation hallucination: an answer that cites a real but wrong source, which the authors argue may be even more pernicious than outright invention, because the presence of a reference is what makes reviewers stop checking. That is the same trap the Australian Commission on Safety and Quality in Health Care describes as automation bias, with &lt;a href="https://www.safetyandquality.gov.au/resources/ai-clinical-use-guide" rel="noopener noreferrer"&gt;errors of commission (acting on an incorrect recommendation) and errors of omission (failing to act when the tool misses something)&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;So we score citation correctness separately from answer correctness, and we show the quoted clause text inline rather than a bare reference number. If the reader can see the sentence the answer came from, checking costs three seconds instead of three minutes.&lt;/p&gt;

&lt;h2&gt;
  
  
  Evaluation before rollout
&lt;/h2&gt;

&lt;p&gt;Do not launch on vibes and a demo to the executive team. The evaluation design we use follows a published 2025 RAG deployment: build a test set of around 100 questions with ground-truth answers reviewed by two subject matter experts, then &lt;a href="https://ai.jmir.org/2025/1/e75262" rel="noopener noreferrer"&gt;score with RAGAS metrics that separate retrieval failure (context precision, context recall) from generation failure (faithfulness, answer relevancy)&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;The separation is what makes the numbers actionable. Low faithfulness with good retrieval means the model is inventing and you fix the prompt or the model. Low context recall means the policy was never retrieved and you fix chunking, embeddings or the index. Add two categories your test set will not otherwise cover: out-of-scope questions that must be refused, and questions whose answer sits in a superseded document, where the correct behaviour is to cite the current version.&lt;/p&gt;

&lt;p&gt;The Commission's guide is structured around "before you use", "while you use" and "after you use", which maps neatly onto a rollout plan: evaluation gate, supervised pilot with feedback capture, then periodic re-evaluation as documents change.&lt;/p&gt;

&lt;h2&gt;
  
  
  Privacy, hosting and cost
&lt;/h2&gt;

&lt;p&gt;The OAIC's October 2024 guidance treats any organisation using a commercial AI product, &lt;a href="https://www.oaic.gov.au/privacy/privacy-guidance-for-organisations-and-government-agencies/guidance-on-privacy-and-the-use-of-commercially-available-ai-products" rel="noopener noreferrer"&gt;including purely internally, as a "deployer" with obligations under the Privacy Act 1988&lt;/a&gt; and the 13 Australian Privacy Principles. Policy documents are usually not personal information, but the query logs can be, because "what do I do if I made this error" is a question about a person.&lt;/p&gt;

&lt;p&gt;That argues for keeping the index and the logs in Australian-hosted, access-controlled infrastructure, with data-retention terms in the model provider contract that prohibit training on your traffic. The risk context is not abstract: health service providers were the most-breached sector in Australia in calendar 2025, with &lt;a href="https://www.oaic.gov.au/news/media-centre/data-breach-notifications-increase-to-all-time-high,-new-ndb-stats-show" rel="noopener noreferrer"&gt;225 of 1,205 notifications, ahead of finance on 157&lt;/a&gt;, and total notifications hit an all-time high.&lt;/p&gt;

&lt;p&gt;On cost, the honest version: for a corpus in the low thousands of documents, inference and vector storage are usually a few hundred dollars a month, and sometimes less. The real spend is the work around it. Getting a clean, versioned document set out of SharePoint, building the evaluation questions with your subject matter experts, and running the pilot will dominate the budget. Expect a first build to be a matter of weeks rather than months, and expect the document cleanup to take longer than the software.&lt;/p&gt;

&lt;p&gt;The limitation worth naming up front: this system is only as current as your policy library. If half your documents are past their review date, an assistant will surface that problem to everyone at once. Several clients have found that useful. A few have found it uncomfortable.&lt;/p&gt;

&lt;p&gt;PicNet builds production AI systems for Australian organisations. &lt;a href="https://picnet.com.au/ai-services/" rel="noopener noreferrer"&gt;Talk to us&lt;/a&gt; about what a first project could look like.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Originally published at &lt;a href="https://picnet.com.au/blog/a-policy-and-procedure-chat-assistant-rag-for-health-organisations/" rel="noopener noreferrer"&gt;picnet.com.au&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>healthcare</category>
      <category>rag</category>
      <category>policydocuments</category>
    </item>
    <item>
      <title>Where deterministic matching ends: cleaning and matching patient records with AI</title>
      <dc:creator>Guido Tapia</dc:creator>
      <pubDate>Wed, 05 Aug 2026 04:45:34 +0000</pubDate>
      <link>https://dev.to/gatapia/where-deterministic-matching-ends-cleaning-and-matching-patient-records-with-ai-2o75</link>
      <guid>https://dev.to/gatapia/where-deterministic-matching-ends-cleaning-and-matching-patient-records-with-ai-2o75</guid>
      <description>&lt;p&gt;Every organisation that runs more than one system eventually holds the same person several times over. A referral arrives with a shortened first name and no date of birth. The patient administration system has them at their old address. The billing system has a typo from a phone booking in 2019. Someone in reception creates a new record because search returned nothing useful, and now there are four.&lt;/p&gt;

&lt;p&gt;This post is part of the &lt;a href="https://picnet.com.au/blog/practical-ai-in-health-the-series/" rel="noopener noreferrer"&gt;Practical AI in Health&lt;/a&gt; series, and it covers the least glamorous work in the whole series: getting records to agree on who a person is before you build anything on top of them.&lt;/p&gt;

&lt;h2&gt;
  
  
  Matching is an accreditation obligation, not just data hygiene
&lt;/h2&gt;

&lt;p&gt;NSQHS Standard 6 requires health service organisations to use at least three approved patient identifiers at registration, at every point of care, and on handover, transfer and discharge documents. Room number, bed number and diagnosis are explicitly not approved identifiers. Action 6.6 goes further and requires organisations to document the processes used to match patients to their intended care (&lt;a href="https://www.safetyandquality.gov.au/standards/nsqhs-standards/communicating-safety-standard/correct-identification-and-procedure-matching" rel="noopener noreferrer"&gt;ACSQHC&lt;/a&gt;).&lt;/p&gt;

&lt;p&gt;That last point changes the engineering. Your matching design is an artefact an assessor can ask to see. If the answer is "the vendor's algorithm does it", you have a documentation problem as well as a data problem.&lt;/p&gt;

&lt;h2&gt;
  
  
  Start with the identifiers that actually hold
&lt;/h2&gt;

&lt;p&gt;The Individual Healthcare Identifier is a 16-digit number assigned automatically to everyone enrolled in Medicare or with DVA, and it stays with the person for life. A Medicare number does not. A person can hold several over a lifetime, which is exactly why so many systems that keyed on it have duplicates (&lt;a href="https://www.digitalhealth.gov.au/healthcare-providers/initiatives-and-programs/healthcare-identifiers" rel="noopener noreferrer"&gt;Australian Digital Health Agency&lt;/a&gt;).&lt;/p&gt;

&lt;p&gt;The IHI is not a free pass either. The ADHA's &lt;a href="https://www.digitalhealth.gov.au/sites/default/files/documents/national-healthcare-identifiers-roadmap-2023-2028-v1.1.pdf" rel="noopener noreferrer"&gt;National Healthcare Identifiers Roadmap 2023-2028&lt;/a&gt; names the defects it is working through, including retired IHI records turning up in search results, failed message searches, identifier status flags that do not do their job, and poor matching outcomes for Aboriginal and Torres Strait Islander peoples. Design as though a returned identifier can be stale.&lt;/p&gt;

&lt;h2&gt;
  
  
  The deterministic layer does most of the work
&lt;/h2&gt;

&lt;p&gt;Most duplicate pairs are boring and resolvable by rules. Normalise first, then block, then compare. Something like this, running inside the integration layer rather than in any one source system:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="c1"&gt;// 1. Trusted identifier match: auto-resolve, no review needed.&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;HasValue&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Ihi&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Ihi&lt;/span&gt; &lt;span class="p"&gt;==&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Ihi&lt;/span&gt; &lt;span class="p"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;IhiStatus&lt;/span&gt; &lt;span class="p"&gt;==&lt;/span&gt; &lt;span class="n"&gt;Active&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;Verdict&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Merge&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;// 2. Normalised composite: strip punctuation, fold case, standardise&lt;/span&gt;
&lt;span class="c1"&gt;//    address via a locality/postcode lookup, keep nickname expansions.&lt;/span&gt;
&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;ka&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;Key&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;kb&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;Key&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;           &lt;span class="c1"&gt;// (soundex(family), dob, postcode)&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ka&lt;/span&gt; &lt;span class="p"&gt;==&lt;/span&gt; &lt;span class="n"&gt;kb&lt;/span&gt; &lt;span class="p"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nf"&gt;SimilarGiven&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="m"&gt;0.9&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;Verdict&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Merge&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;// 3. Everything scoring between the thresholds goes to the grey-zone queue.&lt;/span&gt;
&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;score&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;Weighted&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;score&lt;/span&gt; &lt;span class="p"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="n"&gt;AutoThreshold&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="n"&gt;Verdict&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Merge&lt;/span&gt;
     &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;score&lt;/span&gt; &lt;span class="p"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="n"&gt;RejectThreshold&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="n"&gt;Verdict&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Distinct&lt;/span&gt;
     &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Verdict&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Review&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Tune the thresholds so the deterministic layer never merges on a guess. That leaves a queue, and the queue is where the interesting work sits.&lt;/p&gt;

&lt;h2&gt;
  
  
  What the grey zone looks like
&lt;/h2&gt;

&lt;p&gt;A published comparison of real matching approaches gives useful numbers. Probabilistic matching returned sensitivity of 0.6366 with positive predictive value of 0.9995. Referential matching returned 0.9351 sensitivity at 0.9996 PPV (&lt;a href="https://www.ncbi.nlm.nih.gov/pmc/articles/PMC9277641/" rel="noopener noreferrer"&gt;Grannis et al., JAMIA 2022&lt;/a&gt;). Read those two columns together. Tuned matching almost never merges two different people. What it does is miss matches, and a third of them in the probabilistic case.&lt;/p&gt;

&lt;p&gt;Missed matches are duplicates. Duplicates mean a clinician sees part of a history, a recall letter goes to one record and not the other, and your reporting counts one person as two.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where an LLM earns its place
&lt;/h2&gt;

&lt;p&gt;The candidate pairs that survive the deterministic layer are usually hard for a reason a human can articulate. Transposed given and family names in a culturally diverse patient base. A married name in one system and a maiden name in another with the same date of birth. Two siblings at one address with dates of birth six days apart in different years. A mononymous person whose single name the source system split across two fields, which the HL7 identity matching guidance says should be submitted in the last-name field (&lt;a href="https://build.fhir.org/ig/HL7/fhir-identity-matching-ig/patient-matching.html" rel="noopener noreferrer"&gt;HL7 Interoperable Digital Identity and Patient Matching IG&lt;/a&gt;).&lt;/p&gt;

&lt;p&gt;An LLM is good at exactly this narrow adjudication: read two records, weigh the evidence, and explain the reasoning in a sentence a reviewer can accept or reject. Current research is worth reading before you build it, because the framing of the task changes the results as much as the choice of model. Pairwise match, comparison and selection formulations all perform differently (&lt;a href="https://arxiv.org/pdf/2405.16884" rel="noopener noreferrer"&gt;Wang et al., COLING 2025&lt;/a&gt;).&lt;/p&gt;

&lt;p&gt;Two design requirements we treat as non-negotiable. The model never writes a merge. It ranks the queue and drafts the justification, and a named human approves each merge, with the decision and the reasoning stored against the golden record. And every merge is reversible, because you will be unmerging some of them.&lt;/p&gt;

&lt;h2&gt;
  
  
  Field completeness beats algorithm choice
&lt;/h2&gt;

&lt;p&gt;The AIHW's COVID-19 linked data set achieved over 90 per cent linkage in every jurisdiction, with Tasmania highest at 99 per cent and Victoria carrying the largest volume at 2,536,790 individuals. The Northern Territory lagged, and the stated reason was limited address information (&lt;a href="https://www.aihw.gov.au/reports/covid-19/demonstrating-the-utility-of-covid-19-linked-data/contents/initial-linkage-findings" rel="noopener noreferrer"&gt;AIHW&lt;/a&gt;). No model fixes an empty field.&lt;/p&gt;

&lt;p&gt;The same analysis found people aged 70 and over had the highest proportion of unlinked records at 9.9 per cent, and linkage fell where sex was recorded inconsistently or as 'Other'. Matching failure lands unevenly on older patients and on people whose demographics do not fit a two-value field. Report your match rates by cohort, not just in aggregate, or you will never see it.&lt;/p&gt;

&lt;h2&gt;
  
  
  The integration architecture
&lt;/h2&gt;

&lt;p&gt;We build this as a layer beside the source systems, not inside them, using &lt;a href="https://picnet.com.au/software-engineering/" rel="noopener noreferrer"&gt;Centazio&lt;/a&gt;, our MIT-licensed C# integration and master data platform.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Sources publish changes into Centazio, which handles fault tolerance and near real time delivery into a central database.&lt;/li&gt;
&lt;li&gt;Records land in a canonical Patient shape. &lt;a href="https://hl7.org.au/fhir/core/1.0.0/index.html" rel="noopener noreferrer"&gt;AU Core v1.0.0&lt;/a&gt;, built on FHIR R4 by the CSIRO-led Sparked accelerator, is the sensible target. Note its limits: Patient carries historical names and addresses but only a single birthDate.&lt;/li&gt;
&lt;li&gt;Deterministic rules run on write, producing merges, rejections and a review queue.&lt;/li&gt;
&lt;li&gt;The LLM adjudicator scores and explains the queue. Humans decide.&lt;/li&gt;
&lt;li&gt;The golden record keeps every source identifier as a cross reference, so nothing is destroyed and every merge can be walked back.&lt;/li&gt;
&lt;li&gt;Serve matches out via the FHIR &lt;code&gt;Patient/$match&lt;/code&gt; operation, which returns a scored bundle with a match grade. The base specification deliberately mandates no algorithm and no minimum input data set, so the accuracy is entirely yours to own and document.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Centazio came out of this kind of work. At the Cruising Yacht Club of Australia an initial cross-system reporting dashboard turned into a five-year transformation plan with the systems integrated through Centazio, and it runs in similar integration roles for clients including Guide Dogs NSW/ACT.&lt;/p&gt;

&lt;h2&gt;
  
  
  Costs, limits and the compliance clock
&lt;/h2&gt;

&lt;p&gt;Token cost is not the expensive part, because the LLM only sees the grey zone and the grey zone is small. Reviewer time is the real budget line, so size the queue before you promise anyone a timeline. Expect a heavy first pass on historical data and a much lighter steady state.&lt;/p&gt;

&lt;p&gt;There is also a date to plan for. The Privacy and Other Legislation Amendment Act 2024 inserts APP 1.7, requiring privacy policies to disclose automated decision making that affects people's rights or interests, commencing 10 December 2026, with infringement notices of up to $66,000 per contravention (&lt;a href="https://www.minterellison.com/articles/privacy-and-other-legislation-amendment-act-2024-now-in-effect" rel="noopener noreferrer"&gt;MinterEllison&lt;/a&gt;). An AI-assisted decision about whether two records are the same person sits inside that.&lt;/p&gt;

&lt;p&gt;Leaving the duplicates alone is not the safe option either. Health service providers were the most notified sector in the OAIC's 2025 breach statistics with 225 notifications, 19 per cent of a record 1,205 total, and human error drove 37 per cent of breaches in the January to June period (&lt;a href="https://www.oaic.gov.au/news/media-centre/data-breach-notifications-increase-to-all-time-high,-new-ndb-stats-show" rel="noopener noreferrer"&gt;OAIC&lt;/a&gt;). Fragmented records mean more places to send the wrong document to the wrong person.&lt;/p&gt;

&lt;p&gt;Start by measuring. Run your deterministic rules over a copy of production, count the auto-merges, count the grey zone, and have two experienced staff review a sample of 200 pairs. That gives you a baseline, a queue size and a training set, and it takes a couple of weeks rather than a couple of quarters.&lt;/p&gt;

&lt;p&gt;PicNet builds production AI systems for Australian organisations. &lt;a href="https://picnet.com.au/ai-services/" rel="noopener noreferrer"&gt;Talk to us&lt;/a&gt; about what a first project could look like.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Originally published at &lt;a href="https://picnet.com.au/blog/cleaning-and-matching-patient-and-customer-records-with-ai-and-integration-tooling/" rel="noopener noreferrer"&gt;picnet.com.au&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>healthcare</category>
      <category>entityresolution</category>
      <category>dataintegration</category>
    </item>
    <item>
      <title>From meeting audio to structured minutes in health settings</title>
      <dc:creator>Guido Tapia</dc:creator>
      <pubDate>Wed, 05 Aug 2026 04:39:27 +0000</pubDate>
      <link>https://dev.to/gatapia/from-meeting-audio-to-structured-minutes-in-health-settings-1kp5</link>
      <guid>https://dev.to/gatapia/from-meeting-audio-to-structured-minutes-in-health-settings-1kp5</guid>
      <description>&lt;p&gt;Most health services we talk to are drowning in meetings before they get anywhere near a patient. Clinical governance committees, quality and safety, credentialling, infection control, drug and therapeutics, plus multidisciplinary case conferences that can run through twenty patients in ninety minutes. Someone in the room is taking notes on a laptop while also trying to participate. The minutes land three weeks later, if they land at all, and the action items nobody wrote down quietly disappear.&lt;/p&gt;

&lt;p&gt;This is one of the better places to put AI in a health organisation, mostly because the failure modes are containable. It is also a place where teams get into trouble quickly if they treat it as a software purchase rather than a records and consent problem. This post is part of our &lt;a href="https://picnet.com.au/blog/practical-ai-in-health-the-series/" rel="noopener noreferrer"&gt;Practical AI in Health&lt;/a&gt; series and covers the version we would actually build: transcription plus structured summaries for committees and case conferences, with the clinical record left well alone.&lt;/p&gt;

&lt;h2&gt;
  
  
  Scope: minutes, not notes
&lt;/h2&gt;

&lt;p&gt;Draw the line early and write it into the design. The system produces meeting records: attendance, agenda items, decisions, actions and owners. It does not write clinical notes, does not go near a patient's medical record, and does not generate anything that reads as advice.&lt;/p&gt;

&lt;p&gt;That line has a regulatory meaning, not just a philosophical one. The TGA's position is that a digital scribe intended only to transcribe and translate conversations into written records is not a medical device, but one that &lt;a href="https://www.tga.gov.au/products/medical-devices/software-and-artificial-intelligence-ai/overview/types-software-based-medical-devices/digital-scribes" rel="noopener noreferrer"&gt;analyses or interprets a clinical conversation&lt;/a&gt;, for example by generating a diagnosis or a treatment recommendation the clinician did not state, is a medical device and must be in the ARTG before it is supplied. The Safety and Quality Commission makes the same point in its &lt;a href="https://www.safetyandquality.gov.au/sites/default/files/resources/additional/ai-safety-scenario-ambient-scribe.pdf" rel="noopener noreferrer"&gt;ambient scribe safety scenario&lt;/a&gt;, noting that a tool suggesting a diagnosis can trigger ARTG requirements even when that was never the developer's intent.&lt;/p&gt;

&lt;p&gt;For minuting, the practical consequence is a hard prompt and schema constraint. The model records what the chair declared, in the chair's words. If the meeting decided to refer a patient to radiation oncology, the minute says that. It never adds the reasoning nobody voiced, and it never fills a blank decision field with something plausible.&lt;/p&gt;

&lt;h2&gt;
  
  
  The pipeline
&lt;/h2&gt;

&lt;p&gt;The architecture is not exotic, which is the point.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Capture: a room device or a Teams recording, one file per meeting, uploaded to a storage account in an Australian region.&lt;/li&gt;
&lt;li&gt;Diarisation and transcription: speaker separation first, then automatic speech recognition. Output is a timestamped transcript with speaker labels.&lt;/li&gt;
&lt;li&gt;Speaker mapping: the chair or secretary maps "Speaker 3" to a real name once, at the start of the review. Voice fingerprints stored across meetings sound efficient and create a biometric data problem you do not want.&lt;/li&gt;
&lt;li&gt;Structured extraction: a language model fills a fixed schema, not free prose. Anything it cannot find stays null.&lt;/li&gt;
&lt;li&gt;Review queue: a named reviewer edits and approves before anything is circulated.&lt;/li&gt;
&lt;li&gt;Publication and retention: approved minutes go to the document management system, the raw audio goes to a deletion schedule.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The schema is what makes the output useful rather than merely readable:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"meeting"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Cancer MDT - Colorectal"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"date"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2026-07-14"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"chair"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Dr A Nguyen"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"attendees"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"..."&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"apologies"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"..."&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"quorum_met"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"items"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"case_reference"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"MRN-XXXXXX"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"discussion_summary"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"..."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"decision"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"..."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"actions"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"action"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"..."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"owner"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"..."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"due"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2026-07-28"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}],&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"communication_to_gp"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"pending"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"confidence"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"low|medium|high"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"review_status"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"draft"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That structure maps onto what Cancer Australia expects multidisciplinary teams to record: &lt;a href="https://www.canceraustralia.gov.au/clinicians-hub/multidisciplinary-care/all-about-multidisciplinary-care/running-multidisciplinary-care-meeting" rel="noopener noreferrer"&gt;attendance, meeting frequency and the decisions made&lt;/a&gt;, along with a defined process for communicating outcomes and recommendations to the patient and their GP. Once the data is structured you get accreditation evidence and overdue action reports for free, which is usually the part that sells the project internally.&lt;/p&gt;

&lt;h2&gt;
  
  
  Consent is a state by state problem
&lt;/h2&gt;

&lt;p&gt;There is no single national rule for recording a conversation. In NSW, section 7 of the &lt;a href="https://legislation.nsw.gov.au/view/whole/html/inforce/current/act-2007-064" rel="noopener noreferrer"&gt;Surveillance Devices Act 2007&lt;/a&gt; makes it an offence to record a private conversation without the consent of all principal parties, and WA, SA, Tasmania and the ACT take a similar approach. Victoria, Queensland and the NT allow a participant to record but still penalise passing the recording on without consent. If you run services in more than one state, build for the strictest rule and use an announce and record script at the top of every meeting, captured in the minutes themselves.&lt;/p&gt;

&lt;p&gt;Privacy sits on top of that. The OAIC's guidance on &lt;a href="https://www.oaic.gov.au/privacy/privacy-guidance-for-organisations-and-government-agencies/guidance-on-privacy-and-the-use-of-commercially-available-ai-products" rel="noopener noreferrer"&gt;commercially available AI products&lt;/a&gt; names note taking and transcription assistants specifically, confirms the Privacy Act applies to both inputs and outputs, and tells deploying organisations to run a privacy impact assessment and check that vendor terms do not conflict with the consents they actually hold. Safer Care Victoria's &lt;a href="https://www.safercare.vic.gov.au/sites/default/files/2025-07/Ambient%20AI%20Scribes%20Advisory.pdf" rel="noopener noreferrer"&gt;ambient AI scribes advisory&lt;/a&gt; goes further and requires health services to update their privacy and health information management policies to match the specific product implemented. That is a policy job with a due date, not a paragraph in a business case.&lt;/p&gt;

&lt;h2&gt;
  
  
  Accuracy: plan against the right number
&lt;/h2&gt;

&lt;p&gt;Vendors quote word error rates from clean single speaker audio. MLCommons' 2025 MLPerf inference benchmark used Whisper large-v3 with a &lt;a href="https://mlcommons.org/2025/09/whisper-inferencev5-1/" rel="noopener noreferrer"&gt;reference word accuracy of 97.93% on LibriSpeech&lt;/a&gt;. A committee room with crosstalk, a speakerphone, mixed accents and someone unwrapping a muesli bar is a different problem, and 8 to 20% WER is the honest planning range.&lt;/p&gt;

&lt;p&gt;Raw error rate is not the dangerous part. Koenecke and colleagues found that roughly &lt;a href="https://arxiv.org/abs/2402.08021" rel="noopener noreferrer"&gt;1% of Whisper transcriptions contained entirely hallucinated phrases&lt;/a&gt; that did not exist in the audio at all, and 38% of those included explicit harms such as invented associations or implied authority. Summarisation adds its own layer. In the Gold Coast Hospital and Health Service trial of ambient scribe technology across 100+ clinicians and 21 specialties, an average of only &lt;a href="https://link.springer.com/article/10.1186/s12913-025-13954-5" rel="noopener noreferrer"&gt;58% of outputs were accepted without modification&lt;/a&gt;, with some evidence of hallucination and incorrect outputs. Four in ten needed a human to change something.&lt;/p&gt;

&lt;p&gt;Tell your committee chairs that number before go live. A tool that saves 60% of the typing and needs 100% of the reading is still worth having, but only if people expect to read.&lt;/p&gt;

&lt;h2&gt;
  
  
  The review step is the product
&lt;/h2&gt;

&lt;p&gt;Everything above is plumbing. The control that matters is a named person who reviews, edits and approves each set of minutes before they are circulated, which mirrors the accountability model the RACGP set out for &lt;a href="https://www.pulseit.news/australian-digital-health/racgp-issues-guidance-on-ai-scribes-in-practice/" rel="noopener noreferrer"&gt;AI scribes in general practice&lt;/a&gt;: explicit consent, a policy before adoption, vendor assurances on encryption, storage and destruction, and a human sign off on every output.&lt;/p&gt;

&lt;p&gt;Build it so the reviewer cannot skip. Minutes stay in draft until approved and cannot be exported. The reviewer sees the transcript segment beside each extracted decision, with a timestamp so they can jump to the audio. Low confidence fields are flagged for attention. Approved documents carry a label recording that AI was used in their creation, which the Commission asks for and which matters if a set of minutes is ever produced in a coronial or complaints process. Case conference minutes in particular carry clinical weight even though the tool is administrative, so the chair signs them, not the secretary and not the software.&lt;/p&gt;

&lt;h2&gt;
  
  
  Retention, cost and the boring parts
&lt;/h2&gt;

&lt;p&gt;Retention is not one number. In NSW, committee records sit under &lt;a href="https://www.nsw.gov.au/nsw-government/recordkeeping/records-retention-and-disposal-authorities/administrative-records-ga28/committees" rel="noopener noreferrer"&gt;GA28&lt;/a&gt;, with retention varying by committee type, formal board and sub-committee records treated differently, and the secretary holding the master set while other members hold duplicates only. Public health organisations also need to check GDA21 and their patient records authority. Audio is the easier decision: treat it as working material, delete it on a short fixed schedule after approval, and say so in the consent script.&lt;/p&gt;

&lt;p&gt;On cost, transcription compute is the small line. Reviewer time and the policy work are the real budget, along with the contingency plan the AIDH information sheet asks for, covering what the committee does on the day the tool is unavailable. AIDH also advises choosing healthcare specific tools trained on Australian data rather than generic products, released in a period when &lt;a href="https://digitalhealth.org.au/wp-content/uploads/2025/07/Implementation_AI-scribes-in-healthcare-workflows.pdf" rel="noopener noreferrer"&gt;SA Health had banned open source AI scribes in its public hospitals&lt;/a&gt; over privacy concerns.&lt;/p&gt;

&lt;p&gt;Start with one committee that already has a template and a secretary who is sick of typing. Run it in parallel with the current process for six meetings, measure how much editing each set of minutes takes, and only then decide whether case conferences are next.&lt;/p&gt;

&lt;p&gt;PicNet builds production AI systems for Australian organisations. &lt;a href="https://picnet.com.au/ai-services/" rel="noopener noreferrer"&gt;Talk to us&lt;/a&gt; about what a first project could look like.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Originally published at &lt;a href="https://picnet.com.au/blog/from-meeting-audio-to-structured-minutes-in-health-settings/" rel="noopener noreferrer"&gt;picnet.com.au&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>healthcare</category>
      <category>meetingtranscription</category>
      <category>speechtotext</category>
    </item>
  </channel>
</rss>
