# ReactCode `outputs` Schema Guidance

How to structure the `outputs` attribute on a `<ReactCode>` tag so labeling, Prompts, Label Distribution, and Agreement all work from the same contract.

## Why this matters

`outputs` is **not** documentation for humans. It is the machine-readable contract for the object stored at:

```text
annotation/prediction result → value.reactcode
```

that is, the JSON object your interface passes to `addRegion(...)`.

Several platform features derive behavior from this schema — **not** from the JavaScript rendered inside the iframe:

| Consumer | What it uses `outputs` for |
|---|---|
| **Region serialization / SDK** | Shape of `value.reactcode`; `to_json_schema()` for tooling |
| **Prompts / model runs** | Structured-output schema sent to the LLM; prediction payload shape |
| **Dimensions** | One dimension per leaf field; allowed values → `Dimension.values_enum` |
| **Label Distribution** | Which categories to count; fields without enums are treated as free-form / unsupported |
| **Agreement** | Per-dimension label set and multi-value behavior for agreement matrices |

If the React UI offers a dropdown of five choices but `outputs` only says `{"type":"string"}`, Prompts may invent free text, Label Distribution will not count that field, and Agreement will not have a stable enum for it.

**Rule of thumb:** treat `outputs` as the source of truth for *what can be stored*, and keep the UI dictionaries in sync with it.

---

## What `outputs` describes

Describe **only** the payload you pass to `addRegion`.

Do **not** describe:

- Task `data` fields (`$document`, photographer, upload metadata, …)
- Label Studio envelope fields (`id`, `from_name`, `to_name`, `type`, `origin`)
- Transient UI state (panel width, suggested highlights, view mode)
- Values the UI never persists

### Saved region shape

Whatever you pass to `addRegion(payload)` is stored as:

```json
{
  "id": "…",
  "from_name": "<ReactCode name>",
  "to_name": "<ReactCode toName>",
  "type": "reactcode",
  "value": {
    "reactcode": { /* ← this object must match outputs */ }
  }
}
```

`outputs` is a JSON Schema for that inner object (or a shorthand that expands into one — see formats below).

---

## Supported formats

The SDK (`ReactCodeTag.to_json_schema()`) accepts three compatible styles.

### 1. Full JSON Schema (preferred for real projects)

```xml
<ReactCode name="doc" toName="doc" data="$document"
  outputs='{"type":"object","properties":{"case_type":{"type":["string","null"],"enum":["Civil","Criminal",null]},"tags":{"type":["array","null"],"items":{"type":"string","enum":["urgent","review"]}}}}'>
```

Use when you need nullability, nested objects, arrays, or large enums.

### 2. Property map (JSON object of field → schema)

If the attribute parses as JSON but is not already a complete `type: object` / `type: array` schema, it is wrapped as `{ "type": "object", "properties": … }`:

```xml
outputs='{"score":{"type":"number"},"label":{"type":"string","enum":["a","b"]}}'
```

### 3. Delimited list + type aliases (small configs)

```xml
outputs="rating:choices(good,bad), tags:multichoices(urgent,review), notes"
```

| Alias | Becomes |
|---|---|
| `field:choices(a,b)` | `string` + `enum` |
| `field:multichoices(a,b)` | `array` of `string` + `items.enum` |
| `field:number(min,max)` | `number` with optional bounds |
| `field:rating(n)` | `integer` 1…n |
| bare `field` | `string` |

Prefer full JSON Schema once field count or nesting grows.

---

## Recommended payload shapes

### Flat form (simplest for Prompts + dimensions)

One region = one filled form. Top-level keys become dimensions like `doc.case_type`.

```json
{
  "type": "object",
  "properties": {
    "heading": { "type": ["string", "null"] },
    "case_type": {
      "type": ["string", "null"],
      "enum": ["Civil", "Criminal", "Miscellaneous", "Bankruptcy", null]
    },
    "case_stage": {
      "type": ["array", "null"],
      "items": {
        "type": "string",
        "enum": ["Pre-trial", "Trial", "Appellate"]
      }
    }
  }
}
```

UI / Prompts should write:

