Governed BankOps · claim surveys, fullstack

One rule, three regulators, self serve

A supplier certification, a public budget allocation and a suspicious transaction report look like three different compliance worlds. They are three instances of the same claim: figure · method · boundary · year · issuer, followed by evidence that either reconciles or does not. One schema, one scoring rule, three questionnaires generated from it.

· SurveyJS schema, the real contract · scored deterministically, no model · answers land as jsonb · retrieved with pgvector
the questionnaire

Fill one. It scores as you submit.

Leave a field empty on purpose. Nothing negotiates: the claim is complete and reconciles with its register, or it goes back to whoever authored it, with the missing part named.

The SurveyJS library did not load. This page renders the questionnaire with SurveyJS from a public CDN. The schema below is unaffected and is the real artefact; only the rendered form is missing. Reload with network access to see it.
what the backend generates

The schema is the integration

Spring emits this JSON per certification, conformity or ESG claim type. Angular renders it. Nobody hand builds a form, and a new certification is a new row, not a new release.

-
what comes back

Answers as jsonb, verdict alongside, retrieved by meaning

The response is stored whole. The verdict is computed at write time, never inferred later. The embedding is built from the claim text only, so semantic search finds similar claims rather than similar respondents.

-
Why the verdict is stored, not derived on read. A rule that changes must not silently re-judge history. The verdict recorded is the one that applied on the day it was submitted, and re-scoring is a new row. That is the same reason a withholding rate is versioned by effective date rather than read live.
the stack, in order

Five steps, same architecture as the RSE backend

1

Angular

The shell. Self serve: a supplier signs in and sees only the questionnaires bound to their own contact.

2

SurveyJS

Renders the JSON. Validation, required parts and conditional pages live in the schema, not in components.

3

Spring

Generates one schema per certification, conformity or ESG claim, and owns the scoring rule so the client cannot argue with it.

4

Self serve

Issued, answered, resubmitted by the counterparty. No analyst re-keys anything.

5

Postgres + pgvector

Answers as jsonb, verdict as a column, claim text as an embedding. Filter exactly, retrieve by meaning.

CREATE TABLE survey_response (
  id            bigserial PRIMARY KEY,
  survey_id     text        NOT NULL,          -- which schema version was answered
  respondent_id text        NOT NULL,          -- supplier, programme owner, reporting officer
  submitted_at  timestamptz NOT NULL DEFAULT now(),
  answers       jsonb       NOT NULL,          -- the whole SurveyJS result, untouched
  verdict       text        NOT NULL,          -- computed at WRITE time
  missing       text[]      NOT NULL DEFAULT '{}',
  evidence_seen int, evidence_expected int,
  embedding     vector(1536)                   -- built from the claim text only
);

CREATE INDEX ON survey_response USING hnsw (embedding vector_cosine_ops);
CREATE INDEX ON survey_response USING gin  (answers jsonb_path_ops);
CREATE INDEX ON survey_response (verdict, submitted_at DESC);

-- "which suppliers claimed something like this, and did it hold?"
SELECT respondent_id, verdict, missing, answers->'claim'->>'figure'
FROM   survey_response
WHERE  verdict <> 'VERIFIED'
ORDER  BY embedding <=> :query_embedding
LIMIT  20;
Why both indexes. The GIN index answers exactly which responses named a boundary. The HNSW index answers approximately which responses meant the same thing in different words. A compliance question needs the first; a pattern question needs the second, and asking the second with the first is how a scan turns into a table sweep.
the boundary, stated

What this does not do

It does not decide whether a supplier is ethical, whether a budget line is wise, or whether a transaction is criminal. It decides whether the claim about it is complete and reconciles with the record that already exists. Those are different questions, and only the second one is answerable without discretion. Everything above is deterministic: no model, no network once loaded, no clock branch, no random. Reference extracts are authored here, so no figure belongs to any real counterparty.