Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
65 changes: 34 additions & 31 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -445,39 +445,42 @@ models), `src/analysis/differential.jl`, `src/server/routes/analysis.jl`, and
independent implementation and are quoted to 12 digits; the special functions
are cross-checked against mpmath at 40 digits.

### Evidence Mode

The **Evidence** tab turns a run's outputs into a citable epistemic record. A
claim is a sentence a reader could disagree with, a **scope** naming the part of
the data it is about (its *fibre*: the rows whose value at a rank, or whose
category in a set, is one of the named labels), and a **warrant** saying why it
follows. Claims are stored in `projects/{study}/[<group>/]<run>/evidence/claims.yml`
and can be downloaded as that exact YAML.

Three views share one record:

- **Claims** — the epistemic editor (avec fibre). A claim cannot be saved
without a scope and at least one value, and cannot be marked `supported`
without a measurement, citation or inference warrant: a definition alone
supports nothing.
- **Fibre & echo** — the fibre's share of every sample, its largest members, and
the same fibre seen through a second projection: the other classifier at the
same rank, and the parent rank. Agreement is reported with a verdict;
disagreement is a limit on how strongly the claim may be stated, and an echo
that cannot be computed says so rather than vanishing.
- **Residuals** — where the fibre departs from a null you declare, either
`proportional` (the fibre distributed across samples as the library is) or a
stated `claim_share`, with Pearson residuals, a log2 ratio and a χ² statistic
printed without a p-value. The worst departure is shown first.

The tab's Provenance card states whether `attestation.yml` is absent, degraded
or internally inconsistent before any claim is read. Longer notes on the
definitions, the schema and the limits are kept in `docs/evidence-mode.md`
(documentation under `docs/` is kept local by policy — see `.gitignore`).
### Evidence Mode (API)

Evidence Mode turns a run's outputs into a citable epistemic record. A claim is
a sentence a reader could disagree with, a **scope** naming the part of the data
it is about (its *fibre*: the rows whose value at a rank, or whose category in a
set, is one of the named labels), and a **warrant** saying why it follows.
Claims are stored in `projects/{study}/[<group>/]<run>/evidence/claims.yml` and
can be downloaded as that exact YAML.

The record is served entirely by the Julia backend; the browser surface is
delivered by the Vue/Marid client, so this repository ships no Evidence UI.

| Endpoint | What it answers |
| --- | --- |
| `GET .../runs/{run}/evidence` | The record plus the context a claim may cite (tables, ranks, categories, provenance) |
| `POST .../evidence/claims` | Create, replace or retire a claim; ids are safe names of at most 64 characters |
| `DELETE .../evidence/claims/{id}` | Retire one claim |
| `GET .../evidence/claims.yml` | The record exactly as it sits on disk |
| `POST .../evidence/fiber` | A fibre's share per sample, its largest members, and an **echo**: the same scope through another projection (the other classifier at the same rank, the parent rank) |
| `POST .../evidence/residuals` | Where a fibre departs from a declared null — `proportional` (the fibre spread across samples as the library is) or a stated `claim_share` |

Validation is enforced server-side, so every client sees the same rules. Scope
is one projection (`rank` or `category`) of 1 to 50 values of at most 200
characters; claim text is at most 2000 characters; a claim carries at most 5
warrants and 20 caveats; a blank status is stored as `draft`. A claim cannot be
marked `supported` without at least one measurement, citation or inference
warrant — a definition alone supports nothing. Echo verdicts are `concordant`,
`minor divergence`, `partial agreement` or `discordant`; containment is
`contained`, `inconsistent` or `disjoint`. Residuals report Pearson residuals, a
log2 ratio and a χ² statistic without a p-value, counting `|r| > 2`.

Implementation: `src/core/evidence.jl` (schema, residuals, verdicts),
`src/server/routes/evidence.jl` (the SQL), `frontend/src/evidence/`
(editor, fibre visualiser, residual explorer) and `test/unit/test_evidence.jl`.
`src/server/routes/evidence.jl` (the SQL), `test/unit/test_evidence.jl` and
`test/unit/test_evidence_routes.jl`. Longer notes on the definitions, the schema
and the limits are kept in `docs/evidence-mode.md` (documentation under `docs/`
is kept local by policy — see `.gitignore`).

### Figures and report

Expand Down
35 changes: 0 additions & 35 deletions frontend/src/api/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,10 +27,6 @@ import type {
} from './types'

import type { FigureDoc } from '../figure/types'
import type {
EvidenceClaim, EvidenceContext, FibreRequest, FibreResult,
ResidualRequest, ResidualResult,
} from '../evidence/types'
import { saveBlob } from '../utils/download'

// Base URL for the backend API. Empty string means same-origin.
Expand Down Expand Up @@ -415,37 +411,6 @@ export const api = {
post<AlignmentQC>(`/api/v1/studies/${study}/placements/${id}/trim-preview`, trim),
},

/**
* Evidence Mode. A run's claims live beside its outputs; the fibre and
* residual queries read the results database without writing to it.
*/
evidence: {
context: (study: string, run: string, group?: string | null) =>
get<EvidenceContext>(`/api/v1/studies/${study}/runs/${run}/evidence${gq(group)}`),
saveClaim: (study: string, run: string, claim: EvidenceClaim, group?: string | null) =>
post<{ claim: EvidenceClaim; claims: EvidenceClaim[]; updated: string }>(
`/api/v1/studies/${study}/runs/${run}/evidence/claims${gq(group)}`, { claim }),
deleteClaim: (study: string, run: string, id: string, group?: string | null) =>
del<{ deleted: string; claims: EvidenceClaim[]; updated: string }>(
`/api/v1/studies/${study}/runs/${run}/evidence/claims/${encodeURIComponent(id)}${gq(group)}`),
fiber: (study: string, run: string, body: FibreRequest, group?: string | null) =>
post<FibreResult>(`/api/v1/studies/${study}/runs/${run}/evidence/fiber${gq(group)}`, body),
residuals: (study: string, run: string, body: ResidualRequest, group?: string | null) =>
post<ResidualResult>(`/api/v1/studies/${study}/runs/${run}/evidence/residuals${gq(group)}`, body),
/** The record as the YAML that sits on disk, for download. */
claimsYaml: async (study: string, run: string, group?: string | null): Promise<Blob> => {
const res = await fetch(apiUrl(`/api/v1/studies/${study}/runs/${run}/evidence/claims.yml${gq(group)}`),
{ cache: 'no-store' })
if (!res.ok) {
const err: ApiError = await res.json().catch(() => ({
error: 'network_error', message: res.statusText,
}))
throw Object.assign(new Error(err.message), { apiError: err, status: res.status })
}
return res.blob()
},
},

trees: {
list: (study: string) => get<TreeFileInfo[]>(`/api/v1/studies/${study}/trees`),
get: (study: string, file: string) =>
Expand Down
64 changes: 0 additions & 64 deletions frontend/src/components/EvidenceModeToggle.tsx

This file was deleted.

Loading
Loading