```js
addRegion({ heading: "…", case_type: "Civil", case_stage: ["Pre-trial"] });
```

### Nested envelope (only if the UI already uses one)

Some interfaces store a typed blob (`type`, `entityType`, `fields`, …). Then `outputs` must describe **that same tree**. Dimensions walk nested `properties` and name leaves with dotted paths (e.g. `doc.fields.case_type`).

```json
{
  "type": "object",
  "properties": {
    "type": { "type": "string", "const": "documentForm" },
    "entityType": { "type": "string", "const": "documentForm" },
    "fields": {
      "type": "object",
      "properties": {
        "case_type": {
          "type": ["string", "null"],
          "enum": ["Civil", "Criminal", null]
        }
      }
    },
    "rawCaptures": { "type": ["object", "null"] }
  },
  "required": ["type", "entityType", "fields"]
}
```

**Mismatch symptom:** prediction is saved, but the form panel stays empty because the UI looks for `value.reactcode.type === "documentForm"` / `fields.*` while Prompts wrote a flat object (or the reverse).

### Multiple region kinds

Do not cram unrelated shapes into one ambiguous schema unless the UI truly uses a discriminator (`type` / `const` / `oneOf`). Prefer:

- separate `<ReactCode>` tags, or
- one envelope with an explicit `type` field and clear per-variant properties

---

## Field-level rules

### Finite choices → use `enum` / `items.enum`

| UI control | Schema |
|---|---|
| Single select / radio | `"type": ["string","null"], "enum": ["A","B", null]` |
| Multi select | `"type": ["array","null"], "items": { "type": "string", "enum": ["A","B"] }` |
| Boolean | `"type": ["boolean","null"]` (dimensions treat as `true` / `false`) |
| Rating / small int range | `"type": "integer", "minimum": 1, "maximum": 5` |
| Free text | `"type": ["string","null"]` — **no** enum (expected to stay unsupported in Label Distribution) |

Copy enum strings **exactly** from the UI dictionary (including punctuation and spacing). Label Distribution and Agreement use schema enums; they do **not** expand categories from historically observed annotation values.

### Nullability

If annotators or models leave a field empty, allow `null` in `type` (and in `enum` when the field is constrained). Prefer explicit `null` over omitting keys inconsistently.

### Keep schema and code in lockstep

Whenever you change a dropdown list in JS/React:

1. Update the UI constant.
2. Update `outputs` enums to match.
3. Re-save the labeling config.
4. Re-run Prompts if predictions should pick up the new constraint.
5. Expect dimensions / Label Distribution enums to refresh from the new config (backfill value counts if you rely on cached distributions).

### What belongs in overflow / notes

Rationale text, OCR snippets, and “model couldn’t map to enum” dumps belong in unconstrained fields (e.g. `rawCaptures`, `notes`) — **not** forced into a constrained enum. That keeps Label Distribution and Agreement categories clean.

---

## Impact on Label Distribution and Agreement

### Dimensions

For ReactCode, the platform creates **one dimension per leaf field** in the JSON Schema (nested objects are walked; leaves are named `\<tagName\>.\<dotted.path\>`).

- Fields with `enum` / `items.enum` / boolean / small integer ranges get a `values_enum`.
- Free-form strings/objects without enums get dimensions that Label Distribution treats as unsupported for category counts.

### Label Distribution

Counts are driven by schema-defined enums. If `outputs` says plain `string` while the app shows chips, the dashboard will not show useful distributions for that field until `outputs` is fixed and counts are recomputed/backfilled.

### Agreement

Agreement matrices key off the same dimensions and `values_enum`. Wrong or missing enums mean:

- labels fall outside the expected set,
- multi-select vs single-select (`array` vs `string`) is wrong,
- nested vs flat path names don’t match stored values.

**Practical test:** after fixing `outputs`, confirm Data Quality → Label Distribution lists the expected categories, then spot-check agreement for those dimensions.

---

## Prompts-specific notes

Prompts send `outputs` (via `to_json_schema()`) as the structured-output schema for the model.

