Skip to content

Commit dbfb270

Browse files
committed
major changes to the web and clean specification
1 parent 015769c commit dbfb270

File tree

9 files changed

+1852
-1713
lines changed

9 files changed

+1852
-1713
lines changed

DEVELOPMENT.md

Lines changed: 29 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ proteomics-metadata-standard/
1414
│ ├── inject-headers.py # Navigation header injection
1515
│ ├── transform-links.py # .adoc→.html link transformation
1616
│ ├── transform-sdrf-tables.py # SDRF example table styling
17+
│ ├── build_sdrf_builder_data.py # SDRF builder JSON data compiler
1718
│ └── add-dev-banner.sh # Dev version banner
1819
├── sdrf-proteomics/ # Main specification source
1920
│ ├── README.adoc # Main specification (AsciiDoc)
@@ -28,8 +29,9 @@ proteomics-metadata-standard/
2829
├── site/ # Website assets and build scripts
2930
│ ├── css/style.css # Main stylesheet
3031
│ ├── js/search.js # Search functionality
32+
│ ├── js/sdrf-builder.js # Interactive SDRF builder logic
3133
│ ├── index.html # Homepage
32-
│ ├── quickstart.html # Quick start guide
34+
│ ├── quickstart.html # Quick start guide + interactive builder
3335
│ ├── sdrf-explorer.html # SDRF dataset explorer
3436
│ ├── sdrf-terms.html # Terms reference page
3537
│ ├── build-sdrf-index.py # Builds dataset index
@@ -354,6 +356,32 @@ npm run build -- --configuration=production
354356
# (temporarily change jsDelivr URLs to local paths for testing)
355357
```
356358

359+
## Interactive SDRF Builder
360+
361+
The quickstart page (`/quickstart.html`) includes an interactive SDRF template builder that helps users create custom SDRF templates by answering questions.
362+
363+
### How It Works
364+
365+
1. **Build time:** `scripts/build_sdrf_builder_data.py` resolves all YAML templates and sdrf-terms.tsv into `sdrf-builder-data.json`
366+
2. **Runtime:** `site/js/sdrf-builder.js` loads the JSON and renders a branching questionnaire
367+
3. Users select technology, organism, experiment type, review columns, and download a TSV
368+
369+
### Builder Files
370+
371+
| File | Purpose |
372+
|------|---------|
373+
| `scripts/build_sdrf_builder_data.py` | Compiles YAML templates + terms into JSON |
374+
| `site/js/sdrf-builder.js` | Builder UI logic (vanilla JS) |
375+
| `site/quickstart.html` | Page with simplified intro + builder scaffold |
376+
| `site/css/style.css` | Builder-specific styles (`.builder-*`, `.option-*`) |
377+
378+
### Updating the Builder
379+
380+
When templates change (new columns, new templates):
381+
1. The build script automatically recompiles `sdrf-builder-data.json`
382+
2. No JS changes needed unless new template layers or combination rules are added
383+
3. New templates appear automatically in the appropriate step
384+
357385
## Contributing
358386

359387
1. Make changes to the appropriate AsciiDoc or HTML files

scripts/build-docs.sh

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -160,6 +160,7 @@ cp site/css/style.css "$OUTPUT_DIR/css/"
160160

161161
# Copy JavaScript
162162
cp site/js/search.js "$OUTPUT_DIR/js/"
163+
cp site/js/sdrf-builder.js "$OUTPUT_DIR/js/"
163164

164165
# Copy images
165166
cp -r sdrf-proteomics/images/* "$OUTPUT_DIR/images/" 2>/dev/null || true
@@ -173,8 +174,9 @@ cp site/quickstart.html "$OUTPUT_DIR/"
173174
cp site/sdrf-explorer.html "$OUTPUT_DIR/"
174175
cp site/sdrf-editor.html "$OUTPUT_DIR/"
175176

176-
# Copy SDRF terms TSV (if present)
177+
# Copy SDRF terms TSV (if present) — also create TERMS.tsv alias for AsciiDoc links
177178
cp sdrf-proteomics/metadata-guidelines/sdrf-terms.tsv "$OUTPUT_DIR/" 2>/dev/null || true
179+
cp sdrf-proteomics/TERMS.tsv "$OUTPUT_DIR/TERMS.tsv" 2>/dev/null || true
178180

179181
# Auto-generate index.html template section from YAML
180182
echo "Updating index template section..."
@@ -198,6 +200,11 @@ python3 scripts/transform-links.py "$OUTPUT_DIR"
198200
echo "Transforming SDRF example tables..."
199201
python3 scripts/transform-sdrf-tables.py "$OUTPUT_DIR"
200202

203+
# Build SDRF builder data
204+
echo "Building SDRF builder data..."
205+
python3 scripts/build_sdrf_builder_data.py \
206+
sdrf-proteomics/sdrf-templates "$OUTPUT_DIR/sdrf-builder-data.json"
207+
201208
# Build search index
202209
echo "Building search index..."
203210
python3 site/build-search-index.py . "$OUTPUT_DIR/search-index.json"

scripts/build_sdrf_builder_data.py

Lines changed: 190 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,190 @@
1+
#!/usr/bin/env python3
2+
"""Build JSON data file for the interactive SDRF builder UI.
3+
4+
Compiles YAML template definitions into a single JSON file containing
5+
resolved templates, combination rules, and term definitions.
6+
7+
Usage:
8+
python3 build_sdrf_builder_data.py <sdrf-templates-dir> <output-json-path>
9+
"""
10+
11+
import csv
12+
import json
13+
import sys
14+
from pathlib import Path
15+
from typing import Any
16+
17+
sys.path.insert(0, str(Path(__file__).parent))
18+
from resolve_templates import resolve_all
19+
20+
# Fallback example values for well-known column names
21+
FALLBACK_EXAMPLES: dict[str, str] = {
22+
"source name": "sample_1",
23+
"assay name": "run_1",
24+
"comment[data file]": "sample1.raw",
25+
"comment[fraction identifier]": "1",
26+
"comment[technical replicate]": "1",
27+
"comment[label]": "label free sample",
28+
}
29+
30+
31+
def _compact_validators(validators: list[dict] | None) -> list[dict]:
32+
"""Return a compact representation of validators."""
33+
if not validators:
34+
return []
35+
result = []
36+
for v in validators:
37+
compact: dict[str, Any] = {"type": v.get("validator_name", "")}
38+
params = v.get("params", {})
39+
if params:
40+
compact["params"] = params
41+
result.append(compact)
42+
return result
43+
44+
45+
def _example_value(column: dict) -> str:
46+
"""Derive an example value for a column from validators or fallbacks."""
47+
validators = column.get("validators") or []
48+
for v in validators:
49+
params = v.get("params", {})
50+
if params.get("examples"):
51+
return str(params["examples"][0])
52+
if params.get("values"):
53+
return str(params["values"][0])
54+
return FALLBACK_EXAMPLES.get(column.get("name", ""), "")
55+
56+
57+
def _serialize_column(col: dict) -> dict:
58+
"""Serialize a single column dict for JSON output."""
59+
return {
60+
"name": col.get("name", ""),
61+
"requirement": col.get("requirement", "optional"),
62+
"description": col.get("description", ""),
63+
"ontology_accession": col.get("ontology_accession", ""),
64+
"cardinality": col.get("cardinality", "single"),
65+
"allow_not_applicable": col.get("allow_not_applicable", False),
66+
"allow_not_available": col.get("allow_not_available", False),
67+
"source_template": col.get("source_template", ""),
68+
"validators": _compact_validators(col.get("validators")),
69+
"example_value": _example_value(col),
70+
}
71+
72+
73+
def _extract_combination_rules(
74+
all_templates: dict[str, dict],
75+
) -> dict[str, Any]:
76+
"""Extract combination rules from all resolved templates."""
77+
# Deduplicate mutually_exclusive groups (stored as frozensets)
78+
exclusive_groups: set[frozenset[str]] = set()
79+
requires: dict[str, list[str]] = {}
80+
excludes: dict[str, list[str]] = {}
81+
82+
for name, tpl in all_templates.items():
83+
me = tpl.get("mutually_exclusive_with") or []
84+
if me:
85+
group = frozenset([name] + list(me))
86+
exclusive_groups.add(group)
87+
88+
req = tpl.get("requires")
89+
if req:
90+
requires[name] = list(req) if isinstance(req, list) else [req]
91+
92+
exc = tpl.get("excludes")
93+
if exc:
94+
excludes[name] = list(exc) if isinstance(exc, list) else [exc]
95+
96+
return {
97+
"mutually_exclusive": [sorted(g) for g in exclusive_groups],
98+
"requires": requires,
99+
"excludes": excludes,
100+
}
101+
102+
103+
def _load_terms(repo_root: Path) -> list[dict]:
104+
"""Load sdrf-terms.tsv from known locations."""
105+
candidates = [
106+
repo_root / "sdrf-proteomics" / "metadata-guidelines" / "sdrf-terms.tsv",
107+
repo_root / "local-info" / "demo_web" / "sdrf-terms.tsv",
108+
]
109+
terms_path = None
110+
for p in candidates:
111+
if p.exists():
112+
terms_path = p
113+
break
114+
115+
if terms_path is None:
116+
print("Warning: sdrf-terms.tsv not found, terms list will be empty.")
117+
return []
118+
119+
terms: list[dict] = []
120+
with open(terms_path, newline="") as f:
121+
reader = csv.DictReader(f, delimiter="\t")
122+
for row in reader:
123+
terms.append(
124+
{
125+
"term": row.get("term", ""),
126+
"type": row.get("type", ""),
127+
"description": row.get("description", ""),
128+
"values": row.get("values", ""),
129+
"ontology_accession": row.get("ontology_term_accession", ""),
130+
"allow_not_available": row.get("allow_not_available", "false")
131+
== "true",
132+
"allow_not_applicable": row.get("allow_not_applicable", "false")
133+
== "true",
134+
}
135+
)
136+
print(f"Loaded {len(terms)} terms from {terms_path}")
137+
return terms
138+
139+
140+
def main() -> None:
141+
if len(sys.argv) < 3:
142+
print(
143+
"Usage: python3 build_sdrf_builder_data.py "
144+
"<sdrf-templates-dir> <output-json-path>"
145+
)
146+
sys.exit(1)
147+
148+
templates_dir = Path(sys.argv[1])
149+
output_path = Path(sys.argv[2])
150+
output_path.parent.mkdir(parents=True, exist_ok=True)
151+
152+
repo_root = Path(__file__).resolve().parent.parent
153+
154+
# Resolve all templates
155+
all_templates = resolve_all(templates_dir)
156+
print(f"Resolved {len(all_templates)} templates")
157+
158+
# Serialize templates
159+
templates_json: dict[str, dict] = {}
160+
for name, tpl in all_templates.items():
161+
templates_json[name] = {
162+
"description": tpl.get("description", ""),
163+
"layer": tpl.get("layer"),
164+
"usable_alone": tpl.get("usable_alone", False),
165+
"extends": tpl.get("extends"),
166+
"inheritance_chain": tpl.get("inheritance_chain", []),
167+
"columns": [_serialize_column(c) for c in tpl["all_columns"]],
168+
"own_columns": [_serialize_column(c) for c in tpl["own_columns"]],
169+
}
170+
171+
# Extract combination rules
172+
combination_rules = _extract_combination_rules(all_templates)
173+
174+
# Load terms
175+
terms = _load_terms(repo_root)
176+
177+
# Write output
178+
output = {
179+
"templates": templates_json,
180+
"combination_rules": combination_rules,
181+
"terms": terms,
182+
}
183+
with open(output_path, "w") as f:
184+
json.dump(output, f, indent=2)
185+
186+
print(f"Wrote builder data to {output_path}")
187+
188+
189+
if __name__ == "__main__":
190+
main()

scripts/inject-headers.py

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,11 @@
2222

2323
'guidelines': '''<header class="doc-header"><div class="doc-header-brand"><a href="../index.html">SDRF-Proteomics</a></div><nav class="doc-header-nav"><a href="../index.html">Home</a><a href="../specification.html">Specification</a><a href="../index.html#metadata-guidelines" class="nav-current">Metadata Guidelines</a><a href="../index.html#templates">Templates</a><a href="../index.html#tools">Tools</a><a href="../sdrf-explorer.html">Explorer</a><a href="../sdrf-editor.html">Editor</a><a href="../index.html#contributors">Contributors</a><a href="/dev/" class="version-link">Dev Version</a><a href="https://github.com/bigbio/proteomics-metadata-standard" target="_blank">GitHub</a></nav></header>''',
2424

25-
'templates': '''<header class="doc-header"><div class="doc-header-brand"><a href="../index.html">SDRF-Proteomics</a></div><nav class="doc-header-nav"><a href="../index.html">Home</a><a href="../specification.html">Specification</a><a href="../index.html#metadata-guidelines">Metadata Guidelines</a><a href="../index.html#templates" class="nav-current">Templates</a><a href="../index.html#tools">Tools</a><a href="../sdrf-explorer.html">Explorer</a><a href="../sdrf-editor.html">Editor</a><a href="../index.html#contributors">Contributors</a><a href="/dev/" class="version-link">Dev Version</a><a href="https://github.com/bigbio/proteomics-metadata-standard" target="_blank">GitHub</a></nav></header>'''
25+
'templates': '''<header class="doc-header"><div class="doc-header-brand"><a href="../index.html">SDRF-Proteomics</a></div><nav class="doc-header-nav"><a href="../index.html">Home</a><a href="../specification.html">Specification</a><a href="../index.html#metadata-guidelines">Metadata Guidelines</a><a href="../index.html#templates" class="nav-current">Templates</a><a href="../index.html#tools">Tools</a><a href="../sdrf-explorer.html">Explorer</a><a href="../sdrf-editor.html">Editor</a><a href="../index.html#contributors">Contributors</a><a href="/dev/" class="version-link">Dev Version</a><a href="https://github.com/bigbio/proteomics-metadata-standard" target="_blank">GitHub</a></nav></header>''',
26+
27+
'sample_guidelines': '''<header class="doc-header"><div class="doc-header-brand"><a href="./index.html">SDRF-Proteomics</a></div><nav class="doc-header-nav"><a href="./index.html">Home</a><a href="./specification.html">Specification</a><a href="./index.html#metadata-guidelines" class="nav-current">Metadata Guidelines</a><a href="./index.html#templates">Templates</a><a href="./index.html#tools">Tools</a><a href="./sdrf-explorer.html">Explorer</a><a href="./sdrf-editor.html">Editor</a><a href="./index.html#contributors">Contributors</a><a href="/dev/" class="version-link">Dev Version</a><a href="https://github.com/bigbio/proteomics-metadata-standard" target="_blank">GitHub</a></nav></header>''',
28+
29+
'templates_guide': '''<header class="doc-header"><div class="doc-header-brand"><a href="./index.html">SDRF-Proteomics</a></div><nav class="doc-header-nav"><a href="./index.html">Home</a><a href="./specification.html">Specification</a><a href="./index.html#metadata-guidelines">Metadata Guidelines</a><a href="./index.html#templates" class="nav-current">Templates</a><a href="./index.html#tools">Tools</a><a href="./sdrf-explorer.html">Explorer</a><a href="./sdrf-editor.html">Editor</a><a href="./index.html#contributors">Contributors</a><a href="/dev/" class="version-link">Dev Version</a><a href="https://github.com/bigbio/proteomics-metadata-standard" target="_blank">GitHub</a></nav></header>'''
2630
}
2731

2832

@@ -65,13 +69,13 @@ def main():
6569
sg_file = output_dir / "sample-guidelines.html"
6670
if sg_file.exists():
6771
print(f"Injecting header into: {sg_file}")
68-
inject_header(str(sg_file), HEADERS['root'])
72+
inject_header(str(sg_file), HEADERS['sample_guidelines'])
6973

7074
# Inject header into templates.html (templates guide)
7175
tpl_guide = output_dir / "templates.html"
7276
if tpl_guide.exists():
7377
print(f"Injecting header into: {tpl_guide}")
74-
inject_header(str(tpl_guide), HEADERS['root'])
78+
inject_header(str(tpl_guide), HEADERS['templates_guide'])
7579

7680
# Inject headers into metadata-guidelines pages
7781
guidelines_dir = output_dir / "metadata-guidelines"

0 commit comments

Comments
 (0)