Integration · data model
Nine objects. The interesting part of this page is not what they contain but what they refuse to contain, and why those refusals are enforced by code that throws rather than by a policy document nobody reads.
A claims platform usually models everything a claim is. This one models only what is needed to answer two questions - what do we owe this customer by when, and do these documents agree - and deliberately leaves the rest in your system of record.
| Object | Owns | Cardinality |
|---|---|---|
| Claim | Only the attributes that select which rules apply | 1 per claim |
| Event | Something happened, and when | Many per claim, append-only |
| Document | Classification, quality, pages | Many per claim |
| Fact | One extracted assertion, anchored | Many per document |
| Anchor | Document, page, region, verbatim quote | 1 per fact, 2+ per finding |
| Finding | A reconciliation across documents | Many per claim |
| Rule | An obligation as data, with its citation | 142 shipped, tenant-extensible |
| Product | PDS and schedule clauses, verbatim | 1 per policy wording version |
| Report | A replayable compliance artefact | Generated on demand |
Three of the things you would most expect to find in the database are absent from it on purpose.
| Not stored | Computed from | Why it matters |
|---|---|---|
| Obligation status | Events + rule set + asOf | A stored status column goes stale silently. A computed one cannot - and it lets you ask what the position was on the day the decision was made, which is the question AFCA asks. |
| Due dates | Trigger event + duration + jurisdiction calendar | Public holidays change. Storing 6 November as a due date freezes an answer that depended on a calendar; recomputing it does not. |
| Risk or fraud score | Nothing - it does not exist | There is no score, no percentile and no ranking of customers. Findings are individually explainable or they are not produced. |
Deliberately thin. It carries what determines which rules apply and nothing else - not the reserve, not the customer's name, not the notes. Those live in your claims system and we have no reason to hold a second copy.
{
"id": "WC-NSW-2026-0912",
"productClass": "workers-compensation", // selects the rule families
"jurisdiction": "NSW", // selects the business-day calendar
"scheme": "nsw-workers-compensation", // optional, for scheme-specific rules
"catastrophe": null, // declared event code, if any
"events": [ /* see below */ ]
}
Four fields, append-only. This object is the entire input to the obligation engine - everything else it knows, it derives.
{
"type": "claim.lodged", // one of 81 - see the API reference
"at": "2026-08-05", // when it happened, not when you told us
"source": "guidewire.claimcenter",
"sourceRef": "CC-4471902" // your id, for tracing back
}
Corrections are appended, not edited. If an event was recorded against the wrong date, you post the correction and the position recomputes - and both the original and the correction remain visible, because a compliance record that can be quietly rewritten is not evidence of anything.
A document is classified and its quality recorded - because a 150 dpi scan with handwriting is a different evidentiary object from a native PDF, and pretending otherwise is how extraction errors become claim decisions.
{
"id": "D-0912-04",
"claimId": "WC-NSW-2026-0912",
"type": "Certificate of Capacity",
"instrument": "NSW workers compensation",
"pageCount": 2,
"classification": { "label": "Certificate of Capacity (NSW, SIRA form)",
"confidence": 0.97 },
"quality": { "source": "Scanned, 200 dpi",
"skew": "1.2 deg corrected",
"handwriting": "present" }
}
One extracted assertion. A fact without an anchor is not stored - the constructor throws. There is no configuration flag that relaxes this.
{
"type": "capacity.restriction",
"value": { "activity": "lifting >5kg",
"from": "2026-04-14", "to": "2026-05-12" },
"documentDate": null,
"subject": null,
"anchor": { /* required - see below */ }
}
The smallest object and the most important one. It is what makes a finding checkable by a person in fifteen seconds rather than a claim you have to take on faith.
{
"documentId": "D-4417-01",
"documentType": "Motor claim form",
"page": 1, // 1-indexed; constructor rejects 0
"region": null, // [x, y, w, h] when available
"quote": "24 May 2026", // verbatim source text, required
"extractedAt": null,
"extractor": null
}
documentId, page and quote are all mandatory and
each is validated at construction. An anchor with a zero page, an empty quote or no
document raises immediately rather than producing a finding that points at nothing.
A finding carries three things a score does not: the pages it came from, the factors it decomposes into, and the innocent explanations that would also fit.
{
"id": "MOT-2026-04417:temporal:D-4417-01:D-4417-02",
"claimId": "MOT-2026-04417",
"kind": "temporal",
"summary": "Incident date differs by 3 days between two documents",
"detail": "Motor claim form records 2026-05-24; Police event report records 2026-05-21.",
"anchors": [ /* 2 or more, from 2 or more distinct documents */ ],
"confidence": "probable", // corroborated | probable | possible
"factors": [
{ "name": "Date discrepancy", "value": "3 days" },
{ "name": "Benign explanations",
"value": "Late reporting, gradual-onset injury, or transcription error at intake" }
],
"recommendation": "review", // always. not configurable.
"decidedBy": null // only a person ever fills this
}
confidence is a statement about how well-corroborated the reconciliation is,
not a probability that the customer did something wrong. The capacity detector is
hard-capped at possible regardless of how clean the match looks, because the
cost of being confidently wrong about someone's certified incapacity is not symmetric with
the cost of being cautious.
Rules are data, not code. That is what makes it possible to show a regulator the rule that produced a position, and to fingerprint the whole set so a report can be replayed.
{
"id": "gi-code-claim-decision",
"instrument": "General Insurance Code of Practice 2020",
"citation": "paragraph 78",
"quote": "We will decide to accept or deny your claim ...", // verbatim
"obligation": "Decide the claim and notify the customer",
"appliesTo": { "productClass": ["general"] },
"trigger": ["claim.info.complete"],
"discharge": ["claim.decision.notified"],
"duration": { "days": 10, "unit": "business" },
"severity": "high",
"remedy": "Notify the customer of the decision in writing",
"horizon": false, // true = instrument not yet commenced
"verified": false // see below
}
| Field | Why it exists |
|---|---|
quote | The verbatim wording of the instrument. A rule that cannot quote its source is a rule you cannot defend, so the schema requires it. |
horizon | Marks obligations under instruments that have not commenced. Horizon rules are evaluated and reported as forward exposure but never counted as live breaches. |
verified | All 142 shipped rules are false today. It becomes true only when a named person has checked that rule against the primary instrument and recorded who and when. The count of unverified rules applied appears in every position and every report. |
unit | calendar, business or months. Business days resolve against the claim's jurisdiction - eight Australian calendars, including the ones that differ on Anzac Day substitution and the Melbourne Cup. |
The computed result of one rule against one claim as at one date. Never persisted; always returned with the inputs that produced it, so it can be checked rather than trusted.
{
"ruleId": "wc-nsw-provisional-liability",
"obligation": "Commence provisional weekly payments",
"citation": "Workers Compensation Act 1987 (NSW) s 267",
"status": "at-risk", // discharged | breached | at-risk | open | not-triggered
"triggeredBy": "injury.notified",
"triggeredAt": "2026-08-04",
"dueAt": "2026-08-18",
"daysRemaining": 1,
"breachedBy": null,
"severity": "high",
"remedy": "Commence payments or issue a reasonable excuse notice",
"horizon": false,
"verified": false
}
at-risk means the obligation has entered its escalation window, which scales
with the length of the clock rather than being a fixed number of days: two days on a
seven-day payment obligation, thirty on a twelve-month one. A four-month Code obligation
gets thirty days' warning, because a fortnight is not enough runway to decide a claim that
has already run four months.
Claims are assessed against the policy the customer actually holds. The product object
holds PDS and schedule clauses with their verbatim wording; clauseRef()
throws if wording is missing, because a coverage position quoting a clause number you
cannot read is not something a customer can dispute.
{
"effect": "adverse", // covered | adverse | conditional | neutral
"strength": "arguable",
"summary": "Loss may fall within the flood exclusion",
"clause": { "documentType": "PDS", "section": "What we do not cover",
"clause": "4.2(b)", "page": 31,
"wording": "We do not cover loss caused by flood ..." },
"counterArguments": [ // REQUIRED whenever effect is "adverse"
"Insurance Contracts Act 1984 (Cth) s 54 may limit reliance where the act did not cause the loss",
"The hydrology report attributes only the dominant cause; storm contribution is not excluded"
]
}
The compliance artefact. Fourteen top-level sections, generated as a pure function of
(claim, rules, asOf) - so the same inputs always produce the same report.
{
"schemaVersion": "1.0",
"reportType": "claim-compliance",
"claim": { /* id, jurisdiction, age, document count */ },
"position": { /* counts, compliant, nextAction */ },
"breachRegister": [],
"obligations": [],
"timeline": [ /* every event, what it triggered and discharged */ ],
"integrity": { "examined": true, "findingCount": 0, "findings": [] },
"coverage": { "assessed": false, "note": "No product wording supplied" },
"vulnerability": { "signalsOnFile": 0,
"usedInAutomatedAssessment": false, "statement": "..." },
"automatedDecisionMaking": { /* APP 1 disclosure - see below */ },
"limitations": [ /* 5 - stated in the body, not a footnote */ ],
"attestation": { "reviewedBy": null, "reviewedAt": null, /* ... */ },
"provenance": { "ruleSet": { /* fingerprint */ }, "replayable": true }
}
| Section | What it carries and why |
|---|---|
automatedDecisionMaking | The APP 1 disclosure, commencing 10 December 2026: what is decided automatically, what is not, what personal information is used, how a person is involved, and the attributes firewalled from automated assessment. Generated per claim, not written once as boilerplate. |
limitations | Five limitation statements in the body of the report, not an appendix. Including the count of unverified rules the position depends on. A limitation you have to go looking for has not been disclosed. |
attestation | reviewedBy and reviewedAt start null and can only be filled by a person. The report states plainly which decisions require one. |
provenance | FNV-1a fingerprint of the exact rule set. Change one rule and the fingerprint changes, so a report can never be silently reinterpreted under a newer rule set than the one that produced it. |
These are enforced by constructors that throw, not by review or by policy. Every one of
them is asserted by the test suite, and node scripts/report.js checks all
five on demand.
recommendation is hardcoded to "review" and
decidedBy starts null. There is no code path that declines, reduces or
recovers on a claim.
finding() throws with fewer than two anchors, and throws again if they
come from the same document. Provenance findings are the single exception, being
about one document by definition.
Including the benign explanations. A finding with an empty factors
array does not construct.
Not filtered - firewalled. If one reaches the examination, it raises a runtime error and refuses to run. See below.
Horizon rules are evaluated and reported as forward exposure. Counting them as breaches would inflate the numbers in our favour, which is exactly why the rule exists.
These attributes cannot enter integrity assessment. An indicator that declares one fails at registration - the engine will not start, rather than starting and quietly excluding it. The list is deliberately broader than anti-discrimination law strictly requires, because proxies do the same damage as the attributes they stand in for.
Vulnerability signals still travel with the claim and still reach the handler - they must, because they change how a customer should be supported. They simply never reach a detector. The report states, per claim, how many signals were on file and that none were used in automated assessment.
Most of the mapping work is one table: your status codes and activity types to our event vocabulary. Nothing else about your data model needs to change.
| Your side | Maps to | Typical effort |
|---|---|---|
| Claim status transitions | Events | The bulk of it. Usually 10-20 mappings to start. |
| Activity / diary entries | Events | Often a richer source than status alone, especially for information requests. |
| Correspondence log | *.notified events | Where discharge evidence usually lives. |
| Line of business / product code | productClass | One lookup table. |
| Risk state, scheme | jurisdiction, scheme | One lookup table. |
| Document management metadata | Document + classification | We reclassify rather than trusting the folder it was filed in. |
| Customer, reserve, notes, payments | Not mapped | Stays in your system. We do not need it and do not want it. |
Start with the events you already emit reliably. Each additional event switches on the
obligations that depend on it; the ones you have not mapped yet report as
not-triggered rather than silently passing, so coverage of the rule set is
always visible rather than assumed.