- Tight enums reduce invented labels (`"Feature Article"` when only legal categories exist).
- Nested envelopes must match what the ReactCode reader expects, or predictions look “saved but blank.”
- After changing `outputs`, re-run the prompt; old predictions keep the old shape.
- Avoid leaving obsolete parallel tags (`TextArea`, renamed ReactCode `name`) in the config — mixed result types confuse both UI and model wiring.

---

## XML encoding (common hard failures)

`outputs` lives in an **XML attribute**. Invalid XML fails project save before any schema logic runs.

| Character | Problem | Fix |
|---|---|---|
| `&` in enum text | `xmlParseEntityRef: no name` | `&amp;` inside the attribute |
| `'` inside a single-quoted attribute | Attribute terminates early | `\u0027` in JSON, or switch to double-quoted attr |
| `"` inside a double-quoted attribute | Same | Escape or use single-quoted attr |
| `<` / `>` in attribute | Breaks XML | `&lt;` / `&gt;` (rare in enums) |
| `&` / `</` in React source | Breaks tag body | Wrap code in `<![CDATA[ … ]]>` |

Recommended pattern:

```xml
<ReactCode name="doc" toName="doc" data="$document" outputs='{…JSON with &amp; and \u0027 as needed…}'>
  <![CDATA[
  function App({ React, addRegion, regions, data, viewState }) {
    /* … */
  }
  ]]>
</ReactCode>
```

Validate before shipping:

1. XML parse the full labeling config.
2. Read the `outputs` attribute (after XML unescape) and `json.loads` it.
3. Confirm `LabelInterface(config).get_control('doc').to_json_schema()` returns the expected properties/enums.

---

## Authoring checklist

Use this when creating or repairing a ReactCode project:

1. **List every key** the UI writes via `addRegion` / updates on regions.
2. **Choose flat vs nested** to match the existing reader (`regions[].value.reactcode`).
3. **Mark types accurately** (`string` vs `array`, nullable).
4. **Add enums** for every finite choice list; leave free text without enums.
5. **XML-escape** the attribute; CDATA the code body.
6. **Save config** and open the labeling UI (config must parse).
7. **Manual annotate** once; export/inspect `value.reactcode` vs schema.
8. **Prompts (if used):** run once; confirm prediction envelope + form render.
9. **Label Distribution / Agreement:** confirm dimensions and enums appear for constrained fields; backfill value counts if needed after schema changes.

---

## Failure → likely cause

| Symptom | Likely cause |
|---|---|
| Config save / parse error (`xmlParseEntityRef`, …) | Unescaped `&` / quotes in `outputs` |
| Prompts produce nonsense or free-form junk | Missing `enum` / overly loose schema |
| Prediction saved, form blank | Schema/payload shape ≠ UI reader (flat vs envelope) |
| Manual labeling works; Prompts don’t | `outputs` out of date vs JS dictionaries |
| Label Distribution “unsupported” for a dropdown | Field is `string` without `enum` in `outputs` |
| Agreement weird / missing categories | `values_enum` stale or path (`doc.fields.x` vs `doc.x`) mismatch |
| Mixed `textarea` + `reactcode` results | Stale second control tag still in labeling config |

---

## Minimal examples

### Good: constrained + free-form together

```xml
outputs='{"type":"object","properties":{"verdict":{"type":["string","null"],"enum":["accept","reject",null]},"notes":{"type":["string","null"]}}}'
```

- `verdict` → dimension with enum → Label Distribution + Agreement + Prompts constrained  
- `notes` → free-form → no category distribution (expected)

### Bad: UI has choices, schema does not

```xml
outputs='{"type":"object","properties":{"verdict":{"type":"string"}}}'
```

Looks fine in the editor; breaks Prompts quality and Data Quality analytics for `verdict`.

### Bad: schema flat, UI nested

```xml
outputs='{"case_type":{"type":"string","enum":["Civil"]}}'
```

while the UI only reads:

```js
const form = regions.find(r => r.value?.reactcode?.type === "documentForm");
form.fields.case_type
```

---

## One-liner

> **`outputs` must be an XML-safe JSON Schema of exactly what `addRegion` writes, with the same enums your UI accepts. Prompts, Label Distribution, and Agreement all read that schema — not your React source.**
