From c7f7a015ac6e5710b0dff317906ca2bd07d4fab8 Mon Sep 17 00:00:00 2001
From: HendrikBorgelt <84382772+HendrikBorgelt@users.noreply.github.com>
Date: Mon, 9 Mar 2026 11:22:04 +0100
Subject: [PATCH 1/6] Logo file and changes in the converter
---
catcore_converter(1).py | 388 ---------------------------------
catcore_converter(3).py | 411 -----------------------------------
catcore_converter.py | 426 +++++++++++++------------------------
docs/characterization.md | 78 +++----
docs/coremeta4cat-users.md | 6 +-
docs/reaction.md | 34 +--
docs/simulation.md | 24 +--
docs/synthesis.md | 316 +++++++++++++--------------
mkdocs.yml | 2 +-
9 files changed, 374 insertions(+), 1311 deletions(-)
delete mode 100644 catcore_converter(1).py
delete mode 100644 catcore_converter(3).py
diff --git a/catcore_converter(1).py b/catcore_converter(1).py
deleted file mode 100644
index 111e294f2..000000000
--- a/catcore_converter(1).py
+++ /dev/null
@@ -1,388 +0,0 @@
-import yaml
-from pathlib import Path
-from typing import List, Set, Optional
-
-# catcore.yaml is loaded FIRST so its generic stubs (range: Plan, range: AgenticEntity)
-# are overwritten by the specific ranges in the subprofile modules.
-MODULE_FILES = [
- "catcore.yaml", # load first: generic stubs get overwritten by subprofiles
- "catcore_common.yaml",
- "catcore_synthesis_ap.yaml",
- "catcore_characterization_ap.yaml",
- "catcore_reaction_ap.yaml",
- "catcore_simulation_ap.yaml",
-]
-
-
-# ─────────────────────────────────────────────────────────────────────────────
-# Schema loading & merging
-# ─────────────────────────────────────────────────────────────────────────────
-
-def load_yaml_file(file_path: str) -> dict:
- path = Path(file_path)
- if not path.exists():
- print(f" [WARNING] Module not found, skipping: {file_path}")
- return {}
- with open(path, "r", encoding="utf-8") as f:
- return yaml.safe_load(f) or {}
-
-
-def merge_schemas(modules: List[dict]) -> dict:
- """
- Merge all module dicts into one flat schema.
- Later modules win on key collision, so subprofile modules (loaded last)
- correctly override the generic stubs in catcore.yaml (loaded first).
- """
- merged: dict = {"prefixes": {}, "classes": {}, "slots": {}, "enums": {}}
-
- for module in modules:
- if not module:
- continue
- for key in ("id", "name", "title", "description", "license",
- "version", "default_prefix", "default_range"):
- if key not in merged and key in module:
- merged[key] = module[key]
-
- for section in ("prefixes", "classes", "slots", "enums"):
- section_data = module.get(section) or {}
- for name, definition in section_data.items():
- if section == "classes" and name in merged["classes"]:
- existing = merged["classes"][name]
- incoming = definition or {}
- # slot_usage: merge entry by entry; incoming wins per key
- if "slot_usage" in incoming:
- existing.setdefault("slot_usage", {})
- existing["slot_usage"].update(incoming["slot_usage"])
- # slots list: union (incoming appends new names)
- if "slots" in incoming:
- existing_slots = existing.get("slots", [])
- for s in incoming["slots"]:
- if s not in existing_slots:
- existing_slots.append(s)
- existing["slots"] = existing_slots
- # all other keys: incoming wins
- for k, v in incoming.items():
- if k not in ("slot_usage", "slots"):
- existing[k] = v
- else:
- merged[section][name] = (
- dict(definition) if isinstance(definition, dict) else definition
- )
- return merged
-
-
-def load_merged_schema(schema_dir: str) -> dict:
- schema_dir_path = Path(schema_dir)
- modules = []
- for filename in MODULE_FILES:
- full_path = schema_dir_path / filename
- print(f" Loading: {full_path}")
- modules.append(load_yaml_file(str(full_path)))
- merged = merge_schemas(modules)
- print(f" Merged schema: {len(merged.get('classes', {}))} classes, "
- f"{len(merged.get('slots', {}))} slots\n")
- return merged
-
-
-# ─────────────────────────────────────────────────────────────────────────────
-# Mixin-aware slot resolution
-# ─────────────────────────────────────────────────────────────────────────────
-
-def get_all_class_slots(schema: dict, class_name: str,
- _seen: Optional[Set[str]] = None) -> List[str]:
- """Return all slots on a class including those contributed by mixins."""
- if _seen is None:
- _seen = set()
- if class_name in _seen:
- return []
- _seen.add(class_name)
- class_def = schema.get("classes", {}).get(class_name, {})
- own_slots = class_def.get("slots", []) or []
- mixin_names = class_def.get("mixins", []) or []
- mixin_slots: List[str] = []
- for mixin_name in mixin_names:
- for s in get_all_class_slots(schema, mixin_name, _seen.copy()):
- if s not in mixin_slots:
- mixin_slots.append(s)
- combined = list(own_slots)
- for s in mixin_slots:
- if s not in combined:
- combined.append(s)
- return combined
-
-
-def get_class_ranged_slot_usage(schema: dict, class_name: str) -> List[tuple]:
- """
- Return (slot_name, synthetic_slot_def) pairs for every slot_usage entry
- whose range points to a class defined in the merged schema.
-
- These are the 'gateway' slots that expand into nested class/subclass docs:
- Synthesis → realized_plan:PreparationMethod, had_input_entity:Precursor, had_output_entity:CatalystSample
- Characterization → realized_plan:CharacterizationTechnique
- Reaction → carried_out_by:ReactorDesignType, product_identification_method:ProductIdentificationMethod
- Simulation → realized_plan:SimulationMethod
- """
- classes = schema.get("classes", {})
- slots_dict = schema.get("slots", {})
- class_def = classes.get(class_name, {})
- slot_usage = class_def.get("slot_usage", {}) or {}
-
- result = []
- for su_name, su_def in slot_usage.items():
- if not su_def:
- continue
- rng = su_def.get("range")
- if not rng or rng not in classes:
- continue
- if classes.get(rng, {}).get("mixin"):
- continue
- # Build synthetic slot def: base slot (if any) + slot_usage overrides
- base = dict(slots_dict.get(su_name, {}))
- base.update({k: v for k, v in su_def.items() if v is not None})
- if not base.get("description"):
- base["description"] = f"Link to {rng} — see subclasses for details."
- base["range"] = rng
- result.append((su_name, base))
- return result
-
-
-# ─────────────────────────────────────────────────────────────────────────────
-# Standard helpers
-# ─────────────────────────────────────────────────────────────────────────────
-
-def is_mixin(schema: dict, class_name: str) -> bool:
- return bool(schema.get("classes", {}).get(class_name, {}).get("mixin", False))
-
-
-def get_subclasses(schema: dict, parent_class: str) -> List[str]:
- """Return all (recursive) non-mixin subclasses of parent_class."""
- subclasses = []
- for class_name, class_def in schema.get("classes", {}).items():
- if class_def.get("is_a") == parent_class and not is_mixin(schema, class_name):
- subclasses.append(class_name)
- subclasses.extend(get_subclasses(schema, class_name))
- return subclasses
-
-
-def get_slot_details(schema: dict, slot_name: str) -> dict:
- return schema.get("slots", {}).get(slot_name, {})
-
-
-def is_class_in_schema(schema: dict, class_name: str) -> bool:
- return class_name in schema.get("classes", {})
-
-
-def snake_to_readable(text: str) -> str:
- return text.replace("_", " ")
-
-
-def expand_curie(schema: dict, value: str) -> str:
- if ":" not in value or value.startswith("http"):
- return value
- prefix, local = value.split(":", 1)
- entry = schema.get("prefixes", {}).get(prefix)
- if not entry:
- return value
- if isinstance(entry, str):
- base = entry
- elif isinstance(entry, dict):
- base = entry.get("prefix_reference") or entry.get("uri") or entry.get("prefix") or ""
- else:
- return value
- if not base:
- return value
- return base + local if base.endswith(("/", "#")) else base + local
-
-
-def get_slot_cardinality(schema: dict, class_name: str,
- slot_name: str, slot_details: dict) -> str:
- class_def = schema.get("classes", {}).get(class_name, {})
- usage = (class_def.get("slot_usage", {}) or {}).get(slot_name, {}) or {}
- required = usage.get("required", slot_details.get("required", False))
- recommended = usage.get("recommended", slot_details.get("recommended", False))
- multivalued = usage.get("multivalued", slot_details.get("multivalued", False))
- parts = ["Required" if required else ("Recommended" if recommended else "Optional")]
- if multivalued:
- parts.append("Multivalued")
- return ", ".join(parts)
-
-
-# ─────────────────────────────────────────────────────────────────────────────
-# Markdown formatters
-# ─────────────────────────────────────────────────────────────────────────────
-
-def format_class_markdown(schema: dict, class_name: str, level: int = 3,
- processed_classes: Optional[Set[str]] = None,
- parent_class: Optional[str] = None) -> str:
- if processed_classes is None:
- processed_classes = set()
- if class_name in processed_classes:
- return ""
- processed_classes.add(class_name)
-
- class_def = schema.get("classes", {}).get(class_name, {})
- description = class_def.get("description", "No description available")
- class_uri = class_def.get("class_uri", "")
- is_abstract = class_def.get("abstract", False)
-
- md = '\n'
- md += f'{snake_to_readable(class_name)}
\n\n'
- if is_abstract:
- md += "**Abstract Class**\n\n"
- md += f"**Description:** {description}\n\n"
- if class_uri:
- md += f"**CURIE:** [`{class_uri}`]({expand_curie(schema, class_uri)})\n\n"
- md += f"**Schema Reference:** [{class_name}](./elements/{class_name}.md)\n\n"
-
- slots = get_all_class_slots(schema, class_name)
- if slots:
- md += "**Slots**\n\n"
- for slot_name in slots:
- slot_details = get_slot_details(schema, slot_name)
- md += format_slot_markdown(schema, slot_name, slot_details, level + 2,
- processed_classes.copy(), class_name)
-
- md += (f"\n \n'
- f" 💡 Submit Term Feedback\n \n
")
- md += " \n\n"
- return md
-
-
-def format_slot_markdown(schema: dict, slot_name: str, slot_details: dict,
- level: int = 3, processed_classes: Optional[Set[str]] = None,
- parent_class: Optional[str] = None) -> str:
- if processed_classes is None:
- processed_classes = set()
-
- description = slot_details.get("description", "No description available")
- range_type = slot_details.get("range", "string")
- slot_uri = slot_details.get("slot_uri", "")
- unit = slot_details.get("unit", {})
-
- cardinality = striped = ""
- if parent_class:
- cardinality = f" ({get_slot_cardinality(schema, parent_class, slot_name, slot_details)})"
- striped = cardinality.replace("(", "").replace(")", "")
-
- md = '\n'
- md += f"{snake_to_readable(slot_name)}{cardinality}
\n\n"
- md += f"**Description:** {description}\n\n"
- md += f"**Data Type:** {range_type}\n\n"
- md += f"**Cardinality:** {striped}\n\n"
- if slot_uri:
- md += f"**CURIE:** [`{slot_uri}`]({expand_curie(schema, slot_uri)})\n\n"
- md += f"**Schema Reference:** [{slot_name}](./elements/{slot_name}.md)\n\n"
- if unit and unit.get("ucum_code"):
- md += f"**Unit:** {unit['ucum_code']}\n\n"
-
- # If the range is a known schema class, expand it inline with all subclasses
- if is_class_in_schema(schema, range_type) and not is_mixin(schema, range_type):
- md += "**Data Type Class Details:**\n\n"
- md += format_class_markdown(schema, range_type, level, processed_classes, None)
- subclasses = get_subclasses(schema, range_type)
- if subclasses:
- md += f"**Possible Subclasses / Enumerations of {snake_to_readable(range_type)}:**\n\n"
- for subclass in subclasses:
- if subclass not in processed_classes:
- md += format_class_markdown(schema, subclass, level + 1,
- processed_classes, None)
-
- md += (f"\n \n'
- f" 💡 Submit Term Feedback\n \n
")
- md += " \n\n"
- return md
-
-
-LEGEND = """\
- **Legend**
-
-- **Description:**A short description for Comprehension purposes
-
-- **Data Type:** This specifies exactly what kind of information belongs in this field. Most simply, it could be a direct value, such as a number (float) or a piece of text (string). However, the Data Type can also point to another Class in the schema. When this happens, the field is not just a single value; it becomes a structured container. Designating a Class as the Data Type causes the field to contain a complete structured record, defined entirely by its own comprehensive collection of specific fields. Consequently, this allows for the systematic construction of complex data structures via organized, nested information layers within the broader schema architecture.
-
-- **Cardinality:** This controls how many entries a specific data field must have. It defines if a field is required or optional, and whether it can accept a single value versus a list of multiple values.
-
-- **CURIE:** A CURIE (Compact URI) is a short, easy-to-read reference that acts as a useful shortcut for a long, complex web address. Instead of seeing a full URL, you will see a two-part reference like gene:symbol, where the parts are separated by a colon. The first part is the prefix (a short code for the source website), and the second is the local identifier for the specific item. This structure is much easier to read and type, making the schema less cluttered and reducing errors. The full web link (URI) for the CURIE is always available if you click the provided link.
-
-- **Schema Reference:** This link directs you to the complete, technical documentation for this part of the schema. This detailed view is generated automatically by LinkML's documentation tool and provides all underlying rules, data types, and complex relationships for expert users and developers.
-
-- **Slots:** A Slot represents an individual data field or attribute that belongs to a specific Class (entity type) within the schema. If a Class defines an entity like 'Book', the Slots define the individual pieces of information about that book, such as the 'title', 'author', and 'ISBN'. Essentially, Slots are the essential building blocks that define the characteristics and permissible data for every record in the schema.
-
-- **Enumerations:** Often called an Enum, Enumerations are a predefined, fixed list of permissible values that a Slot can accept. It is used to strictly limit the available choices for a data field to ensure consistency and prevent errors. For example, a 'Status' field might be restricted to the Enumeration list of only 'Active', 'Inactive', or 'Pending'. Any data entered that is not on this limited list is considered invalid by the schema.
-
- """
-
-
-def generate_markdown_for_main_class(schema: dict, main_class: str, output_file: str):
- classes = schema.get("classes", {})
- main_class_def = classes.get(main_class, {})
- class_uri = main_class_def.get("class_uri", "")
- is_abstract = main_class_def.get("abstract", False)
-
- md = f"# {snake_to_readable(main_class)}\n\ntest description\n\n"
- if is_abstract:
- md += "**Abstract Class**\n\n"
- if class_uri:
- md += f"**CURIE:** [`{class_uri}`]({class_uri})\n\n"
-
- # Slots section: direct/mixin slots first, then class-ranged slot_usage entries
- direct_slots = get_all_class_slots(schema, main_class)
- class_ranged_su = get_class_ranged_slot_usage(schema, main_class)
-
- if direct_slots or class_ranged_su:
- md += LEGEND + "\n## Slots\n\n"
- processed: Set[str] = set()
- for slot_name in direct_slots:
- md += format_slot_markdown(schema, slot_name,
- get_slot_details(schema, slot_name),
- 3, processed.copy(), main_class)
- for slot_name, synthetic in class_ranged_su:
- md += format_slot_markdown(schema, slot_name, synthetic,
- 3, processed.copy(), main_class)
-
- md += (f'')
-
- with open(output_file, "w", encoding="utf-8") as f:
- f.write(md)
- print(f" ✓ {output_file}")
-
-
-# ─────────────────────────────────────────────────────────────────────────────
-# Entry point
-# ─────────────────────────────────────────────────────────────────────────────
-
-def main(schema_dir: str, output_dir: str = "."):
- print(f"\nLoading CatCore modules from: {schema_dir}")
- schema = load_merged_schema(schema_dir)
-
- output_path = Path(output_dir)
- output_path.mkdir(parents=True, exist_ok=True)
-
- main_classes = {
- "Synthesis": "synthesis.md",
- "Characterization": "characterization.md",
- "Reaction": "reaction.md",
- "Simulation": "simulation.md",
- }
-
- print(f"Generating Markdown docs in: {output_dir}")
- for main_class, filename in main_classes.items():
- generate_markdown_for_main_class(schema, main_class, str(output_path / filename))
-
- print(f"\n✓ All done — {len(main_classes)} files written to '{output_dir}'.")
-
-
-if __name__ == "__main__":
- # ── Edit these two paths to match your local setup ──────────────────────
- schema_dir = "./schema"
- output_dir = "./docs"
- # ────────────────────────────────────────────────────────────────────────
- main(schema_dir, output_dir)
-
diff --git a/catcore_converter(3).py b/catcore_converter(3).py
deleted file mode 100644
index cfde8acb4..000000000
--- a/catcore_converter(3).py
+++ /dev/null
@@ -1,411 +0,0 @@
-import yaml
-from pathlib import Path
-from typing import List, Set, Optional
-
-# catcore.yaml is loaded FIRST so its generic stubs (range: Plan, range: AgenticEntity)
-# are overwritten by the specific ranges in the subprofile modules.
-MODULE_FILES = [
- "catcore.yaml", # load first: generic stubs get overwritten by subprofiles
- "catcore_common.yaml",
- "catcore_synthesis_ap.yaml",
- "catcore_characterization_ap.yaml",
- "catcore_reaction_ap.yaml",
- "catcore_simulation_ap.yaml",
-]
-
-
-# ─────────────────────────────────────────────────────────────────────────────
-# Schema loading & merging
-# ─────────────────────────────────────────────────────────────────────────────
-
-def load_yaml_file(file_path: str) -> dict:
- path = Path(file_path)
- if not path.exists():
- print(f" [WARNING] Module not found, skipping: {file_path}")
- return {}
- with open(path, "r", encoding="utf-8") as f:
- return yaml.safe_load(f) or {}
-
-
-def merge_schemas(modules: List[dict]) -> dict:
- """
- Merge all module dicts into one flat schema.
- Later modules win on key collision, so subprofile modules (loaded last)
- correctly override the generic stubs in catcore.yaml (loaded first).
- """
- merged: dict = {"prefixes": {}, "classes": {}, "slots": {}, "enums": {}}
-
- for module in modules:
- if not module:
- continue
- for key in ("id", "name", "title", "description", "license",
- "version", "default_prefix", "default_range"):
- if key not in merged and key in module:
- merged[key] = module[key]
-
- for section in ("prefixes", "classes", "slots", "enums"):
- section_data = module.get(section) or {}
- for name, definition in section_data.items():
- if section == "classes" and name in merged["classes"]:
- existing = merged["classes"][name]
- incoming = definition or {}
- # slot_usage: merge entry by entry; incoming wins per key
- if "slot_usage" in incoming:
- existing.setdefault("slot_usage", {})
- existing["slot_usage"].update(incoming["slot_usage"])
- # slots list: union (incoming appends new names)
- if "slots" in incoming:
- existing_slots = existing.get("slots", [])
- for s in incoming["slots"]:
- if s not in existing_slots:
- existing_slots.append(s)
- existing["slots"] = existing_slots
- # all other keys: incoming wins
- for k, v in incoming.items():
- if k not in ("slot_usage", "slots"):
- existing[k] = v
- else:
- merged[section][name] = (
- dict(definition) if isinstance(definition, dict) else definition
- )
- return merged
-
-
-def load_merged_schema(schema_dir: str) -> dict:
- schema_dir_path = Path(schema_dir)
- modules = []
- for filename in MODULE_FILES:
- full_path = schema_dir_path / filename
- print(f" Loading: {full_path}")
- modules.append(load_yaml_file(str(full_path)))
- merged = merge_schemas(modules)
- print(f" Merged schema: {len(merged.get('classes', {}))} classes, "
- f"{len(merged.get('slots', {}))} slots\n")
- return merged
-
-
-# ─────────────────────────────────────────────────────────────────────────────
-# Mixin-aware slot resolution
-# ─────────────────────────────────────────────────────────────────────────────
-
-def get_all_class_slots(schema: dict, class_name: str,
- _seen: Optional[Set[str]] = None) -> List[str]:
- """Return all slots on a class including those contributed by mixins."""
- if _seen is None:
- _seen = set()
- if class_name in _seen:
- return []
- _seen.add(class_name)
- class_def = schema.get("classes", {}).get(class_name, {})
- own_slots = class_def.get("slots", []) or []
- mixin_names = class_def.get("mixins", []) or []
- mixin_slots: List[str] = []
- for mixin_name in mixin_names:
- for s in get_all_class_slots(schema, mixin_name, _seen.copy()):
- if s not in mixin_slots:
- mixin_slots.append(s)
- combined = list(own_slots)
- for s in mixin_slots:
- if s not in combined:
- combined.append(s)
- return combined
-
-
-def get_class_ranged_slot_usage(schema: dict, class_name: str) -> List[tuple]:
- """
- Return (slot_name, synthetic_slot_def) pairs for every slot_usage entry
- whose range points to a class defined in the merged schema.
-
- These are the 'gateway' slots that expand into nested class/subclass docs:
- Synthesis → realized_plan:PreparationMethod, had_input_entity:Precursor, had_output_entity:CatalystSample
- Characterization → realized_plan:CharacterizationTechnique
- Reaction → carried_out_by:ReactorDesignType, product_identification_method:ProductIdentificationMethod
- Simulation → realized_plan:SimulationMethod
- """
- classes = schema.get("classes", {})
- slots_dict = schema.get("slots", {})
- class_def = classes.get(class_name, {})
- slot_usage = class_def.get("slot_usage", {}) or {}
-
- result = []
- for su_name, su_def in slot_usage.items():
- if not su_def:
- continue
- rng = su_def.get("range")
- if not rng or rng not in classes:
- continue
- if classes.get(rng, {}).get("mixin"):
- continue
- # Build synthetic slot def: base slot (if any) + slot_usage overrides
- base = dict(slots_dict.get(su_name, {}))
- base.update({k: v for k, v in su_def.items() if v is not None})
- if not base.get("description"):
- base["description"] = f"Link to {rng} — see subclasses for details."
- base["range"] = rng
- result.append((su_name, base))
- return result
-
-
-# ─────────────────────────────────────────────────────────────────────────────
-# Standard helpers
-# ─────────────────────────────────────────────────────────────────────────────
-
-def is_mixin(schema: dict, class_name: str) -> bool:
- return bool(schema.get("classes", {}).get(class_name, {}).get("mixin", False))
-
-
-def get_subclasses(schema: dict, parent_class: str) -> List[str]:
- """Return all (recursive) non-mixin subclasses of parent_class."""
- subclasses = []
- for class_name, class_def in schema.get("classes", {}).items():
- if class_def.get("is_a") == parent_class and not is_mixin(schema, class_name):
- subclasses.append(class_name)
- subclasses.extend(get_subclasses(schema, class_name))
- return subclasses
-
-
-def get_slot_details(schema: dict, slot_name: str) -> dict:
- return schema.get("slots", {}).get(slot_name, {})
-
-
-def is_class_in_schema(schema: dict, class_name: str) -> bool:
- return class_name in schema.get("classes", {})
-
-
-def snake_to_readable(text: str) -> str:
- return text.replace("_", " ")
-
-
-def expand_curie(schema: dict, value: str) -> str:
- if ":" not in value or value.startswith("http"):
- return value
- prefix, local = value.split(":", 1)
- entry = schema.get("prefixes", {}).get(prefix)
- if not entry:
- return value
- if isinstance(entry, str):
- base = entry
- elif isinstance(entry, dict):
- base = entry.get("prefix_reference") or entry.get("uri") or entry.get("prefix") or ""
- else:
- return value
- if not base:
- return value
- return base + local if base.endswith(("/", "#")) else base + local
-
-
-def get_slot_cardinality(schema: dict, class_name: str,
- slot_name: str, slot_details: dict) -> str:
- class_def = schema.get("classes", {}).get(class_name, {})
- usage = (class_def.get("slot_usage", {}) or {}).get(slot_name, {}) or {}
- required = usage.get("required", slot_details.get("required", False))
- recommended = usage.get("recommended", slot_details.get("recommended", False))
- multivalued = usage.get("multivalued", slot_details.get("multivalued", False))
- parts = ["Required" if required else ("Recommended" if recommended else "Optional")]
- if multivalued:
- parts.append("Multivalued")
- return ", ".join(parts)
-
-
-# ─────────────────────────────────────────────────────────────────────────────
-# Markdown formatters
-# ─────────────────────────────────────────────────────────────────────────────
-
-def format_class_markdown(schema: dict, class_name: str, level: int = 3,
- processed_classes: Optional[Set[str]] = None,
- parent_class: Optional[str] = None) -> str:
- if processed_classes is None:
- processed_classes = set()
- if class_name in processed_classes:
- return ""
- processed_classes.add(class_name)
-
- class_def = schema.get("classes", {}).get(class_name, {})
- description = class_def.get("description", "No description available")
- class_uri = class_def.get("class_uri", "")
- is_abstract = class_def.get("abstract", False)
-
- open_attr = " open" if level == 3 else ""
- md = f'\n'
- md += f'{snake_to_readable(class_name)}
\n\n'
- if is_abstract:
- md += "**Abstract Class**\n\n"
- md += f"**Description:** {description}\n\n"
- if class_uri:
- md += f"**CURIE:** [`{class_uri}`]({expand_curie(schema, class_uri)})\n\n"
- md += f"**Schema Reference:** [{class_name}](./elements/{class_name}.md)\n\n"
-
- slots = get_all_class_slots(schema, class_name)
- if slots:
- md += "**Slots**\n\n"
- for slot_name in slots:
- slot_details = get_slot_details(schema, slot_name)
- md += format_slot_markdown(schema, slot_name, slot_details, level + 2,
- processed_classes.copy(), class_name)
-
- md += (f"\n \n'
- f" 💡 Submit Term Feedback\n \n
")
- md += " \n\n"
- return md
-
-
-def format_slot_markdown(schema: dict, slot_name: str, slot_details: dict,
- level: int = 3, processed_classes: Optional[Set[str]] = None,
- parent_class: Optional[str] = None) -> str:
- if processed_classes is None:
- processed_classes = set()
-
- description = slot_details.get("description", "No description available")
- range_type = slot_details.get("range", "string")
- slot_uri = slot_details.get("slot_uri", "")
- unit = slot_details.get("unit", {})
-
- cardinality = striped = ""
- if parent_class:
- cardinality = f" ({get_slot_cardinality(schema, parent_class, slot_name, slot_details)})"
- striped = cardinality.replace("(", "").replace(")", "")
-
- open_attr = " open" if level == 3 else ""
- md = f'\n'
- md += f"{snake_to_readable(slot_name)}{cardinality}
\n\n"
- md += f"**Description:** {description}\n\n"
- md += f"**Data Type:** {range_type}\n\n"
- md += f"**Cardinality:** {striped}\n\n"
- if slot_uri:
- md += f"**CURIE:** [`{slot_uri}`]({expand_curie(schema, slot_uri)})\n\n"
- md += f"**Schema Reference:** [{slot_name}](./elements/{slot_name}.md)\n\n"
- if unit and unit.get("ucum_code"):
- md += f"**Unit:** {unit['ucum_code']}\n\n"
-
- # If the range is a known schema class, expand it inline with all subclasses
- if is_class_in_schema(schema, range_type) and not is_mixin(schema, range_type):
- md += "**Data Type Class Details:**\n\n"
- md += format_class_markdown(schema, range_type, level, processed_classes, None)
- subclasses = get_subclasses(schema, range_type)
- if subclasses:
- md += f"**Possible Subclasses / Enumerations of {snake_to_readable(range_type)}:**\n\n"
- for subclass in subclasses:
- if subclass not in processed_classes:
- md += format_class_markdown(schema, subclass, level + 1,
- processed_classes, None)
-
- md += (f"\n \n'
- f" 💡 Submit Term Feedback\n \n
")
- md += " \n\n"
- return md
-
-LEGEND = """\
- **Legend**
-
-- **Description:**A short description for Comprehension purposes
-
-- **Data Type:** This specifies exactly what kind of information belongs in this field. Most simply, it could be a direct value, such as a number (float) or a piece of text (string). However, the Data Type can also point to another Class in the schema. When this happens, the field is not just a single value; it becomes a structured container. Designating a Class as the Data Type causes the field to contain a complete structured record, defined entirely by its own comprehensive collection of specific fields. Consequently, this allows for the systematic construction of complex data structures via organized, nested information layers within the broader schema architecture.
-
-- **Cardinality:** This controls how many entries a specific data field must have. It defines if a field is required or optional, and whether it can accept a single value versus a list of multiple values.
-
-- **CURIE:** A CURIE (Compact URI) is a short, easy-to-read reference that acts as a useful shortcut for a long, complex web address. Instead of seeing a full URL, you will see a two-part reference like gene:symbol, where the parts are separated by a colon. The first part is the prefix (a short code for the source website), and the second is the local identifier for the specific item. This structure is much easier to read and type, making the schema less cluttered and reducing errors. The full web link (URI) for the CURIE is always available if you click the provided link.
-
-- **Schema Reference:** This link directs you to the complete, technical documentation for this part of the schema. This detailed view is generated automatically by LinkML's documentation tool and provides all underlying rules, data types, and complex relationships for expert users and developers.
-
-- **Slots:** A Slot represents an individual data field or attribute that belongs to a specific Class (entity type) within the schema. If a Class defines an entity like 'Book', the Slots define the individual pieces of information about that book, such as the 'title', 'author', and 'ISBN'. Essentially, Slots are the essential building blocks that define the characteristics and permissible data for every record in the schema.
-
-- **Enumerations:** Often called an Enum, Enumerations are a predefined, fixed list of permissible values that a Slot can accept. It is used to strictly limit the available choices for a data field to ensure consistency and prevent errors. For example, a 'Status' field might be restricted to the Enumeration list of only 'Active', 'Inactive', or 'Pending'. Any data entered that is not on this limited list is considered invalid by the schema.
-
- """
-
-# ── Per-class introductory text shown below the page title ────────────────────
-# Edit these strings to replace the placeholder "test description" sections.
-CLASS_DESCRIPTIONS: dict = {
- "Synthesis": """The Synthesis data class captures metadata required to document catalyst preparation procedures in a structured and reproducible manner. It defines the minimum information necessary to describe synthesis routes and their relevant parameters.
-
-Metadata are organized hierarchically based on the selected synthesis method. Method-specific child fields are activated depending on the preparation approach (e.g., co-precipitation requiring fields such as precipitating agent, synthesis pH, aging time, and aging temperature). In addition, method-independent fields—such as precursor identity, precursor quantity, and storage conditions—are included to ensure consistent documentation across synthesis strategies.
-""",
- "Characterization": """The Characterization data class documents experimental techniques used to determine structural, electronic, compositional, and physicochemical properties of catalysts. It captures both measurement parameters and relevant contextual information required for interpretation and comparison.
-
-The class follows a hierarchical structure in which selection of a characterization technique activates technique-specific metadata fields (e.g., radiation source for X-ray diffraction or solvent for nuclear magnetic resonance spectroscopy). Metadata on sample preparation and pre-treatment are also included, as these factors directly influence measurement outcomes.""",
- "Reaction": """The Reaction data class defines metadata required to document catalytic testing procedures, reactor configurations, operating conditions, and analytical methods. It provides structured descriptors necessary to contextualize catalytic performance data.
-
-Core fields include reactor design type, operational parameters, and product identification and quantification methods. The class also specifies metadata required to report and evaluate catalyst performance metrics, enabling structured comparison across experimental studies.""",
- "Simulation": """The Simulation data class captures metadata describing theoretical and computational studies in catalysis. It documents methodological background, computational settings, and modeling approaches required to interpret simulation results.
-
-Computational approaches are organized under the parent field simulation method, which includes techniques such as density functional theory, molecular dynamics, microkinetic modeling, and Monte Carlo simulations. Selection of a specific method activates the corresponding method-specific metadata fields necessary to describe model setup and computational parameters.""",
-}
-
-
-def generate_markdown_for_main_class(schema: dict, main_class: str, output_file: str):
- classes = schema.get("classes", {})
- main_class_def = classes.get(main_class, {})
- class_uri = main_class_def.get("class_uri", "")
- is_abstract = main_class_def.get("abstract", False)
-
- description = CLASS_DESCRIPTIONS.get(main_class, "test description")
- md = f"# {snake_to_readable(main_class)}\n\n{description}\n\n"
-
-
-
- if is_abstract:
- md += "**Abstract Class**\n\n"
- if class_uri:
- md += f"**CURIE:** [`{class_uri}`]({class_uri})\n\n"
-
- md += (f'')
-
- # Slots section: direct/mixin slots first, then class-ranged slot_usage entries
- direct_slots = get_all_class_slots(schema, main_class)
- class_ranged_su = get_class_ranged_slot_usage(schema, main_class)
-
- if direct_slots or class_ranged_su:
- md += LEGEND + "\n## Slots\n\n"
- processed: Set[str] = set()
- for slot_name in direct_slots:
- md += format_slot_markdown(schema, slot_name,
- get_slot_details(schema, slot_name),
- 3, processed.copy(), main_class)
- for slot_name, synthetic in class_ranged_su:
- md += format_slot_markdown(schema, slot_name, synthetic,
- 3, processed.copy(), main_class)
-
- with open(output_file, "w", encoding="utf-8") as f:
- f.write(md)
- print(f" ✓ {output_file}")
-
-
-
-# ─────────────────────────────────────────────────────────────────────────────
-# Entry point
-# ─────────────────────────────────────────────────────────────────────────────
-
-def main(schema_dir: str, output_dir: str = "."):
- print(f"\nLoading CatCore modules from: {schema_dir}")
- schema = load_merged_schema(schema_dir)
-
- output_path = Path(output_dir)
- output_path.mkdir(parents=True, exist_ok=True)
-
- main_classes = {
- "Synthesis": "synthesis.md",
- "Characterization": "characterization.md",
- "Reaction": "reaction.md",
- "Simulation": "simulation.md",
- }
-
- print(f"Generating Markdown docs in: {output_dir}")
- for main_class, filename in main_classes.items():
- generate_markdown_for_main_class(schema, main_class, str(output_path / filename))
-
- print(f"\n✓ All done — {len(main_classes)} files written to '{output_dir}'.")
-
-
-if __name__ == "__main__":
- # ── Edit these two paths to match your local setup ──────────────────────
- schema_dir = "./src/catcore/schema"
- output_dir = "./docs"
- # ────────────────────────────────────────────────────────────────────────
- main(schema_dir, output_dir)
diff --git a/catcore_converter.py b/catcore_converter.py
index 946a9e389..444ef58c8 100644
--- a/catcore_converter.py
+++ b/catcore_converter.py
@@ -1,15 +1,16 @@
import yaml
from pathlib import Path
-from typing import Dict, List, Set, Optional
+from typing import List, Set, Optional
-# ── Module filenames (relative to the schema directory) ────────────────────────
+# catcore.yaml is loaded FIRST so its generic stubs (range: Plan, range: AgenticEntity)
+# are overwritten by the specific ranges in the subprofile modules.
MODULE_FILES = [
+ "catcore.yaml", # load first: generic stubs get overwritten by subprofiles
"catcore_common.yaml",
"catcore_synthesis_ap.yaml",
"catcore_characterization_ap.yaml",
"catcore_reaction_ap.yaml",
"catcore_simulation_ap.yaml",
- "catcore.yaml",
]
@@ -29,17 +30,14 @@ def load_yaml_file(file_path: str) -> dict:
def merge_schemas(modules: List[dict]) -> dict:
"""
Merge all module dicts into one flat schema.
-
- Classes that appear in multiple modules (e.g. Synthesis is defined in
- catcore_synthesis_ap and stubbed in catcore.yaml) are intelligently merged:
- slot_usage and slots lists are unioned; all other keys let the later module win.
+ Later modules win on key collision, so subprofile modules (loaded last)
+ correctly override the generic stubs in catcore.yaml (loaded first).
"""
merged: dict = {"prefixes": {}, "classes": {}, "slots": {}, "enums": {}}
for module in modules:
if not module:
continue
-
for key in ("id", "name", "title", "description", "license",
"version", "default_prefix", "default_range"):
if key not in merged and key in module:
@@ -51,21 +49,25 @@ def merge_schemas(modules: List[dict]) -> dict:
if section == "classes" and name in merged["classes"]:
existing = merged["classes"][name]
incoming = definition or {}
+ # slot_usage: merge entry by entry; incoming wins per key
if "slot_usage" in incoming:
existing.setdefault("slot_usage", {})
existing["slot_usage"].update(incoming["slot_usage"])
+ # slots list: union (incoming appends new names)
if "slots" in incoming:
existing_slots = existing.get("slots", [])
for s in incoming["slots"]:
if s not in existing_slots:
existing_slots.append(s)
existing["slots"] = existing_slots
+ # all other keys: incoming wins
for k, v in incoming.items():
if k not in ("slot_usage", "slots"):
existing[k] = v
else:
- merged[section][name] = dict(definition) if isinstance(definition, dict) else definition
-
+ merged[section][name] = (
+ dict(definition) if isinstance(definition, dict) else definition
+ )
return merged
@@ -76,7 +78,6 @@ def load_merged_schema(schema_dir: str) -> dict:
full_path = schema_dir_path / filename
print(f" Loading: {full_path}")
modules.append(load_yaml_file(str(full_path)))
-
merged = merge_schemas(modules)
print(f" Merged schema: {len(merged.get('classes', {}))} classes, "
f"{len(merged.get('slots', {}))} slots\n")
@@ -84,76 +85,77 @@ def load_merged_schema(schema_dir: str) -> dict:
# ─────────────────────────────────────────────────────────────────────────────
-# BUG FIX 1: Mixin slot resolution
-#
-# In LinkML, a class can declare `mixins: [MixinA, MixinB]`. The converter's
-# original get_class_slots() only looked at `slots`, completely ignoring mixin
-# contributions. This means e.g. PowderXRD was missing xray_source/monochromator
-# (from XRaySourceMixin), and BandGap was missing 5 slots from two mixins.
-#
-# get_all_class_slots() walks the full mixin chain and returns the complete
-# deduplicated slot list: own slots first, then each mixin's slots in order.
+# Mixin-aware slot resolution
# ─────────────────────────────────────────────────────────────────────────────
def get_all_class_slots(schema: dict, class_name: str,
_seen: Optional[Set[str]] = None) -> List[str]:
- """
- Return ALL slots available on a class, including those contributed by mixins
- (resolved recursively so that mixin-of-mixin chains work too).
-
- Order: own `slots` first, then each mixin's slots in declaration order.
- Duplicates are removed (first occurrence wins).
- """
+ """Return all slots on a class including those contributed by mixins."""
if _seen is None:
_seen = set()
if class_name in _seen:
return []
_seen.add(class_name)
-
- classes = schema.get("classes", {})
- class_def = classes.get(class_name, {})
-
+ class_def = schema.get("classes", {}).get(class_name, {})
own_slots = class_def.get("slots", []) or []
mixin_names = class_def.get("mixins", []) or []
-
- # Gather mixin slots (recursively, in case mixins themselves have mixins)
mixin_slots: List[str] = []
for mixin_name in mixin_names:
for s in get_all_class_slots(schema, mixin_name, _seen.copy()):
if s not in mixin_slots:
mixin_slots.append(s)
-
- # Deduplicate: own slots take priority
- combined: List[str] = list(own_slots)
+ combined = list(own_slots)
for s in mixin_slots:
if s not in combined:
combined.append(s)
-
return combined
+def get_class_ranged_slot_usage(schema: dict, class_name: str) -> List[tuple]:
+ """
+ Return (slot_name, synthetic_slot_def) pairs for every slot_usage entry
+ whose range points to a class defined in the merged schema.
+
+ These are the 'gateway' slots that expand into nested class/subclass docs:
+ Synthesis → realized_plan:PreparationMethod, had_input_entity:Precursor, had_output_entity:CatalystSample
+ Characterization → realized_plan:CharacterizationTechnique
+ Reaction → carried_out_by:ReactorDesignType, product_identification_method:ProductIdentificationMethod
+ Simulation → realized_plan:SimulationMethod
+ """
+ classes = schema.get("classes", {})
+ slots_dict = schema.get("slots", {})
+ class_def = classes.get(class_name, {})
+ slot_usage = class_def.get("slot_usage", {}) or {}
+
+ result = []
+ for su_name, su_def in slot_usage.items():
+ if not su_def:
+ continue
+ rng = su_def.get("range")
+ if not rng or rng not in classes:
+ continue
+ if classes.get(rng, {}).get("mixin"):
+ continue
+ # Build synthetic slot def: base slot (if any) + slot_usage overrides
+ base = dict(slots_dict.get(su_name, {}))
+ base.update({k: v for k, v in su_def.items() if v is not None})
+ if not base.get("description"):
+ base["description"] = f"Link to {rng} — see subclasses for details."
+ base["range"] = rng
+ result.append((su_name, base))
+ return result
+
+
# ─────────────────────────────────────────────────────────────────────────────
-# BUG FIX 2: Mixin class filtering in subclass listings
-#
-# The original get_subclasses() returned mixin classes (e.g. DryingMixin,
-# XRaySourceMixin) alongside real output classes because both use `is_a`.
-# Mixin classes are purely internal slot-containers and must NOT appear as
-# subclasses in the generated docs.
-#
-# is_mixin() checks the `mixin: true` flag set on each mixin class definition.
-# get_subclasses() now skips classes where is_mixin() returns True.
+# Standard helpers
# ─────────────────────────────────────────────────────────────────────────────
def is_mixin(schema: dict, class_name: str) -> bool:
- """Return True if the class is a LinkML mixin (mixin: true)."""
return bool(schema.get("classes", {}).get(class_name, {}).get("mixin", False))
def get_subclasses(schema: dict, parent_class: str) -> List[str]:
- """
- Return all (recursive) non-mixin subclasses of parent_class.
- Mixin classes are excluded from the result.
- """
+ """Return all (recursive) non-mixin subclasses of parent_class."""
subclasses = []
for class_name, class_def in schema.get("classes", {}).items():
if class_def.get("is_a") == parent_class and not is_mixin(schema, class_name):
@@ -162,10 +164,6 @@ def get_subclasses(schema: dict, parent_class: str) -> List[str]:
return subclasses
-# ─────────────────────────────────────────────────────────────────────────────
-# Standard helpers
-# ─────────────────────────────────────────────────────────────────────────────
-
def get_slot_details(schema: dict, slot_name: str) -> dict:
return schema.get("slots", {}).get(slot_name, {})
@@ -182,15 +180,13 @@ def expand_curie(schema: dict, value: str) -> str:
if ":" not in value or value.startswith("http"):
return value
prefix, local = value.split(":", 1)
- prefixes = schema.get("prefixes", {})
- if prefix not in prefixes:
+ entry = schema.get("prefixes", {}).get(prefix)
+ if not entry:
return value
- entry = prefixes[prefix]
if isinstance(entry, str):
base = entry
elif isinstance(entry, dict):
- base = (entry.get("prefix_reference") or entry.get("uri")
- or entry.get("prefix") or "")
+ base = entry.get("prefix_reference") or entry.get("uri") or entry.get("prefix") or ""
else:
return value
if not base:
@@ -200,26 +196,12 @@ def expand_curie(schema: dict, value: str) -> str:
def get_slot_cardinality(schema: dict, class_name: str,
slot_name: str, slot_details: dict) -> str:
- """
- Determine the cardinality label for a slot in the context of a class.
- Falls back to slot-level required/recommended flags when no slot_usage
- override exists (important for modular files where cardinality is set
- directly on the slot definition rather than in slot_usage).
- """
class_def = schema.get("classes", {}).get(class_name, {})
- usage = class_def.get("slot_usage", {}).get(slot_name, {})
-
+ usage = (class_def.get("slot_usage", {}) or {}).get(slot_name, {}) or {}
required = usage.get("required", slot_details.get("required", False))
recommended = usage.get("recommended", slot_details.get("recommended", False))
multivalued = usage.get("multivalued", slot_details.get("multivalued", False))
-
- parts = []
- if required:
- parts.append("Required")
- elif recommended:
- parts.append("Recommended")
- else:
- parts.append("Optional")
+ parts = ["Mandatory" if required else ("Recommended" if recommended else "Optional")]
if multivalued:
parts.append("Multivalued")
return ", ".join(parts)
@@ -229,73 +211,49 @@ def get_slot_cardinality(schema: dict, class_name: str,
# Markdown formatters
# ─────────────────────────────────────────────────────────────────────────────
-def format_class_markdown(
- schema: dict,
- class_name: str,
- level: int = 3,
- processed_classes: Optional[Set[str]] = None,
- parent_class: Optional[str] = None,
-) -> str:
+def format_class_markdown(schema: dict, class_name: str, level: int = 3,
+ processed_classes: Optional[Set[str]] = None,
+ parent_class: Optional[str] = None) -> str:
if processed_classes is None:
processed_classes = set()
if class_name in processed_classes:
return ""
processed_classes.add(class_name)
- classes = schema.get("classes", {})
- class_def = classes.get(class_name, {})
-
+ class_def = schema.get("classes", {}).get(class_name, {})
description = class_def.get("description", "No description available")
class_uri = class_def.get("class_uri", "")
is_abstract = class_def.get("abstract", False)
- schema_link = f"./elements/{class_name}.md"
- md = f'\n'
+ open_attr = " open" if level == 3 else ""
+ md = f'\n'
md += f'{snake_to_readable(class_name)}
\n\n'
-
if is_abstract:
md += "**Abstract Class**\n\n"
-
md += f"**Description:** {description}\n\n"
-
if class_uri:
- expanded = expand_curie(schema, class_uri)
- md += f"**CURIE:** [`{class_uri}`]({expanded})\n\n"
-
- md += f"**Schema Reference:** [{class_name}]({schema_link})\n\n"
+ md += f"**CURIE:** [`{class_uri}`]({expand_curie(schema, class_uri)})\n\n"
+ md += f"**Schema Reference:** [{class_name}](./elements/{class_name}.md)\n\n"
- # BUG FIX 1 applied: use get_all_class_slots instead of class_def.get('slots')
slots = get_all_class_slots(schema, class_name)
if slots:
md += "**Slots**\n\n"
for slot_name in slots:
slot_details = get_slot_details(schema, slot_name)
- md += format_slot_markdown(
- schema, slot_name, slot_details, level + 2,
- processed_classes.copy(), class_name
- )
-
- md += (
- f"\n"
- f" \n'
- f" 💡 Submit Term Feedback\n"
- f" \n"
- f"
"
- )
+ md += format_slot_markdown(schema, slot_name, slot_details, level + 2,
+ processed_classes.copy(), class_name)
+
+ md += (f"\n \n'
+ f" 💡 Submit Term Feedback\n \n
")
md += " \n\n"
return md
-def format_slot_markdown(
- schema: dict,
- slot_name: str,
- slot_details: dict,
- level: int = 3,
- processed_classes: Optional[Set[str]] = None,
- parent_class: Optional[str] = None,
-) -> str:
+def format_slot_markdown(schema: dict, slot_name: str, slot_details: dict,
+ level: int = 3, processed_classes: Optional[Set[str]] = None,
+ parent_class: Optional[str] = None) -> str:
if processed_classes is None:
processed_classes = set()
@@ -303,59 +261,43 @@ def format_slot_markdown(
range_type = slot_details.get("range", "string")
slot_uri = slot_details.get("slot_uri", "")
unit = slot_details.get("unit", {})
- schema_link = f"./elements/{slot_name}.md"
- cardinality = ""
- striped_cardinality = ""
+ cardinality = striped = ""
if parent_class:
- cardinality = get_slot_cardinality(schema, parent_class, slot_name, slot_details)
- cardinality = f" ({cardinality})"
- striped_cardinality = cardinality.replace("(", "").replace(")", "")
+ cardinality = f" ({get_slot_cardinality(schema, parent_class, slot_name, slot_details)})"
+ striped = cardinality.replace("(", "").replace(")", "")
- md = f'\n'
+ open_attr = " open" if level == 3 else ""
+ md = f'\n'
md += f"{snake_to_readable(slot_name)}{cardinality}
\n\n"
md += f"**Description:** {description}\n\n"
md += f"**Data Type:** {range_type}\n\n"
- md += f"**Cardinality:** {striped_cardinality}\n\n"
-
+ md += f"**Cardinality:** {striped}\n\n"
if slot_uri:
- expanded = expand_curie(schema, slot_uri)
- md += f"**CURIE:** [`{slot_uri}`]({expanded})\n\n"
-
- md += f"**Schema Reference:** [{slot_name}]({schema_link})\n\n"
-
- if unit:
- ucum_code = unit.get("ucum_code", "")
- if ucum_code:
- md += f"**Unit:** {ucum_code}\n\n"
+ md += f"**CURIE:** [`{slot_uri}`]({expand_curie(schema, slot_uri)})\n\n"
+ md += f"**Schema Reference:** [{slot_name}](./elements/{slot_name}.md)\n\n"
+ if unit and unit.get("ucum_code"):
+ md += f"**Unit:** {unit['ucum_code']}\n\n"
+ # If the range is a known schema class, expand it inline with all subclasses
if is_class_in_schema(schema, range_type) and not is_mixin(schema, range_type):
md += "**Data Type Class Details:**\n\n"
md += format_class_markdown(schema, range_type, level, processed_classes, None)
-
- # BUG FIX 2 applied: get_subclasses already filters out mixins
subclasses = get_subclasses(schema, range_type)
if subclasses:
md += f"**Possible Subclasses / Enumerations of {snake_to_readable(range_type)}:**\n\n"
for subclass in subclasses:
if subclass not in processed_classes:
- md += format_class_markdown(
- schema, subclass, level + 1, processed_classes, None
- )
+ md += format_class_markdown(schema, subclass, level + 1,
+ processed_classes, None)
- md += (
- f"\n"
- f" \n'
- f" 💡 Submit Term Feedback\n"
- f" \n"
- f"
"
- )
+ md += (f"\n \n'
+ f" 💡 Submit Term Feedback\n \n
")
md += " \n\n"
return md
-
LEGEND = """\
**Legend**
@@ -375,159 +317,75 @@ def format_slot_markdown(
"""
+# ── Per-class introductory text shown below the page title ────────────────────
+# Edit these strings to replace the placeholder "test description" sections.
+CLASS_DESCRIPTIONS: dict = {
+ "Synthesis": """The Synthesis data class captures metadata required to document catalyst preparation procedures in a structured and reproducible manner. It defines the minimum information necessary to describe synthesis routes and their relevant parameters.
+
+Metadata are organized hierarchically based on the selected synthesis method. Method-specific child fields are activated depending on the preparation approach (e.g., co-precipitation requiring fields such as precipitating agent, synthesis pH, aging time, and aging temperature). In addition, method-independent fields—such as precursor identity, precursor quantity, and storage conditions—are included to ensure consistent documentation across synthesis strategies.
+""",
+ "Characterization": """The Characterization data class documents experimental techniques used to determine structural, electronic, compositional, and physicochemical properties of catalysts. It captures both measurement parameters and relevant contextual information required for interpretation and comparison.
+
+The class follows a hierarchical structure in which selection of a characterization technique activates technique-specific metadata fields (e.g., radiation source for X-ray diffraction or solvent for nuclear magnetic resonance spectroscopy). Metadata on sample preparation and pre-treatment are also included, as these factors directly influence measurement outcomes.""",
+ "Reaction": """The Reaction data class defines metadata required to document catalytic testing procedures, reactor configurations, operating conditions, and analytical methods. It provides structured descriptors necessary to contextualize catalytic performance data.
+
+Core fields include reactor design type, operational parameters, and product identification and quantification methods. The class also specifies metadata required to report and evaluate catalyst performance metrics, enabling structured comparison across experimental studies.""",
+ "Simulation": """The Simulation data class captures metadata describing theoretical and computational studies in catalysis. It documents methodological background, computational settings, and modeling approaches required to interpret simulation results.
-def generate_markdown_for_main_class(
- schema: dict, main_class: str, output_file: str
-):
+Computational approaches are organized under the parent field simulation method, which includes techniques such as density functional theory, molecular dynamics, microkinetic modeling, and Monte Carlo simulations. Selection of a specific method activates the corresponding method-specific metadata fields necessary to describe model setup and computational parameters.""",
+}
+
+
+def generate_markdown_for_main_class(schema: dict, main_class: str, output_file: str):
classes = schema.get("classes", {})
main_class_def = classes.get(main_class, {})
class_uri = main_class_def.get("class_uri", "")
is_abstract = main_class_def.get("abstract", False)
- md_content = f"# {snake_to_readable(main_class)}\n\n"
- md_content += "test description\n\n"
+ description = CLASS_DESCRIPTIONS.get(main_class, "test description")
+ md = f"# {snake_to_readable(main_class)}\n\n{description}\n\n"
- if is_abstract:
- md_content += "**Abstract Class**\n\n"
- if class_uri:
- md_content += f"**CURIE:** [`{class_uri}`]({class_uri})\n\n"
- # BUG FIX 1 applied: use get_all_class_slots for the main class too
- slots = get_all_class_slots(schema, main_class)
- if slots:
- md_content += LEGEND + "\n"
- md_content += "## Slots\n\n"
- processed_classes: Set[str] = set()
- for slot_name in slots:
- slot_details = get_slot_details(schema, slot_name)
- md_content += format_slot_markdown(
- schema, slot_name, slot_details, 3,
- processed_classes.copy(), main_class
- )
-
- processed_classes = {main_class}
- # BUG FIX 2 applied: get_subclasses already filters mixin classes
- subclasses = get_subclasses(schema, main_class)
-
- if subclasses:
- md_content += "## Subclasses\n\n"
-
- direct_subclasses = [
- sc for sc in subclasses
- if classes.get(sc, {}).get("is_a") == main_class
- ]
-
- for subclass in direct_subclasses:
- if subclass not in processed_classes:
- md_content += format_class_markdown(
- schema, subclass, level=3,
- processed_classes=processed_classes
- )
-
- nested_subclasses = [
- sc for sc in subclasses
- if classes.get(sc, {}).get("is_a") == subclass
- ]
- if nested_subclasses:
- md_content += f"**Nested Subclasses of {snake_to_readable(subclass)}:**\n\n"
- for nested in nested_subclasses:
- if nested not in processed_classes:
- md_content += format_class_markdown(
- schema, nested, level=4,
- processed_classes=processed_classes
- )
-
- main_class_lower = main_class.lower()
- md_content += (
- f''
- )
+ if is_abstract:
+ md += "**Abstract Class**\n\n"
+ if class_uri:
+ md += f"**CURIE:** [`{class_uri}`]({class_uri})\n\n"
+
+ md += (f'')
+
+ # Slots section: direct/mixin slots first, then class-ranged slot_usage entries
+ direct_slots = get_all_class_slots(schema, main_class)
+ class_ranged_su = get_class_ranged_slot_usage(schema, main_class)
+
+ if direct_slots or class_ranged_su:
+ md += LEGEND + "\n## Slots\n\n"
+ processed: Set[str] = set()
+ for slot_name in direct_slots:
+ md += format_slot_markdown(schema, slot_name,
+ get_slot_details(schema, slot_name),
+ 3, processed.copy(), main_class)
+ for slot_name, synthetic in class_ranged_su:
+ md += format_slot_markdown(schema, slot_name, synthetic,
+ 3, processed.copy(), main_class)
with open(output_file, "w", encoding="utf-8") as f:
- f.write(md_content)
-
+ f.write(md)
print(f" ✓ {output_file}")
-# ─────────────────────────────────────────────────────────────────────────────
-# Smoke-test: run against the uploaded files and report slot counts
-# ─────────────────────────────────────────────────────────────────────────────
-
-def smoke_test(schema: dict):
- """Print expected vs resolved slot counts for key classes."""
- tests = {
- # class expected_min_slots reason
- "Synthesis": (6, "6 direct slots"),
- "Characterization": (5, "5 direct slots"),
- "Reaction": (8, "8 direct slots"),
- "Simulation": (2, "2 direct slots"),
- "WetImpregnation": (1, "inherits from Impregnation via is_a chain → but Impregnation has 3+mixin slots"),
- "Impregnation": (9, "3 own + DryingMixin(4) + CalcinationMixin(6)"),
- "CoPrecipitation": (14, "PrecipitationMixin(4) + DryingMixin(4) + CalcinationMixin(6)"),
- "PowderXRD": (10, "8 own + XRaySourceMixin(2)"),
- "BandGap": (12, "7 own + MaterialDescriptorMixin(2) + DFTSettingsMixin(3)"),
- "DielectricTensors": (7, "2 own + MaterialDescriptorMixin(2) + DFTSettingsMixin(3)"),
- }
- print("── Smoke test: resolved slot counts ─────────────────────────────")
- all_ok = True
- for cls, (expected_min, reason) in tests.items():
- resolved = get_all_class_slots(schema, cls)
- ok = len(resolved) >= expected_min
- status = "✓" if ok else "✗"
- if not ok:
- all_ok = False
- print(f" {status} {cls}: {len(resolved)} slots (expected ≥{expected_min} — {reason})")
- if not ok:
- print(f" got: {resolved}")
- print()
-
- print("── Smoke test: mixin filtering ───────────────────────────────────")
- sim_subclasses = get_subclasses(schema, "Simulation")
- char_subclasses = get_subclasses(schema, "Characterization")
- syn_subclasses = get_subclasses(schema, "Synthesis")
-
- mixin_names = [n for n, d in schema.get("classes", {}).items() if d.get("mixin")]
- leaked_sim = [c for c in sim_subclasses if c in mixin_names]
- leaked_char = [c for c in char_subclasses if c in mixin_names]
- leaked_syn = [c for c in syn_subclasses if c in mixin_names]
-
- for label, leaked, subclasses in [
- ("Simulation", leaked_sim, sim_subclasses),
- ("Characterization", leaked_char, char_subclasses),
- ("Synthesis", leaked_syn, syn_subclasses),
- ]:
- if leaked:
- print(f" ✗ {label}: mixin classes leaked into subclasses: {leaked}")
- all_ok = False
- else:
- print(f" ✓ {label}: {len(subclasses)} subclasses, no mixins leaked")
-
- print()
- print("── Overall:", "ALL TESTS PASSED ✓" if all_ok else "SOME TESTS FAILED ✗")
- print()
-
# ─────────────────────────────────────────────────────────────────────────────
# Entry point
# ─────────────────────────────────────────────────────────────────────────────
def main(schema_dir: str, output_dir: str = "."):
- """
- Load all CatCore module YAML files from schema_dir, merge them, optionally
- run the smoke test, then generate one Markdown doc page per main class.
- """
print(f"\nLoading CatCore modules from: {schema_dir}")
schema = load_merged_schema(schema_dir)
- smoke_test(schema)
-
output_path = Path(output_dir)
output_path.mkdir(parents=True, exist_ok=True)
@@ -540,14 +398,14 @@ def main(schema_dir: str, output_dir: str = "."):
print(f"Generating Markdown docs in: {output_dir}")
for main_class, filename in main_classes.items():
- output_file = output_path / filename
- generate_markdown_for_main_class(schema, main_class, str(output_file))
+ generate_markdown_for_main_class(schema, main_class, str(output_path / filename))
print(f"\n✓ All done — {len(main_classes)} files written to '{output_dir}'.")
+
if __name__ == "__main__":
# ── Edit these two paths to match your local setup ──────────────────────
- schema_dir = "./schema"
+ schema_dir = "./src/catcore/schema"
output_dir = "./docs"
# ────────────────────────────────────────────────────────────────────────
main(schema_dir, output_dir)
diff --git a/docs/characterization.md b/docs/characterization.md
index 107077acd..764a048df 100644
--- a/docs/characterization.md
+++ b/docs/characterization.md
@@ -98,7 +98,7 @@ The class follows a hierarchical structure in which selection of a characterizat
**Cardinality:** Optional, Multivalued
-**CURIE:** [`voc4cat:0000122`](https://w3id.org/nfdi4cat/voc4cat_0000122)
+**CURIE:** [`VOC4CAT:0000122`](https://w3id.org/nfdi4cat/voc4cat_0000122)
**Schema Reference:** [sample_pretreatment](./elements/sample_pretreatment.md)
@@ -128,13 +128,13 @@ The class follows a hierarchical structure in which selection of a characterizat
-realized plan (Required)
+realized plan (Mandatory)
**Description:** The CharacterizationTechnique (protocol) realized in this Characterization.
**Data Type:** CharacterizationTechnique
-**Cardinality:** Required
+**Cardinality:** Mandatory
**Schema Reference:** [realized_plan](./elements/realized_plan.md)
@@ -175,7 +175,7 @@ Linked from Characterization via realized_plan.
minimum 2theta (Optional, Multivalued)
-**Description:** Minimum 2θ angle in the diffraction scan.
+**Description:** Minimum 2theta angle in the diffraction scan.
**Data Type:** float
@@ -196,7 +196,7 @@ Linked from Characterization via realized_plan.
maximum 2theta (Optional, Multivalued)
-**Description:** Maximum 2θ angle in the diffraction scan.
+**Description:** Maximum 2theta angle in the diffraction scan.
**Data Type:** float
@@ -242,7 +242,7 @@ Linked from Characterization via realized_plan.
**Cardinality:** Optional, Multivalued
-**CURIE:** [`voc4cat:0000108`](https://w3id.org/nfdi4cat/voc4cat_0000108)
+**CURIE:** [`VOC4CAT:0000108`](https://w3id.org/nfdi4cat/voc4cat_0000108)
**Schema Reference:** [operation_mode](./elements/operation_mode.md)
@@ -337,7 +337,7 @@ Linked from Characterization via realized_plan.
xray source (Optional, Multivalued)
-**Description:** X-ray source used (e.g. Cu Kα, Mo Kα, synchrotron).
+**Description:** X-ray source used (e.g. Cu K-alpha, Mo K-alpha, synchrotron).
**Data Type:** string
@@ -413,7 +413,7 @@ Linked from Characterization via realized_plan.
xray source (Optional, Multivalued)
-**Description:** X-ray source used (e.g. Cu Kα, Mo Kα, synchrotron).
+**Description:** X-ray source used (e.g. Cu K-alpha, Mo K-alpha, synchrotron).
**Data Type:** string
@@ -459,7 +459,7 @@ Linked from Characterization via realized_plan.
**Description:** X-ray absorption spectroscopy (XAS/XANES/EXAFS) for electronic and local structure analysis.
-**CURIE:** [`voc4cat:0000286`](https://w3id.org/nfdi4cat/voc4cat_0000286)
+**CURIE:** [`VOC4CAT:0000286`](https://w3id.org/nfdi4cat/voc4cat_0000286)
**Schema Reference:** [XRayAbsorptionSpectroscopy](./elements/XRayAbsorptionSpectroscopy.md)
@@ -474,7 +474,7 @@ Linked from Characterization via realized_plan.
**Cardinality:** Optional, Multivalued
-**CURIE:** [`voc4cat:0000108`](https://w3id.org/nfdi4cat/voc4cat_0000108)
+**CURIE:** [`VOC4CAT:0000108`](https://w3id.org/nfdi4cat/voc4cat_0000108)
**Schema Reference:** [operation_mode](./elements/operation_mode.md)
@@ -624,7 +624,7 @@ Linked from Characterization via realized_plan.
xray source (Optional, Multivalued)
-**Description:** X-ray source used (e.g. Cu Kα, Mo Kα, synchrotron).
+**Description:** X-ray source used (e.g. Cu K-alpha, Mo K-alpha, synchrotron).
**Data Type:** string
@@ -828,7 +828,7 @@ Linked from Characterization via realized_plan.
**Cardinality:** Optional, Multivalued
-**CURIE:** [`voc4cat:0000108`](https://w3id.org/nfdi4cat/voc4cat_0000108)
+**CURIE:** [`VOC4CAT:0000108`](https://w3id.org/nfdi4cat/voc4cat_0000108)
**Schema Reference:** [lense_mode](./elements/lense_mode.md)
@@ -879,7 +879,7 @@ Linked from Characterization via realized_plan.
xray source (Optional, Multivalued)
-**Description:** X-ray source used (e.g. Cu Kα, Mo Kα, synchrotron).
+**Description:** X-ray source used (e.g. Cu K-alpha, Mo K-alpha, synchrotron).
**Data Type:** string
@@ -1079,7 +1079,7 @@ Linked from Characterization via realized_plan.
**Cardinality:** Optional, Multivalued
-**CURIE:** [`voc4cat:0000108`](https://w3id.org/nfdi4cat/voc4cat_0000108)
+**CURIE:** [`VOC4CAT:0000108`](https://w3id.org/nfdi4cat/voc4cat_0000108)
**Schema Reference:** [operation_mode](./elements/operation_mode.md)
@@ -1451,7 +1451,7 @@ surface species identification under reactive gas conditions.
**Description:** Raman spectroscopy for vibrational and structural characterization.
-**CURIE:** [`voc4cat:0000069`](https://w3id.org/nfdi4cat/voc4cat_0000069)
+**CURIE:** [`VOC4CAT:0000069`](https://w3id.org/nfdi4cat/voc4cat_0000069)
**Schema Reference:** [RamanSpectroscopy](./elements/RamanSpectroscopy.md)
@@ -1631,7 +1631,7 @@ Note: for detailed liquid-state NMR minimum information, the dedicated
nmr_dcat_ap profile (MARGARITAS) should be used in combination with
this subprofile.
-**CURIE:** [`voc4cat:0000073`](https://w3id.org/nfdi4cat/voc4cat_0000073)
+**CURIE:** [`VOC4CAT:0000073`](https://w3id.org/nfdi4cat/voc4cat_0000073)
**Schema Reference:** [NMRSpectroscopy](./elements/NMRSpectroscopy.md)
@@ -1665,7 +1665,7 @@ this subprofile.
**Cardinality:** Optional, Multivalued
-**CURIE:** [`voc4cat:0007246`](https://w3id.org/nfdi4cat/voc4cat_0007246)
+**CURIE:** [`VOC4CAT:0007246`](https://w3id.org/nfdi4cat/voc4cat_0007246)
**Schema Reference:** [solvent](./elements/solvent.md)
@@ -1804,7 +1804,7 @@ this subprofile.
**Description:** TEM for atomic-resolution imaging and diffraction of catalyst particles.
-**CURIE:** [`voc4cat:0000078`](https://w3id.org/nfdi4cat/voc4cat_0000078)
+**CURIE:** [`VOC4CAT:0000078`](https://w3id.org/nfdi4cat/voc4cat_0000078)
**Schema Reference:** [TransmissionElectronMicroscopy](./elements/TransmissionElectronMicroscopy.md)
@@ -1819,7 +1819,7 @@ this subprofile.
**Cardinality:** Optional, Multivalued
-**CURIE:** [`voc4cat:0000108`](https://w3id.org/nfdi4cat/voc4cat_0000108)
+**CURIE:** [`VOC4CAT:0000108`](https://w3id.org/nfdi4cat/voc4cat_0000108)
**Schema Reference:** [operation_mode](./elements/operation_mode.md)
@@ -1899,7 +1899,7 @@ this subprofile.
**Description:** SEM for surface morphology and particle size/shape imaging.
-**CURIE:** [`voc4cat:0000075`](https://w3id.org/nfdi4cat/voc4cat_0000075)
+**CURIE:** [`VOC4CAT:0000075`](https://w3id.org/nfdi4cat/voc4cat_0000075)
**Schema Reference:** [ScanningElectronMicroscopy](./elements/ScanningElectronMicroscopy.md)
@@ -2030,7 +2030,7 @@ this subprofile.
**Cardinality:** Optional, Multivalued
-**CURIE:** [`voc4cat:0000108`](https://w3id.org/nfdi4cat/voc4cat_0000108)
+**CURIE:** [`VOC4CAT:0000108`](https://w3id.org/nfdi4cat/voc4cat_0000108)
**Schema Reference:** [operation_mode](./elements/operation_mode.md)
@@ -2738,7 +2738,7 @@ this subprofile.
**Description:** UV-Vis spectroscopy for electronic transitions, band gap, and concentration determination.
-**CURIE:** [`voc4cat:0000079`](https://w3id.org/nfdi4cat/voc4cat_0000079)
+**CURIE:** [`VOC4CAT:0000079`](https://w3id.org/nfdi4cat/voc4cat_0000079)
**Schema Reference:** [UVVisSpectroscopy](./elements/UVVisSpectroscopy.md)
@@ -2816,7 +2816,7 @@ this subprofile.
**Cardinality:** Optional, Multivalued
-**CURIE:** [`voc4cat:0007246`](https://w3id.org/nfdi4cat/voc4cat_0007246)
+**CURIE:** [`VOC4CAT:0007246`](https://w3id.org/nfdi4cat/voc4cat_0007246)
**Schema Reference:** [solvent](./elements/solvent.md)
@@ -3189,7 +3189,7 @@ this subprofile.
**Cardinality:** Optional, Multivalued
-**CURIE:** [`voc4cat:0007213`](https://w3id.org/nfdi4cat/voc4cat_0007213)
+**CURIE:** [`VOC4CAT:0007213`](https://w3id.org/nfdi4cat/voc4cat_0007213)
**Schema Reference:** [scan_rate](./elements/scan_rate.md)
@@ -3252,7 +3252,7 @@ this subprofile.
**Cardinality:** Optional, Multivalued
-**CURIE:** [`voc4cat:0007218`](https://w3id.org/nfdi4cat/voc4cat_0007218)
+**CURIE:** [`VOC4CAT:0007218`](https://w3id.org/nfdi4cat/voc4cat_0007218)
**Schema Reference:** [step_size_potential](./elements/step_size_potential.md)
@@ -3292,7 +3292,7 @@ this subprofile.
**Cardinality:** Optional, Multivalued
-**CURIE:** [`voc4cat:0007204`](https://w3id.org/nfdi4cat/voc4cat_0007204)
+**CURIE:** [`VOC4CAT:0007204`](https://w3id.org/nfdi4cat/voc4cat_0007204)
**Schema Reference:** [reference_electrode](./elements/reference_electrode.md)
@@ -3311,7 +3311,7 @@ this subprofile.
**Cardinality:** Optional, Multivalued
-**CURIE:** [`voc4cat:0007202`](https://w3id.org/nfdi4cat/voc4cat_0007202)
+**CURIE:** [`VOC4CAT:0007202`](https://w3id.org/nfdi4cat/voc4cat_0007202)
**Schema Reference:** [working_electrode](./elements/working_electrode.md)
@@ -3330,7 +3330,7 @@ this subprofile.
**Cardinality:** Optional, Multivalued
-**CURIE:** [`voc4cat:0007203`](https://w3id.org/nfdi4cat/voc4cat_0007203)
+**CURIE:** [`VOC4CAT:0007203`](https://w3id.org/nfdi4cat/voc4cat_0007203)
**Schema Reference:** [counter_electrode](./elements/counter_electrode.md)
@@ -3457,7 +3457,7 @@ this subprofile.
-frequency (Optional, Multivalued)
+ac frequency (Optional, Multivalued)
**Description:** Frequency of AC signal applied in impedance or conductivity measurement.
@@ -3465,14 +3465,14 @@ this subprofile.
**Cardinality:** Optional, Multivalued
-**CURIE:** [`voc4cat:0007239`](https://w3id.org/nfdi4cat/voc4cat_0007239)
+**CURIE:** [`VOC4CAT:0007239`](https://w3id.org/nfdi4cat/voc4cat_0007239)
-**Schema Reference:** [frequency](./elements/frequency.md)
+**Schema Reference:** [ac_frequency](./elements/ac_frequency.md)
**Unit:** Hz
-
+
💡 Submit Term Feedback
@@ -3524,7 +3524,7 @@ this subprofile.
**Cardinality:** Optional, Multivalued
-**CURIE:** [`voc4cat:0007204`](https://w3id.org/nfdi4cat/voc4cat_0007204)
+**CURIE:** [`VOC4CAT:0007204`](https://w3id.org/nfdi4cat/voc4cat_0007204)
**Schema Reference:** [reference_electrode](./elements/reference_electrode.md)
@@ -3543,7 +3543,7 @@ this subprofile.
**Cardinality:** Optional, Multivalued
-**CURIE:** [`voc4cat:0007202`](https://w3id.org/nfdi4cat/voc4cat_0007202)
+**CURIE:** [`VOC4CAT:0007202`](https://w3id.org/nfdi4cat/voc4cat_0007202)
**Schema Reference:** [working_electrode](./elements/working_electrode.md)
@@ -3562,7 +3562,7 @@ this subprofile.
**Cardinality:** Optional, Multivalued
-**CURIE:** [`voc4cat:0007203`](https://w3id.org/nfdi4cat/voc4cat_0007203)
+**CURIE:** [`VOC4CAT:0007203`](https://w3id.org/nfdi4cat/voc4cat_0007203)
**Schema Reference:** [counter_electrode](./elements/counter_electrode.md)
@@ -3678,7 +3678,7 @@ this subprofile.
**Cardinality:** Optional, Multivalued
-**CURIE:** [`voc4cat:0007246`](https://w3id.org/nfdi4cat/voc4cat_0007246)
+**CURIE:** [`VOC4CAT:0007246`](https://w3id.org/nfdi4cat/voc4cat_0007246)
**Schema Reference:** [solvent](./elements/solvent.md)
@@ -3718,7 +3718,7 @@ this subprofile.
**Cardinality:** Optional, Multivalued
-**CURIE:** [`voc4cat:0000176`](https://w3id.org/nfdi4cat/voc4cat_0000176)
+**CURIE:** [`VOC4CAT:0000176`](https://w3id.org/nfdi4cat/voc4cat_0000176)
**Schema Reference:** [light_wavelength](./elements/light_wavelength.md)
@@ -3857,7 +3857,7 @@ this subprofile.
**Cardinality:** Optional, Multivalued
-**CURIE:** [`voc4cat:0000108`](https://w3id.org/nfdi4cat/voc4cat_0000108)
+**CURIE:** [`VOC4CAT:0000108`](https://w3id.org/nfdi4cat/voc4cat_0000108)
**Schema Reference:** [operation_mode](./elements/operation_mode.md)
@@ -3918,7 +3918,7 @@ this subprofile.
**Cardinality:** Optional, Multivalued
-**CURIE:** [`voc4cat:0007246`](https://w3id.org/nfdi4cat/voc4cat_0007246)
+**CURIE:** [`VOC4CAT:0007246`](https://w3id.org/nfdi4cat/voc4cat_0007246)
**Schema Reference:** [solvent_composition](./elements/solvent_composition.md)
diff --git a/docs/coremeta4cat-users.md b/docs/coremeta4cat-users.md
index afbf63453..712386ce2 100644
--- a/docs/coremeta4cat-users.md
+++ b/docs/coremeta4cat-users.md
@@ -17,7 +17,11 @@ This page lists projects, data repositories, and communities that have adopted C
## NFDI4Cat
-
+
CoreMeta4Cat is developed within the [NFDI4Cat](https://nfdi4cat.org) initiative — the National Research Data Infrastructure consortium for catalysis sciences in Germany. NFDI4Cat brings together universities, research institutions, and industrial partners to build shared data infrastructure for the catalysis community.
diff --git a/docs/reaction.md b/docs/reaction.md
index 06563419c..4a8d5430d 100644
--- a/docs/reaction.md
+++ b/docs/reaction.md
@@ -33,13 +33,13 @@ Core fields include reactor design type, operational parameters, and product ide
## Slots
-catalyst quantity (Required, Multivalued)
+catalyst quantity (Mandatory, Multivalued)
**Description:** Mass of catalyst loaded into the reactor.
**Data Type:** float
-**Cardinality:** Required, Multivalued
+**Cardinality:** Mandatory, Multivalued
**CURIE:** [`catcore:catalyst_quantity`](https://w3id.org/nfdi4cat/catcore/catalyst_quantity)
@@ -54,16 +54,16 @@ Core fields include reactor design type, operational parameters, and product ide
-reactant (Required, Multivalued)
+reactant (Mandatory, Multivalued)
**Description:** Reactant(s) used in the reaction. Provide compound name, CAS number,
or SMILES. For feeds, include composition and flow rate where known.
**Data Type:** string
-**Cardinality:** Required, Multivalued
+**Cardinality:** Mandatory, Multivalued
-**CURIE:** [`voc4cat:0000101`](https://w3id.org/nfdi4cat/voc4cat_0000101)
+**CURIE:** [`VOC4CAT:0000101`](https://w3id.org/nfdi4cat/voc4cat_0000101)
**Schema Reference:** [reactant](./elements/reactant.md)
@@ -83,7 +83,7 @@ For heterogeneous catalysts, use voc4cat terms where available.
**Cardinality:** Recommended, Multivalued
-**CURIE:** [`voc4cat:0007014`](https://w3id.org/nfdi4cat/voc4cat_0007014)
+**CURIE:** [`VOC4CAT:0007014`](https://w3id.org/nfdi4cat/voc4cat_0007014)
**Schema Reference:** [catalyst_type](./elements/catalyst_type.md)
@@ -103,7 +103,7 @@ as a string range (e.g. "200–400 °C") or a single set-point.
**Cardinality:** Optional, Multivalued
-**CURIE:** [`voc4cat:0007032`](https://w3id.org/nfdi4cat/voc4cat_0007032)
+**CURIE:** [`VOC4CAT:0007032`](https://w3id.org/nfdi4cat/voc4cat_0007032)
**Schema Reference:** [reactor_temperature_range](./elements/reactor_temperature_range.md)
@@ -141,7 +141,7 @@ as a string range (e.g. "200–400 °C") or a single set-point.
**Cardinality:** Optional, Multivalued
-**CURIE:** [`voc4cat:0000118`](https://w3id.org/nfdi4cat/voc4cat_0000118)
+**CURIE:** [`VOC4CAT:0000118`](https://w3id.org/nfdi4cat/voc4cat_0000118)
**Schema Reference:** [experiment_pressure](./elements/experiment_pressure.md)
@@ -195,14 +195,14 @@ Record as a string; for individual component concentrations use reactant.
-carried out by (Required, Multivalued)
+carried out by (Mandatory, Multivalued)
**Description:** The reactor in which the Reaction takes place, provided as a
ReactorDesignType (Device) instance.
**Data Type:** ReactorDesignType
-**Cardinality:** Required, Multivalued
+**Cardinality:** Mandatory, Multivalued
**Schema Reference:** [carried_out_by](./elements/carried_out_by.md)
@@ -217,7 +217,7 @@ ReactorDesignType (Device) instance.
Concrete subclasses specify the reactor geometry and operating mode.
Linked from Reaction via carried_out_by.
-**CURIE:** [`voc4cat:0007018`](https://w3id.org/nfdi4cat/voc4cat_0007018)
+**CURIE:** [`VOC4CAT:0007018`](https://w3id.org/nfdi4cat/voc4cat_0007018)
**Schema Reference:** [ReactorDesignType](./elements/ReactorDesignType.md)
@@ -235,7 +235,7 @@ Linked from Reaction via carried_out_by.
**Description:** Electrochemical reactor used in electrocatalytic experiments, including
H-cells, flow cells, and membrane electrode assemblies.
-**CURIE:** [`voc4cat:0000193`](https://w3id.org/nfdi4cat/voc4cat_0000193)
+**CURIE:** [`VOC4CAT:0000193`](https://w3id.org/nfdi4cat/voc4cat_0000193)
**Schema Reference:** [ElectrochemicalReactor](./elements/ElectrochemicalReactor.md)
@@ -251,7 +251,7 @@ H-cells, flow cells, and membrane electrode assemblies.
**Description:** Continuous stirred tank reactor (CSTR) — a well-mixed, continuous-flow
reactor operating at steady state.
-**CURIE:** [`voc4cat:0007019`](https://w3id.org/nfdi4cat/voc4cat_0007019)
+**CURIE:** [`VOC4CAT:0007019`](https://w3id.org/nfdi4cat/voc4cat_0007019)
**Schema Reference:** [CSTR](./elements/CSTR.md)
@@ -267,7 +267,7 @@ reactor operating at steady state.
**Description:** Plug flow reactor (PFR) — a tubular reactor in which reactant composition
varies along the axis with no axial mixing.
-**CURIE:** [`voc4cat:0007102`](https://w3id.org/nfdi4cat/voc4cat_0007102)
+**CURIE:** [`VOC4CAT:0007102`](https://w3id.org/nfdi4cat/voc4cat_0007102)
**Schema Reference:** [PlugFlowReactor](./elements/PlugFlowReactor.md)
@@ -316,7 +316,7 @@ suspended in a liquid phase through which gas is bubbled.
in the sub-millimetre range, enabling precise thermal control and rapid
screening.
-**CURIE:** [`voc4cat:0000234`](https://w3id.org/nfdi4cat/voc4cat_0000234)
+**CURIE:** [`VOC4CAT:0000234`](https://w3id.org/nfdi4cat/voc4cat_0000234)
**Schema Reference:** [Microreactor](./elements/Microreactor.md)
@@ -426,7 +426,7 @@ suspended in an upward-flowing gas or liquid stream.
-product identification method (Required, Multivalued)
+product identification method (Mandatory, Multivalued)
**Description:** The analytical method used to identify and/or quantify reaction products.
Should reference a CharacterizationTechnique instance (e.g. a GCMS or
@@ -435,7 +435,7 @@ ProductIdentificationMethod is retained for backward compatibility.
**Data Type:** ProductIdentificationMethod
-**Cardinality:** Required, Multivalued
+**Cardinality:** Mandatory, Multivalued
**Schema Reference:** [product_identification_method](./elements/product_identification_method.md)
diff --git a/docs/simulation.md b/docs/simulation.md
index 52e85d107..9cbce3e10 100644
--- a/docs/simulation.md
+++ b/docs/simulation.md
@@ -33,14 +33,14 @@ Computational approaches are organized under the parent field simulation method,
## Slots
-software package (Required, Multivalued)
+software package (Mandatory, Multivalued)
**Description:** Software package or code used for the simulation (e.g. VASP, Quantum ESPRESSO,
LAMMPS, CP2K, ORCA, Zacros). Include version number where possible.
**Data Type:** string
-**Cardinality:** Required, Multivalued
+**Cardinality:** Mandatory, Multivalued
**CURIE:** [`catcore:software_package`](https://w3id.org/nfdi4cat/catcore/software_package)
@@ -53,14 +53,14 @@ LAMMPS, CP2K, ORCA, Zacros). Include version number where possible.
-calculated property (Required, Multivalued)
+calculated property (Mandatory, Multivalued)
**Description:** A property computed by this Simulation, provided as a CalculatedProperty
instance. Multiple properties may be computed in a single simulation run.
**Data Type:** CalculatedProperty
-**Cardinality:** Required, Multivalued
+**Cardinality:** Mandatory, Multivalued
**CURIE:** [`catcore:calculated_property`](https://w3id.org/nfdi4cat/catcore/calculated_property)
@@ -565,7 +565,7 @@ Characterises the optical and static dielectric response of a material.
dielectric tensor (Optional, Multivalued)
-**Description:** Components of the static and/or high-frequency dielectric tensor ε_ij,
+**Description:** Components of the static and/or high-frequency dielectric tensor epsilon_ij,
computed from DFPT.
**Data Type:** string
@@ -1225,7 +1225,7 @@ sintering, and charge/defect segregation.
grain boundary plane (Optional, Multivalued)
**Description:** Crystallographic plane of the grain boundary, expressed using
-Miller indices (e.g. "Σ5 (310)[001]").
+Miller indices (e.g. "Sigma5 (310)[001]").
**Data Type:** string
@@ -1425,7 +1425,7 @@ electronic properties of a catalyst relevant to activity descriptors
smearing method (Optional, Multivalued)
**Description:** Electronic smearing scheme and width used in the SCF calculation
-(e.g. Methfessel-Paxton order 1 with σ=0.2 eV, Gaussian with σ=0.05 eV).
+(e.g. Methfessel-Paxton order 1 with sigma=0.2 eV, Gaussian with sigma=0.05 eV).
**Data Type:** string
@@ -1465,7 +1465,7 @@ electronic properties of a catalyst relevant to activity descriptors
band path (Optional, Multivalued)
**Description:** High-symmetry k-path through the Brillouin zone used to plot the
-band structure (e.g. "Γ-X-M-Γ-R" for cubic, following SeeK-path convention).
+band structure (e.g. "Gamma-X-M-Gamma-R" for cubic, following SeeK-path convention).
**Data Type:** string
@@ -1626,7 +1626,7 @@ ferroelectric-photocatalyst design.
polarization direction (Optional, Multivalued)
**Description:** Crystallographic direction of the spontaneous electric polarization
-(e.g. "[001]" for tetragonal BaTiO₃).
+(e.g. "[001]" for tetragonal BaTiO_3).
**Data Type:** string
@@ -1813,7 +1813,7 @@ calculated band gap.
**Cardinality:** Optional, Multivalued
-**CURIE:** [`voc4cat:0005056`](https://w3id.org/nfdi4cat/voc4cat_0005056)
+**CURIE:** [`VOC4CAT:0005056`](https://w3id.org/nfdi4cat/voc4cat_0005056)
**Schema Reference:** [material_sample](./elements/material_sample.md)
@@ -2061,13 +2061,13 @@ geometry optimisation (e.g. energy < 1e-5 eV, forces < 0.02 eV/Å).
-realized plan (Required, Multivalued)
+realized plan (Mandatory, Multivalued)
**Description:** The SimulationMethod (protocol) realized in this Simulation.
**Data Type:** SimulationMethod
-**Cardinality:** Required, Multivalued
+**Cardinality:** Mandatory, Multivalued
**Schema Reference:** [realized_plan](./elements/realized_plan.md)
diff --git a/docs/synthesis.md b/docs/synthesis.md
index dfbf4821a..54aef2202 100644
--- a/docs/synthesis.md
+++ b/docs/synthesis.md
@@ -34,13 +34,13 @@ Metadata are organized hierarchically based on the selected synthesis method. Me
## Slots
-nominal composition (Required, Multivalued)
+nominal composition (Mandatory, Multivalued)
**Description:** Nominal elemental or chemical composition of the catalyst (e.g. 5wt% Pt/Al2O3).
**Data Type:** string
-**Cardinality:** Required, Multivalued
+**Cardinality:** Mandatory, Multivalued
**CURIE:** [`catcore:nominal_composition`](https://w3id.org/nfdi4cat/catcore/nominal_composition)
@@ -53,14 +53,14 @@ Metadata are organized hierarchically based on the selected synthesis method. Me
-catalyst measured properties (Required, Multivalued)
+catalyst measured properties (Mandatory, Multivalued)
**Description:** Key measured properties of the resulting catalyst
(e.g. BET surface area, sieve fraction, molar ratio).
**Data Type:** string
-**Cardinality:** Required, Multivalued
+**Cardinality:** Mandatory, Multivalued
**CURIE:** [`catcore:catalyst_measured_properties`](https://w3id.org/nfdi4cat/catcore/catalyst_measured_properties)
@@ -119,7 +119,7 @@ Metadata are organized hierarchically based on the selected synthesis method. Me
**Cardinality:** Optional, Multivalued
-**CURIE:** [`voc4cat:0007246`](https://w3id.org/nfdi4cat/voc4cat_0007246)
+**CURIE:** [`VOC4CAT:0007246`](https://w3id.org/nfdi4cat/voc4cat_0007246)
**Schema Reference:** [solvent](./elements/solvent.md)
@@ -138,7 +138,7 @@ Metadata are organized hierarchically based on the selected synthesis method. Me
**Cardinality:** Optional, Multivalued
-**CURIE:** [`voc4cat:0000122`](https://w3id.org/nfdi4cat/voc4cat_0000122)
+**CURIE:** [`VOC4CAT:0000122`](https://w3id.org/nfdi4cat/voc4cat_0000122)
**Schema Reference:** [sample_pretreatment](./elements/sample_pretreatment.md)
@@ -149,13 +149,107 @@ Metadata are organized hierarchically based on the selected synthesis method. Me
-realized plan (Required)
+had input entity (Mandatory, Multivalued)
+
+**Description:** The Precursor(s) consumed during this Synthesis.
+
+**Data Type:** Precursor
+
+**Cardinality:** Mandatory, Multivalued
+
+**Schema Reference:** [had_input_entity](./elements/had_input_entity.md)
+
+**Data Type Class Details:**
+
+
+Precursor
+
+**Description:** A MaterialSample that serves as input material in a catalyst Synthesis.
+Precursors are consumed or transformed during the preparation process.
+
+**CURIE:** [`CHEBI:52717`](http://purl.obolibrary.org/obo/CHEBI_52717)
+
+**Schema Reference:** [Precursor](./elements/Precursor.md)
+
+**Slots**
+
+
+precursor quantity (Mandatory, Multivalued)
+
+**Description:** Quantity of precursor used in synthesis.
+
+**Data Type:** float
+
+**Cardinality:** Mandatory, Multivalued
+
+**CURIE:** [`catcore:precursor_quantity`](https://w3id.org/nfdi4cat/catcore/precursor_quantity)
+
+**Schema Reference:** [precursor_quantity](./elements/precursor_quantity.md)
+
+**Unit:** g
+
+
+
+ 💡 Submit Term Feedback
+
+
+
+
+
+ 💡 Submit Term Feedback
+
+
+
+
+
+ 💡 Submit Term Feedback
+
+
+
+
+had output entity (Recommended, Multivalued)
+
+**Description:** The CatalystSample produced by this Synthesis.
+
+**Data Type:** CatalystSample
+
+**Cardinality:** Recommended, Multivalued
+
+**Schema Reference:** [had_output_entity](./elements/had_output_entity.md)
+
+**Data Type Class Details:**
+
+
+CatalystSample
+
+**Description:** A MaterialSample that is the product of a catalyst Synthesis.
+The specific type of catalyst (e.g. heterogeneous, supported metal)
+is expressed via rdf_type using a voc4cat term.
+
+**CURIE:** [`OBI:0000747`](http://purl.obolibrary.org/obo/OBI_0000747)
+
+**Schema Reference:** [CatalystSample](./elements/CatalystSample.md)
+
+
+
+ 💡 Submit Term Feedback
+
+
+
+
+
+ 💡 Submit Term Feedback
+
+
+
+
+realized plan (Mandatory)
**Description:** The PreparationMethod (protocol) realized in this Synthesis.
**Data Type:** PreparationMethod
-**Cardinality:** Required
+**Cardinality:** Mandatory
**Schema Reference:** [realized_plan](./elements/realized_plan.md)
@@ -172,7 +266,7 @@ method-specific parameters. Linked from Synthesis via realized_plan.
The specific preparation method type should additionally be expressed
via rdf_type on the Synthesis activity using a voc4cat term
-(e.g. voc4cat:0007016 for preparation method).
+(e.g. VOC4CAT:0007016 for preparation method).
**CURIE:** [`OBI:0000272`](http://purl.obolibrary.org/obo/OBI_0000272)
@@ -348,7 +442,7 @@ precursor is brought into contact with the support material.
**Cardinality:** Optional, Multivalued
-**CURIE:** [`voc4cat:0000057`](https://w3id.org/nfdi4cat/voc4cat_0000057)
+**CURIE:** [`VOC4CAT:0000057`](https://w3id.org/nfdi4cat/voc4cat_0000057)
**Schema Reference:** [calcination_initial_temperature](./elements/calcination_initial_temperature.md)
@@ -369,7 +463,7 @@ precursor is brought into contact with the support material.
**Cardinality:** Optional, Multivalued
-**CURIE:** [`voc4cat:0000058`](https://w3id.org/nfdi4cat/voc4cat_0000058)
+**CURIE:** [`VOC4CAT:0000058`](https://w3id.org/nfdi4cat/voc4cat_0000058)
**Schema Reference:** [calcination_final_temperature](./elements/calcination_final_temperature.md)
@@ -390,7 +484,7 @@ precursor is brought into contact with the support material.
**Cardinality:** Optional, Multivalued
-**CURIE:** [`voc4cat:0000060`](https://w3id.org/nfdi4cat/voc4cat_0000060)
+**CURIE:** [`VOC4CAT:0000060`](https://w3id.org/nfdi4cat/voc4cat_0000060)
**Schema Reference:** [calcination_dwelling_time](./elements/calcination_dwelling_time.md)
@@ -430,7 +524,7 @@ precursor is brought into contact with the support material.
**Cardinality:** Optional, Multivalued
-**CURIE:** [`voc4cat:0000055`](https://w3id.org/nfdi4cat/voc4cat_0000055)
+**CURIE:** [`VOC4CAT:0000055`](https://w3id.org/nfdi4cat/voc4cat_0000055)
**Schema Reference:** [calcination_gaseous_environment](./elements/calcination_gaseous_environment.md)
@@ -449,7 +543,7 @@ precursor is brought into contact with the support material.
**Cardinality:** Optional, Multivalued
-**CURIE:** [`voc4cat:0000059`](https://w3id.org/nfdi4cat/voc4cat_0000059)
+**CURIE:** [`VOC4CAT:0000059`](https://w3id.org/nfdi4cat/voc4cat_0000059)
**Schema Reference:** [calcination_heating_rate](./elements/calcination_heating_rate.md)
@@ -470,7 +564,7 @@ precursor is brought into contact with the support material.
**Cardinality:** Optional, Multivalued
-**CURIE:** [`voc4cat:0000056`](https://w3id.org/nfdi4cat/voc4cat_0000056)
+**CURIE:** [`VOC4CAT:0000056`](https://w3id.org/nfdi4cat/voc4cat_0000056)
**Schema Reference:** [calcination_gas_flow_rate](./elements/calcination_gas_flow_rate.md)
@@ -549,7 +643,7 @@ simultaneously precipitated from solution by a precipitating agent.
**Cardinality:** Optional, Multivalued
-**CURIE:** [`voc4cat:0000052`](https://w3id.org/nfdi4cat/voc4cat_0000052)
+**CURIE:** [`VOC4CAT:0000052`](https://w3id.org/nfdi4cat/voc4cat_0000052)
**Schema Reference:** [synthesis_ph](./elements/synthesis_ph.md)
@@ -810,7 +904,7 @@ simultaneously precipitated from solution by a precipitating agent.
**Cardinality:** Optional, Multivalued
-**CURIE:** [`voc4cat:0000057`](https://w3id.org/nfdi4cat/voc4cat_0000057)
+**CURIE:** [`VOC4CAT:0000057`](https://w3id.org/nfdi4cat/voc4cat_0000057)
**Schema Reference:** [calcination_initial_temperature](./elements/calcination_initial_temperature.md)
@@ -831,7 +925,7 @@ simultaneously precipitated from solution by a precipitating agent.
**Cardinality:** Optional, Multivalued
-**CURIE:** [`voc4cat:0000058`](https://w3id.org/nfdi4cat/voc4cat_0000058)
+**CURIE:** [`VOC4CAT:0000058`](https://w3id.org/nfdi4cat/voc4cat_0000058)
**Schema Reference:** [calcination_final_temperature](./elements/calcination_final_temperature.md)
@@ -852,7 +946,7 @@ simultaneously precipitated from solution by a precipitating agent.
**Cardinality:** Optional, Multivalued
-**CURIE:** [`voc4cat:0000060`](https://w3id.org/nfdi4cat/voc4cat_0000060)
+**CURIE:** [`VOC4CAT:0000060`](https://w3id.org/nfdi4cat/voc4cat_0000060)
**Schema Reference:** [calcination_dwelling_time](./elements/calcination_dwelling_time.md)
@@ -892,7 +986,7 @@ simultaneously precipitated from solution by a precipitating agent.
**Cardinality:** Optional, Multivalued
-**CURIE:** [`voc4cat:0000055`](https://w3id.org/nfdi4cat/voc4cat_0000055)
+**CURIE:** [`VOC4CAT:0000055`](https://w3id.org/nfdi4cat/voc4cat_0000055)
**Schema Reference:** [calcination_gaseous_environment](./elements/calcination_gaseous_environment.md)
@@ -911,7 +1005,7 @@ simultaneously precipitated from solution by a precipitating agent.
**Cardinality:** Optional, Multivalued
-**CURIE:** [`voc4cat:0000059`](https://w3id.org/nfdi4cat/voc4cat_0000059)
+**CURIE:** [`VOC4CAT:0000059`](https://w3id.org/nfdi4cat/voc4cat_0000059)
**Schema Reference:** [calcination_heating_rate](./elements/calcination_heating_rate.md)
@@ -932,7 +1026,7 @@ simultaneously precipitated from solution by a precipitating agent.
**Cardinality:** Optional, Multivalued
-**CURIE:** [`voc4cat:0000056`](https://w3id.org/nfdi4cat/voc4cat_0000056)
+**CURIE:** [`VOC4CAT:0000056`](https://w3id.org/nfdi4cat/voc4cat_0000056)
**Schema Reference:** [calcination_gas_flow_rate](./elements/calcination_gas_flow_rate.md)
@@ -1208,7 +1302,7 @@ sealed vessel using a non-aqueous solvent.
**Cardinality:** Optional, Multivalued
-**CURIE:** [`voc4cat:0000051`](https://w3id.org/nfdi4cat/voc4cat_0000051)
+**CURIE:** [`VOC4CAT:0000051`](https://w3id.org/nfdi4cat/voc4cat_0000051)
**Schema Reference:** [synthesis_temperature](./elements/synthesis_temperature.md)
@@ -1229,7 +1323,7 @@ sealed vessel using a non-aqueous solvent.
**Cardinality:** Optional, Multivalued
-**CURIE:** [`voc4cat:0000050`](https://w3id.org/nfdi4cat/voc4cat_0000050)
+**CURIE:** [`VOC4CAT:0000050`](https://w3id.org/nfdi4cat/voc4cat_0000050)
**Schema Reference:** [synthesis_duration](./elements/synthesis_duration.md)
@@ -1250,7 +1344,7 @@ sealed vessel using a non-aqueous solvent.
**Cardinality:** Optional, Multivalued
-**CURIE:** [`voc4cat:0000187`](https://w3id.org/nfdi4cat/voc4cat_0000187)
+**CURIE:** [`VOC4CAT:0000187`](https://w3id.org/nfdi4cat/voc4cat_0000187)
**Schema Reference:** [equipment](./elements/equipment.md)
@@ -1386,7 +1480,7 @@ properties or deposit active components.
**Cardinality:** Optional, Multivalued
-**CURIE:** [`voc4cat:0000053`](https://w3id.org/nfdi4cat/voc4cat_0000053)
+**CURIE:** [`VOC4CAT:0000053`](https://w3id.org/nfdi4cat/voc4cat_0000053)
**Schema Reference:** [synthesis_pressure](./elements/synthesis_pressure.md)
@@ -1407,7 +1501,7 @@ properties or deposit active components.
**Cardinality:** Optional, Multivalued
-**CURIE:** [`voc4cat:0000051`](https://w3id.org/nfdi4cat/voc4cat_0000051)
+**CURIE:** [`VOC4CAT:0000051`](https://w3id.org/nfdi4cat/voc4cat_0000051)
**Schema Reference:** [synthesis_temperature](./elements/synthesis_temperature.md)
@@ -1428,7 +1522,7 @@ properties or deposit active components.
**Cardinality:** Optional, Multivalued
-**CURIE:** [`voc4cat:0000050`](https://w3id.org/nfdi4cat/voc4cat_0000050)
+**CURIE:** [`VOC4CAT:0000050`](https://w3id.org/nfdi4cat/voc4cat_0000050)
**Schema Reference:** [synthesis_duration](./elements/synthesis_duration.md)
@@ -1449,7 +1543,7 @@ properties or deposit active components.
**Cardinality:** Optional, Multivalued
-**CURIE:** [`voc4cat:0000187`](https://w3id.org/nfdi4cat/voc4cat_0000187)
+**CURIE:** [`VOC4CAT:0000187`](https://w3id.org/nfdi4cat/voc4cat_0000187)
**Schema Reference:** [equipment](./elements/equipment.md)
@@ -1621,7 +1715,7 @@ producing metal oxide catalysts in a single rapid step.
**Cardinality:** Optional, Multivalued
-**CURIE:** [`voc4cat:0000051`](https://w3id.org/nfdi4cat/voc4cat_0000051)
+**CURIE:** [`VOC4CAT:0000051`](https://w3id.org/nfdi4cat/voc4cat_0000051)
**Schema Reference:** [synthesis_temperature](./elements/synthesis_temperature.md)
@@ -1642,7 +1736,7 @@ producing metal oxide catalysts in a single rapid step.
**Cardinality:** Optional, Multivalued
-**CURIE:** [`voc4cat:0000050`](https://w3id.org/nfdi4cat/voc4cat_0000050)
+**CURIE:** [`VOC4CAT:0000050`](https://w3id.org/nfdi4cat/voc4cat_0000050)
**Schema Reference:** [synthesis_duration](./elements/synthesis_duration.md)
@@ -1663,7 +1757,7 @@ producing metal oxide catalysts in a single rapid step.
**Cardinality:** Optional, Multivalued
-**CURIE:** [`voc4cat:0000187`](https://w3id.org/nfdi4cat/voc4cat_0000187)
+**CURIE:** [`VOC4CAT:0000187`](https://w3id.org/nfdi4cat/voc4cat_0000187)
**Schema Reference:** [equipment](./elements/equipment.md)
@@ -1739,7 +1833,7 @@ of active phase onto a substrate.
**Cardinality:** Optional, Multivalued
-**CURIE:** [`voc4cat:0000024`](https://w3id.org/nfdi4cat/voc4cat_0000024)
+**CURIE:** [`VOC4CAT:0000024`](https://w3id.org/nfdi4cat/voc4cat_0000024)
**Schema Reference:** [substrate](./elements/substrate.md)
@@ -1779,7 +1873,7 @@ of active phase onto a substrate.
**Cardinality:** Optional, Multivalued
-**CURIE:** [`voc4cat:0000112`](https://w3id.org/nfdi4cat/voc4cat_0000112)
+**CURIE:** [`VOC4CAT:0000112`](https://w3id.org/nfdi4cat/voc4cat_0000112)
**Schema Reference:** [purging_duration](./elements/purging_duration.md)
@@ -1959,7 +2053,7 @@ is precipitated directly onto the support surface.
**Cardinality:** Optional, Multivalued
-**CURIE:** [`voc4cat:0000052`](https://w3id.org/nfdi4cat/voc4cat_0000052)
+**CURIE:** [`VOC4CAT:0000052`](https://w3id.org/nfdi4cat/voc4cat_0000052)
**Schema Reference:** [synthesis_ph](./elements/synthesis_ph.md)
@@ -2220,7 +2314,7 @@ is precipitated directly onto the support surface.
**Cardinality:** Optional, Multivalued
-**CURIE:** [`voc4cat:0000057`](https://w3id.org/nfdi4cat/voc4cat_0000057)
+**CURIE:** [`VOC4CAT:0000057`](https://w3id.org/nfdi4cat/voc4cat_0000057)
**Schema Reference:** [calcination_initial_temperature](./elements/calcination_initial_temperature.md)
@@ -2241,7 +2335,7 @@ is precipitated directly onto the support surface.
**Cardinality:** Optional, Multivalued
-**CURIE:** [`voc4cat:0000058`](https://w3id.org/nfdi4cat/voc4cat_0000058)
+**CURIE:** [`VOC4CAT:0000058`](https://w3id.org/nfdi4cat/voc4cat_0000058)
**Schema Reference:** [calcination_final_temperature](./elements/calcination_final_temperature.md)
@@ -2262,7 +2356,7 @@ is precipitated directly onto the support surface.
**Cardinality:** Optional, Multivalued
-**CURIE:** [`voc4cat:0000060`](https://w3id.org/nfdi4cat/voc4cat_0000060)
+**CURIE:** [`VOC4CAT:0000060`](https://w3id.org/nfdi4cat/voc4cat_0000060)
**Schema Reference:** [calcination_dwelling_time](./elements/calcination_dwelling_time.md)
@@ -2302,7 +2396,7 @@ is precipitated directly onto the support surface.
**Cardinality:** Optional, Multivalued
-**CURIE:** [`voc4cat:0000055`](https://w3id.org/nfdi4cat/voc4cat_0000055)
+**CURIE:** [`VOC4CAT:0000055`](https://w3id.org/nfdi4cat/voc4cat_0000055)
**Schema Reference:** [calcination_gaseous_environment](./elements/calcination_gaseous_environment.md)
@@ -2321,7 +2415,7 @@ is precipitated directly onto the support surface.
**Cardinality:** Optional, Multivalued
-**CURIE:** [`voc4cat:0000059`](https://w3id.org/nfdi4cat/voc4cat_0000059)
+**CURIE:** [`VOC4CAT:0000059`](https://w3id.org/nfdi4cat/voc4cat_0000059)
**Schema Reference:** [calcination_heating_rate](./elements/calcination_heating_rate.md)
@@ -2342,7 +2436,7 @@ is precipitated directly onto the support surface.
**Cardinality:** Optional, Multivalued
-**CURIE:** [`voc4cat:0000056`](https://w3id.org/nfdi4cat/voc4cat_0000056)
+**CURIE:** [`VOC4CAT:0000056`](https://w3id.org/nfdi4cat/voc4cat_0000056)
**Schema Reference:** [calcination_gas_flow_rate](./elements/calcination_gas_flow_rate.md)
@@ -2423,7 +2517,7 @@ uniformly heat the reaction mixture.
**Cardinality:** Optional, Multivalued
-**CURIE:** [`voc4cat:0000051`](https://w3id.org/nfdi4cat/voc4cat_0000051)
+**CURIE:** [`VOC4CAT:0000051`](https://w3id.org/nfdi4cat/voc4cat_0000051)
**Schema Reference:** [synthesis_temperature](./elements/synthesis_temperature.md)
@@ -2444,7 +2538,7 @@ uniformly heat the reaction mixture.
**Cardinality:** Optional, Multivalued
-**CURIE:** [`voc4cat:0000050`](https://w3id.org/nfdi4cat/voc4cat_0000050)
+**CURIE:** [`VOC4CAT:0000050`](https://w3id.org/nfdi4cat/voc4cat_0000050)
**Schema Reference:** [synthesis_duration](./elements/synthesis_duration.md)
@@ -2465,7 +2559,7 @@ uniformly heat the reaction mixture.
**Cardinality:** Optional, Multivalued
-**CURIE:** [`voc4cat:0000187`](https://w3id.org/nfdi4cat/voc4cat_0000187)
+**CURIE:** [`VOC4CAT:0000187`](https://w3id.org/nfdi4cat/voc4cat_0000187)
**Schema Reference:** [equipment](./elements/equipment.md)
@@ -2683,7 +2777,7 @@ reactions via acoustic cavitation.
**Cardinality:** Optional, Multivalued
-**CURIE:** [`voc4cat:0000057`](https://w3id.org/nfdi4cat/voc4cat_0000057)
+**CURIE:** [`VOC4CAT:0000057`](https://w3id.org/nfdi4cat/voc4cat_0000057)
**Schema Reference:** [calcination_initial_temperature](./elements/calcination_initial_temperature.md)
@@ -2704,7 +2798,7 @@ reactions via acoustic cavitation.
**Cardinality:** Optional, Multivalued
-**CURIE:** [`voc4cat:0000058`](https://w3id.org/nfdi4cat/voc4cat_0000058)
+**CURIE:** [`VOC4CAT:0000058`](https://w3id.org/nfdi4cat/voc4cat_0000058)
**Schema Reference:** [calcination_final_temperature](./elements/calcination_final_temperature.md)
@@ -2725,7 +2819,7 @@ reactions via acoustic cavitation.
**Cardinality:** Optional, Multivalued
-**CURIE:** [`voc4cat:0000060`](https://w3id.org/nfdi4cat/voc4cat_0000060)
+**CURIE:** [`VOC4CAT:0000060`](https://w3id.org/nfdi4cat/voc4cat_0000060)
**Schema Reference:** [calcination_dwelling_time](./elements/calcination_dwelling_time.md)
@@ -2765,7 +2859,7 @@ reactions via acoustic cavitation.
**Cardinality:** Optional, Multivalued
-**CURIE:** [`voc4cat:0000055`](https://w3id.org/nfdi4cat/voc4cat_0000055)
+**CURIE:** [`VOC4CAT:0000055`](https://w3id.org/nfdi4cat/voc4cat_0000055)
**Schema Reference:** [calcination_gaseous_environment](./elements/calcination_gaseous_environment.md)
@@ -2784,7 +2878,7 @@ reactions via acoustic cavitation.
**Cardinality:** Optional, Multivalued
-**CURIE:** [`voc4cat:0000059`](https://w3id.org/nfdi4cat/voc4cat_0000059)
+**CURIE:** [`VOC4CAT:0000059`](https://w3id.org/nfdi4cat/voc4cat_0000059)
**Schema Reference:** [calcination_heating_rate](./elements/calcination_heating_rate.md)
@@ -2805,7 +2899,7 @@ reactions via acoustic cavitation.
**Cardinality:** Optional, Multivalued
-**CURIE:** [`voc4cat:0000056`](https://w3id.org/nfdi4cat/voc4cat_0000056)
+**CURIE:** [`VOC4CAT:0000056`](https://w3id.org/nfdi4cat/voc4cat_0000056)
**Schema Reference:** [calcination_gas_flow_rate](./elements/calcination_gas_flow_rate.md)
@@ -2829,7 +2923,7 @@ reactions via acoustic cavitation.
**Description:** Catalyst preparation by flame spray pyrolysis (FSP): a liquid precursor
solution is atomised and combusted in a flame to produce nanoparticles.
-**CURIE:** [`voc4cat:0007031`](https://w3id.org/nfdi4cat/voc4cat_0007031)
+**CURIE:** [`VOC4CAT:0007031`](https://w3id.org/nfdi4cat/voc4cat_0007031)
**Schema Reference:** [FlameSprayPyrolysis](./elements/FlameSprayPyrolysis.md)
@@ -3178,7 +3272,7 @@ combined with thermal treatment.
**Cardinality:** Optional, Multivalued
-**CURIE:** [`voc4cat:0000051`](https://w3id.org/nfdi4cat/voc4cat_0000051)
+**CURIE:** [`VOC4CAT:0000051`](https://w3id.org/nfdi4cat/voc4cat_0000051)
**Schema Reference:** [synthesis_temperature](./elements/synthesis_temperature.md)
@@ -3199,7 +3293,7 @@ combined with thermal treatment.
**Cardinality:** Optional, Multivalued
-**CURIE:** [`voc4cat:0000050`](https://w3id.org/nfdi4cat/voc4cat_0000050)
+**CURIE:** [`VOC4CAT:0000050`](https://w3id.org/nfdi4cat/voc4cat_0000050)
**Schema Reference:** [synthesis_duration](./elements/synthesis_duration.md)
@@ -3220,7 +3314,7 @@ combined with thermal treatment.
**Cardinality:** Optional, Multivalued
-**CURIE:** [`voc4cat:0000187`](https://w3id.org/nfdi4cat/voc4cat_0000187)
+**CURIE:** [`VOC4CAT:0000187`](https://w3id.org/nfdi4cat/voc4cat_0000187)
**Schema Reference:** [equipment](./elements/equipment.md)
@@ -3295,7 +3389,7 @@ and deposited onto a substrate without passing through a liquid phase.
**Cardinality:** Optional, Multivalued
-**CURIE:** [`voc4cat:0000053`](https://w3id.org/nfdi4cat/voc4cat_0000053)
+**CURIE:** [`VOC4CAT:0000053`](https://w3id.org/nfdi4cat/voc4cat_0000053)
**Schema Reference:** [synthesis_pressure](./elements/synthesis_pressure.md)
@@ -3316,7 +3410,7 @@ and deposited onto a substrate without passing through a liquid phase.
**Cardinality:** Optional, Multivalued
-**CURIE:** [`voc4cat:0000051`](https://w3id.org/nfdi4cat/voc4cat_0000051)
+**CURIE:** [`VOC4CAT:0000051`](https://w3id.org/nfdi4cat/voc4cat_0000051)
**Schema Reference:** [synthesis_temperature](./elements/synthesis_temperature.md)
@@ -3337,7 +3431,7 @@ and deposited onto a substrate without passing through a liquid phase.
**Cardinality:** Optional, Multivalued
-**CURIE:** [`voc4cat:0000050`](https://w3id.org/nfdi4cat/voc4cat_0000050)
+**CURIE:** [`VOC4CAT:0000050`](https://w3id.org/nfdi4cat/voc4cat_0000050)
**Schema Reference:** [synthesis_duration](./elements/synthesis_duration.md)
@@ -3358,7 +3452,7 @@ and deposited onto a substrate without passing through a liquid phase.
**Cardinality:** Optional, Multivalued
-**CURIE:** [`voc4cat:0000187`](https://w3id.org/nfdi4cat/voc4cat_0000187)
+**CURIE:** [`VOC4CAT:0000187`](https://w3id.org/nfdi4cat/voc4cat_0000187)
**Schema Reference:** [equipment](./elements/equipment.md)
@@ -3807,7 +3901,7 @@ a perovskite oxide surface by reduction/oxidation cycling.
**Cardinality:** Optional, Multivalued
-**CURIE:** [`voc4cat:0000057`](https://w3id.org/nfdi4cat/voc4cat_0000057)
+**CURIE:** [`VOC4CAT:0000057`](https://w3id.org/nfdi4cat/voc4cat_0000057)
**Schema Reference:** [calcination_initial_temperature](./elements/calcination_initial_temperature.md)
@@ -3828,7 +3922,7 @@ a perovskite oxide surface by reduction/oxidation cycling.
**Cardinality:** Optional, Multivalued
-**CURIE:** [`voc4cat:0000058`](https://w3id.org/nfdi4cat/voc4cat_0000058)
+**CURIE:** [`VOC4CAT:0000058`](https://w3id.org/nfdi4cat/voc4cat_0000058)
**Schema Reference:** [calcination_final_temperature](./elements/calcination_final_temperature.md)
@@ -3849,7 +3943,7 @@ a perovskite oxide surface by reduction/oxidation cycling.
**Cardinality:** Optional, Multivalued
-**CURIE:** [`voc4cat:0000060`](https://w3id.org/nfdi4cat/voc4cat_0000060)
+**CURIE:** [`VOC4CAT:0000060`](https://w3id.org/nfdi4cat/voc4cat_0000060)
**Schema Reference:** [calcination_dwelling_time](./elements/calcination_dwelling_time.md)
@@ -3889,7 +3983,7 @@ a perovskite oxide surface by reduction/oxidation cycling.
**Cardinality:** Optional, Multivalued
-**CURIE:** [`voc4cat:0000055`](https://w3id.org/nfdi4cat/voc4cat_0000055)
+**CURIE:** [`VOC4CAT:0000055`](https://w3id.org/nfdi4cat/voc4cat_0000055)
**Schema Reference:** [calcination_gaseous_environment](./elements/calcination_gaseous_environment.md)
@@ -3908,7 +4002,7 @@ a perovskite oxide surface by reduction/oxidation cycling.
**Cardinality:** Optional, Multivalued
-**CURIE:** [`voc4cat:0000059`](https://w3id.org/nfdi4cat/voc4cat_0000059)
+**CURIE:** [`VOC4CAT:0000059`](https://w3id.org/nfdi4cat/voc4cat_0000059)
**Schema Reference:** [calcination_heating_rate](./elements/calcination_heating_rate.md)
@@ -3929,7 +4023,7 @@ a perovskite oxide surface by reduction/oxidation cycling.
**Cardinality:** Optional, Multivalued
-**CURIE:** [`voc4cat:0000056`](https://w3id.org/nfdi4cat/voc4cat_0000056)
+**CURIE:** [`VOC4CAT:0000056`](https://w3id.org/nfdi4cat/voc4cat_0000056)
**Schema Reference:** [calcination_gas_flow_rate](./elements/calcination_gas_flow_rate.md)
@@ -3953,97 +4047,3 @@ a perovskite oxide surface by reduction/oxidation cycling.
-
-had output entity (Recommended, Multivalued)
-
-**Description:** The CatalystSample produced by this Synthesis.
-
-**Data Type:** CatalystSample
-
-**Cardinality:** Recommended, Multivalued
-
-**Schema Reference:** [had_output_entity](./elements/had_output_entity.md)
-
-**Data Type Class Details:**
-
-
-CatalystSample
-
-**Description:** A MaterialSample that is the product of a catalyst Synthesis.
-The specific type of catalyst (e.g. heterogeneous, supported metal)
-is expressed via rdf_type using a voc4cat term.
-
-**CURIE:** [`OBI:0000747`](http://purl.obolibrary.org/obo/OBI_0000747)
-
-**Schema Reference:** [CatalystSample](./elements/CatalystSample.md)
-
-
-
- 💡 Submit Term Feedback
-
-
-
-
-
- 💡 Submit Term Feedback
-
-
-
-
-had input entity (Required, Multivalued)
-
-**Description:** The Precursor(s) consumed during this Synthesis.
-
-**Data Type:** Precursor
-
-**Cardinality:** Required, Multivalued
-
-**Schema Reference:** [had_input_entity](./elements/had_input_entity.md)
-
-**Data Type Class Details:**
-
-
-Precursor
-
-**Description:** A MaterialSample that serves as input material in a catalyst Synthesis.
-Precursors are consumed or transformed during the preparation process.
-
-**CURIE:** [`CHEBI:52717`](http://purl.obolibrary.org/obo/CHEBI_52717)
-
-**Schema Reference:** [Precursor](./elements/Precursor.md)
-
-**Slots**
-
-
-precursor quantity (Required, Multivalued)
-
-**Description:** Quantity of precursor used in synthesis.
-
-**Data Type:** float
-
-**Cardinality:** Required, Multivalued
-
-**CURIE:** [`catcore:precursor_quantity`](https://w3id.org/nfdi4cat/catcore/precursor_quantity)
-
-**Schema Reference:** [precursor_quantity](./elements/precursor_quantity.md)
-
-**Unit:** g
-
-
-
- 💡 Submit Term Feedback
-
-
-
-
-
- 💡 Submit Term Feedback
-
-
-
-
-
- 💡 Submit Term Feedback
-
-
-
diff --git a/mkdocs.yml b/mkdocs.yml
index 68b40cbcc..7b9c2ec58 100644
--- a/mkdocs.yml
+++ b/mkdocs.yml
@@ -12,7 +12,7 @@ theme:
- navigation.instant
- navigation.instant.progress
- navigation.tracking
-logo: /CoreMeta4Cat/images/CoreMeta4Cat_Picture.png
+ logo: /CoreMeta4Cat/images/CoreMeta4Cat_Picture.png
# for adding extra css/js see https://www.mkdocs.org/user-guide/customizing-your-theme/
From 2986c4ee9637eb21460abdc6db4a9a670fbf7df1 Mon Sep 17 00:00:00 2001
From: HendrikBorgelt <84382772+HendrikBorgelt@users.noreply.github.com>
Date: Mon, 9 Mar 2026 14:19:45 +0100
Subject: [PATCH 2/6] uUpdated GitHub README and cleaned repo structure
---
2025-09-17_ref_metadata_model_v1.1_share.xlsx | Bin 58263 -> 0 bytes
README.md | 30 +++++++-
catcore_linkml.xlsx | Bin 14839 -> 0 bytes
docs/index.md | 69 +++++++++++++++---
.../2025-08-21_generate_metadata_collapsed.py | 0
.../catcore_converter.py | 0
6 files changed, 86 insertions(+), 13 deletions(-)
delete mode 100644 2025-09-17_ref_metadata_model_v1.1_share.xlsx
delete mode 100644 catcore_linkml.xlsx
rename 2025-08-21_generate_metadata_collapsed.py => scripts/2025-08-21_generate_metadata_collapsed.py (100%)
rename catcore_converter.py => scripts/catcore_converter.py (100%)
diff --git a/2025-09-17_ref_metadata_model_v1.1_share.xlsx b/2025-09-17_ref_metadata_model_v1.1_share.xlsx
deleted file mode 100644
index dca37ab62b52cd7b5db81e743962c30be4bb989a..0000000000000000000000000000000000000000
GIT binary patch
literal 0
HcmV?d00001
literal 58263
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¥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