diff --git a/data/STRchive-loci.schema.json b/data/STRchive-loci.schema.json
index 833f805f..227c7eb6 100644
--- a/data/STRchive-loci.schema.json
+++ b/data/STRchive-loci.schema.json
@@ -7,7 +7,7 @@
"type": "object",
"properties": {
"id": {
- "section": "Overview",
+ "section": "ID",
"title": "ID",
"description": "Unique identifier for the locus within STRchive in the form [disease_id]_[gene]. Additional characters may be added to the end of the ID to make it unique within STRchive, e.g. HFG_HOXA13-I, HFG_HOXA13-II.",
"placeholder": "[disease_id]_[gene]",
@@ -16,14 +16,14 @@
"pattern": "^[\\S]+_[\\S]+$"
},
"disease_id": {
- "section": "Overview",
+ "section": "ID",
"title": "Disease ID",
"description": "Disease abbreviation",
"examples": ["CANVAS"],
"type": ["string"]
},
"gene": {
- "section": "Overview",
+ "section": "ID",
"title": "Gene",
"description": "Gene symbol",
"examples": ["RFC1"],
diff --git a/data/STRtr-kit-evidence.json b/data/STRtr-kit-evidence.json
new file mode 100644
index 00000000..fe51488c
--- /dev/null
+++ b/data/STRtr-kit-evidence.json
@@ -0,0 +1 @@
+[]
diff --git a/data/STRtr-kit-evidence.schema.json b/data/STRtr-kit-evidence.schema.json
new file mode 100644
index 00000000..151a06ab
--- /dev/null
+++ b/data/STRtr-kit-evidence.schema.json
@@ -0,0 +1,297 @@
+{
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
+ "$id": "STRtr-kit-evidence.schema",
+ "title": "STRtr-kit Evidence",
+ "description": "Evidence to support pathogenicity of tandem repeat loci in human disease. Each entry describes evidence from a single publication or similar source. There will typically be multiple entries per locus, each describing evidence from different sources.",
+ "citation_format": "In free text strings: 'Some text [@doi:12345; @pmid:12345]'. In regular lists: ['doi:12345', 'pmid:12345']",
+ "type": "object",
+ "properties": {
+ "id": {
+ "section": "Evidence",
+ "title": "Locus",
+ "description": "Unique identifier for the locus within STRchive in the form [disease_id]_[gene]. Additional characters may be added to the end of the ID to make it unique within STRchive, e.g. HFG_HOXA13-I, HFG_HOXA13-II.",
+ "placeholder": "[disease_id]_[gene]",
+ "examples": ["CANVAS_RFC1", "HFG_HOXA13-I", "HFG_HOXA13-II"],
+ "type": ["string"],
+ "pattern": "^[\\S]+_[\\S]+$"
+ },
+
+ "citation": {
+ "section": "Evidence",
+ "title": "Citation",
+ "description": "Citation for the source of the evidence",
+ "examples": ["pmid:29507423"],
+ "type": ["string"],
+ "pattern": "^(?:doi|pmc|pmid|arxiv|isbn|url|mondo|omim|genereviews|malacard|orphanet|stripy|gnomad):.+$"
+ },
+
+ "genetic_evidence": {
+ "section": "Evidence",
+ "title": "Genetic Evidence",
+ "description": "One or more pieces of genetic evidence provided in the source",
+ "type": "array",
+ "uniqueItems": true,
+ "items": {
+ "title": "",
+ "type": "object",
+ "properties": {
+ "type": {
+ "title": "Type",
+ "description": "The type(s) of genetic evidence provided in the source",
+ "type": ["string", "null"],
+ "enum": [
+ "probands",
+ "allele_effect",
+ "method",
+ "segregation",
+ "case_control"
+ ],
+ "enum_descriptions": {
+ "probands": "Unrelated Probands",
+ "allele_effect": "Relationship between allele size and/or motif sequence and phenotype e.g. age of onset and/or severity",
+ "method": "Method of predicting Pathogenicity",
+ "segregation": "Linkage Region for disease",
+ "case_control": "Case-control study"
+ }
+ },
+ "notes": {
+ "title": "Notes",
+ "description": "Additional notes about the genetic evidence provided in the source including point upgrades/downgrades",
+ "examples": [
+ "Segregation analysis in 3 affected families with LOD score of 3.5"
+ ],
+ "type": ["string", "null"],
+ "multiline": true
+ }
+ },
+ "required": ["type"],
+ "allOf": [
+ {
+ "if": { "properties": { "type": { "const": "probands" } } },
+ "then": {
+ "properties": {
+ "probands": {
+ "title": "Probands",
+ "description": "Number of probands with the pathogenic expansion reported in the source",
+ "type": ["integer"],
+ "minimum": 0
+ },
+ "points": {
+ "title": "Points",
+ "description": "Points assigned to this locus based on the genetic evidence provided in the source, according to the STRtr-kit scoring system.",
+ "type": ["number"],
+ "minimum": 0,
+ "maximum": 6,
+ "default": 0.5,
+ "calc": "min(6, {probands} * 0.5)",
+ "scoring_range": "0–1.5 points per proband",
+ "upgrades": "+0.5 for inheritance/de novo evidence, +0.5 for functional evidence"
+ }
+ },
+ "required": ["probands", "points"]
+ }
+ }
+ ]
+ }
+ },
+
+ "experimental_evidence": {
+ "section": "Evidence",
+ "title": "Experimental Evidence",
+ "description": "Experimental evidence supporting pathogenicity of the tandem repeat locus. Maximum overall score for all experimental evidence is 6.0 points.",
+ "type": "array",
+ "uniqueItems": true,
+ "category_max": 6.0,
+ "items": {
+ "type": "object",
+ "properties": {
+ "type": {
+ "title": "Type",
+ "description": "The type of experimental evidence provided in the source",
+ "type": ["string", "null"],
+ "enum": [
+ "biochemical_function",
+ "protein_interaction",
+ "regulatory_impact",
+ "functional_alteration_patient_cells",
+ "functional_alteration_non_patient_cells",
+ "model_non_human",
+ "model_cell_culture",
+ "rescue_human_control",
+ "rescue_non_human_model",
+ "rescue_cell_culture",
+ "rescue_patient_cells"
+ ],
+ "enum_descriptions": {
+ "biochemical_function": "Biochemical function of the gene or repeat",
+ "protein_interaction": "Protein–protein interaction evidence",
+ "regulatory_impact": "Impact on regulation such as splicing, expression, or epigenetics",
+ "functional_alteration_patient_cells": "Functional alteration observed in patient-derived cells",
+ "functional_alteration_non_patient_cells": "Functional alteration observed in non-patient cells",
+ "model_non_human": "Evidence from non-human model organisms",
+ "model_cell_culture": "Evidence from cultured cell models",
+ "rescue_human_control": "Rescue experiments in healthy human cells",
+ "rescue_non_human_model": "Rescue experiments in non-human model organisms",
+ "rescue_cell_culture": "Rescue experiments in cultured cells",
+ "rescue_patient_cells": "Rescue experiments in patient-derived cells"
+ }
+ },
+
+ "notes": {
+ "title": "Notes",
+ "description": "Additional notes about the experimental evidence including point upgrades/downgrades",
+ "type": ["string", "null"],
+ "multiline": true
+ },
+
+ "points": {
+ "title": "Points",
+ "description": "Points assigned based on the experimental evidence provided in the source",
+ "type": ["number"],
+ "minimum": 0
+ }
+ },
+ "required": ["type", "points"],
+ "allOf": [
+ {
+ "if": {
+ "properties": {
+ "type": {
+ "enum": [
+ "biochemical_function",
+ "protein_interaction",
+ "regulatory_impact"
+ ]
+ }
+ }
+ },
+ "then": {
+ "properties": {
+ "points": {
+ "default": 0.5,
+ "maximum": 2.0,
+ "scoring_range": "0–2",
+ "upgrades": "Multiple forms of evidence"
+ }
+ }
+ }
+ },
+ {
+ "if": {
+ "properties": {
+ "type": { "const": "functional_alteration_patient_cells" }
+ }
+ },
+ "then": {
+ "properties": {
+ "points": {
+ "default": 1.0,
+ "maximum": 2.0,
+ "scoring_range": "0–2",
+ "upgrades": "Multiple patient cells studied"
+ }
+ }
+ }
+ },
+ {
+ "if": {
+ "properties": {
+ "type": { "const": "functional_alteration_non_patient_cells" }
+ }
+ },
+ "then": {
+ "properties": {
+ "points": {
+ "default": 0.5,
+ "maximum": 1.0,
+ "scoring_range": "0–1",
+ "upgrades": "Multiple control cell types"
+ }
+ }
+ }
+ },
+ {
+ "if": {
+ "properties": {
+ "type": { "const": "model_non_human" }
+ }
+ },
+ "then": {
+ "properties": {
+ "points": {
+ "default": 2.0,
+ "maximum": 4.0,
+ "scoring_range": "0–4",
+ "upgrades": "Multiple model organisms"
+ }
+ }
+ }
+ },
+ {
+ "if": {
+ "properties": {
+ "type": { "const": "model_cell_culture" }
+ }
+ },
+ "then": {
+ "properties": {
+ "points": {
+ "default": 1.0,
+ "maximum": 2.0,
+ "scoring_range": "0–2",
+ "upgrades": "Multiple cell types cultured"
+ }
+ }
+ }
+ },
+ {
+ "if": {
+ "properties": {
+ "type": {
+ "enum": [
+ "rescue_human_control",
+ "rescue_non_human_model"
+ ]
+ }
+ }
+ },
+ "then": {
+ "properties": {
+ "points": {
+ "default": 2.0,
+ "maximum": 4.0,
+ "scoring_range": "0–4",
+ "upgrades": "Multiple rescue systems"
+ }
+ }
+ }
+ },
+ {
+ "if": {
+ "properties": {
+ "type": {
+ "enum": [
+ "rescue_cell_culture",
+ "rescue_patient_cells"
+ ]
+ }
+ }
+ },
+ "then": {
+ "properties": {
+ "points": {
+ "default": 1.0,
+ "maximum": 2.0,
+ "scoring_range": "0–2",
+ "upgrades": "Multiple rescue approaches"
+ }
+ }
+ }
+ }
+ ]
+ }
+ }
+ },
+
+ "required": ["id", "citation"]
+}
diff --git a/site/bun.lock b/site/bun.lock
index 8b0d5a93..0ac05398 100644
--- a/site/bun.lock
+++ b/site/bun.lock
@@ -15,6 +15,7 @@
"astro-google-analytics": "^1.0.3",
"clsx": "^2.1.1",
"echarts": "^6.0.0",
+ "expr-eval": "^2.0.2",
"json-schema-library": "^10.2.1",
"lodash-es": "^4.17.21",
"plotly.js-dist": "^3.1.0",
@@ -582,6 +583,8 @@
"eventemitter3": ["eventemitter3@5.0.1", "", {}, "sha512-GWkBvjiSZK87ELrYOSESUYeVIc9mvLLf/nXalMOS5dYrgZq9o5OVkbZAVM06CVxYsCwH9BDZFPlQTlPA1j4ahA=="],
+ "expr-eval": ["expr-eval@2.0.2", "", {}, "sha512-4EMSHGOPSwAfBiibw3ndnP0AvjDWLsMvGOvWEZ2F96IGk0bIVdjQisOHxReSkE13mHcfbuCiXw+G4y0zv6N8Eg=="],
+
"extend": ["extend@3.0.2", "", {}, "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g=="],
"fast-copy": ["fast-copy@3.0.2", "", {}, "sha512-dl0O9Vhju8IrcLndv2eU4ldt1ftXMqqfgN4H1cpmGV7P6jeB9FwpN9a2c8DPGE1Ys88rNUJVYDHq73CGAGOPfQ=="],
diff --git a/site/package.json b/site/package.json
index 1214df65..b031cded 100755
--- a/site/package.json
+++ b/site/package.json
@@ -22,6 +22,7 @@
"astro-google-analytics": "^1.0.3",
"clsx": "^2.1.1",
"echarts": "^6.0.0",
+ "expr-eval": "^2.0.2",
"json-schema-library": "^10.2.1",
"lodash-es": "^4.17.21",
"plotly.js-dist": "^3.1.0",
diff --git a/site/src/components/SchemaForm.jsx b/site/src/components/SchemaForm.jsx
index 93a507e6..4498e094 100644
--- a/site/src/components/SchemaForm.jsx
+++ b/site/src/components/SchemaForm.jsx
@@ -1,5 +1,12 @@
-import { cloneElement, Fragment, useMemo } from "react";
+import {
+ cloneElement,
+ createContext,
+ Fragment,
+ useContext,
+ useMemo,
+} from "react";
import { FaArrowDown, FaArrowUp, FaPlus, FaTrash } from "react-icons/fa6";
+import { Parser } from "expr-eval";
import { compileSchema, draft2020, extendDraft } from "json-schema-library";
import {
cloneDeep,
@@ -18,24 +25,31 @@ import NumberBox from "@/components/NumberBox";
import Select from "@/components/Select";
import TextBox from "@/components/TextBox";
import { makeList } from "@/util/format";
+import { usePrevious } from "@/util/hooks";
+import { sleep } from "@/util/misc";
import classes from "./SchemaForm.module.css";
+/** calc expression parser */
+const parser = new Parser();
+
+const FormContext = createContext({});
+
/** form automatically generated from json schema */
const SchemaForm = ({ schema, sections, data, onChange, children }) => {
/** compile schema */
- const rootNode = useMemo(
+ const root = useMemo(
() => compileSchema(schema, { drafts: [draft] }),
[schema],
);
/** if no initial data, generate blank values */
- data ??= rootNode.getData(null, { extendDefaults: false });
+ data ??= root.getData(null, { extendDefaults: false });
/** revalidate when data changes */
const { errors } = useMemo(() => {
- rootNode.context.data = data;
- return rootNode.validate(data);
- }, [rootNode, data]);
+ root.context.data = data;
+ return root.validate(data);
+ }, [root, data]);
return (
<>
@@ -45,15 +59,11 @@ const SchemaForm = ({ schema, sections, data, onChange, children }) => {