Add a new data type
You do not need to wait for Biosimulant to add a central profile. A model package can register its own compatibility checker as it is developed.
First decide whether a new type is needed
Use an existing type when the data has the same scientific meaning. A different
file layout or unit usually belongs in format, shape, schema,
emitted_unit or accepted_units; it is not a new semantic type.
Create a type when using the data incorrectly would change its scientific meaning and the current types cannot express that distinction.
Start with one real wire:
- the producer model and output port;
- the consumer model and input port;
- one value that should pass;
- one value that should warn, if uncertainty is possible; and
- one value that must be blocked.
Avoid designing a general vocabulary before you have these examples.
1. Choose a namespaced name
Use a name your project owns so it cannot collide with another package:
my_lab.single_cell_expressionShared Biosimulant types use names such as chemical.smiles. Do not claim that
namespace for a model-local checker.
2. Write a small checker
A checker receives the source contract, target contract and the value being sent. It returns issues only when something needs attention:
from biosimulant import CompatibilityIssue, NO_SAMPLE, register_checker
def check_probability(source, target, value):
# value is omitted during the declaration-only check
if value is NO_SAMPLE:
return []
if not isinstance(value, (int, float)):
return [
CompatibilityIssue(
level="blocked",
code="NOT_NUMERIC",
message="Probability must be numeric.",
)
]
if not 0 <= value <= 1:
return [
CompatibilityIssue(
level="blocked",
code="OUTSIDE_PROBABILITY_RANGE",
message="Probability must be between zero and one.",
)
]
return []
register_checker("my_lab.probability", check_probability)Register the checker when the model package is imported, before the world is wired. Registering the same type twice is an error unless replacement is explicit, which prevents two dependencies from silently changing a type’s meaning.
Checker messages may appear in logs and user interfaces. Explain the scientific problem in one sentence, and do not include sensitive payload values.
3. Declare the type on the ports
Use the same name in Python and model.yaml:
SignalSpec.scalar(
dtype="float64",
contract={"type": "my_lab.probability"},
)contract:
type: my_lab.probabilityDo not add custom keys to the contract. If the checker needs model-specific
configuration, keep it in the model code or make the distinction a separate,
clearly named type. The shared contract remains limited to type, species and
identifier_namespace.
4. Test it with real examples
At minimum, test the declaration and the actual payload:
from biosimulant import SignalSpec, check_compatibility
contract = {"type": "my_lab.probability"}
source = SignalSpec.scalar(dtype="float64", contract=contract)
target = SignalSpec.scalar(dtype="float64", contract=contract)
assert check_compatibility(source, target).status == "ok"
assert check_compatibility(source, target, sample=0.7).status == "ok"
assert check_compatibility(source, target, sample=1.4).status == "blocked"Also run a small BioWorld test. That proves registration happens early enough
and the live value is checked when it crosses the wire.
5. Ship locally or propose a built-in
Keep the checker in the model package when only that package understands the type. This is the normal route for early or specialist models.
Propose a shared built-in when several independent models exchange the same
data. Open a pull request against
Biosimulant/biosimulant that changes only:
src/biosim/compatibility.py— add and register the checker;tests/test_compatibility.py— add accepted, warning and blocked cases; and- these compatibility docs — add the type and its boundary.
The pull request should name the real producer and consumer interfaces and state what the check does not establish. Do not add generated schemas, catalogue files, profile versions, digests or a second implementation.
If you cannot open a pull request, email the same proposal and examples to
demi@biosimulant.com. A prose-only type name is not enough; include the two
port declarations and the three test values.
Review checklist
- Does the type represent scientific meaning rather than packaging?
- Can the important claim be checked from the actual value?
- Are shape, format and unit still using normal
SignalSpecfields? - Does every blocked result describe a real unsafe connection?
- Does missing information warn rather than invent a fact?
- Are transformations visible as models or adapters instead of hidden in the checker?
- Are the checker and its tests small enough for a model builder to understand?