-
Notifications
You must be signed in to change notification settings - Fork 116
Major refactoring to improve in examples for metaproteomics and in the PSI file generation #805
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 12 commits
4ccec87
4e56ad2
4dcafff
219deb9
db09d60
52b18cb
0da669a
8c99f99
6ca4224
4980afa
ccc0e2f
7412e87
3a9de5c
2c21c80
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
| @@ -1,12 +1,12 @@ | ||||||
| # Proteomics Sample Metadata Format | ||||||
|
|
||||||
| [](CHANGELOG.md) | ||||||
| [](https://github.com/bigbio/proteomics-sample-metadata/blob/master/LICENSE) | ||||||
| [](https://github.com/bigbio/proteomics-sample-metadata/issues) | ||||||
| [](https://github.com/bigbio/proteomics-sample-metadata/pulls) | ||||||
|  | ||||||
|  | ||||||
|  | ||||||
| [](https://github.com/bigbio/proteomics-metadata-standard/blob/master/LICENSE) | ||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Point the badge at the local Line 4 is the exact URL Lychee is failing on with 429s. Using the repo-local file removes the external GitHub request and keeps the badge on the current branch. 🔧 Suggested change-[](https://github.com/bigbio/proteomics-metadata-standard/blob/master/LICENSE)
+[](LICENSE)📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||
| [](https://github.com/bigbio/proteomics-metadata-standard/issues) | ||||||
| [](https://github.com/bigbio/proteomics-metadata-standard/pulls) | ||||||
|  | ||||||
|  | ||||||
|  | ||||||
| [](llms.txt) | ||||||
|
|
||||||
| ## Improving metadata annotation of Proteomics datasets | ||||||
|
|
@@ -45,7 +45,7 @@ In the [annotated projects](https://github.com/bigbio/proteomics-metadata-standa | |||||
| Annotate a dataset in 5 steps: | ||||||
|
|
||||||
| - Read the [SDRF-Proteomics specification](https://github.com/bigbio/proteomics-metadata-standard/tree/master/sdrf-proteomics). | ||||||
| - Depending on the type of dataset, choose the appropriate [sample template](https://github.com/bigbio/proteomics-sample-metadata/tree/master/sdrf-proteomics#sdrf-templates). | ||||||
| - Depending on the type of dataset, choose the appropriate [sample template](https://github.com/bigbio/proteomics-metadata-standard/tree/master/sdrf-proteomics#sdrf-templates). | ||||||
| - Annotate the corresponding ProteomeXchange PXD dataset following the guidelines. | ||||||
| - Validate your SDRF file: | ||||||
|
|
||||||
|
|
||||||
Large diffs are not rendered by default.
Large diffs are not rendered by default.
Large diffs are not rendered by default.
| Original file line number | Diff line number | Diff line change | ||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,294 @@ | ||||||||||||||
| #!/usr/bin/env python3 | ||||||||||||||
| """Generate AsciiDoc template definitions and inject into README.adoc. | ||||||||||||||
|
|
||||||||||||||
| Reads all YAML templates from sdrf-templates/ and injects a "Template Definitions" | ||||||||||||||
| section directly into README.adoc, before the "Intellectual Property Statement" | ||||||||||||||
| section. This keeps the PDF in sync with YAML templates without a separate file. | ||||||||||||||
|
|
||||||||||||||
| Usage: | ||||||||||||||
| python scripts/generate_templates_appendix.py [--templates-dir PATH] [--readme PATH] | ||||||||||||||
| """ | ||||||||||||||
|
|
||||||||||||||
| from __future__ import annotations | ||||||||||||||
|
|
||||||||||||||
| import argparse | ||||||||||||||
| import re | ||||||||||||||
| import sys | ||||||||||||||
| from pathlib import Path | ||||||||||||||
| from typing import Any | ||||||||||||||
|
|
||||||||||||||
| # Add scripts dir to path so we can import resolve_templates | ||||||||||||||
| sys.path.insert(0, str(Path(__file__).parent)) | ||||||||||||||
|
|
||||||||||||||
| from resolve_templates import load_manifest, load_template_yaml | ||||||||||||||
|
|
||||||||||||||
| # Marker used to identify the injected section | ||||||||||||||
| MARKER_START = "// AUTO-GENERATED: Template Definitions (do not edit below this line)" | ||||||||||||||
| MARKER_END = "// AUTO-GENERATED: End of Template Definitions" | ||||||||||||||
|
|
||||||||||||||
| # Injection point: insert before this heading | ||||||||||||||
| INJECT_BEFORE = "== Intellectual Property Statement" | ||||||||||||||
|
|
||||||||||||||
| # Ordered template groups for the appendix | ||||||||||||||
| TEMPLATE_ORDER: list[list[str]] = [ | ||||||||||||||
| # Infrastructure | ||||||||||||||
| ["base", "sample-metadata"], | ||||||||||||||
| # Technology | ||||||||||||||
| ["ms-proteomics", "affinity-proteomics"], | ||||||||||||||
| # Sample (organism) | ||||||||||||||
| ["human", "vertebrates", "invertebrates", "plants"], | ||||||||||||||
| # Sample (study type) | ||||||||||||||
| ["clinical-metadata", "oncology-metadata"], | ||||||||||||||
| # Experiment (MS) | ||||||||||||||
| ["dia-acquisition", "single-cell", "immunopeptidomics", "crosslinking", "cell-lines"], | ||||||||||||||
| # Experiment (affinity) | ||||||||||||||
| ["olink", "somascan"], | ||||||||||||||
| # Metaproteomics branch | ||||||||||||||
| ["metaproteomics", "human-gut", "soil", "water"], | ||||||||||||||
| ] | ||||||||||||||
|
|
||||||||||||||
|
|
||||||||||||||
| def _escape_adoc(text: str) -> str: | ||||||||||||||
| """Escape special AsciiDoc characters in table cells.""" | ||||||||||||||
| return text.replace("|", "\\|") | ||||||||||||||
|
|
||||||||||||||
|
|
||||||||||||||
| def _summarize_validators(validators: list[dict[str, Any]]) -> str: | ||||||||||||||
| """Produce a short human-readable summary of column validators.""" | ||||||||||||||
| if not validators: | ||||||||||||||
| return "" | ||||||||||||||
|
|
||||||||||||||
| parts: list[str] = [] | ||||||||||||||
| for v in validators: | ||||||||||||||
| vname = v.get("validator_name", "") | ||||||||||||||
| params = v.get("params", {}) | ||||||||||||||
|
|
||||||||||||||
| if vname == "ontology": | ||||||||||||||
| ontologies = params.get("ontologies", []) | ||||||||||||||
| parts.append(f"ontology: {', '.join(ontologies)}") | ||||||||||||||
| elif vname == "pattern": | ||||||||||||||
| desc = params.get("description", "") | ||||||||||||||
| if desc: | ||||||||||||||
| parts.append(f"pattern: {desc}") | ||||||||||||||
| else: | ||||||||||||||
| pat = params.get("pattern", "") | ||||||||||||||
| parts.append(f"pattern: `{pat}`") | ||||||||||||||
| elif vname == "values": | ||||||||||||||
| values = params.get("values", []) | ||||||||||||||
| if len(values) <= 5: | ||||||||||||||
| parts.append(f"values: {', '.join(str(v) for v in values)}") | ||||||||||||||
| else: | ||||||||||||||
| shown = ", ".join(str(v) for v in values[:4]) | ||||||||||||||
| parts.append(f"values: {shown}, ...") | ||||||||||||||
| elif vname == "number_with_unit": | ||||||||||||||
| units = params.get("units", []) | ||||||||||||||
| parts.append(f"number with unit ({', '.join(units)})") | ||||||||||||||
| elif vname == "single_cardinality_validator": | ||||||||||||||
| parts.append("single value only") | ||||||||||||||
| elif vname == "accession": | ||||||||||||||
| fmt = params.get("format", "") | ||||||||||||||
| parts.append(f"accession: {fmt}") | ||||||||||||||
|
Comment on lines
+88
to
+90
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Skip the empty accession suffix when When 🔧 Suggested change elif vname == "accession":
fmt = params.get("format", "")
- parts.append(f"accession: {fmt}")
+ parts.append(f"accession: {fmt}" if fmt else "accession")📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||
| elif vname == "mz_value": | ||||||||||||||
| parts.append("m/z value") | ||||||||||||||
| elif vname == "mz_range_interval": | ||||||||||||||
| parts.append("m/z range interval") | ||||||||||||||
| elif vname == "identifier": | ||||||||||||||
| parts.append("identifier") | ||||||||||||||
| else: | ||||||||||||||
| parts.append(vname) | ||||||||||||||
|
|
||||||||||||||
| return "; ".join(parts) | ||||||||||||||
|
|
||||||||||||||
|
|
||||||||||||||
| def _collect_examples(validators: list[dict[str, Any]]) -> str: | ||||||||||||||
| """Collect example values from validators.""" | ||||||||||||||
| examples: list[str] = [] | ||||||||||||||
| for v in validators: | ||||||||||||||
| params = v.get("params", {}) | ||||||||||||||
| for ex in params.get("examples", []): | ||||||||||||||
| ex_str = str(ex) | ||||||||||||||
| if ex_str not in examples: | ||||||||||||||
| examples.append(ex_str) | ||||||||||||||
|
|
||||||||||||||
| if not examples: | ||||||||||||||
| return "" | ||||||||||||||
| shown = examples[:4] | ||||||||||||||
| result = ", ".join(shown) | ||||||||||||||
| if len(examples) > 4: | ||||||||||||||
| result += ", ..." | ||||||||||||||
| return result | ||||||||||||||
|
|
||||||||||||||
|
|
||||||||||||||
| def _format_extends(extends: str | None) -> str: | ||||||||||||||
| """Format the extends field, stripping version constraint.""" | ||||||||||||||
| if not extends: | ||||||||||||||
| return "none" | ||||||||||||||
| return extends.split("@")[0] | ||||||||||||||
|
|
||||||||||||||
|
|
||||||||||||||
| def generate_template_section( | ||||||||||||||
| name: str, | ||||||||||||||
| tpl: dict[str, Any], | ||||||||||||||
| manifest_entry: dict[str, Any], | ||||||||||||||
| ) -> str: | ||||||||||||||
| """Generate AsciiDoc for a single template.""" | ||||||||||||||
| lines: list[str] = [] | ||||||||||||||
|
|
||||||||||||||
| # Heading | ||||||||||||||
| lines.append(f"=== {name}") | ||||||||||||||
| lines.append("") | ||||||||||||||
|
|
||||||||||||||
| # Metadata line | ||||||||||||||
| version = tpl.get("version", manifest_entry.get("latest", "")) | ||||||||||||||
| layer = tpl.get("layer") or manifest_entry.get("layer") or "internal" | ||||||||||||||
| extends = _format_extends( | ||||||||||||||
| tpl.get("extends") or manifest_entry.get("extends") | ||||||||||||||
| ) | ||||||||||||||
| usable_alone = tpl.get("usable_alone", manifest_entry.get("usable_alone", False)) | ||||||||||||||
|
|
||||||||||||||
| lines.append( | ||||||||||||||
| f"**Version:** {version} | " | ||||||||||||||
| f"**Layer:** {layer} | " | ||||||||||||||
| f"**Extends:** {extends} | " | ||||||||||||||
| f"**Usable alone:** {'Yes' if usable_alone else 'No'}" | ||||||||||||||
| ) | ||||||||||||||
| lines.append("") | ||||||||||||||
|
|
||||||||||||||
| # Description | ||||||||||||||
| desc = tpl.get("description", "") | ||||||||||||||
| if desc: | ||||||||||||||
| lines.append(_escape_adoc(desc.strip())) | ||||||||||||||
| lines.append("") | ||||||||||||||
|
|
||||||||||||||
| # Columns table | ||||||||||||||
| columns = tpl.get("columns", []) | ||||||||||||||
| if not columns: | ||||||||||||||
| lines.append("_No own columns defined (inherits all from parent)._") | ||||||||||||||
| lines.append("") | ||||||||||||||
| return "\n".join(lines) | ||||||||||||||
|
|
||||||||||||||
| lines.append('[cols="2,1,3,2,2", options="header"]') | ||||||||||||||
| lines.append("|===") | ||||||||||||||
| lines.append("| Column Name | Req. | Description | Validators | Examples") | ||||||||||||||
| lines.append("") | ||||||||||||||
|
|
||||||||||||||
| for col in columns: | ||||||||||||||
| col_name = col.get("name", "") | ||||||||||||||
| requirement = col.get("requirement", "") | ||||||||||||||
| col_desc = col.get("description", "") | ||||||||||||||
| validators = col.get("validators", []) | ||||||||||||||
|
|
||||||||||||||
| validator_summary = _summarize_validators(validators) | ||||||||||||||
| examples = _collect_examples(validators) | ||||||||||||||
|
|
||||||||||||||
| # If column is a minimal override (only name + requirement, no description), | ||||||||||||||
| # note it as an override | ||||||||||||||
| if not col_desc and requirement: | ||||||||||||||
| col_desc = f"_(override: requirement set to {requirement})_" | ||||||||||||||
|
|
||||||||||||||
|
Comment on lines
+184
to
+188
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Resolve inherited metadata before rendering override-only columns. This branch only handles requirement-only overrides. If a child template changes validators but inherits the requirement/description, the generated row ends up with empty Req./Description cells — that's already visible for 🤖 Prompt for AI Agents |
||||||||||||||
| lines.append(f"| `{_escape_adoc(col_name)}`") | ||||||||||||||
| lines.append(f"| {requirement}") | ||||||||||||||
| lines.append(f"| {_escape_adoc(col_desc)}") | ||||||||||||||
| lines.append(f"| {_escape_adoc(validator_summary)}") | ||||||||||||||
| lines.append(f"| {_escape_adoc(examples)}") | ||||||||||||||
| lines.append("") | ||||||||||||||
|
|
||||||||||||||
| lines.append("|===") | ||||||||||||||
| lines.append("") | ||||||||||||||
|
|
||||||||||||||
| return "\n".join(lines) | ||||||||||||||
|
|
||||||||||||||
|
|
||||||||||||||
| def generate_appendix(templates_dir: Path) -> str: | ||||||||||||||
| """Generate the full AsciiDoc appendix content.""" | ||||||||||||||
| manifest = load_manifest(templates_dir) | ||||||||||||||
|
|
||||||||||||||
| lines: list[str] = [] | ||||||||||||||
| lines.append(MARKER_START) | ||||||||||||||
| lines.append("") | ||||||||||||||
| lines.append("[[template-definitions]]") | ||||||||||||||
| lines.append("== Template Definitions") | ||||||||||||||
| lines.append("") | ||||||||||||||
| lines.append( | ||||||||||||||
| "This section provides the column definitions for each SDRF-Proteomics template. " | ||||||||||||||
| "Each template shows only its *own* columns (not inherited ones). " | ||||||||||||||
| 'See the "Extends" field to identify which parent template\'s columns are also included.' | ||||||||||||||
| ) | ||||||||||||||
| lines.append("") | ||||||||||||||
|
|
||||||||||||||
| # Flatten ordered list, skipping templates not in manifest | ||||||||||||||
| ordered_names: list[str] = [] | ||||||||||||||
| for group in TEMPLATE_ORDER: | ||||||||||||||
| for name in group: | ||||||||||||||
| if name in manifest: | ||||||||||||||
| ordered_names.append(name) | ||||||||||||||
|
|
||||||||||||||
| # Add any templates from manifest not in our explicit order | ||||||||||||||
| for name in manifest: | ||||||||||||||
| if name not in ordered_names: | ||||||||||||||
| ordered_names.append(name) | ||||||||||||||
|
|
||||||||||||||
| for name in ordered_names: | ||||||||||||||
| entry = manifest[name] | ||||||||||||||
| version = entry["latest"] | ||||||||||||||
| tpl = load_template_yaml(templates_dir, name, version) | ||||||||||||||
| section = generate_template_section(name, tpl, entry) | ||||||||||||||
| lines.append(section) | ||||||||||||||
|
|
||||||||||||||
| lines.append(MARKER_END) | ||||||||||||||
| return "\n".join(lines) | ||||||||||||||
|
|
||||||||||||||
|
|
||||||||||||||
| def inject_into_readme(readme_path: Path, appendix_content: str) -> None: | ||||||||||||||
| """Inject template definitions into README.adoc. | ||||||||||||||
|
|
||||||||||||||
| If markers from a previous run exist, replace that section. | ||||||||||||||
| Otherwise, insert before the 'Intellectual Property Statement' heading. | ||||||||||||||
| """ | ||||||||||||||
| readme_text = readme_path.read_text() | ||||||||||||||
|
|
||||||||||||||
| # Check if markers from a previous run exist | ||||||||||||||
| if MARKER_START in readme_text: | ||||||||||||||
| # Replace existing auto-generated section | ||||||||||||||
| pattern = re.escape(MARKER_START) + r".*?" + re.escape(MARKER_END) | ||||||||||||||
| readme_text = re.sub(pattern, appendix_content, readme_text, flags=re.DOTALL) | ||||||||||||||
| else: | ||||||||||||||
| # Insert before the injection point | ||||||||||||||
| if INJECT_BEFORE not in readme_text: | ||||||||||||||
| raise ValueError( | ||||||||||||||
| f"Could not find '{INJECT_BEFORE}' in {readme_path}. " | ||||||||||||||
| "Cannot determine where to inject template definitions." | ||||||||||||||
| ) | ||||||||||||||
| readme_text = readme_text.replace( | ||||||||||||||
| INJECT_BEFORE, | ||||||||||||||
| appendix_content + "\n\n" + INJECT_BEFORE, | ||||||||||||||
| ) | ||||||||||||||
|
|
||||||||||||||
| readme_path.write_text(readme_text) | ||||||||||||||
|
|
||||||||||||||
|
|
||||||||||||||
| def main() -> None: | ||||||||||||||
| parser = argparse.ArgumentParser( | ||||||||||||||
| description="Generate and inject template definitions into README.adoc." | ||||||||||||||
| ) | ||||||||||||||
| parser.add_argument( | ||||||||||||||
| "--templates-dir", | ||||||||||||||
| type=Path, | ||||||||||||||
| default=Path(__file__).parent.parent.parent / "sdrf-templates", | ||||||||||||||
| help="Path to sdrf-templates directory (default: ../../sdrf-templates)", | ||||||||||||||
| ) | ||||||||||||||
|
Comment on lines
+274
to
+279
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Make the default The workflow has to override this to 🔧 Suggested change parser.add_argument(
"--templates-dir",
type=Path,
- default=Path(__file__).parent.parent.parent / "sdrf-templates",
- help="Path to sdrf-templates directory (default: ../../sdrf-templates)",
+ default=Path(__file__).parent.parent / "sdrf-proteomics" / "sdrf-templates",
+ help="Path to sdrf-templates directory (default: sdrf-proteomics/sdrf-templates)",
)🤖 Prompt for AI Agents |
||||||||||||||
| parser.add_argument( | ||||||||||||||
| "--readme", | ||||||||||||||
| type=Path, | ||||||||||||||
| default=Path(__file__).parent.parent / "sdrf-proteomics" / "README.adoc", | ||||||||||||||
| help="Path to README.adoc to inject into", | ||||||||||||||
| ) | ||||||||||||||
| args = parser.parse_args() | ||||||||||||||
|
|
||||||||||||||
| appendix_content = generate_appendix(args.templates_dir) | ||||||||||||||
| inject_into_readme(args.readme, appendix_content) | ||||||||||||||
| print(f"Injected template definitions into {args.readme} ({len(appendix_content)} bytes)") | ||||||||||||||
|
|
||||||||||||||
|
|
||||||||||||||
| if __name__ == "__main__": | ||||||||||||||
| main() | ||||||||||||||
Uh oh!
There was an error while loading. Please reload this page.