inputs = null;
+
+ // The format will be sniffed if it is not provided.
+ @CommandLine.Option(names = {"-f", "--format"},
+ description = {"Phenopacket format.",
+ "Choose from: {${COMPLETION-CANDIDATES}}"})
+ public PhenopacketFormat format = null;
+
+ @CommandLine.Option(names = {"-e", "--element"},
+ description = {"Top-level element.",
+ "Choose from {${COMPLETION-CANDIDATES}}",
+ "Default: phenopacket"})
+ public PhenopacketElement element = null;
+
+ }
+ protected BaseIOCommand() {
+ parserFactory = PhenopacketParserFactory.getInstance();
+ }
+
+ /**
+ * Attempt to read the input in the provided {@code schemaVersion} and exit upon any failure. As a side effect,
+ * {@link org.phenopackets.phenopackettools.cli.command.BaseIOCommand.InputSection#format}
+ * and {@link org.phenopackets.phenopackettools.cli.command.BaseIOCommand.InputSection#element}
+ * fields are set after the function returns.
+ *
+ * Note that the function does not return if reading fails.
+ */
+ protected List readMessagesOrExit(PhenopacketSchemaVersion schemaVersion) {
+ PhenopacketParser parser = parserFactory.forFormat(schemaVersion);
+ if (inputSection.inputs == null) {
+ // The user did not set `-i | --input` option, assuming a single input is coming from STDIN.
+ InputStream is = System.in;
+ try {
+ setFormatAndElement(is, schemaVersion);
+ Message message = parser.parse(inputSection.format, inputSection.element, is);
+ return List.of(new MessageAndPath(message, null));
+ } catch (SniffException e) {
+ System.err.println("Unable to detect input format from STDIN.\nConsider using the `--format` option.");
+ } catch (IOException e) {
+ System.err.println("Unable to read STDIN: " + e.getMessage() + "\nPlease check the input format.");
+ }
+ System.exit(1);
+ } else {
+ // Assuming a one or more input are provided via `-i | --input`.
+ //
+ // Picocli should ensure that `input` is never an empty list. `input` is `null` if no `-i` was supplied.
+ assert !inputSection.inputs.isEmpty();
+
+ List messages = new ArrayList<>();
+ for (Path input : inputSection.inputs) {
+ try (InputStream is = new BufferedInputStream(Files.newInputStream(input))) {
+ setFormatAndElement(is, schemaVersion);
+ Message message = parser.parse(inputSection.format, inputSection.element, is);
+ messages.add(new MessageAndPath(message, input));
+ } catch (SniffException e) {
+ System.err.printf("Unable to detect input format of %s.\nConsider using the `--format` option.%n", input.toAbsolutePath());
+ System.exit(1);
+ } catch (IOException e) {
+ System.err.printf("Unable to read input file %s: %s\nPlease check the input format.%n", input.toAbsolutePath(), e.getMessage());
+ System.exit(1);
+ }
+ }
+ return messages;
+ }
+ return null; // Cannot happen since System.exit() never returns, but to make the compiler happy...
+ }
+
+ /**
+ * Peek into the provided {@link InputStream} {@code is} to set {@link InputSection#format}
+ * and {@link InputSection#element} items
+ *
+ * @throws IOException if I/O error happens
+ * @throws SniffException if we cannot sniff the format
+ */
+ private void setFormatAndElement(InputStream is, PhenopacketSchemaVersion schemaVersion) throws IOException, SniffException {
+ // Set format.
+ PhenopacketFormat fmt = FormatSniffer.sniff(is);
+ if (inputSection.format == null) {
+ LOGGER.info("Input format was not provided, making an educated guess..");
+ LOGGER.info("The input looks like a {} file", fmt);
+ inputSection.format = fmt;
+ } else {
+ if (!inputSection.format.equals(fmt))
+ // This can happen e.g. if processing multiple files at once but one turns out to be a different format.
+ // We emit warning because this is likely not what the user intended and the code will likely explode
+ // further downstream.
+ LOGGER.warn("Input format is set to {} but the current input looks like a {}", inputSection.format, fmt);
+ }
+
+ // Set element.
+ PhenopacketElement element = ElementSniffer.sniff(is, schemaVersion, inputSection.format);
+ if (inputSection.element == null) {
+ LOGGER.info("Input element type (-e | --element) was not provided, making an educated guess..");
+ LOGGER.info("The input looks like a {} ", element);
+ inputSection.element = element;
+ }
+// else {
+ // TODO - enable once element sniffing is implemented
+// if (!inputSection.element.equals(element))
+// Let's go an extra mile and check for the user.
+// LOGGER.warn("Input element is set to {} but the current input looks like a {}", inputSection.element, element);
+// }
+ }
+
+ protected record MessageAndPath(Message message, Path path) {}
+
+}
diff --git a/phenopacket-tools-cli/src/main/java/org/phenopackets/phenopackettools/command/ConvertCommand.java b/phenopacket-tools-cli/src/main/java/org/phenopackets/phenopackettools/cli/command/ConvertCommand.java
similarity index 79%
rename from phenopacket-tools-cli/src/main/java/org/phenopackets/phenopackettools/command/ConvertCommand.java
rename to phenopacket-tools-cli/src/main/java/org/phenopackets/phenopackettools/cli/command/ConvertCommand.java
index 48a8528a..2681098d 100644
--- a/phenopacket-tools-cli/src/main/java/org/phenopackets/phenopackettools/command/ConvertCommand.java
+++ b/phenopacket-tools-cli/src/main/java/org/phenopackets/phenopackettools/cli/command/ConvertCommand.java
@@ -1,9 +1,11 @@
-package org.phenopackets.phenopackettools.command;
+package org.phenopackets.phenopackettools.cli.command;
import com.google.protobuf.Message;
-import com.google.protobuf.util.JsonFormat;
import org.phenopackets.phenopackettools.converter.converters.V1ToV2Converter;
-import org.phenopackets.phenopackettools.util.format.PhenopacketFormat;
+import org.phenopackets.phenopackettools.core.PhenopacketSchemaVersion;
+import org.phenopackets.phenopackettools.core.PhenopacketFormat;
+import org.phenopackets.phenopackettools.io.PhenopacketPrinter;
+import org.phenopackets.phenopackettools.io.PhenopacketPrinterFactory;
import org.phenopackets.schema.v1.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -51,10 +53,7 @@ public static class ConvertSection {
}
@Override
- public Integer call() {
- // (0) Print banner.
- printBanner();
-
+ protected Integer execute() {
if (!checkInputArgumentsAreOk())
return 1;
@@ -77,14 +76,11 @@ public Integer call() {
converted.add(new MessageAndPath(v2, mp.path()));
}
- // (3) Set the output format if necessary.
- if (convertSection.outputFormat == null) {
- LOGGER.info("Output format (--output-format) not provided, writing data in the input format `{}`", inputSection.format);
- convertSection.outputFormat = inputSection.format;
- }
+ // (3) Configure the output format.
+ PhenopacketPrinter printer = configurePhenopacketPrinter();
// (4) Write out the output(s).
- return writeOutConverted(converted);
+ return writeOutConverted(converted, printer);
}
/**
@@ -112,7 +108,19 @@ private boolean checkInputArgumentsAreOk() {
return true;
}
- private int writeOutConverted(List converted) {
+ private PhenopacketPrinter configurePhenopacketPrinter() {
+ PhenopacketFormat format;
+ if (convertSection.outputFormat == null) {
+ LOGGER.info("Output format (--output-format) not provided, writing data in the input format `{}`", inputSection.format);
+ format = inputSection.format;
+ } else
+ format = convertSection.outputFormat;
+
+ PhenopacketPrinterFactory factory = PhenopacketPrinterFactory.getInstance();
+ return factory.forFormat(PhenopacketSchemaVersion.V2, format);
+ }
+
+ private int writeOutConverted(List converted, PhenopacketPrinter printer) {
if (converted.size() == 1) {
// Writing out item, either from STDIN or from one `-i` options.
MessageAndPath mp = converted.get(0);
@@ -124,7 +132,7 @@ private int writeOutConverted(List converted) {
} else {
os = openOutputStream(mp.path());
}
- writeMessage(mp.message(), convertSection.outputFormat, os);
+ printer.print(mp.message(), os);
} catch (IOException e) {
LOGGER.error("Error while writing out a phenopacket: {}", e.getMessage(), e);
return 1;
@@ -141,7 +149,7 @@ private int writeOutConverted(List converted) {
// Writing out >1 items provided by `-i` options.
for (MessageAndPath mp : converted) {
try (OutputStream os = openOutputStream(mp.path())) {
- writeMessage(mp.message(), convertSection.outputFormat, os);
+ printer.print(mp.message(), os);
} catch (IOException e) {
LOGGER.error("Error while writing out a phenopacket: {}", e.getMessage(), e);
return 1;
@@ -171,32 +179,4 @@ private BufferedOutputStream openOutputStream(Path inputPath) throws IOException
return new BufferedOutputStream(Files.newOutputStream(output));
}
- /**
- * Write the {@code message} in an appropriate {@code format} into the provided {@link OutputStream} {@code os}.
- *
- * Uses {@link }
- * @param message message to be written out.
- * @param format format to write out
- * @param os where to write
- * @throws IOException in case of I/O errors during the output
- */
- protected static void writeMessage(Message message, PhenopacketFormat format, OutputStream os) throws IOException {
- switch (format) {
- case PROTOBUF -> {
- LOGGER.debug("Writing protobuf message");
- message.writeTo(os);
- }
- case JSON -> {
- LOGGER.debug("Writing JSON message");
- BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(os));
- JsonFormat.printer().appendTo(message, writer);
- writer.flush();
- }
- case YAML -> {
- // TODO - implement
- throw new RuntimeException("YAML printer is not yet implemented");
- }
- }
- }
-
}
diff --git a/phenopacket-tools-cli/src/main/java/org/phenopackets/phenopackettools/cli/command/ExamplesCommand.java b/phenopacket-tools-cli/src/main/java/org/phenopackets/phenopackettools/cli/command/ExamplesCommand.java
new file mode 100644
index 00000000..2685e4d9
--- /dev/null
+++ b/phenopacket-tools-cli/src/main/java/org/phenopackets/phenopackettools/cli/command/ExamplesCommand.java
@@ -0,0 +1,102 @@
+package org.phenopackets.phenopackettools.cli.command;
+
+import com.google.protobuf.Message;
+
+import org.phenopackets.phenopackettools.core.PhenopacketFormat;
+import org.phenopackets.phenopackettools.core.PhenopacketSchemaVersion;
+import org.phenopackets.phenopackettools.core.PhenopacketToolsRuntimeException;
+import org.phenopackets.phenopackettools.cli.examples.*;
+import org.phenopackets.phenopackettools.io.PhenopacketPrinter;
+import org.phenopackets.phenopackettools.io.PhenopacketPrinterFactory;
+import picocli.CommandLine;
+import picocli.CommandLine.Command;
+
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+
+@Command(name = "examples",
+ mixinStandardHelpOptions = true,
+ sortOptions = false,
+ description = "Write example phenopackets to a directory.")
+public class ExamplesCommand extends BaseCommand {
+
+ @CommandLine.Option(names = {"-o", "--output"},
+ description = "Output directory (default: ${DEFAULT-VALUE})")
+ public Path output = Path.of(".");
+
+ private final PhenopacketPrinter jsonPrinter;
+ private final PhenopacketPrinter yamlPrinter;
+
+ public ExamplesCommand() {
+ PhenopacketPrinterFactory factory = PhenopacketPrinterFactory.getInstance();
+ jsonPrinter = factory.forFormat(PhenopacketSchemaVersion.V2, PhenopacketFormat.JSON);
+ yamlPrinter = factory.forFormat(PhenopacketSchemaVersion.V2, PhenopacketFormat.YAML);
+ }
+
+ @Override
+ protected Integer execute() {
+ try {
+ Path phenopacketDir = createADirectoryIfDoesNotExist(output.resolve("phenopackets"));
+ Path familyDir = createADirectoryIfDoesNotExist(output.resolve("families"));
+ Path cohortDir = createADirectoryIfDoesNotExist(output.resolve("cohorts"));
+
+ // Phenopackets
+ printJsonAndYaml(new AtaxiaWithVitaminEdeficiency().getPhenopacket(), phenopacketDir, "AVED");
+ printJsonAndYaml(new BethlehamMyopathy().getPhenopacket(), phenopacketDir, "bethleham-myopathy");
+ printJsonAndYaml(new Holoprosencephaly5().getPhenopacket(), phenopacketDir, "holoprosencephaly5");
+ printJsonAndYaml(new Marfan().getPhenopacket(), phenopacketDir, "marfan");
+ printJsonAndYaml(new NemalineMyopathyPrenatal().getPhenopacket(), phenopacketDir, "nemalineMyopathy");
+ printJsonAndYaml(new Pseudoexfoliation().getPhenopacket(), phenopacketDir, "pseudoexfoliation");
+ printJsonAndYaml(new DuchenneExon51Deletion().getPhenopacket(), phenopacketDir, "duchenne");
+ printJsonAndYaml(new SquamousCellCancer().getPhenopacket(), phenopacketDir, "squamous-cell-esophageal-carcinoma");
+ printJsonAndYaml(new UrothelialCancer().getPhenopacket(), phenopacketDir, "urothelial-cancer");
+ printJsonAndYaml(new Covid().getPhenopacket(), phenopacketDir, "covid");
+ printJsonAndYaml(new Retinoblastoma().getPhenopacket(), phenopacketDir, "retinoblastoma");
+ printJsonAndYaml(new WarburgMicroSyndrome().getPhenopacket(), phenopacketDir, "warburg-micro-syndrome");
+ printJsonAndYaml(new SevereStatinInducedAutoimmuneMyopathy().getPhenopacket(), phenopacketDir, "statin-myopathy");
+
+ // Families
+ printJsonAndYaml(new FamilyWithPedigree().getFamily(), familyDir, "family");
+
+ // Cohorts
+ // TODO - write a cohort
+
+ } catch (Exception e) {
+ System.err.println(e.getMessage());
+ return 1;
+ }
+ return 0;
+ }
+
+ private static Path createADirectoryIfDoesNotExist(Path path) throws IOException {
+ return Files.exists(path)
+ ? path
+ : Files.createDirectories(path);
+ }
+
+ private void printJsonAndYaml(Message message, Path outDir, String basename) {
+ Path jsonPath = outDir.resolve(basename + ".json");
+ printJsonMessage(message, jsonPath);
+
+ Path yamlPath = outDir.resolve(basename + ".yml");
+ printYamlMessage(message, yamlPath);
+ }
+
+ private void printJsonMessage(Message message, Path path) {
+ try {
+ jsonPrinter.print(message, path);
+ } catch (IOException e) {
+ throw new PhenopacketToolsRuntimeException(e);
+ }
+ }
+
+ private void printYamlMessage(Message message, Path path) {
+ try {
+ yamlPrinter.print(message, path);
+ } catch (IOException e) {
+ throw new PhenopacketToolsRuntimeException(e);
+ }
+ }
+
+}
diff --git a/phenopacket-tools-cli/src/main/java/org/phenopackets/phenopackettools/command/ValidateCommand.java b/phenopacket-tools-cli/src/main/java/org/phenopackets/phenopackettools/cli/command/ValidateCommand.java
similarity index 55%
rename from phenopacket-tools-cli/src/main/java/org/phenopackets/phenopackettools/command/ValidateCommand.java
rename to phenopacket-tools-cli/src/main/java/org/phenopackets/phenopackettools/cli/command/ValidateCommand.java
index 843ad62e..ce2a34cf 100644
--- a/phenopacket-tools-cli/src/main/java/org/phenopackets/phenopackettools/command/ValidateCommand.java
+++ b/phenopacket-tools-cli/src/main/java/org/phenopackets/phenopackettools/cli/command/ValidateCommand.java
@@ -1,15 +1,19 @@
-package org.phenopackets.phenopackettools.command;
+package org.phenopackets.phenopackettools.cli.command;
import com.google.protobuf.MessageOrBuilder;
+import org.monarchinitiative.phenol.base.PhenolRuntimeException;
import org.monarchinitiative.phenol.io.OntologyLoader;
import org.monarchinitiative.phenol.ontology.data.Ontology;
+import org.monarchinitiative.phenol.ontology.data.TermId;
+import org.phenopackets.phenopackettools.core.PhenopacketElement;
+import org.phenopackets.phenopackettools.core.PhenopacketSchemaVersion;
import org.phenopackets.phenopackettools.validator.core.*;
import org.phenopackets.phenopackettools.validator.core.metadata.MetaDataValidators;
import org.phenopackets.phenopackettools.validator.core.phenotype.HpoPhenotypeValidators;
import org.phenopackets.phenopackettools.validator.core.writer.ValidationResultsAndPath;
import org.phenopackets.phenopackettools.validator.jsonschema.JsonSchemaValidationWorkflowRunner;
-import org.phenopackets.phenopackettools.writer.CSVValidationResultsWriter;
+import org.phenopackets.phenopackettools.cli.writer.CSVValidationResultsWriter;
import org.phenopackets.schema.v2.CohortOrBuilder;
import org.phenopackets.schema.v2.FamilyOrBuilder;
import org.phenopackets.schema.v2.PhenopacketOrBuilder;
@@ -25,6 +29,9 @@
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;
+import java.util.Optional;
+import java.util.function.Function;
+import java.util.stream.Collectors;
@Command(name = "validate",
description = "Validate top-level elements of the Phenopacket schema.",
@@ -38,21 +45,28 @@ public class ValidateCommand extends BaseIOCommand {
public ValidateSection validateSection = new ValidateSection();
public static class ValidateSection {
+ @CommandLine.Option(names = {"-H", "--include-header"},
+ description = {"Include header in the output", "Default: ${DEFAULT-VALUE}"})
+ public boolean includeHeader = false;
+
@CommandLine.Option(names = {"--require"},
arity = "*",
description = "Path to JSON schema with additional requirements to enforce.")
- protected List requirements = List.of();
+ public List requirements = List.of();
@CommandLine.Option(names = "--hpo",
description = "Path to hp.json file")
- protected Path hpJson;
+ public Path hpJson;
+
+ @CommandLine.Option(names = {"-s", "--organ-system"},
+ arity = "*",
+ description = {"Organ system HPO term IDs",
+ "Default: empty"})
+ public List organSystems = List.of();
}
@Override
- public Integer call() {
- // (0) Print banner.
- printBanner();
-
+ protected Integer execute() {
// (1) Read the input v2 message(s).
List messages = readMessagesOrExit(PhenopacketSchemaVersion.V2);
@@ -67,7 +81,10 @@ public Integer call() {
// (4) Write out the validation results into STDOUT.
try {
- CSVValidationResultsWriter writer = new CSVValidationResultsWriter(System.out, PHENOPACKET_TOOLS_VERSION, LocalDateTime.now());
+ CSVValidationResultsWriter writer = new CSVValidationResultsWriter(System.out,
+ PHENOPACKET_TOOLS_VERSION,
+ LocalDateTime.now(),
+ validateSection.includeHeader);
writer.writeValidationResults(runner.validators(), results);
return 0;
} catch (IOException e) {
@@ -129,36 +146,111 @@ private List prepareCustomSchemaUrls() {
* Prepare semantic validators for given {@link T}.
*
* Warning - it is important to request the {@link T} that is appropriate
- * for the current {@link org.phenopackets.phenopackettools.command.BaseIOCommand.InputSection#element}.
+ * for the current {@link org.phenopackets.phenopackettools.cli.command.BaseIOCommand.InputSection#element}.
* The app will crash and burn if e.g. {@link T} is {@link PhenopacketOrBuilder}
- * while {@link org.phenopackets.phenopackettools.command.BaseIOCommand.InputSection#element}
- * is {@link org.phenopackets.phenopackettools.util.format.PhenopacketElement#FAMILY}.
+ * while {@link org.phenopackets.phenopackettools.cli.command.BaseIOCommand.InputSection#element}
+ * is {@link PhenopacketElement#FAMILY}.
*/
private List> configureSemanticValidators() {
// Right now we only have one semantic validator, but we'll extend this in the future.
LOGGER.debug("Configuring semantic validators");
List> validators = new ArrayList<>();
+ Ontology hpo = null;
if (validateSection.hpJson != null) {
- LOGGER.debug("Reading HPO from '{}}'", validateSection.hpJson.toAbsolutePath());
- Ontology hpo = OntologyLoader.loadOntology(validateSection.hpJson.toFile());
+ LOGGER.debug("Reading HPO from {}", validateSection.hpJson.toAbsolutePath());
+ hpo = OntologyLoader.loadOntology(validateSection.hpJson.toFile());
// The entire logic of this command stands and falls on correct state of `element` and the read message(s).
// This method requires an appropriate combination of `T` and `element`, as described in Javadoc.
// We suppress warning and perform an unchecked cast here, assuming `T` and `element` are appropriate.
// The app will crash and burn if this is not the case.
- PhenopacketValidator validator = switch (inputSection.element) {
+ switch (inputSection.element) {
+ case PHENOPACKET -> {
+ //noinspection unchecked
+ validators.add((PhenopacketValidator) HpoPhenotypeValidators.Primary.phenopacketHpoPhenotypeValidator(hpo));
+ //noinspection unchecked
+ validators.add((PhenopacketValidator) HpoPhenotypeValidators.Ancestry.phenopacketHpoAncestryValidator(hpo));
+ }
+ case FAMILY -> {
+ //noinspection unchecked
+ validators.add((PhenopacketValidator) HpoPhenotypeValidators.Primary.familyHpoPhenotypeValidator(hpo));
+ //noinspection unchecked
+ validators.add((PhenopacketValidator) HpoPhenotypeValidators.Ancestry.familyHpoAncestryValidator(hpo));
+ }
+ case COHORT -> {
+ //noinspection unchecked
+ validators.add((PhenopacketValidator) HpoPhenotypeValidators.Primary.cohortHpoPhenotypeValidator(hpo));
+ //noinspection unchecked
+ validators.add((PhenopacketValidator) HpoPhenotypeValidators.Ancestry.cohortHpoAncestryValidator(hpo));
+ }
+ }
+ }
+
+ if (!validateSection.organSystems.isEmpty()) {
+ PhenopacketValidator validator = prepareOrganSystemValidator(hpo, validateSection.organSystems, inputSection.element);
+ if (validator != null)
+ validators.add(validator);
+
+ }
+
+ LOGGER.debug("Configured {} semantic validator(s)", validators.size());
+ return validators;
+ }
+
+ private static PhenopacketValidator prepareOrganSystemValidator(Ontology hpo,
+ List organSystems,
+ PhenopacketElement element) {
+ // Organ system validation can only be done when HPO is provided.
+ if (hpo == null) {
+ LOGGER.warn("Terms for organ system validation were provided but the path to HPO is unset. Use --hpo option to enable organ system validation.");
+ return null;
+ }
+
+ // Prepare organ system IDs.
+ List organSystemIds = prepareOrganSystemIds(organSystems);
+
+ // Create the validator.
+ if (!organSystemIds.isEmpty()) {
+ return switch (element) {
case PHENOPACKET -> //noinspection unchecked
- (PhenopacketValidator) HpoPhenotypeValidators.phenopacketHpoPhenotypeValidator(hpo);
+ (PhenopacketValidator) HpoPhenotypeValidators.OrganSystem.phenopacketHpoOrganSystemValidator(hpo, organSystemIds);
case FAMILY -> //noinspection unchecked
- (PhenopacketValidator) HpoPhenotypeValidators.familyHpoPhenotypeValidator(hpo);
+ (PhenopacketValidator) HpoPhenotypeValidators.OrganSystem.familyHpoOrganSystemValidator(hpo, organSystemIds);
case COHORT -> //noinspection unchecked
- (PhenopacketValidator) HpoPhenotypeValidators.cohortHpoPhenotypeValidator(hpo);
+ (PhenopacketValidator) HpoPhenotypeValidators.OrganSystem.cohortHpoOrganSystemValidator(hpo, organSystemIds);
};
- validators.add(validator);
}
- LOGGER.debug("Configured {} semantic validator(s)", validators.size());
- return validators;
+ return null;
+ }
+
+ private static List prepareOrganSystemIds(List organSystems) {
+ LOGGER.trace("Found {} organ system IDs: {}", organSystems.size(), organSystems.stream()
+ .collect(Collectors.joining(", ", "{", "}")));
+ List organSystemIds = organSystems.stream()
+ .map(toTermId())
+ .flatMap(Optional::stream)
+ .toList();
+ LOGGER.trace("{} organ system IDs are valid term IDs: {}", organSystemIds.size(),
+ organSystemIds.stream()
+ .map(TermId::getValue)
+ .collect(Collectors.joining(", ", "{", "}")));
+ return organSystemIds;
+ }
+
+ /**
+ * @return a function that maps a {@link String} into a {@link TermId} or emits a warning if the value
+ * cannot be mapped.
+ */
+ private static Function> toTermId() {
+ return value -> {
+ try {
+ return Optional.of(TermId.of(value));
+ } catch (PhenolRuntimeException e) {
+ LOGGER.warn("Invalid term ID {}", value);
+ return Optional.empty();
+ }
+ };
}
}
\ No newline at end of file
diff --git a/phenopacket-tools-cli/src/main/java/org/phenopackets/phenopackettools/command/validate/BaseValidateCommand.java b/phenopacket-tools-cli/src/main/java/org/phenopackets/phenopackettools/cli/command/validate/BaseValidateCommand.java
similarity index 98%
rename from phenopacket-tools-cli/src/main/java/org/phenopackets/phenopackettools/command/validate/BaseValidateCommand.java
rename to phenopacket-tools-cli/src/main/java/org/phenopackets/phenopackettools/cli/command/validate/BaseValidateCommand.java
index 9cbb28a1..d4ecad43 100644
--- a/phenopacket-tools-cli/src/main/java/org/phenopackets/phenopackettools/command/validate/BaseValidateCommand.java
+++ b/phenopacket-tools-cli/src/main/java/org/phenopackets/phenopackettools/cli/command/validate/BaseValidateCommand.java
@@ -1,4 +1,4 @@
-package org.phenopackets.phenopackettools.command.validate;
+package org.phenopackets.phenopackettools.cli.command.validate;
import com.google.protobuf.MessageOrBuilder;
import org.monarchinitiative.phenol.io.OntologyLoader;
diff --git a/phenopacket-tools-cli/src/main/java/org/phenopackets/phenopackettools/command/validate/ValidateCohortCommand.java b/phenopacket-tools-cli/src/main/java/org/phenopackets/phenopackettools/cli/command/validate/ValidateCohortCommand.java
similarity index 95%
rename from phenopacket-tools-cli/src/main/java/org/phenopackets/phenopackettools/command/validate/ValidateCohortCommand.java
rename to phenopacket-tools-cli/src/main/java/org/phenopackets/phenopackettools/cli/command/validate/ValidateCohortCommand.java
index f04288da..cd9f5ece 100644
--- a/phenopacket-tools-cli/src/main/java/org/phenopackets/phenopackettools/command/validate/ValidateCohortCommand.java
+++ b/phenopacket-tools-cli/src/main/java/org/phenopackets/phenopackettools/cli/command/validate/ValidateCohortCommand.java
@@ -1,4 +1,4 @@
-package org.phenopackets.phenopackettools.command.validate;
+package org.phenopackets.phenopackettools.cli.command.validate;
import org.monarchinitiative.phenol.ontology.data.Ontology;
import org.phenopackets.phenopackettools.validator.core.PhenopacketValidator;
diff --git a/phenopacket-tools-cli/src/main/java/org/phenopackets/phenopackettools/command/validate/ValidateFamilyCommand.java b/phenopacket-tools-cli/src/main/java/org/phenopackets/phenopackettools/cli/command/validate/ValidateFamilyCommand.java
similarity index 95%
rename from phenopacket-tools-cli/src/main/java/org/phenopackets/phenopackettools/command/validate/ValidateFamilyCommand.java
rename to phenopacket-tools-cli/src/main/java/org/phenopackets/phenopackettools/cli/command/validate/ValidateFamilyCommand.java
index edc7cd65..ba0ce5ef 100644
--- a/phenopacket-tools-cli/src/main/java/org/phenopackets/phenopackettools/command/validate/ValidateFamilyCommand.java
+++ b/phenopacket-tools-cli/src/main/java/org/phenopackets/phenopackettools/cli/command/validate/ValidateFamilyCommand.java
@@ -1,4 +1,4 @@
-package org.phenopackets.phenopackettools.command.validate;
+package org.phenopackets.phenopackettools.cli.command.validate;
import org.monarchinitiative.phenol.ontology.data.Ontology;
import org.phenopackets.phenopackettools.validator.core.PhenopacketValidator;
diff --git a/phenopacket-tools-cli/src/main/java/org/phenopackets/phenopackettools/command/validate/ValidatePhenopacketCommand.java b/phenopacket-tools-cli/src/main/java/org/phenopackets/phenopackettools/cli/command/validate/ValidatePhenopacketCommand.java
similarity index 96%
rename from phenopacket-tools-cli/src/main/java/org/phenopackets/phenopackettools/command/validate/ValidatePhenopacketCommand.java
rename to phenopacket-tools-cli/src/main/java/org/phenopackets/phenopackettools/cli/command/validate/ValidatePhenopacketCommand.java
index 65bce015..da2fcec8 100644
--- a/phenopacket-tools-cli/src/main/java/org/phenopackets/phenopackettools/command/validate/ValidatePhenopacketCommand.java
+++ b/phenopacket-tools-cli/src/main/java/org/phenopackets/phenopackettools/cli/command/validate/ValidatePhenopacketCommand.java
@@ -1,4 +1,4 @@
-package org.phenopackets.phenopackettools.command.validate;
+package org.phenopackets.phenopackettools.cli.command.validate;
import org.monarchinitiative.phenol.ontology.data.Ontology;
import org.phenopackets.phenopackettools.validator.core.PhenopacketValidator;
diff --git a/phenopacket-tools-cli/src/main/java/org/phenopackets/phenopackettools/examples/AtaxiaWithVitaminEdeficiency.java b/phenopacket-tools-cli/src/main/java/org/phenopackets/phenopackettools/cli/examples/AtaxiaWithVitaminEdeficiency.java
similarity index 93%
rename from phenopacket-tools-cli/src/main/java/org/phenopackets/phenopackettools/examples/AtaxiaWithVitaminEdeficiency.java
rename to phenopacket-tools-cli/src/main/java/org/phenopackets/phenopackettools/cli/examples/AtaxiaWithVitaminEdeficiency.java
index 11219cf0..9cbeb206 100644
--- a/phenopacket-tools-cli/src/main/java/org/phenopackets/phenopackettools/examples/AtaxiaWithVitaminEdeficiency.java
+++ b/phenopacket-tools-cli/src/main/java/org/phenopackets/phenopackettools/cli/examples/AtaxiaWithVitaminEdeficiency.java
@@ -1,4 +1,4 @@
-package org.phenopackets.phenopackettools.examples;
+package org.phenopackets.phenopackettools.cli.examples;
import org.ga4gh.vrsatile.v1.GeneDescriptor;
import org.phenopackets.phenopackettools.builder.PhenopacketBuilder;
@@ -10,7 +10,7 @@
import static org.phenopackets.phenopackettools.builder.builders.OntologyClassBuilder.ontologyClass;
import static org.phenopackets.phenopackettools.builder.constants.Assays.creatineKinaseActivity;
-import static org.phenopackets.phenopackettools.builder.constants.Response.favorable;
+import static org.phenopackets.phenopackettools.builder.constants.Response.favorableResponse;
import static org.phenopackets.phenopackettools.builder.constants.SpatialPattern.generalized;
import static org.phenopackets.phenopackettools.builder.constants.Unit.*;
@@ -179,30 +179,30 @@ private Interpretation aved() {
private List getPhenotypicFeatures() {
String iso8601age = "P16Y";
var pf1 = PhenotypicFeatureBuilder.builder("HP:0002066","Gait ataxia")
- .isoISO8601onset(iso8601age).build();
+ .iso8601onset(iso8601age).build();
var pf2 = PhenotypicFeatureBuilder.builder("HP:0001308","Tongue fasciculations")
- .isoISO8601onset(iso8601age).build();
+ .iso8601onset(iso8601age).build();
var pf3 = PhenotypicFeatureBuilder.builder("HP:0002080","Intention tremor")
- .isoISO8601onset(iso8601age).build();
+ .iso8601onset(iso8601age).build();
var pf4 = PhenotypicFeatureBuilder.builder("HP:0002075","Dysdiadochokinesis")
- .isoISO8601onset(iso8601age).build();
+ .iso8601onset(iso8601age).build();
var pf5 = PhenotypicFeatureBuilder.builder("HP:0001251","Ataxia")
- .isoISO8601onset(iso8601age).build();
+ .iso8601onset(iso8601age).build();
var pf6 = PhenotypicFeatureBuilder.builder("HP:0001284","Areflexia")
- .isoISO8601onset(iso8601age).build();
+ .iso8601onset(iso8601age).build();
var pf7 = PhenotypicFeatureBuilder.builder("HP:0011448","Ankle clonus")
- .isoISO8601onset(iso8601age).build();
+ .iso8601onset(iso8601age).build();
var pf8 = PhenotypicFeatureBuilder.builder("HP:0003690","Limb muscle weakness")
- .isoISO8601onset(iso8601age).build();
+ .iso8601onset(iso8601age).build();
var pf9 = PhenotypicFeatureBuilder.builder("HP:0003474","Somatic sensory dysfunction")
.excluded()
- .isoISO8601onset(iso8601age).build();
+ .iso8601onset(iso8601age).build();
var pf10 = PhenotypicFeatureBuilder.builder("HP:0002599","Head titubation")
.excluded()
- .isoISO8601onset(iso8601age).build();
+ .iso8601onset(iso8601age).build();
var pf11 = PhenotypicFeatureBuilder.builder("HP:0031910","Abnormal cranial nerve physiology")
.excluded()
- .isoISO8601onset(iso8601age).build();
+ .iso8601onset(iso8601age).build();
return List.of(pf1,pf2,pf3, pf4, pf5, pf6, pf7, pf8, pf9, pf10,pf11);
}
@@ -217,7 +217,7 @@ private MedicalAction vitaminEtreatment() {
OntologyClass vitE = ontologyClass("DrugCentral:257", "Vitamin E");
TreatmentBuilder tbuilder = TreatmentBuilder.oralAdministration(vitE);
return MedicalActionBuilder.builder(tbuilder.build())
- .responseToTreatment(favorable())
+ .responseToTreatment(favorableResponse())
.build();
}
@@ -227,11 +227,11 @@ private MedicalAction vitaminEtreatment() {
private List getMedicalHistory() {
String iso8601age = "P10Y";
var gaitDisturbance = PhenotypicFeatureBuilder.builder("HP:0001288", "Gait disturbance")
- .isoISO8601onset(iso8601age)
+ .iso8601onset(iso8601age)
.build();
var weakness = PhenotypicFeatureBuilder.builder("HP:0001324", "Muscle weakness")
.addModifier(generalized())
- .isoISO8601onset(iso8601age)
+ .iso8601onset(iso8601age)
.build();
return List.of(gaitDisturbance, weakness);
}
diff --git a/phenopacket-tools-cli/src/main/java/org/phenopackets/phenopackettools/examples/BethlehamMyopathy.java b/phenopacket-tools-cli/src/main/java/org/phenopackets/phenopackettools/cli/examples/BethlehamMyopathy.java
similarity index 90%
rename from phenopacket-tools-cli/src/main/java/org/phenopackets/phenopackettools/examples/BethlehamMyopathy.java
rename to phenopacket-tools-cli/src/main/java/org/phenopackets/phenopackettools/cli/examples/BethlehamMyopathy.java
index d7bb1d11..e4683464 100644
--- a/phenopacket-tools-cli/src/main/java/org/phenopackets/phenopackettools/examples/BethlehamMyopathy.java
+++ b/phenopacket-tools-cli/src/main/java/org/phenopackets/phenopackettools/cli/examples/BethlehamMyopathy.java
@@ -1,10 +1,11 @@
-package org.phenopackets.phenopackettools.examples;
+package org.phenopackets.phenopackettools.cli.examples;
import org.phenopackets.phenopackettools.builder.PhenopacketBuilder;
import org.phenopackets.phenopackettools.builder.builders.*;
import org.phenopackets.phenopackettools.builder.constants.Status;
import org.phenopackets.schema.v2.Phenopacket;
+import org.phenopackets.schema.v2.core.GenomicInterpretation;
import static org.phenopackets.phenopackettools.builder.builders.OntologyClassBuilder.ontologyClass;
@@ -24,17 +25,9 @@ public BethlehamMyopathy() {
.addResource(Resources.genoVersion("2020-03-08"))
.addExternalReference(authorAssertion.getReference())
.build();
- var variationDescriptor =
- VariationDescriptorBuilder.builder("variant id")
- .heterozygous()
- .hgvs("NM_001848.2:c.877G>A")
- .build();
- var col6a1VariantInterpretation =
- VariantInterpretationBuilder.of(variationDescriptor, Status.pathogenic());
- var genomicInterpretation =
- GenomicInterpretationBuilder.builder(INTERPRETATION_ID)
- .causative()
- .variantInterpretation(col6a1VariantInterpretation).build();
+
+ var genomicInterpretation = COL6A1variant();
+
var diagnosis = DiagnosisBuilder.builder(bethlehamMyopathy).addGenomicInterpretation(genomicInterpretation).build();
var interpretation = InterpretationBuilder.builder(INTERPRETATION_ID).completed(diagnosis);
var ventricularSeptalDefect =
@@ -100,6 +93,22 @@ public BethlehamMyopathy() {
.build();
}
+
+ private GenomicInterpretation COL6A1variant() {
+ var variationDescriptor =
+ VariationDescriptorBuilder.builder("variant id")
+ .heterozygous()
+ .hgvs("NM_001848.2:c.877G>A")
+ .geneContext(GeneDescriptorBuilder.of("HGNC:2211", "COL6A1"))
+ .vcfHg38("chr21",45989626, "G","A")
+ .build();
+ var col6a1VariantInterpretation =
+ VariantInterpretationBuilder.of(variationDescriptor, Status.pathogenic());
+ return GenomicInterpretationBuilder.builder(INTERPRETATION_ID)
+ .causative()
+ .variantInterpretation(col6a1VariantInterpretation).build();
+ }
+
@Override
public Phenopacket getPhenopacket() {
return phenopacket;
diff --git a/phenopacket-tools-cli/src/main/java/org/phenopackets/phenopackettools/examples/Covid.java b/phenopacket-tools-cli/src/main/java/org/phenopackets/phenopackettools/cli/examples/Covid.java
similarity index 99%
rename from phenopacket-tools-cli/src/main/java/org/phenopackets/phenopackettools/examples/Covid.java
rename to phenopacket-tools-cli/src/main/java/org/phenopackets/phenopackettools/cli/examples/Covid.java
index 39590dac..d107a384 100644
--- a/phenopacket-tools-cli/src/main/java/org/phenopackets/phenopackettools/examples/Covid.java
+++ b/phenopacket-tools-cli/src/main/java/org/phenopackets/phenopackettools/cli/examples/Covid.java
@@ -1,4 +1,4 @@
-package org.phenopackets.phenopackettools.examples;
+package org.phenopackets.phenopackettools.cli.examples;
import org.phenopackets.phenopackettools.builder.PhenopacketBuilder;
import org.phenopackets.phenopackettools.builder.builders.*;
diff --git a/phenopacket-tools-cli/src/main/java/org/phenopackets/phenopackettools/examples/DuchenneExon51Deletion.java b/phenopacket-tools-cli/src/main/java/org/phenopackets/phenopackettools/cli/examples/DuchenneExon51Deletion.java
similarity index 99%
rename from phenopacket-tools-cli/src/main/java/org/phenopackets/phenopackettools/examples/DuchenneExon51Deletion.java
rename to phenopacket-tools-cli/src/main/java/org/phenopackets/phenopackettools/cli/examples/DuchenneExon51Deletion.java
index eb98ae33..4ef12c92 100644
--- a/phenopacket-tools-cli/src/main/java/org/phenopackets/phenopackettools/examples/DuchenneExon51Deletion.java
+++ b/phenopacket-tools-cli/src/main/java/org/phenopackets/phenopackettools/cli/examples/DuchenneExon51Deletion.java
@@ -1,4 +1,4 @@
-package org.phenopackets.phenopackettools.examples;
+package org.phenopackets.phenopackettools.cli.examples;
import org.phenopackets.phenopackettools.builder.PhenopacketBuilder;
import org.phenopackets.phenopackettools.builder.builders.*;
diff --git a/phenopacket-tools-cli/src/main/java/org/phenopackets/phenopackettools/examples/FamilyWithPedigree.java b/phenopacket-tools-cli/src/main/java/org/phenopackets/phenopackettools/cli/examples/FamilyWithPedigree.java
similarity index 98%
rename from phenopacket-tools-cli/src/main/java/org/phenopackets/phenopackettools/examples/FamilyWithPedigree.java
rename to phenopacket-tools-cli/src/main/java/org/phenopackets/phenopackettools/cli/examples/FamilyWithPedigree.java
index b00d2ca8..9c23f284 100644
--- a/phenopacket-tools-cli/src/main/java/org/phenopackets/phenopackettools/examples/FamilyWithPedigree.java
+++ b/phenopacket-tools-cli/src/main/java/org/phenopackets/phenopackettools/cli/examples/FamilyWithPedigree.java
@@ -1,4 +1,4 @@
-package org.phenopackets.phenopackettools.examples;
+package org.phenopackets.phenopackettools.cli.examples;
import org.phenopackets.phenopackettools.builder.FamilyBuilder;
diff --git a/phenopacket-tools-cli/src/main/java/org/phenopackets/phenopackettools/examples/Holoprosencephaly5.java b/phenopacket-tools-cli/src/main/java/org/phenopackets/phenopackettools/cli/examples/Holoprosencephaly5.java
similarity index 98%
rename from phenopacket-tools-cli/src/main/java/org/phenopackets/phenopackettools/examples/Holoprosencephaly5.java
rename to phenopacket-tools-cli/src/main/java/org/phenopackets/phenopackettools/cli/examples/Holoprosencephaly5.java
index 2b41fdf9..aeb86e0f 100644
--- a/phenopacket-tools-cli/src/main/java/org/phenopackets/phenopackettools/examples/Holoprosencephaly5.java
+++ b/phenopacket-tools-cli/src/main/java/org/phenopackets/phenopackettools/cli/examples/Holoprosencephaly5.java
@@ -1,4 +1,4 @@
-package org.phenopackets.phenopackettools.examples;
+package org.phenopackets.phenopackettools.cli.examples;
import org.ga4gh.vrsatile.v1.GeneDescriptor;
import org.phenopackets.phenopackettools.builder.PhenopacketBuilder;
diff --git a/phenopacket-tools-cli/src/main/java/org/phenopackets/phenopackettools/examples/Marfan.java b/phenopacket-tools-cli/src/main/java/org/phenopackets/phenopackettools/cli/examples/Marfan.java
similarity index 97%
rename from phenopacket-tools-cli/src/main/java/org/phenopackets/phenopackettools/examples/Marfan.java
rename to phenopacket-tools-cli/src/main/java/org/phenopackets/phenopackettools/cli/examples/Marfan.java
index 728aa8f4..e401c7ed 100644
--- a/phenopacket-tools-cli/src/main/java/org/phenopackets/phenopackettools/examples/Marfan.java
+++ b/phenopacket-tools-cli/src/main/java/org/phenopackets/phenopackettools/cli/examples/Marfan.java
@@ -1,4 +1,4 @@
-package org.phenopackets.phenopackettools.examples;
+package org.phenopackets.phenopackettools.cli.examples;
import org.phenopackets.phenopackettools.builder.PhenopacketBuilder;
import org.phenopackets.phenopackettools.builder.builders.*;
diff --git a/phenopacket-tools-cli/src/main/java/org/phenopackets/phenopackettools/examples/NemalineMyopathyPrenatal.java b/phenopacket-tools-cli/src/main/java/org/phenopackets/phenopackettools/cli/examples/NemalineMyopathyPrenatal.java
similarity index 99%
rename from phenopacket-tools-cli/src/main/java/org/phenopackets/phenopackettools/examples/NemalineMyopathyPrenatal.java
rename to phenopacket-tools-cli/src/main/java/org/phenopackets/phenopackettools/cli/examples/NemalineMyopathyPrenatal.java
index 8e7471cc..eb7a00e6 100644
--- a/phenopacket-tools-cli/src/main/java/org/phenopackets/phenopackettools/examples/NemalineMyopathyPrenatal.java
+++ b/phenopacket-tools-cli/src/main/java/org/phenopackets/phenopackettools/cli/examples/NemalineMyopathyPrenatal.java
@@ -1,4 +1,4 @@
-package org.phenopackets.phenopackettools.examples;
+package org.phenopackets.phenopackettools.cli.examples;
import org.ga4gh.vrs.v1.Variation;
import org.ga4gh.vrsatile.v1.Expression;
diff --git a/phenopacket-tools-cli/src/main/java/org/phenopackets/phenopackettools/examples/PhenopacketExample.java b/phenopacket-tools-cli/src/main/java/org/phenopackets/phenopackettools/cli/examples/PhenopacketExample.java
similarity index 67%
rename from phenopacket-tools-cli/src/main/java/org/phenopackets/phenopackettools/examples/PhenopacketExample.java
rename to phenopacket-tools-cli/src/main/java/org/phenopackets/phenopackettools/cli/examples/PhenopacketExample.java
index bef790d8..ac0c0934 100644
--- a/phenopacket-tools-cli/src/main/java/org/phenopackets/phenopackettools/examples/PhenopacketExample.java
+++ b/phenopacket-tools-cli/src/main/java/org/phenopackets/phenopackettools/cli/examples/PhenopacketExample.java
@@ -1,4 +1,4 @@
-package org.phenopackets.phenopackettools.examples;
+package org.phenopackets.phenopackettools.cli.examples;
import org.phenopackets.schema.v2.Phenopacket;
diff --git a/phenopacket-tools-cli/src/main/java/org/phenopackets/phenopackettools/examples/PneumothoraxSecondaryToCOVID.java b/phenopacket-tools-cli/src/main/java/org/phenopackets/phenopackettools/cli/examples/PneumothoraxSecondaryToCOVID.java
similarity index 99%
rename from phenopacket-tools-cli/src/main/java/org/phenopackets/phenopackettools/examples/PneumothoraxSecondaryToCOVID.java
rename to phenopacket-tools-cli/src/main/java/org/phenopackets/phenopackettools/cli/examples/PneumothoraxSecondaryToCOVID.java
index c063181c..d24fbeb8 100644
--- a/phenopacket-tools-cli/src/main/java/org/phenopackets/phenopackettools/examples/PneumothoraxSecondaryToCOVID.java
+++ b/phenopacket-tools-cli/src/main/java/org/phenopackets/phenopackettools/cli/examples/PneumothoraxSecondaryToCOVID.java
@@ -1,4 +1,4 @@
-package org.phenopackets.phenopackettools.examples;
+package org.phenopackets.phenopackettools.cli.examples;
import org.phenopackets.phenopackettools.builder.PhenopacketBuilder;
diff --git a/phenopacket-tools-cli/src/main/java/org/phenopackets/phenopackettools/examples/Pseudoexfoliation.java b/phenopacket-tools-cli/src/main/java/org/phenopackets/phenopackettools/cli/examples/Pseudoexfoliation.java
similarity index 99%
rename from phenopacket-tools-cli/src/main/java/org/phenopackets/phenopackettools/examples/Pseudoexfoliation.java
rename to phenopacket-tools-cli/src/main/java/org/phenopackets/phenopackettools/cli/examples/Pseudoexfoliation.java
index ed544b26..b1036f4c 100644
--- a/phenopacket-tools-cli/src/main/java/org/phenopackets/phenopackettools/examples/Pseudoexfoliation.java
+++ b/phenopacket-tools-cli/src/main/java/org/phenopackets/phenopackettools/cli/examples/Pseudoexfoliation.java
@@ -1,4 +1,4 @@
-package org.phenopackets.phenopackettools.examples;
+package org.phenopackets.phenopackettools.cli.examples;
import org.phenopackets.phenopackettools.builder.PhenopacketBuilder;
import org.phenopackets.schema.v2.Phenopacket;
diff --git a/phenopacket-tools-cli/src/main/java/org/phenopackets/phenopackettools/examples/Retinoblastoma.java b/phenopacket-tools-cli/src/main/java/org/phenopackets/phenopackettools/cli/examples/Retinoblastoma.java
similarity index 90%
rename from phenopacket-tools-cli/src/main/java/org/phenopackets/phenopackettools/examples/Retinoblastoma.java
rename to phenopacket-tools-cli/src/main/java/org/phenopackets/phenopackettools/cli/examples/Retinoblastoma.java
index c731cce2..0a28729d 100644
--- a/phenopacket-tools-cli/src/main/java/org/phenopackets/phenopackettools/examples/Retinoblastoma.java
+++ b/phenopacket-tools-cli/src/main/java/org/phenopackets/phenopackettools/cli/examples/Retinoblastoma.java
@@ -1,5 +1,7 @@
-package org.phenopackets.phenopackettools.examples;
+package org.phenopackets.phenopackettools.cli.examples;
+import org.ga4gh.vrs.v1.*;
+import org.ga4gh.vrs.v1.Number;
import org.phenopackets.phenopackettools.builder.PhenopacketBuilder;
import org.phenopackets.phenopackettools.builder.builders.*;
import org.phenopackets.phenopackettools.builder.constants.Laterality;
@@ -71,10 +73,10 @@ Interpretation interpretation() {
* @return Genomic interpretation related to a somatic missense mutation in the RB1 gene.
*/
GenomicInterpretation somaticRb1Missense() {
- AlleleBuilder abuilder = AlleleBuilder.builder();
- abuilder.sequenceId("refseq:NC_000013.11");
- abuilder.interbaseStartEnd( 48367511, 48367512);
- abuilder.altAllele("T");
+ AlleleBuilder abuilder = AlleleBuilder.builder()
+ .sequenceId("refseq:NC_000013.11")
+ .interbaseStartEnd( 48367511, 48367512)
+ .altAllele("T");
VariationDescriptorBuilder vbuilder = VariationDescriptorBuilder.builder("rs121913300")
.variation(abuilder.buildVariation())
.genomic()
@@ -100,15 +102,31 @@ GenomicInterpretation somaticRb1Missense() {
GenomicInterpretation germlineRb1Deletion() {
- CopyNumberBuilder abuilder = CopyNumberBuilder.builder();
+ CopyNumber cnv = CopyNumber.newBuilder()
+ .setDerivedSequenceExpression(DerivedSequenceExpression.newBuilder()
+ .setLocation(SequenceLocation.newBuilder()
+ .setSequenceId("refseq:NC_000013.14")
+ .setSequenceInterval(SequenceInterval.newBuilder()
+ .setStartNumber(Number.newBuilder().
+ setValue(25981249)
+ .build())
+ .setEndNumber(Number.newBuilder()
+ .setValue(61706822)
+ .build())
+ .build())
+ .build())
+ .build())
+ .setNumber(Number.newBuilder().setValue(1).build())
+ .build();
//abuilder.copyNumberId("ga4gh:VCN.AFfJws1M4Lg8w1O3XknmHYc9TU2hHYpp");
// original coordinates in paper were given as 13q12.13q21.2(26,555,387–62,280,955 for hg19
//chr13 25981249 61706822 -- lifted over to hg38
+ Variation variation = Variation.newBuilder()
+ .setCopyNumber(cnv)
+ .build();
- abuilder.alleleLocation("refseq:NC_000013.14",25981249, 61706822);//VRS uses inter-residue coordinates
- abuilder.oneCopy();
VariationDescriptorBuilder vbuilder = VariationDescriptorBuilder.builder();
- vbuilder.variation(abuilder.buildVariation());
+ vbuilder.variation(variation);
vbuilder.mosaicism(40.0);
VariantInterpretationBuilder vibuilder = VariantInterpretationBuilder.builder(vbuilder);
vibuilder.pathogenic();
diff --git a/phenopacket-tools-cli/src/main/java/org/phenopackets/phenopackettools/examples/SevereStatinInducedAutoimmuneMyopathy.java b/phenopacket-tools-cli/src/main/java/org/phenopackets/phenopackettools/cli/examples/SevereStatinInducedAutoimmuneMyopathy.java
similarity index 98%
rename from phenopacket-tools-cli/src/main/java/org/phenopackets/phenopackettools/examples/SevereStatinInducedAutoimmuneMyopathy.java
rename to phenopacket-tools-cli/src/main/java/org/phenopackets/phenopackettools/cli/examples/SevereStatinInducedAutoimmuneMyopathy.java
index 8947bdf3..25255a24 100644
--- a/phenopacket-tools-cli/src/main/java/org/phenopackets/phenopackettools/examples/SevereStatinInducedAutoimmuneMyopathy.java
+++ b/phenopacket-tools-cli/src/main/java/org/phenopackets/phenopackettools/cli/examples/SevereStatinInducedAutoimmuneMyopathy.java
@@ -1,4 +1,4 @@
-package org.phenopackets.phenopackettools.examples;
+package org.phenopackets.phenopackettools.cli.examples;
import org.phenopackets.phenopackettools.builder.PhenopacketBuilder;
import org.phenopackets.phenopackettools.builder.builders.*;
@@ -9,7 +9,7 @@
import static org.phenopackets.phenopackettools.builder.builders.OntologyClassBuilder.ontologyClass;
import static org.phenopackets.phenopackettools.builder.constants.MedicalActions.*;
-import static org.phenopackets.phenopackettools.builder.constants.Response.favorable;
+import static org.phenopackets.phenopackettools.builder.constants.Response.favorableResponse;
import static org.phenopackets.phenopackettools.builder.constants.Unit.*;
/**
@@ -86,7 +86,7 @@ List previousTreatments() {
var metformin = ontologyClass( "DrugCentral:1725", "metformin");
var fiveHundredMg = QuantityBuilder.builder(milligram(), 500).build();
var metforminAction = MedicalActionBuilder
- .oralAdministration(metformin, fiveHundredMg, threetimesDaily(), interval)
+ .oralAdministration(metformin, fiveHundredMg, threeTimesDaily(), interval)
.build();
return List.of(atorvastatinAction, aspirinAction, ramiprilAction, metforminAction);
}
@@ -110,7 +110,7 @@ private MedicalAction treatment() {
TimeInterval interval = TimeIntervalBuilder.of("2020-09-02", "2021-03-02");
return MedicalActionBuilder
.intravenousAdministration(ivIg, quantity, everySixWeeks, interval)
- .responseToTreatment(favorable())
+ .responseToTreatment(favorableResponse())
.build();
}
diff --git a/phenopacket-tools-cli/src/main/java/org/phenopackets/phenopackettools/examples/SquamousCellCancer.java b/phenopacket-tools-cli/src/main/java/org/phenopackets/phenopackettools/cli/examples/SquamousCellCancer.java
similarity index 98%
rename from phenopacket-tools-cli/src/main/java/org/phenopackets/phenopackettools/examples/SquamousCellCancer.java
rename to phenopacket-tools-cli/src/main/java/org/phenopackets/phenopackettools/cli/examples/SquamousCellCancer.java
index 1a205f55..83c94428 100644
--- a/phenopacket-tools-cli/src/main/java/org/phenopackets/phenopackettools/examples/SquamousCellCancer.java
+++ b/phenopacket-tools-cli/src/main/java/org/phenopackets/phenopackettools/cli/examples/SquamousCellCancer.java
@@ -1,4 +1,4 @@
-package org.phenopackets.phenopackettools.examples;
+package org.phenopackets.phenopackettools.cli.examples;
import org.phenopackets.phenopackettools.builder.PhenopacketBuilder;
import org.phenopackets.phenopackettools.builder.builders.*;
diff --git a/phenopacket-tools-cli/src/main/java/org/phenopackets/phenopackettools/examples/UrothelialCancer.java b/phenopacket-tools-cli/src/main/java/org/phenopackets/phenopackettools/cli/examples/UrothelialCancer.java
similarity index 99%
rename from phenopacket-tools-cli/src/main/java/org/phenopackets/phenopackettools/examples/UrothelialCancer.java
rename to phenopacket-tools-cli/src/main/java/org/phenopackets/phenopackettools/cli/examples/UrothelialCancer.java
index c8ed23b5..a5adac4a 100644
--- a/phenopacket-tools-cli/src/main/java/org/phenopackets/phenopackettools/examples/UrothelialCancer.java
+++ b/phenopacket-tools-cli/src/main/java/org/phenopackets/phenopackettools/cli/examples/UrothelialCancer.java
@@ -1,4 +1,4 @@
-package org.phenopackets.phenopackettools.examples;
+package org.phenopackets.phenopackettools.cli.examples;
import org.phenopackets.phenopackettools.builder.PhenopacketBuilder;
diff --git a/phenopacket-tools-cli/src/main/java/org/phenopackets/phenopackettools/examples/WarburgMicroSyndrome.java b/phenopacket-tools-cli/src/main/java/org/phenopackets/phenopackettools/cli/examples/WarburgMicroSyndrome.java
similarity index 98%
rename from phenopacket-tools-cli/src/main/java/org/phenopackets/phenopackettools/examples/WarburgMicroSyndrome.java
rename to phenopacket-tools-cli/src/main/java/org/phenopackets/phenopackettools/cli/examples/WarburgMicroSyndrome.java
index 43881115..5e5115f2 100644
--- a/phenopacket-tools-cli/src/main/java/org/phenopackets/phenopackettools/examples/WarburgMicroSyndrome.java
+++ b/phenopacket-tools-cli/src/main/java/org/phenopackets/phenopackettools/cli/examples/WarburgMicroSyndrome.java
@@ -1,4 +1,4 @@
-package org.phenopackets.phenopackettools.examples;
+package org.phenopackets.phenopackettools.cli.examples;
import org.phenopackets.phenopackettools.builder.PhenopacketBuilder;
import org.phenopackets.phenopackettools.builder.builders.*;
diff --git a/phenopacket-tools-cli/src/main/java/org/phenopackets/phenopackettools/writer/CSVValidationResultsWriter.java b/phenopacket-tools-cli/src/main/java/org/phenopackets/phenopackettools/cli/writer/CSVValidationResultsWriter.java
similarity index 89%
rename from phenopacket-tools-cli/src/main/java/org/phenopackets/phenopackettools/writer/CSVValidationResultsWriter.java
rename to phenopacket-tools-cli/src/main/java/org/phenopackets/phenopackettools/cli/writer/CSVValidationResultsWriter.java
index 1040a24a..8676dacc 100644
--- a/phenopacket-tools-cli/src/main/java/org/phenopackets/phenopackettools/writer/CSVValidationResultsWriter.java
+++ b/phenopacket-tools-cli/src/main/java/org/phenopackets/phenopackettools/cli/writer/CSVValidationResultsWriter.java
@@ -1,4 +1,4 @@
-package org.phenopackets.phenopackettools.writer;
+package org.phenopackets.phenopackettools.cli.writer;
import org.apache.commons.csv.CSVFormat;
import org.apache.commons.csv.CSVPrinter;
@@ -27,18 +27,21 @@ public class CSVValidationResultsWriter implements ValidationResultsWriter {
private final OutputStream os;
private final String phenopacketToolsVersion;
private final LocalDateTime dateTime;
+ private final boolean printHeader;
/**
* Create the writer using a given {@link OutputStream}. Note that the {@link OutputStream} is not closed.
*
* @param os where to write to
* @param phenopacketToolsVersion phenopacket tools version
- * @param dateTime
+ * @param dateTime the time of validation
+ * @param printHeader print header into the output
*/
- public CSVValidationResultsWriter(OutputStream os, String phenopacketToolsVersion, LocalDateTime dateTime) {
+ public CSVValidationResultsWriter(OutputStream os, String phenopacketToolsVersion, LocalDateTime dateTime, boolean printHeader) {
this.os = os;
this.phenopacketToolsVersion = phenopacketToolsVersion;
this.dateTime = dateTime;
+ this.printHeader = printHeader;
}
@Override
@@ -50,7 +53,10 @@ public void writeValidationResults(List validators, List results, CSVPrinter printer) throws
for (ValidatorInfo validator : results) {
printer.printComment("validator_id=%s;validator_name=%s;description=%s".formatted(validator.validatorId(), validator.validatorName(), validator.description()));
}
+
+ // Print column names
+ printer.printRecord("PATH", "LEVEL", "VALIDATOR_ID", "CATEGORY", "MESSAGE");
}
private static void printValidationResults(List results, CSVPrinter printer) throws IOException {
- // Header
- printer.printRecord("PATH", "LEVEL", "VALIDATOR_ID", "CATEGORY", "MESSAGE");
- // Validation results
for (ValidationResultsAndPath rp : results) {
String path = rp.path() == null ? "-" : rp.path().toAbsolutePath().toString();
for (ValidationResult result : rp.results().validationResults()) {
diff --git a/phenopacket-tools-cli/src/main/java/org/phenopackets/phenopackettools/command/BaseIOCommand.java b/phenopacket-tools-cli/src/main/java/org/phenopackets/phenopackettools/command/BaseIOCommand.java
deleted file mode 100644
index 183f2013..00000000
--- a/phenopacket-tools-cli/src/main/java/org/phenopackets/phenopackettools/command/BaseIOCommand.java
+++ /dev/null
@@ -1,180 +0,0 @@
-package org.phenopackets.phenopackettools.command;
-
-import com.google.protobuf.Message;
-import com.google.protobuf.util.JsonFormat;
-import org.phenopackets.phenopackettools.util.format.FormatSniffException;
-import org.phenopackets.phenopackettools.util.format.FormatSniffer;
-import org.phenopackets.phenopackettools.util.format.PhenopacketElement;
-import org.phenopackets.phenopackettools.util.format.PhenopacketFormat;
-import org.phenopackets.schema.v1.Cohort;
-import org.phenopackets.schema.v1.Family;
-import org.phenopackets.schema.v1.Phenopacket;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-import picocli.CommandLine;
-
-import java.io.*;
-import java.nio.file.Files;
-import java.nio.file.Path;
-import java.util.ArrayList;
-import java.util.List;
-
-/**
- * A command that provides routines for reading as well as {@link PhenopacketFormat}s and {@link PhenopacketElement}s
- * for processing of a single top-level Phenopacket schema element.
- */
-public abstract class BaseIOCommand extends BaseCommand {
-
- private static final Logger LOGGER = LoggerFactory.getLogger(BaseIOCommand.class);
-
- @CommandLine.ArgGroup(validate = false, heading = "Inputs:%s")
- public InputSection inputSection = new InputSection();
-
- public static class InputSection {
- @CommandLine.Option(names = {"-i", "--input"},
- arity = "0..*",
- description = "Input phenopacket(s).%nLeave empty for STDIN")
- public List inputs = null;
-
- // The format will be sniffed if it is uninitialized.
- @CommandLine.Option(names = {"-f", "--format"},
- description = "Phenopacket format.%nChoose from: {${COMPLETION-CANDIDATES}}")
- public PhenopacketFormat format = null;
-
- // TODO - is it too hard to implement element sniffing?
- @CommandLine.Option(names = {"-e", "--element"},
- description = "Top-level element.%nChoose from {${COMPLETION-CANDIDATES}}%nDefault: phenopacket")
- public PhenopacketElement element = null;
- }
-
- /**
- * Attempt to read the input in the provided {@code schemaVersion} and exit upon any failure. As a side effect,
- * {@link org.phenopackets.phenopackettools.command.BaseIOCommand.InputSection#format}
- * and {@link org.phenopackets.phenopackettools.command.BaseIOCommand.InputSection#element}
- * fields are set after the function returns.
- *
- * Note that the function does not return if reading fails.
- */
- protected List readMessagesOrExit(PhenopacketSchemaVersion schemaVersion) {
- if (inputSection.inputs == null) {
- // Assuming a single input is coming from STDIN
- InputStream is = System.in;
- try {
- setFormatAndElement(is);
- return List.of(new MessageAndPath(parseMessage(schemaVersion, is), null));
- } catch (FormatSniffException e) {
- System.err.println("Unable to detect input format from STDIN.\nConsider using the `--format` option.");
- System.exit(1);
- } catch (IOException e) {
- System.err.println("Unable to read STDIN: " + e.getMessage() + "\nPlease check the input format.");
- System.exit(1);
- }
- } else {
- // Assuming a one or more input are provided via `-i | --input`.
-
- // Picocli should ensure that `input` is never an empty list. `input` is `null` if no `-i` was supplied.
- assert !inputSection.inputs.isEmpty();
-
- List messages = new ArrayList<>();
- for (Path input : inputSection.inputs) {
- try (InputStream is = new BufferedInputStream(Files.newInputStream(input))) {
- setFormatAndElement(is);
- Message message = parseMessage(schemaVersion, is);
- messages.add(new MessageAndPath(message, input));
- } catch (FormatSniffException e) {
- System.err.printf("Unable to detect input format of %s.\nConsider using the `--format` option.%n", input.toAbsolutePath());
- System.exit(1);
- } catch (IOException e) {
- System.err.printf("Unable to read input file %s: %s\nPlease check the input format.%n", input.toAbsolutePath(), e.getMessage());
- System.exit(1);
- }
- }
- return messages;
- }
- return null; // Cannot happen but to make the compiler happy...
- }
-
- private void setFormatAndElement(InputStream is) throws IOException, FormatSniffException {
- PhenopacketFormat sniffed = parseFormat(is);
- if (inputSection.format == null) {
- inputSection.format = sniffed;
- } else {
- if (!inputSection.format.equals(sniffed))
- // This can happen e.g. if processing multiple files at once but one turns out to be a different format.
- // We emit warning because this is likely not what the user intended and the code will likely explode
- // further downstream.
- LOGGER.warn("Input format is set to {} but the current input looks like {}", inputSection.format, sniffed);
- }
-
- if (inputSection.element == null) {
- LOGGER.info("Input element type (-e | --element) was not provided, assuming phenopacket..");
- inputSection.element = PhenopacketElement.PHENOPACKET;
- }
- }
-
- private Message parseMessage(PhenopacketSchemaVersion schemaVersion, InputStream is) throws IOException {
- return switch (inputSection.format) {
- case PROTOBUF -> readProtobufMessage(schemaVersion, is);
- case JSON -> readJsonMessage(schemaVersion, is);
- // TODO - implement YAML parsing
- case YAML -> throw new RuntimeException("YAML parser is not yet implemented");
- };
- }
-
- private Message readProtobufMessage(PhenopacketSchemaVersion schemaVersion, InputStream is) throws IOException {
- LOGGER.debug("Reading protobuf message");
- return switch (schemaVersion) {
- case V1 -> switch (inputSection.element) {
- case PHENOPACKET -> Phenopacket.parseFrom(is);
- case FAMILY -> Family.parseFrom(is);
- case COHORT -> Cohort.parseFrom(is);
- };
- case V2 -> switch (inputSection.element) {
-
- case PHENOPACKET -> org.phenopackets.schema.v2.Phenopacket.parseFrom(is);
- case FAMILY -> org.phenopackets.schema.v2.Family.parseFrom(is);
- case COHORT -> org.phenopackets.schema.v2.Cohort.parseFrom(is);
- };
- };
- }
-
- private Message readJsonMessage(PhenopacketSchemaVersion schemaVersion, InputStream is) throws IOException {
- LOGGER.debug("Reading JSON message");
- BufferedReader reader = new BufferedReader(new InputStreamReader(is));
- Message.Builder builder = prepareBuilder(schemaVersion, inputSection.element);
- JsonFormat.parser().merge(reader, builder);
- return builder.build();
- }
-
- private static Message.Builder prepareBuilder(PhenopacketSchemaVersion schemaVersion, PhenopacketElement element) {
- return switch (schemaVersion) {
- case V1 -> switch (element) {
- case PHENOPACKET -> org.phenopackets.schema.v1.Phenopacket.newBuilder();
- case FAMILY -> org.phenopackets.schema.v1.Family.newBuilder();
- case COHORT -> org.phenopackets.schema.v1.Cohort.newBuilder();
- };
- case V2 -> switch (element) {
- case PHENOPACKET -> org.phenopackets.schema.v2.Phenopacket.newBuilder();
- case FAMILY -> org.phenopackets.schema.v2.Family.newBuilder();
- case COHORT -> org.phenopackets.schema.v2.Cohort.newBuilder();
- };
- };
- }
-
- private PhenopacketFormat parseFormat(InputStream is) throws IOException, FormatSniffException {
- if (inputSection.format == null) {
- LOGGER.info("Input format was not provided, making an educated guess..");
- PhenopacketFormat fmt = FormatSniffer.sniff(is);
- LOGGER.info("The input looks like a {} file", fmt);
- return fmt;
- }
- return inputSection.format;
- }
-
- protected record MessageAndPath(Message message, Path path) {}
-
- protected enum PhenopacketSchemaVersion {
- V1,
- V2;
- }
-}
diff --git a/phenopacket-tools-cli/src/main/java/org/phenopackets/phenopackettools/command/ExamplesCommand.java b/phenopacket-tools-cli/src/main/java/org/phenopackets/phenopackettools/command/ExamplesCommand.java
deleted file mode 100644
index e46b69e3..00000000
--- a/phenopacket-tools-cli/src/main/java/org/phenopackets/phenopackettools/command/ExamplesCommand.java
+++ /dev/null
@@ -1,136 +0,0 @@
-package org.phenopackets.phenopackettools.command;
-
-
-
-import com.fasterxml.jackson.databind.JsonNode;
-import com.fasterxml.jackson.databind.ObjectMapper;
-import com.fasterxml.jackson.databind.node.JsonNodeFactory;
-import com.fasterxml.jackson.dataformat.yaml.YAMLFactory;
-import com.fasterxml.jackson.dataformat.yaml.YAMLGenerator;
-import com.google.protobuf.Message;
-import com.google.protobuf.util.JsonFormat;
-
-import org.phenopackets.phenopackettools.builder.exceptions.PhenotoolsRuntimeException;
-import org.phenopackets.phenopackettools.examples.*;
-import picocli.CommandLine;
-import picocli.CommandLine.Command;
-
-import java.io.BufferedWriter;
-import java.io.IOException;
-import java.nio.charset.StandardCharsets;
-import java.nio.file.Files;
-import java.nio.file.Path;
-import java.util.concurrent.Callable;
-
-@Command(name = "examples",
- mixinStandardHelpOptions = true,
- sortOptions = false,
- description = "Write example phenopackets to a directory.")
-public class ExamplesCommand extends BaseCommand {
-
- @CommandLine.Option(names = {"-o", "--output"},
- description = "Output directory (default: ${DEFAULT-VALUE})")
- public Path output = Path.of(".");
-
-
- @Override
- public Integer call() throws Exception {
- printBanner();
-
- Path phenopacketDir = createADirectoryIfDoesNotExist(output.resolve("phenopackets"));
- Path familyDir = createADirectoryIfDoesNotExist(output.resolve("families"));
- Path cohortDir = createADirectoryIfDoesNotExist(output.resolve("cohorts"));
-
- try {
- // Phenopackets
- output(new AtaxiaWithVitaminEdeficiency().getPhenopacket(), phenopacketDir, "AVED");
- output(new BethlehamMyopathy().getPhenopacket(), phenopacketDir, "bethleham-myopathy");
- output(new Holoprosencephaly5().getPhenopacket(), phenopacketDir, "holoprosencephaly5");
- output(new Marfan().getPhenopacket(), phenopacketDir, "marfan");
- output(new NemalineMyopathyPrenatal().getPhenopacket(), phenopacketDir, "nemalineMyopathy");
- output(new Pseudoexfoliation().getPhenopacket(), phenopacketDir,"pseudoexfoliation");
- output(new DuchenneExon51Deletion().getPhenopacket(), phenopacketDir, "duchenne");
- output(new SquamousCellCancer().getPhenopacket(), phenopacketDir, "squamous-cell-esophageal-carcinoma");
- output(new UrothelialCancer().getPhenopacket(), phenopacketDir, "urothelial-cancer");
- output(new Covid().getPhenopacket(), phenopacketDir, "covid");
- output(new Retinoblastoma().getPhenopacket(), phenopacketDir, "retinoblastoma");
- output(new WarburgMicroSyndrome().getPhenopacket(), phenopacketDir, "warburg-micro-syndrome");
- output(new SevereStatinInducedAutoimmuneMyopathy().getPhenopacket(), phenopacketDir, "statin-myopathy");
-
- // Families
- outputFamily(new FamilyWithPedigree().getFamily(), familyDir, "family");
-
- // Cohorts
- // TODO - write a cohort
-
- } catch (Exception e) {
- System.err.println(e.getMessage());
- return 1;
- }
- return 0;
- }
-
- private static Path createADirectoryIfDoesNotExist(Path path) throws IOException {
- return Files.exists(path)
- ? path
- : Files.createDirectories(path);
- }
-
- private static void output(Message phenopacket, Path outDir, String basename) {
- String yamlName = basename + ".yml";
- outputYamlPhenopacket(phenopacket, outDir, yamlName);
- String jsonName = basename + ".json";
- outputPhenopacket(phenopacket, outDir,jsonName);
- }
-
- private static void outputPhenopacket(Message phenopacket, Path outdir,String fileName) {
- outputJsonMessage(phenopacket, outdir, fileName);
- }
-
- private static void outputYamlPhenopacket(Message phenopacket, Path outdir, String fileName) {
- outputYamlMessage(phenopacket, outdir, fileName, "phenopacket");
-
- }
-
- private static void outputFamily(Message family, Path outDir, String basename) {
- String yamlName = basename + ".yml";
- outputYamlFamily(family, outDir, yamlName);
- String jsonName = basename + ".json";
- outputJsonFamily(family, outDir,jsonName);
- }
-
- private static void outputJsonFamily(Message family, Path outDir, String jsonName) {
- outputJsonMessage(family, outDir, jsonName);
- }
-
- private static void outputYamlFamily(Message family, Path outDir, String yamlName) {
- outputYamlMessage(family, outDir, yamlName, "family");
- }
-
- private static void outputJsonMessage(Message message, Path outDir, String fileName) {
- Path path = outDir.resolve(fileName);
- try (BufferedWriter writer = Files.newBufferedWriter(path, StandardCharsets.UTF_8)) {
- String json = JsonFormat.printer().print(message);
- writer.write(json);
- } catch (IOException e) {
- throw new PhenotoolsRuntimeException(e.getMessage());
- }
- }
-
- private static void outputYamlMessage(Message family, Path outDir, String yamlName, String messageName) {
- Path path = outDir.resolve(yamlName);
- ObjectMapper mapper = new ObjectMapper(new YAMLFactory().disable(YAMLGenerator.Feature.WRITE_DOC_START_MARKER));
- try (BufferedWriter writer = Files.newBufferedWriter(path, StandardCharsets.UTF_8)) {
- String jsonString = JsonFormat.printer().print(family);
- JsonNode jsonNodeTree = new ObjectMapper().readTree(jsonString);
- JsonNode node = JsonNodeFactory.instance.objectNode().set(messageName, jsonNodeTree);
- mapper.writeValue(writer, node);
- } catch (IOException e) {
- throw new PhenotoolsRuntimeException(e.getMessage());
- }
- }
-
-
-
-
-}
diff --git a/phenopacket-tools-cli/src/main/resources/logback.xml b/phenopacket-tools-cli/src/main/resources/logback.xml
index 7b5f00ec..6d55578e 100644
--- a/phenopacket-tools-cli/src/main/resources/logback.xml
+++ b/phenopacket-tools-cli/src/main/resources/logback.xml
@@ -3,19 +3,13 @@
-
- INFO
-
System.err
${pattern}
-
-
-
-
+
\ No newline at end of file
diff --git a/phenopacket-tools-cli/src/main/resources/org/phenopackets/phenopackettools/application.properties b/phenopacket-tools-cli/src/main/resources/org/phenopackets/phenopackettools/cli/application.properties
similarity index 100%
rename from phenopacket-tools-cli/src/main/resources/org/phenopackets/phenopackettools/application.properties
rename to phenopacket-tools-cli/src/main/resources/org/phenopackets/phenopackettools/cli/application.properties
diff --git a/phenopacket-tools-cli/src/main/resources/org/phenopackets/phenopackettools/banner.txt b/phenopacket-tools-cli/src/main/resources/org/phenopackets/phenopackettools/cli/banner.txt
similarity index 100%
rename from phenopacket-tools-cli/src/main/resources/org/phenopackets/phenopackettools/banner.txt
rename to phenopacket-tools-cli/src/main/resources/org/phenopackets/phenopackettools/cli/banner.txt
diff --git a/phenopacket-tools-cli/src/test/java/org/phenopackets/phenopackettools/command/BasePTCommandTest.java b/phenopacket-tools-cli/src/test/java/org/phenopackets/phenopackettools/cli/command/BaseCommandTest.java
similarity index 78%
rename from phenopacket-tools-cli/src/test/java/org/phenopackets/phenopackettools/command/BasePTCommandTest.java
rename to phenopacket-tools-cli/src/test/java/org/phenopackets/phenopackettools/cli/command/BaseCommandTest.java
index 9074843a..ca329296 100644
--- a/phenopacket-tools-cli/src/test/java/org/phenopackets/phenopackettools/command/BasePTCommandTest.java
+++ b/phenopacket-tools-cli/src/test/java/org/phenopackets/phenopackettools/cli/command/BaseCommandTest.java
@@ -1,11 +1,11 @@
-package org.phenopackets.phenopackettools.command;
+package org.phenopackets.phenopackettools.cli.command;
import org.junit.jupiter.api.Test;
import static org.hamcrest.MatcherAssert.*;
import static org.hamcrest.Matchers.*;
-public class BasePTCommandTest {
+public class BaseCommandTest {
@Test
public void markIsSupportedForStdin() {
diff --git a/phenopacket-tools-converter/pom.xml b/phenopacket-tools-converter/pom.xml
index c8628e2a..2d48914e 100644
--- a/phenopacket-tools-converter/pom.xml
+++ b/phenopacket-tools-converter/pom.xml
@@ -7,7 +7,7 @@
org.phenopackets.phenopackettools
phenopacket-tools
- 0.4.6
+ 0.4.7
phenopacket-tools-converter
@@ -22,10 +22,6 @@
org.phenopackets
phenopacket-schema
-
- com.google.protobuf
- protobuf-java
-
org.phenopackets.phenopackettools
diff --git a/phenopacket-tools-converter/src/main/java/module-info.java b/phenopacket-tools-converter/src/main/java/module-info.java
index 1eed0a0f..5d4a9fce 100644
--- a/phenopacket-tools-converter/src/main/java/module-info.java
+++ b/phenopacket-tools-converter/src/main/java/module-info.java
@@ -1,6 +1,11 @@
+/**
+ * A module for converting between the {@link org.phenopackets.phenopackettools.core.PhenopacketSchemaVersion}s.
+ */
module org.phenopackets.phenopackettools.converter {
requires transitive org.phenopackets.schema;
+ requires org.phenopackets.phenopackettools.core;
requires org.phenopackets.phenopackettools.builder;
+ requires org.slf4j;
exports org.phenopackets.phenopackettools.converter.converters;
}
\ No newline at end of file
diff --git a/phenopacket-tools-converter/src/main/java/org/phenopackets/phenopackettools/converter/converters/V1ToV2Converter.java b/phenopacket-tools-converter/src/main/java/org/phenopackets/phenopackettools/converter/converters/V1ToV2Converter.java
index adafb39e..397d6e29 100644
--- a/phenopacket-tools-converter/src/main/java/org/phenopackets/phenopackettools/converter/converters/V1ToV2Converter.java
+++ b/phenopacket-tools-converter/src/main/java/org/phenopackets/phenopackettools/converter/converters/V1ToV2Converter.java
@@ -12,7 +12,7 @@
* assuming all {@link org.phenopackets.schema.v1.core.Variant}s are
* {@link org.phenopackets.schema.v2.core.GenomicInterpretation.InterpretationStatus#CAUSATIVE}. For this to work,
* there must be exactly one {@link org.phenopackets.schema.v1.core.Disease} in the phenopacket, otherwise
- * a {@link org.phenopackets.phenopackettools.builder.exceptions.PhenotoolsRuntimeException} is thrown.
+ * a {@link org.phenopackets.phenopackettools.core.PhenopacketToolsRuntimeException} is thrown.
*/
public interface V1ToV2Converter {
diff --git a/phenopacket-tools-converter/src/main/java/org/phenopackets/phenopackettools/converter/converters/V1ToV2ConverterImpl.java b/phenopacket-tools-converter/src/main/java/org/phenopackets/phenopackettools/converter/converters/V1ToV2ConverterImpl.java
index 7d9b9cd5..14934cfe 100644
--- a/phenopacket-tools-converter/src/main/java/org/phenopackets/phenopackettools/converter/converters/V1ToV2ConverterImpl.java
+++ b/phenopacket-tools-converter/src/main/java/org/phenopackets/phenopackettools/converter/converters/V1ToV2ConverterImpl.java
@@ -2,13 +2,15 @@
import org.ga4gh.vrsatile.v1.VariationDescriptor;
import org.phenopackets.phenopackettools.builder.builders.*;
-import org.phenopackets.phenopackettools.builder.exceptions.PhenotoolsRuntimeException;
+import org.phenopackets.phenopackettools.core.PhenopacketToolsRuntimeException;
import org.phenopackets.schema.v1.core.Variant;
import org.phenopackets.schema.v2.Cohort;
import org.phenopackets.schema.v2.Family;
import org.phenopackets.schema.v2.Phenopacket;
import org.phenopackets.schema.v2.core.Interpretation;
import org.phenopackets.schema.v2.core.OntologyClass;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
import java.util.List;
import java.util.function.Function;
@@ -27,6 +29,8 @@
*/
class V1ToV2ConverterImpl implements V1ToV2Converter {
+ private static final Logger LOGGER = LoggerFactory.getLogger(V1ToV2ConverterImpl.class);
+
private final boolean convertVariants;
V1ToV2ConverterImpl(boolean convertVariants) {
@@ -49,8 +53,11 @@ public Phenopacket convertPhenopacket(org.phenopackets.schema.v1.Phenopacket phe
builder.addAllBiosamples(toBiosamples(phenopacket.getBiosamplesList()));
}
- if (convertVariants)
- builder.addInterpretations(toV2Interpretation(phenopacket));
+ if (convertVariants) {
+ Interpretation interpretation = toV2Interpretation(phenopacket);
+ if (!Interpretation.getDefaultInstance().equals(interpretation))
+ builder.addInterpretations(interpretation);
+ }
if (phenopacket.getDiseasesCount() > 0) {
builder.addAllDiseases(toDiseases(phenopacket.getDiseasesList()));
@@ -123,7 +130,16 @@ private static Interpretation toV2Interpretation(org.phenopackets.schema.v1.Phen
so we will use the v1 phenopacket id for the interpretation id.
*/
if (v1.getDiseasesCount() != 1) {
- throw new PhenotoolsRuntimeException("Can only convert variants if there is exactly one disease in v1 phenopacket!");
+ if (v1.getVariantsCount() == 0) {
+ // If there are no variants then we do not care about having exactly one disease.
+ // We can still create a meaningful phenopacket, however, this may be not what the user intended,
+ // and we'll warn.
+ LOGGER.warn("Unable to convert disease and variant data since there are no variants in phenopacket '{}'", v1.getId());
+ return Interpretation.getDefaultInstance();
+ } else {
+ // Non-empty variant list but not a single disease, we throw.
+ throw new PhenopacketToolsRuntimeException("Can only convert variants if there is exactly one disease in v1 phenopacket!");
+ }
}
var v1disease = v1.getDiseases(0);
@@ -190,7 +206,7 @@ private static Function toVariationDescriptor() {
.build();
}
// cannot ever happen, but if it does...
- case ALLELE_NOT_SET -> throw new PhenotoolsRuntimeException("Did not recognize variant type");
+ case ALLELE_NOT_SET -> throw new PhenopacketToolsRuntimeException("Did not recognize variant type");
};
};
}
diff --git a/phenopacket-tools-converter/src/main/java/org/phenopackets/phenopackettools/converter/converters/package-info.java b/phenopacket-tools-converter/src/main/java/org/phenopackets/phenopackettools/converter/converters/package-info.java
new file mode 100644
index 00000000..144d0c37
--- /dev/null
+++ b/phenopacket-tools-converter/src/main/java/org/phenopackets/phenopackettools/converter/converters/package-info.java
@@ -0,0 +1,6 @@
+/**
+ * The package provides a {@link org.phenopackets.phenopackettools.converter.converters.V1ToV2Converter} to convert
+ * from {@link org.phenopackets.phenopackettools.core.PhenopacketSchemaVersion#V1}
+ * to {@link org.phenopackets.phenopackettools.core.PhenopacketSchemaVersion#V2}.
+ */
+package org.phenopackets.phenopackettools.converter.converters;
\ No newline at end of file
diff --git a/phenopacket-tools-core/pom.xml b/phenopacket-tools-core/pom.xml
new file mode 100644
index 00000000..46f0eab8
--- /dev/null
+++ b/phenopacket-tools-core/pom.xml
@@ -0,0 +1,14 @@
+
+
+ 4.0.0
+
+ phenopacket-tools
+ org.phenopackets.phenopackettools
+ 0.4.7
+
+
+ phenopacket-tools-core
+
+
\ No newline at end of file
diff --git a/phenopacket-tools-core/src/main/java/module-info.java b/phenopacket-tools-core/src/main/java/module-info.java
new file mode 100644
index 00000000..ddf4c946
--- /dev/null
+++ b/phenopacket-tools-core/src/main/java/module-info.java
@@ -0,0 +1,6 @@
+/**
+ * The module defines core concepts shared by (almost) all modules of phenopacket-tools.
+ */
+module org.phenopackets.phenopackettools.core {
+ exports org.phenopackets.phenopackettools.core;
+}
\ No newline at end of file
diff --git a/phenopacket-tools-util/src/main/java/org/phenopackets/phenopackettools/util/format/PhenopacketElement.java b/phenopacket-tools-core/src/main/java/org/phenopackets/phenopackettools/core/PhenopacketElement.java
similarity index 95%
rename from phenopacket-tools-util/src/main/java/org/phenopackets/phenopackettools/util/format/PhenopacketElement.java
rename to phenopacket-tools-core/src/main/java/org/phenopackets/phenopackettools/core/PhenopacketElement.java
index 852c8ddc..27d5cf62 100644
--- a/phenopacket-tools-util/src/main/java/org/phenopackets/phenopackettools/util/format/PhenopacketElement.java
+++ b/phenopacket-tools-core/src/main/java/org/phenopackets/phenopackettools/core/PhenopacketElement.java
@@ -1,4 +1,4 @@
-package org.phenopackets.phenopackettools.util.format;
+package org.phenopackets.phenopackettools.core;
import java.util.Arrays;
import java.util.stream.Collectors;
diff --git a/phenopacket-tools-util/src/main/java/org/phenopackets/phenopackettools/util/format/PhenopacketFormat.java b/phenopacket-tools-core/src/main/java/org/phenopackets/phenopackettools/core/PhenopacketFormat.java
similarity index 96%
rename from phenopacket-tools-util/src/main/java/org/phenopackets/phenopackettools/util/format/PhenopacketFormat.java
rename to phenopacket-tools-core/src/main/java/org/phenopackets/phenopackettools/core/PhenopacketFormat.java
index d9020991..3db508a4 100644
--- a/phenopacket-tools-util/src/main/java/org/phenopackets/phenopackettools/util/format/PhenopacketFormat.java
+++ b/phenopacket-tools-core/src/main/java/org/phenopackets/phenopackettools/core/PhenopacketFormat.java
@@ -1,4 +1,4 @@
-package org.phenopackets.phenopackettools.util.format;
+package org.phenopackets.phenopackettools.core;
import java.util.Arrays;
import java.util.stream.Collectors;
diff --git a/phenopacket-tools-core/src/main/java/org/phenopackets/phenopackettools/core/PhenopacketSchemaVersion.java b/phenopacket-tools-core/src/main/java/org/phenopackets/phenopackettools/core/PhenopacketSchemaVersion.java
new file mode 100644
index 00000000..6fa23845
--- /dev/null
+++ b/phenopacket-tools-core/src/main/java/org/phenopackets/phenopackettools/core/PhenopacketSchemaVersion.java
@@ -0,0 +1,18 @@
+package org.phenopackets.phenopackettools.core;
+
+/**
+ * An enum with currently supported Phenopacket schema versions.
+ */
+public enum PhenopacketSchemaVersion {
+
+ /**
+ * The version 1 of the GA4GH Phenopacket schema released in 2019 to elicit community response.
+ * The {@code V1} has been deprecated in favor of {@link #V2}.
+ */
+ V1,
+ /**
+ * The version 2 of the GA4GH Phenopacket schema. This is the current version.
+ */
+ V2
+
+}
diff --git a/phenopacket-tools-core/src/main/java/org/phenopackets/phenopackettools/core/PhenopacketToolsException.java b/phenopacket-tools-core/src/main/java/org/phenopackets/phenopackettools/core/PhenopacketToolsException.java
new file mode 100644
index 00000000..8f1f664a
--- /dev/null
+++ b/phenopacket-tools-core/src/main/java/org/phenopackets/phenopackettools/core/PhenopacketToolsException.java
@@ -0,0 +1,28 @@
+package org.phenopackets.phenopackettools.core;
+
+/**
+ * Base checked exception thrown by phenopacket-tools.
+ */
+public class PhenopacketToolsException extends Exception {
+
+ public PhenopacketToolsException() {
+ super();
+ }
+
+ public PhenopacketToolsException(String message) {
+ super(message);
+ }
+
+ public PhenopacketToolsException(String message, Throwable cause) {
+ super(message, cause);
+ }
+
+ public PhenopacketToolsException(Throwable cause) {
+ super(cause);
+ }
+
+ protected PhenopacketToolsException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) {
+ super(message, cause, enableSuppression, writableStackTrace);
+ }
+
+}
diff --git a/phenopacket-tools-core/src/main/java/org/phenopackets/phenopackettools/core/PhenopacketToolsRuntimeException.java b/phenopacket-tools-core/src/main/java/org/phenopackets/phenopackettools/core/PhenopacketToolsRuntimeException.java
new file mode 100644
index 00000000..b67798c5
--- /dev/null
+++ b/phenopacket-tools-core/src/main/java/org/phenopackets/phenopackettools/core/PhenopacketToolsRuntimeException.java
@@ -0,0 +1,28 @@
+package org.phenopackets.phenopackettools.core;
+
+/**
+ * Base unchecked exception thrown by phenopacket-tools.
+ */
+public class PhenopacketToolsRuntimeException extends RuntimeException {
+
+ public PhenopacketToolsRuntimeException() {
+ super();
+ }
+
+ public PhenopacketToolsRuntimeException(String message) {
+ super(message);
+ }
+
+ public PhenopacketToolsRuntimeException(String message, Throwable cause) {
+ super(message, cause);
+ }
+
+ public PhenopacketToolsRuntimeException(Throwable cause) {
+ super(cause);
+ }
+
+ protected PhenopacketToolsRuntimeException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) {
+ super(message, cause, enableSuppression, writableStackTrace);
+ }
+
+}
diff --git a/phenopacket-tools-core/src/main/java/org/phenopackets/phenopackettools/core/package-info.java b/phenopacket-tools-core/src/main/java/org/phenopackets/phenopackettools/core/package-info.java
new file mode 100644
index 00000000..0aa8cba5
--- /dev/null
+++ b/phenopacket-tools-core/src/main/java/org/phenopackets/phenopackettools/core/package-info.java
@@ -0,0 +1,14 @@
+/**
+ * A package with constants and types used across the entire application, including the base exception classes.
+ *
+ * The package contains the base checked exception {@link org.phenopackets.phenopackettools.core.PhenopacketToolsException}
+ * and unchecked exception {@link org.phenopackets.phenopackettools.core.PhenopacketToolsRuntimeException}.
+ *
+ * Several useful enumerations complete the circle:
+ *
+ * - {@link org.phenopackets.phenopackettools.core.PhenopacketElement}
+ * - {@link org.phenopackets.phenopackettools.core.PhenopacketFormat}
+ * - {@link org.phenopackets.phenopackettools.core.PhenopacketSchemaVersion}
+ *
+ */
+package org.phenopackets.phenopackettools.core;
\ No newline at end of file
diff --git a/phenopacket-tools-io/pom.xml b/phenopacket-tools-io/pom.xml
new file mode 100644
index 00000000..3768f2eb
--- /dev/null
+++ b/phenopacket-tools-io/pom.xml
@@ -0,0 +1,49 @@
+
+
+ 4.0.0
+
+ phenopacket-tools
+ org.phenopackets.phenopackettools
+ 0.4.7
+
+
+ phenopacket-tools-io
+
+
+
+ org.phenopackets.phenopackettools
+ phenopacket-tools-util
+ ${project.parent.version}
+
+
+ org.phenopackets
+ phenopacket-schema
+
+
+ com.fasterxml.jackson.core
+ jackson-databind
+
+
+ com.fasterxml.jackson.dataformat
+ jackson-dataformat-yaml
+
+
+ com.google.protobuf
+ protobuf-java
+
+
+ com.google.protobuf
+ protobuf-java-util
+
+
+
+ org.phenopackets.phenopackettools
+ phenopacket-tools-test
+ ${project.parent.version}
+ test
+
+
+
+
\ No newline at end of file
diff --git a/phenopacket-tools-io/src/main/java/module-info.java b/phenopacket-tools-io/src/main/java/module-info.java
new file mode 100644
index 00000000..d5534857
--- /dev/null
+++ b/phenopacket-tools-io/src/main/java/module-info.java
@@ -0,0 +1,15 @@
+/**
+ * A module for reading and writing top-level elements of Phenopacket Schema.
+ */
+module org.phenopackets.phenopackettools.io {
+ requires org.phenopackets.phenopackettools.util;
+
+ requires org.phenopackets.schema;
+ requires com.google.protobuf;
+ requires com.google.protobuf.util;
+ requires com.fasterxml.jackson.databind;
+ requires com.fasterxml.jackson.dataformat.yaml;
+ requires org.slf4j;
+
+ exports org.phenopackets.phenopackettools.io;
+}
\ No newline at end of file
diff --git a/phenopacket-tools-io/src/main/java/org/phenopackets/phenopackettools/io/JsonPrinter.java b/phenopacket-tools-io/src/main/java/org/phenopackets/phenopackettools/io/JsonPrinter.java
new file mode 100644
index 00000000..3e6825d6
--- /dev/null
+++ b/phenopacket-tools-io/src/main/java/org/phenopackets/phenopackettools/io/JsonPrinter.java
@@ -0,0 +1,30 @@
+package org.phenopackets.phenopackettools.io;
+
+import com.google.protobuf.Message;
+import com.google.protobuf.util.JsonFormat;
+
+import java.io.BufferedWriter;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.io.OutputStreamWriter;
+
+class JsonPrinter implements PhenopacketPrinter {
+
+ private static final JsonFormat.Printer PRINTER = JsonFormat.printer();
+
+ private static final JsonPrinter INSTANCE = new JsonPrinter();
+
+ static JsonPrinter getInstance() {
+ return INSTANCE;
+ }
+
+ private JsonPrinter() {
+ }
+
+ @Override
+ public void print(Message message, OutputStream os) throws IOException {
+ BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(os));
+ PRINTER.appendTo(message, writer);
+ writer.flush();
+ }
+}
diff --git a/phenopacket-tools-io/src/main/java/org/phenopackets/phenopackettools/io/NaiveYamlPrinter.java b/phenopacket-tools-io/src/main/java/org/phenopackets/phenopackettools/io/NaiveYamlPrinter.java
new file mode 100644
index 00000000..9ebc7a0b
--- /dev/null
+++ b/phenopacket-tools-io/src/main/java/org/phenopackets/phenopackettools/io/NaiveYamlPrinter.java
@@ -0,0 +1,47 @@
+package org.phenopackets.phenopackettools.io;
+
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.dataformat.yaml.YAMLGenerator;
+import com.fasterxml.jackson.dataformat.yaml.YAMLMapper;
+import com.google.protobuf.Message;
+import com.google.protobuf.MessageOrBuilder;
+import com.google.protobuf.util.JsonFormat;
+
+import java.io.IOException;
+import java.io.OutputStream;
+
+/**
+ * A naive implementation of YAML printer that first prints the {@link MessageOrBuilder} into a JSON string,
+ * then decodes the string into {@link JsonNode} and prints as YAML document.
+ *
+ * This is, of course, not efficient. However, it works OK as a prototype printer.
+ */
+class NaiveYamlPrinter implements PhenopacketPrinter {
+
+ private static final JsonFormat.Printer PB_PRINTER = JsonFormat.printer();
+
+ private static final NaiveYamlPrinter INSTANCE = new NaiveYamlPrinter();
+
+ static NaiveYamlPrinter getInstance() {
+ return INSTANCE;
+ }
+
+ private final ObjectMapper jsonMapper;
+ private final ObjectMapper yamlMapper;
+
+ private NaiveYamlPrinter() {
+ jsonMapper = new ObjectMapper();
+ yamlMapper = YAMLMapper.builder()
+ .disable(YAMLGenerator.Feature.WRITE_DOC_START_MARKER)
+ .build();
+ }
+
+ @Override
+ public void print(Message message, OutputStream os) throws IOException {
+ String jsonString = PB_PRINTER.print(message);
+ JsonNode jsonNode = jsonMapper.readTree(jsonString);
+ yamlMapper.writeValue(os, jsonNode);
+ }
+
+}
diff --git a/phenopacket-tools-io/src/main/java/org/phenopackets/phenopackettools/io/PhenopacketParser.java b/phenopacket-tools-io/src/main/java/org/phenopackets/phenopackettools/io/PhenopacketParser.java
new file mode 100644
index 00000000..98a4452e
--- /dev/null
+++ b/phenopacket-tools-io/src/main/java/org/phenopackets/phenopackettools/io/PhenopacketParser.java
@@ -0,0 +1,79 @@
+package org.phenopackets.phenopackettools.io;
+
+import com.google.protobuf.Message;
+import org.phenopackets.phenopackettools.util.format.FormatSniffer;
+import org.phenopackets.phenopackettools.core.PhenopacketElement;
+import org.phenopackets.phenopackettools.core.PhenopacketFormat;
+import org.phenopackets.phenopackettools.util.format.SniffException;
+
+import java.io.BufferedInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.nio.file.Files;
+import java.nio.file.Path;
+
+public interface PhenopacketParser {
+
+ Message parse(PhenopacketFormat format, PhenopacketElement element, InputStream is) throws IOException;
+
+ default Message parse(PhenopacketFormat format, PhenopacketElement element, Path path) throws IOException {
+ try (InputStream is = openInputStream(path)) {
+ return parse(format, element, is);
+ }
+ }
+
+ /* ******************************************* CONVENIENCE METHODS ******************************************* */
+
+ // We need to detect the element.
+
+ default Message parse(PhenopacketFormat format, InputStream is) throws IOException {
+ PhenopacketElement element = sniffElement(is);
+ return parse(format, element, is);
+ }
+
+ default Message parse(PhenopacketFormat format, Path path) throws IOException {
+ try (InputStream is = openInputStream(path)) {
+ return parse(format, is);
+ }
+ }
+
+ // We need to detect the format.
+
+ default Message parse(PhenopacketElement element, InputStream is) throws IOException, SniffException {
+ PhenopacketFormat format = sniffFormat(is);
+ return parse(format, element, is);
+ }
+
+ default Message parse(PhenopacketElement element, Path path) throws IOException, SniffException {
+ try (InputStream is = openInputStream(path)) {
+ return parse(element, is);
+ }
+ }
+
+ // We need to detect both the format and the element.
+
+ default Message parse(InputStream is) throws IOException, SniffException {
+ PhenopacketFormat format = sniffFormat(is);
+ return parse(format, is);
+ }
+
+ default Message parse(Path path) throws IOException, SniffException {
+ try (InputStream is = openInputStream(path)) {
+ return parse(is);
+ }
+ }
+
+ /* ******************************************* UTILITY METHODS ******************************************* */
+
+ private static PhenopacketElement sniffElement(InputStream is) {
+ return PhenopacketElement.PHENOPACKET; // TODO - implement
+ }
+
+ private static PhenopacketFormat sniffFormat(InputStream is) throws SniffException, IOException {
+ return FormatSniffer.sniff(is);
+ }
+
+ private static BufferedInputStream openInputStream(Path path) throws IOException {
+ return new BufferedInputStream(Files.newInputStream(path));
+ }
+}
diff --git a/phenopacket-tools-io/src/main/java/org/phenopackets/phenopackettools/io/PhenopacketParserFactory.java b/phenopacket-tools-io/src/main/java/org/phenopackets/phenopackettools/io/PhenopacketParserFactory.java
new file mode 100644
index 00000000..7c29ba7c
--- /dev/null
+++ b/phenopacket-tools-io/src/main/java/org/phenopackets/phenopackettools/io/PhenopacketParserFactory.java
@@ -0,0 +1,19 @@
+package org.phenopackets.phenopackettools.io;
+
+import org.phenopackets.phenopackettools.core.PhenopacketSchemaVersion;
+
+public interface PhenopacketParserFactory {
+
+ static PhenopacketParserFactory getInstance() {
+ return PhenopacketParserFactoryImpl.INSTANCE;
+ }
+
+ /**
+ * Get a {@link PhenopacketParser} to parse phenopacket with given {@link PhenopacketSchemaVersion}.
+ *
+ * @throws PhenopacketParserFactoryException if a {@link PhenopacketParser} for the given {@code version}
+ * is not available
+ */
+ PhenopacketParser forFormat(PhenopacketSchemaVersion version) throws PhenopacketParserFactoryException;
+
+}
diff --git a/phenopacket-tools-io/src/main/java/org/phenopackets/phenopackettools/io/PhenopacketParserFactoryException.java b/phenopacket-tools-io/src/main/java/org/phenopackets/phenopackettools/io/PhenopacketParserFactoryException.java
new file mode 100644
index 00000000..d030dd23
--- /dev/null
+++ b/phenopacket-tools-io/src/main/java/org/phenopackets/phenopackettools/io/PhenopacketParserFactoryException.java
@@ -0,0 +1,26 @@
+package org.phenopackets.phenopackettools.io;
+
+import org.phenopackets.phenopackettools.core.PhenopacketToolsRuntimeException;
+
+public class PhenopacketParserFactoryException extends PhenopacketToolsRuntimeException {
+
+ public PhenopacketParserFactoryException() {
+ super();
+ }
+
+ public PhenopacketParserFactoryException(String message) {
+ super(message);
+ }
+
+ public PhenopacketParserFactoryException(String message, Throwable cause) {
+ super(message, cause);
+ }
+
+ public PhenopacketParserFactoryException(Throwable cause) {
+ super(cause);
+ }
+
+ protected PhenopacketParserFactoryException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) {
+ super(message, cause, enableSuppression, writableStackTrace);
+ }
+}
diff --git a/phenopacket-tools-io/src/main/java/org/phenopackets/phenopackettools/io/PhenopacketParserFactoryImpl.java b/phenopacket-tools-io/src/main/java/org/phenopackets/phenopackettools/io/PhenopacketParserFactoryImpl.java
new file mode 100644
index 00000000..e24654a1
--- /dev/null
+++ b/phenopacket-tools-io/src/main/java/org/phenopackets/phenopackettools/io/PhenopacketParserFactoryImpl.java
@@ -0,0 +1,19 @@
+package org.phenopackets.phenopackettools.io;
+
+import org.phenopackets.phenopackettools.core.PhenopacketSchemaVersion;
+import org.phenopackets.phenopackettools.io.v1.V1PhenopacketParser;
+import org.phenopackets.phenopackettools.io.v2.V2PhenopacketParser;
+
+class PhenopacketParserFactoryImpl implements PhenopacketParserFactory {
+
+ static final PhenopacketParserFactoryImpl INSTANCE = new PhenopacketParserFactoryImpl();
+
+ @Override
+ public PhenopacketParser forFormat(PhenopacketSchemaVersion version) throws PhenopacketParserFactoryException {
+ return switch (version) {
+ case V1 -> V1PhenopacketParser.INSTANCE;
+ case V2 -> V2PhenopacketParser.INSTANCE;
+ };
+ }
+
+}
diff --git a/phenopacket-tools-io/src/main/java/org/phenopackets/phenopackettools/io/PhenopacketPrinter.java b/phenopacket-tools-io/src/main/java/org/phenopackets/phenopackettools/io/PhenopacketPrinter.java
new file mode 100644
index 00000000..878f44fc
--- /dev/null
+++ b/phenopacket-tools-io/src/main/java/org/phenopackets/phenopackettools/io/PhenopacketPrinter.java
@@ -0,0 +1,23 @@
+package org.phenopackets.phenopackettools.io;
+
+import com.google.protobuf.Message;
+
+import java.io.BufferedOutputStream;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.nio.file.Files;
+import java.nio.file.Path;
+
+/**
+ * The implementors can serialize a top-level element of Phenopacket schema into provided {@link OutputStream}.
+ */
+public interface PhenopacketPrinter {
+
+ void print(Message message, OutputStream os) throws IOException;
+
+ default void print(Message message, Path output) throws IOException {
+ try (OutputStream os = new BufferedOutputStream(Files.newOutputStream(output))) {
+ print(message, os);
+ }
+ }
+}
diff --git a/phenopacket-tools-io/src/main/java/org/phenopackets/phenopackettools/io/PhenopacketPrinterFactory.java b/phenopacket-tools-io/src/main/java/org/phenopackets/phenopackettools/io/PhenopacketPrinterFactory.java
new file mode 100644
index 00000000..05a68c01
--- /dev/null
+++ b/phenopacket-tools-io/src/main/java/org/phenopackets/phenopackettools/io/PhenopacketPrinterFactory.java
@@ -0,0 +1,19 @@
+package org.phenopackets.phenopackettools.io;
+
+import org.phenopackets.phenopackettools.core.PhenopacketFormat;
+import org.phenopackets.phenopackettools.core.PhenopacketSchemaVersion;
+
+/**
+ * The implementors provide {@link PhenopacketPrinter}s for serializing top-level phenopacket elements
+ * into {@link PhenopacketFormat} using {@link PhenopacketSchemaVersion}.
+ */
+public interface PhenopacketPrinterFactory {
+
+ static PhenopacketPrinterFactory getInstance() {
+ return PhenopacketPrinterFactoryImpl.INSTANCE;
+ }
+
+ PhenopacketPrinter forFormat(PhenopacketSchemaVersion schemaVersion,
+ PhenopacketFormat format) throws PhenopacketPrinterFactoryException;
+
+}
diff --git a/phenopacket-tools-io/src/main/java/org/phenopackets/phenopackettools/io/PhenopacketPrinterFactoryException.java b/phenopacket-tools-io/src/main/java/org/phenopackets/phenopackettools/io/PhenopacketPrinterFactoryException.java
new file mode 100644
index 00000000..225983cf
--- /dev/null
+++ b/phenopacket-tools-io/src/main/java/org/phenopackets/phenopackettools/io/PhenopacketPrinterFactoryException.java
@@ -0,0 +1,26 @@
+package org.phenopackets.phenopackettools.io;
+
+import org.phenopackets.phenopackettools.core.PhenopacketToolsRuntimeException;
+
+public class PhenopacketPrinterFactoryException extends PhenopacketToolsRuntimeException {
+
+ public PhenopacketPrinterFactoryException() {
+ super();
+ }
+
+ public PhenopacketPrinterFactoryException(String message) {
+ super(message);
+ }
+
+ public PhenopacketPrinterFactoryException(String message, Throwable cause) {
+ super(message, cause);
+ }
+
+ public PhenopacketPrinterFactoryException(Throwable cause) {
+ super(cause);
+ }
+
+ protected PhenopacketPrinterFactoryException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) {
+ super(message, cause, enableSuppression, writableStackTrace);
+ }
+}
diff --git a/phenopacket-tools-io/src/main/java/org/phenopackets/phenopackettools/io/PhenopacketPrinterFactoryImpl.java b/phenopacket-tools-io/src/main/java/org/phenopackets/phenopackettools/io/PhenopacketPrinterFactoryImpl.java
new file mode 100644
index 00000000..55736f6f
--- /dev/null
+++ b/phenopacket-tools-io/src/main/java/org/phenopackets/phenopackettools/io/PhenopacketPrinterFactoryImpl.java
@@ -0,0 +1,20 @@
+package org.phenopackets.phenopackettools.io;
+
+import com.google.protobuf.Message;
+import org.phenopackets.phenopackettools.core.PhenopacketFormat;
+import org.phenopackets.phenopackettools.core.PhenopacketSchemaVersion;
+
+class PhenopacketPrinterFactoryImpl implements PhenopacketPrinterFactory {
+
+ static final PhenopacketPrinterFactoryImpl INSTANCE = new PhenopacketPrinterFactoryImpl();
+
+ @Override
+ public PhenopacketPrinter forFormat(PhenopacketSchemaVersion schemaVersion, PhenopacketFormat format) throws PhenopacketPrinterFactoryException {
+ return switch (format) {
+ case PROTOBUF -> Message::writeTo;
+ case JSON -> JsonPrinter.getInstance();
+ case YAML -> NaiveYamlPrinter.getInstance();
+ };
+ }
+
+}
diff --git a/phenopacket-tools-io/src/main/java/org/phenopackets/phenopackettools/io/base/BasePhenopacketParser.java b/phenopacket-tools-io/src/main/java/org/phenopackets/phenopackettools/io/base/BasePhenopacketParser.java
new file mode 100644
index 00000000..4383228d
--- /dev/null
+++ b/phenopacket-tools-io/src/main/java/org/phenopackets/phenopackettools/io/base/BasePhenopacketParser.java
@@ -0,0 +1,55 @@
+package org.phenopackets.phenopackettools.io.base;
+
+import com.google.protobuf.Message;
+import com.google.protobuf.util.JsonFormat;
+import org.phenopackets.phenopackettools.io.PhenopacketParser;
+import org.phenopackets.phenopackettools.core.PhenopacketElement;
+import org.phenopackets.phenopackettools.core.PhenopacketFormat;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+
+public abstract class BasePhenopacketParser implements PhenopacketParser {
+
+ private static final Logger LOGGER = LoggerFactory.getLogger(BasePhenopacketParser.class);
+
+ @Override
+ public Message parse(PhenopacketFormat format, PhenopacketElement element, InputStream is) throws IOException {
+ return switch (format) {
+ case PROTOBUF -> {
+ LOGGER.debug("Reading protobuf message");
+ yield readProtobufMessage(element, is);
+ }
+ case JSON -> {
+ LOGGER.debug("Reading JSON message");
+ yield readJsonMessage(element, is);
+ }
+ case YAML -> {
+ LOGGER.debug("Reading YAML message");
+ yield readYamlMessage(element, is);
+ }
+ };
+ }
+
+ protected abstract Message readProtobufMessage(PhenopacketElement element, InputStream is) throws IOException;
+
+ private Message readJsonMessage(PhenopacketElement element, InputStream is) throws IOException {
+ // Not closing the BufferedReader as the InputStream should be closed.
+ BufferedReader reader = new BufferedReader(new InputStreamReader(is));
+ Message.Builder builder = prepareBuilder(element);
+ JsonFormat.parser().merge(reader, builder);
+ return builder.build();
+ }
+
+ protected abstract Message.Builder prepareBuilder(PhenopacketElement element);
+
+ private Message readYamlMessage(PhenopacketElement element, InputStream is) throws IOException {
+ Message.Builder builder = prepareBuilder(element);
+ NaiveYamlParser.INSTANCE.deserializeYamlMessage(is, builder);
+ return builder.build();
+ }
+}
diff --git a/phenopacket-tools-io/src/main/java/org/phenopackets/phenopackettools/io/base/NaiveYamlParser.java b/phenopacket-tools-io/src/main/java/org/phenopackets/phenopackettools/io/base/NaiveYamlParser.java
new file mode 100644
index 00000000..c84a0028
--- /dev/null
+++ b/phenopacket-tools-io/src/main/java/org/phenopackets/phenopackettools/io/base/NaiveYamlParser.java
@@ -0,0 +1,33 @@
+package org.phenopackets.phenopackettools.io.base;
+
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.dataformat.yaml.YAMLMapper;
+import com.google.protobuf.Message;
+import com.google.protobuf.util.JsonFormat;
+
+import java.io.IOException;
+import java.io.InputStream;
+
+/**
+ * A naive and inefficient implementation of YAML -> {@link Message} parsing that first maps YAML into JSON String
+ * and then decodes the JSON into {@link Message}.
+ */
+class NaiveYamlParser {
+
+ private static final JsonFormat.Parser JSON_PARSER = JsonFormat.parser();
+
+ static final NaiveYamlParser INSTANCE = new NaiveYamlParser();
+ private final ObjectMapper yamlMapper;
+ private final ObjectMapper jsonMapper;
+ private NaiveYamlParser() {
+ yamlMapper = new YAMLMapper();
+ jsonMapper = new ObjectMapper();
+ }
+
+ void deserializeYamlMessage(InputStream is, Message.Builder builder) throws IOException {
+ JsonNode node = yamlMapper.readTree(is);
+ String jsonString = jsonMapper.writeValueAsString(node);
+ JSON_PARSER.merge(jsonString, builder);
+ }
+}
diff --git a/phenopacket-tools-io/src/main/java/org/phenopackets/phenopackettools/io/package-info.java b/phenopacket-tools-io/src/main/java/org/phenopackets/phenopackettools/io/package-info.java
new file mode 100644
index 00000000..9d18e4ae
--- /dev/null
+++ b/phenopacket-tools-io/src/main/java/org/phenopackets/phenopackettools/io/package-info.java
@@ -0,0 +1,12 @@
+/**
+ * The {@code org.phenopackets.phenopackettools.io} package offers functionality for reading and writing
+ * top-level elements of Phenopacket Schema. The elements can be (de)serialized in any of the supported
+ * {@link org.phenopackets.phenopackettools.core.PhenopacketFormat}s.
+ *
+ * The {@link org.phenopackets.phenopackettools.io.PhenopacketParserFactory}
+ * provides {@link org.phenopackets.phenopackettools.io.PhenopacketParser} for reading the schema elements.
+ *
+ * Use {@link org.phenopackets.phenopackettools.io.PhenopacketPrinterFactory} to get
+ * {@link org.phenopackets.phenopackettools.io.PhenopacketPrinter} for writing a top-level schema element.
+ */
+package org.phenopackets.phenopackettools.io;
\ No newline at end of file
diff --git a/phenopacket-tools-io/src/main/java/org/phenopackets/phenopackettools/io/v1/V1PhenopacketParser.java b/phenopacket-tools-io/src/main/java/org/phenopackets/phenopackettools/io/v1/V1PhenopacketParser.java
new file mode 100644
index 00000000..3d4ab64b
--- /dev/null
+++ b/phenopacket-tools-io/src/main/java/org/phenopackets/phenopackettools/io/v1/V1PhenopacketParser.java
@@ -0,0 +1,35 @@
+package org.phenopackets.phenopackettools.io.v1;
+
+import com.google.protobuf.Message;
+import org.phenopackets.phenopackettools.io.base.BasePhenopacketParser;
+import org.phenopackets.phenopackettools.core.PhenopacketElement;
+import org.phenopackets.schema.v1.Cohort;
+import org.phenopackets.schema.v1.Family;
+import org.phenopackets.schema.v1.Phenopacket;
+
+import java.io.IOException;
+import java.io.InputStream;
+
+public class V1PhenopacketParser extends BasePhenopacketParser {
+
+ public static final V1PhenopacketParser INSTANCE = new V1PhenopacketParser();
+
+ @Override
+ protected Message readProtobufMessage(PhenopacketElement element, InputStream is) throws IOException {
+ return switch (element) {
+ case PHENOPACKET -> Phenopacket.parseFrom(is);
+ case FAMILY -> Family.parseFrom(is);
+ case COHORT -> Cohort.parseFrom(is);
+ };
+ }
+
+ @Override
+ protected Message.Builder prepareBuilder(PhenopacketElement element) {
+ return switch (element) {
+ case PHENOPACKET -> Phenopacket.newBuilder();
+ case FAMILY -> Family.newBuilder();
+ case COHORT -> Cohort.newBuilder();
+ };
+ }
+
+}
diff --git a/phenopacket-tools-io/src/main/java/org/phenopackets/phenopackettools/io/v2/V2PhenopacketParser.java b/phenopacket-tools-io/src/main/java/org/phenopackets/phenopackettools/io/v2/V2PhenopacketParser.java
new file mode 100644
index 00000000..1ace731d
--- /dev/null
+++ b/phenopacket-tools-io/src/main/java/org/phenopackets/phenopackettools/io/v2/V2PhenopacketParser.java
@@ -0,0 +1,34 @@
+package org.phenopackets.phenopackettools.io.v2;
+
+import com.google.protobuf.Message;
+import org.phenopackets.phenopackettools.io.base.BasePhenopacketParser;
+import org.phenopackets.phenopackettools.core.PhenopacketElement;
+import org.phenopackets.schema.v2.Cohort;
+import org.phenopackets.schema.v2.Family;
+import org.phenopackets.schema.v2.Phenopacket;
+
+import java.io.IOException;
+import java.io.InputStream;
+
+public class V2PhenopacketParser extends BasePhenopacketParser {
+
+ public static final V2PhenopacketParser INSTANCE = new V2PhenopacketParser();
+
+ @Override
+ protected Message readProtobufMessage(PhenopacketElement element, InputStream is) throws IOException {
+ return switch (element) {
+ case PHENOPACKET -> Phenopacket.parseFrom(is);
+ case FAMILY -> Family.parseFrom(is);
+ case COHORT -> Cohort.parseFrom(is);
+ };
+ }
+
+ @Override
+ protected Message.Builder prepareBuilder(PhenopacketElement element) {
+ return switch (element) {
+ case PHENOPACKET -> Phenopacket.newBuilder();
+ case FAMILY -> Family.newBuilder();
+ case COHORT -> Cohort.newBuilder();
+ };
+ }
+}
diff --git a/phenopacket-tools-io/src/test/java/org/phenopackets/phenopackettools/io/NaiveYamlPrinterTest.java b/phenopacket-tools-io/src/test/java/org/phenopackets/phenopackettools/io/NaiveYamlPrinterTest.java
new file mode 100644
index 00000000..55dfc1d0
--- /dev/null
+++ b/phenopacket-tools-io/src/test/java/org/phenopackets/phenopackettools/io/NaiveYamlPrinterTest.java
@@ -0,0 +1,35 @@
+package org.phenopackets.phenopackettools.io;
+
+import org.junit.jupiter.api.Disabled;
+import org.junit.jupiter.api.Test;
+import org.phenopackets.phenopackettools.test.TestData;
+import org.phenopackets.schema.v1.Cohort;
+import org.phenopackets.schema.v1.Family;
+import org.phenopackets.schema.v1.Phenopacket;
+
+import java.nio.file.Path;
+
+@Disabled
+public class NaiveYamlPrinterTest {
+
+ private final NaiveYamlPrinter printer = NaiveYamlPrinter.getInstance();
+
+ @Test
+ public void printPhenopacket() throws Exception {
+ Phenopacket pp = TestData.V1.comprehensivePhenopacket();
+ printer.print(pp, Path.of("phenopacket.v1.yaml"));
+ }
+
+ @Test
+ public void printFamily() throws Exception {
+ Family pp = TestData.V1.comprehensiveFamily();
+ printer.print(pp, Path.of("family.v1.yaml"));
+ }
+
+ @Test
+ public void printCohort() throws Exception {
+ Cohort pp = TestData.V1.comprehensiveCohort();
+ printer.print(pp, Path.of("cohort.v1.yaml"));
+ }
+
+}
\ No newline at end of file
diff --git a/phenopacket-tools-io/src/test/java/org/phenopackets/phenopackettools/io/PhenopacketParserFactoryImplTest.java b/phenopacket-tools-io/src/test/java/org/phenopackets/phenopackettools/io/PhenopacketParserFactoryImplTest.java
new file mode 100644
index 00000000..f7415fa1
--- /dev/null
+++ b/phenopacket-tools-io/src/test/java/org/phenopackets/phenopackettools/io/PhenopacketParserFactoryImplTest.java
@@ -0,0 +1,30 @@
+package org.phenopackets.phenopackettools.io;
+
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.CsvSource;
+import org.phenopackets.phenopackettools.core.PhenopacketSchemaVersion;
+
+import static org.hamcrest.MatcherAssert.*;
+import static org.hamcrest.Matchers.*;
+
+public class PhenopacketParserFactoryImplTest {
+
+ private PhenopacketParserFactoryImpl parserFactory;
+
+ @BeforeEach
+ public void setUp() {
+ parserFactory = PhenopacketParserFactoryImpl.INSTANCE;
+ }
+
+ @ParameterizedTest
+ @CsvSource({
+ "V1",
+ "V2"
+ })
+ public void weHaveAParserForAllSchemaVersions(PhenopacketSchemaVersion version) {
+ PhenopacketParser parser = parserFactory.forFormat(version);
+ assertThat(parser, is(notNullValue()));
+ }
+
+}
\ No newline at end of file
diff --git a/phenopacket-tools-io/src/test/java/org/phenopackets/phenopackettools/io/TestBase.java b/phenopacket-tools-io/src/test/java/org/phenopackets/phenopackettools/io/TestBase.java
new file mode 100644
index 00000000..f123327d
--- /dev/null
+++ b/phenopacket-tools-io/src/test/java/org/phenopackets/phenopackettools/io/TestBase.java
@@ -0,0 +1,9 @@
+package org.phenopackets.phenopackettools.io;
+
+import java.nio.file.Path;
+
+public class TestBase {
+
+ public static final Path BASE_DIR = Path.of("src/test/resources/org/phenopackets/phenopackettools/io");
+
+}
diff --git a/phenopacket-tools-io/src/test/java/org/phenopackets/phenopackettools/io/v1/V1PhenopacketParserTest.java b/phenopacket-tools-io/src/test/java/org/phenopackets/phenopackettools/io/v1/V1PhenopacketParserTest.java
new file mode 100644
index 00000000..86fb9ed6
--- /dev/null
+++ b/phenopacket-tools-io/src/test/java/org/phenopackets/phenopackettools/io/v1/V1PhenopacketParserTest.java
@@ -0,0 +1,58 @@
+package org.phenopackets.phenopackettools.io.v1;
+
+import com.google.protobuf.Message;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.CsvSource;
+import org.phenopackets.phenopackettools.io.PhenopacketParser;
+import org.phenopackets.phenopackettools.io.TestBase;
+import org.phenopackets.phenopackettools.core.PhenopacketElement;
+import org.phenopackets.phenopackettools.core.PhenopacketFormat;
+import org.phenopackets.schema.v1.Cohort;
+import org.phenopackets.schema.v1.Family;
+import org.phenopackets.schema.v1.Phenopacket;
+
+import java.nio.file.Path;
+
+import static org.hamcrest.MatcherAssert.*;
+import static org.hamcrest.Matchers.*;
+
+public class V1PhenopacketParserTest {
+
+ private static final Path BASE = TestBase.BASE_DIR.resolve("v1");
+
+ private PhenopacketParser parser;
+
+ @BeforeEach
+ public void setUp() {
+ parser = V1PhenopacketParser.INSTANCE;
+ }
+
+ @ParameterizedTest
+ @CsvSource({
+ "PROTOBUF, PHENOPACKET, phenopacket.pb",
+ "PROTOBUF, FAMILY, family.pb",
+ "PROTOBUF, COHORT, cohort.pb",
+ " JSON, PHENOPACKET, phenopacket.json",
+ " JSON, FAMILY, family.json",
+ " JSON, COHORT, cohort.json",
+ " YAML, PHENOPACKET, phenopacket.yaml",
+ " YAML, FAMILY, family.yaml",
+ " YAML, COHORT, cohort.yaml",
+ })
+ public void weGetExpectedClassForGivenFormatAndElement(PhenopacketFormat format,
+ PhenopacketElement element,
+ String fileName) throws Exception {
+ Message message = parser.parse(format, element, BASE.resolve(fileName));
+
+ assertThat(message, is(instanceOf(getClassForPhenopacketElement(element))));
+ }
+
+ private static Class> getClassForPhenopacketElement(PhenopacketElement element) {
+ return switch (element) {
+ case PHENOPACKET -> Phenopacket.class;
+ case FAMILY -> Family.class;
+ case COHORT -> Cohort.class;
+ };
+ }
+}
diff --git a/phenopacket-tools-io/src/test/java/org/phenopackets/phenopackettools/io/v2/V2PhenopacketParserTest.java b/phenopacket-tools-io/src/test/java/org/phenopackets/phenopackettools/io/v2/V2PhenopacketParserTest.java
new file mode 100644
index 00000000..41ed246d
--- /dev/null
+++ b/phenopacket-tools-io/src/test/java/org/phenopackets/phenopackettools/io/v2/V2PhenopacketParserTest.java
@@ -0,0 +1,58 @@
+package org.phenopackets.phenopackettools.io.v2;
+
+import com.google.protobuf.Message;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.CsvSource;
+import org.phenopackets.phenopackettools.io.PhenopacketParser;
+import org.phenopackets.phenopackettools.io.TestBase;
+import org.phenopackets.phenopackettools.core.PhenopacketElement;
+import org.phenopackets.phenopackettools.core.PhenopacketFormat;
+import org.phenopackets.schema.v2.Cohort;
+import org.phenopackets.schema.v2.Family;
+import org.phenopackets.schema.v2.Phenopacket;
+
+import java.nio.file.Path;
+
+import static org.hamcrest.MatcherAssert.*;
+import static org.hamcrest.Matchers.*;
+
+public class V2PhenopacketParserTest {
+
+ private static final Path BASE = TestBase.BASE_DIR.resolve("v2");
+
+ private PhenopacketParser parser;
+
+ @BeforeEach
+ public void setUp() {
+ parser = V2PhenopacketParser.INSTANCE;
+ }
+
+ @ParameterizedTest
+ @CsvSource({
+ "PROTOBUF, PHENOPACKET, phenopacket.pb",
+ "PROTOBUF, FAMILY, family.pb",
+ "PROTOBUF, COHORT, cohort.pb",
+ " JSON, PHENOPACKET, phenopacket.json",
+ " JSON, FAMILY, family.json",
+ " JSON, COHORT, cohort.json",
+ " YAML, PHENOPACKET, phenopacket.yaml",
+ " YAML, FAMILY, family.yaml",
+ " YAML, COHORT, cohort.yaml",
+ })
+ public void weGetExpectedClassForGivenFormatAndElement(PhenopacketFormat format,
+ PhenopacketElement element,
+ String fileName) throws Exception {
+ Message message = parser.parse(format, element, BASE.resolve(fileName));
+
+ assertThat(message, is(instanceOf(getClassForPhenopacketElement(element))));
+ }
+
+ private static Class> getClassForPhenopacketElement(PhenopacketElement element) {
+ return switch (element) {
+ case PHENOPACKET -> Phenopacket.class;
+ case FAMILY -> Family.class;
+ case COHORT -> Cohort.class;
+ };
+ }
+}
\ No newline at end of file
diff --git a/phenopacket-tools-io/src/test/resources/org/phenopackets/phenopackettools/io/v1/README.md b/phenopacket-tools-io/src/test/resources/org/phenopackets/phenopackettools/io/v1/README.md
new file mode 100644
index 00000000..85c3c57c
--- /dev/null
+++ b/phenopacket-tools-io/src/test/resources/org/phenopackets/phenopackettools/io/v1/README.md
@@ -0,0 +1,8 @@
+# README
+
+The files in this folder correspond to comprehensive, albeit medically invalid, phenopacket elements:
+- phenopacket
+- family, or
+- cohort.
+
+The content corresponds to the output of `TestData.V1.comprehensive*()` as of Oct 27th, 2022.
diff --git a/phenopacket-tools-io/src/test/resources/org/phenopackets/phenopackettools/io/v1/cohort.json b/phenopacket-tools-io/src/test/resources/org/phenopackets/phenopackettools/io/v1/cohort.json
new file mode 100644
index 00000000..17ab00cb
--- /dev/null
+++ b/phenopacket-tools-io/src/test/resources/org/phenopackets/phenopackettools/io/v1/cohort.json
@@ -0,0 +1,251 @@
+{
+ "id": "comprehensive-cohort-id",
+ "description": "A description of the example cohort.",
+ "members": [{
+ "id": "comprehensive-phenopacket-id",
+ "subject": {
+ "id": "14 year-old boy",
+ "alternateIds": ["boy", "patient", "proband"],
+ "dateOfBirth": "1970-01-02T10:17:36.000000100Z",
+ "ageAtCollection": {
+ "age": "P14Y"
+ },
+ "sex": "MALE",
+ "karyotypicSex": "XY",
+ "taxonomy": {
+ "id": "NCBITaxon:9606",
+ "label": "homo sapiens"
+ }
+ },
+ "phenotypicFeatures": [{
+ "type": {
+ "id": "HP:0001558",
+ "label": "Decreased fetal movement"
+ },
+ "classOfOnset": {
+ "id": "HP:0011461",
+ "label": "Fetal onset"
+ },
+ "evidence": [{
+ "evidenceCode": {
+ "id": "ECO:0000033",
+ "label": "author statement supported by traceable reference"
+ },
+ "reference": {
+ "id": "PMID:30808312",
+ "description": "COL6A1 mutation leading to Bethlem myopathy with recurrent hematuria: a case report."
+ }
+ }]
+ }, {
+ "type": {
+ "id": "HP:0031910",
+ "label": "Abnormal cranial nerve physiology"
+ },
+ "negated": true,
+ "evidence": [{
+ "evidenceCode": {
+ "id": "ECO:0000033",
+ "label": "author statement supported by traceable reference"
+ },
+ "reference": {
+ "id": "PMID:30808312",
+ "description": "COL6A1 mutation leading to Bethlem myopathy with recurrent hematuria: a case report."
+ }
+ }]
+ }, {
+ "type": {
+ "id": "HP:0011463",
+ "label": "Macroscopic hematuria"
+ },
+ "modifiers": [{
+ "id": "HP:0031796",
+ "label": "Recurrent"
+ }],
+ "ageOfOnset": {
+ "age": "P14Y"
+ },
+ "evidence": [{
+ "evidenceCode": {
+ "id": "ECO:0000033",
+ "label": "author statement supported by traceable reference"
+ },
+ "reference": {
+ "id": "PMID:30808312",
+ "description": "COL6A1 mutation leading to Bethlem myopathy with recurrent hematuria: a case report."
+ }
+ }]
+ }, {
+ "type": {
+ "id": "HP:0001270",
+ "label": "Motor delay"
+ },
+ "severity": {
+ "id": "HP:0012825",
+ "label": "Mild"
+ },
+ "classOfOnset": {
+ "id": "HP:0011463",
+ "label": "Childhood onset"
+ }
+ }],
+ "biosamples": [{
+ "id": "biosample-id",
+ "individualId": "14 year-old boy",
+ "description": "Muscle biopsy of 14 year-old boy",
+ "sampledTissue": {
+ "id": "UBERON:0003403",
+ "label": "skin of forearm"
+ },
+ "taxonomy": {
+ "id": "NCBITaxon:9606",
+ "label": "homo sapiens"
+ },
+ "ageOfIndividualAtCollection": {
+ "age": "P14Y"
+ },
+ "histologicalDiagnosis": {
+ "id": "NCIT:C38757",
+ "label": "Negative Finding"
+ },
+ "tumorProgression": {
+ "id": "NCIT:C3677",
+ "label": "Benign Neoplasm"
+ },
+ "tumorGrade": {
+ "id": "NCIT:C28076",
+ "label": "Disease Grade Qualifier"
+ },
+ "diagnosticMarkers": [{
+ "id": "NCIT:C68748",
+ "label": "HER2/Neu Positive"
+ }]
+ }],
+ "genes": [{
+ "id": "HGNC1:3688",
+ "symbol": "FGFR1"
+ }],
+ "variants": [{
+ "hgvsAllele": {
+ "hgvs": "NM_001848.2:c.877G\u003eA"
+ },
+ "zygosity": {
+ "id": "GENO:0000135",
+ "label": "heterozygous"
+ }
+ }],
+ "diseases": [{
+ "term": {
+ "id": "OMIM:101600",
+ "label": "PFEIFFER SYNDROME"
+ },
+ "classOfOnset": {
+ "id": "HP:0003577",
+ "label": "Congenital onset"
+ }
+ }],
+ "htsFiles": [{
+ "uri": "file://data/genomes/P000001C",
+ "description": "Whole genome sequencing VCF output",
+ "htsFormat": "VCF",
+ "genomeAssembly": "GRCh38.p13",
+ "individualToSampleIdentifiers": {
+ "14 year-old boy": "P000001C"
+ }
+ }],
+ "metaData": {
+ "created": "2022-10-03T16:39:04.000123456Z",
+ "createdBy": "Peter R.",
+ "submittedBy": "PhenopacketLab",
+ "resources": [{
+ "id": "hp",
+ "name": "human phenotype ontology",
+ "url": "http://purl.obolibrary.org/obo/hp.owl",
+ "version": "2018-03-08",
+ "namespacePrefix": "HP",
+ "iriPrefix": "http://purl.obolibrary.org/obo/HP_"
+ }, {
+ "id": "geno",
+ "name": "Genotype Ontology",
+ "url": "http://purl.obolibrary.org/obo/geno.owl",
+ "version": "19-03-2018",
+ "namespacePrefix": "GENO",
+ "iriPrefix": "http://purl.obolibrary.org/obo/GENO_"
+ }, {
+ "id": "pubmed",
+ "name": "PubMed",
+ "namespacePrefix": "PMID",
+ "iriPrefix": "https://www.ncbi.nlm.nih.gov/pubmed/"
+ }, {
+ "id": "ncit",
+ "name": "NCI Thesaurus",
+ "url": "http://purl.obolibrary.org/obo/ncit.owl",
+ "version": "20-03-2020",
+ "namespacePrefix": "NCIT",
+ "iriPrefix": "http://purl.obolibrary.org/obo/NCIT_"
+ }],
+ "phenopacketSchemaVersion": "1.0.0",
+ "externalReferences": [{
+ "id": "PMID:30808312",
+ "description": "COL6A1 mutation leading to Bethlem myopathy with recurrent hematuria: a case report."
+ }]
+ }
+ }, {
+ "subject": {
+ "id": "MOTHER",
+ "sex": "FEMALE"
+ }
+ }, {
+ "subject": {
+ "id": "FATHER",
+ "sex": "MALE"
+ }
+ }],
+ "htsFiles": [{
+ "uri": "file://data/genomes/FAM000001",
+ "description": "Whole genome sequencing VCF output",
+ "htsFormat": "VCF",
+ "genomeAssembly": "GRCh38.p13",
+ "individualToSampleIdentifiers": {
+ "14 year-old boy": "P000001C",
+ "MOTHER": "P000001M",
+ "FATHER": "P000001F"
+ }
+ }],
+ "metaData": {
+ "created": "2022-10-03T16:39:04.000123456Z",
+ "createdBy": "Peter R.",
+ "submittedBy": "PhenopacketLab",
+ "resources": [{
+ "id": "hp",
+ "name": "human phenotype ontology",
+ "url": "http://purl.obolibrary.org/obo/hp.owl",
+ "version": "2018-03-08",
+ "namespacePrefix": "HP",
+ "iriPrefix": "http://purl.obolibrary.org/obo/HP_"
+ }, {
+ "id": "geno",
+ "name": "Genotype Ontology",
+ "url": "http://purl.obolibrary.org/obo/geno.owl",
+ "version": "19-03-2018",
+ "namespacePrefix": "GENO",
+ "iriPrefix": "http://purl.obolibrary.org/obo/GENO_"
+ }, {
+ "id": "pubmed",
+ "name": "PubMed",
+ "namespacePrefix": "PMID",
+ "iriPrefix": "https://www.ncbi.nlm.nih.gov/pubmed/"
+ }, {
+ "id": "ncit",
+ "name": "NCI Thesaurus",
+ "url": "http://purl.obolibrary.org/obo/ncit.owl",
+ "version": "20-03-2020",
+ "namespacePrefix": "NCIT",
+ "iriPrefix": "http://purl.obolibrary.org/obo/NCIT_"
+ }],
+ "phenopacketSchemaVersion": "1.0.0",
+ "externalReferences": [{
+ "id": "PMID:30808312",
+ "description": "COL6A1 mutation leading to Bethlem myopathy with recurrent hematuria: a case report."
+ }]
+ }
+}
\ No newline at end of file
diff --git a/phenopacket-tools-io/src/test/resources/org/phenopackets/phenopackettools/io/v1/cohort.pb b/phenopacket-tools-io/src/test/resources/org/phenopackets/phenopackettools/io/v1/cohort.pb
new file mode 100644
index 00000000..5c57f5ee
--- /dev/null
+++ b/phenopacket-tools-io/src/test/resources/org/phenopackets/phenopackettools/io/v1/cohort.pb
@@ -0,0 +1,80 @@
+
+comprehensive-cohort-id$A description of the example cohort.
+comprehensive-phenopacket-id\
+14 year-old boyboypatientprobandd"
+P14Y08B
+NCBITaxon:9606homo sapiens&
+
+HP:0001558Decreased fetal movementJ
+
+HP:0011461Fetal onsetR
+@
+ECO:00000331author statement supported by traceable referencee
+
PMID:30808312TCOL6A1 mutation leading to Bethlem myopathy with recurrent hematuria: a case report./
+
+HP:0031910!Abnormal cranial nerve physiologyR
+@
+ECO:00000331author statement supported by traceable referencee
+
PMID:30808312TCOL6A1 mutation leading to Bethlem myopathy with recurrent hematuria: a case report.#
+
+HP:0011463Macroscopic hematuria*
+
+HP:0031796 Recurrent2
+P14YR
+@
+ECO:00000331author statement supported by traceable referencee
+
PMID:30808312TCOL6A1 mutation leading to Bethlem myopathy with recurrent hematuria: a case report.N
+
+HP:0001270Motor delay"
+
+HP:0012825MildJ
+
+HP:0011463Childhood onset"
+biosample-id14 year-old boy Muscle biopsy of 14 year-old boy"!
+UBERON:0003403skin of forearm2
+NCBITaxon:9606homo sapiens:
+P14YJ
+NCIT:C38757Negative FindingR
+
+NCIT:C3677Benign NeoplasmZ&
+NCIT:C28076Disease Grade Qualifierb
+NCIT:C68748HER2/Neu Positive*
+
+HGNC1:3688FGFR126NM_001848.2:c.877G>A2
+GENO:0000135heterozygous:B
+
+OMIM:101600PFEIFFER SYNDROME"
+
+HP:0003577Congenital onsetBm
+file://data/genomes/P000001C"Whole genome sequencing VCF output"
+GRCh38.p13*
+14 year-old boyP000001CJ
+
+Peter R.PhenopacketLab"y
+hphuman phenotype ontology%http://purl.obolibrary.org/obo/hp.owl"
+2018-03-08*HP2"http://purl.obolibrary.org/obo/HP_"z
+genoGenotype Ontology'http://purl.obolibrary.org/obo/geno.owl"
+19-03-2018*GENO2$http://purl.obolibrary.org/obo/GENO_"<
+pubmedPubMed*PMID2$https://www.ncbi.nlm.nih.gov/pubmed/"v
+ncit
NCI Thesaurus'http://purl.obolibrary.org/obo/ncit.owl"
+20-03-2020*NCIT2$http://purl.obolibrary.org/obo/NCIT_21.0.0:e
+
PMID:30808312TCOL6A1 mutation leading to Bethlem myopathy with recurrent hematuria: a case report.
+
+MOTHER0
+
+FATHER0"
+file://data/genomes/FAM000001"Whole genome sequencing VCF output"
+GRCh38.p13*
+14 year-old boyP000001C*
+MOTHERP000001M*
+FATHERP000001F*
+
+Peter R.PhenopacketLab"y
+hphuman phenotype ontology%http://purl.obolibrary.org/obo/hp.owl"
+2018-03-08*HP2"http://purl.obolibrary.org/obo/HP_"z
+genoGenotype Ontology'http://purl.obolibrary.org/obo/geno.owl"
+19-03-2018*GENO2$http://purl.obolibrary.org/obo/GENO_"<
+pubmedPubMed*PMID2$https://www.ncbi.nlm.nih.gov/pubmed/"v
+ncit
NCI Thesaurus'http://purl.obolibrary.org/obo/ncit.owl"
+20-03-2020*NCIT2$http://purl.obolibrary.org/obo/NCIT_21.0.0:e
+
PMID:30808312TCOL6A1 mutation leading to Bethlem myopathy with recurrent hematuria: a case report.
\ No newline at end of file
diff --git a/phenopacket-tools-io/src/test/resources/org/phenopackets/phenopackettools/io/v1/cohort.yaml b/phenopacket-tools-io/src/test/resources/org/phenopackets/phenopackettools/io/v1/cohort.yaml
new file mode 100644
index 00000000..5f9ac9c2
--- /dev/null
+++ b/phenopacket-tools-io/src/test/resources/org/phenopackets/phenopackettools/io/v1/cohort.yaml
@@ -0,0 +1,196 @@
+id: "comprehensive-cohort-id"
+description: "A description of the example cohort."
+members:
+- id: "comprehensive-phenopacket-id"
+ subject:
+ id: "14 year-old boy"
+ alternateIds:
+ - "boy"
+ - "patient"
+ - "proband"
+ dateOfBirth: "1970-01-02T10:17:36.000000100Z"
+ ageAtCollection:
+ age: "P14Y"
+ sex: "MALE"
+ karyotypicSex: "XY"
+ taxonomy:
+ id: "NCBITaxon:9606"
+ label: "homo sapiens"
+ phenotypicFeatures:
+ - type:
+ id: "HP:0001558"
+ label: "Decreased fetal movement"
+ classOfOnset:
+ id: "HP:0011461"
+ label: "Fetal onset"
+ evidence:
+ - evidenceCode:
+ id: "ECO:0000033"
+ label: "author statement supported by traceable reference"
+ reference:
+ id: "PMID:30808312"
+ description: "COL6A1 mutation leading to Bethlem myopathy with recurrent hematuria:\
+ \ a case report."
+ - type:
+ id: "HP:0031910"
+ label: "Abnormal cranial nerve physiology"
+ negated: true
+ evidence:
+ - evidenceCode:
+ id: "ECO:0000033"
+ label: "author statement supported by traceable reference"
+ reference:
+ id: "PMID:30808312"
+ description: "COL6A1 mutation leading to Bethlem myopathy with recurrent hematuria:\
+ \ a case report."
+ - type:
+ id: "HP:0011463"
+ label: "Macroscopic hematuria"
+ modifiers:
+ - id: "HP:0031796"
+ label: "Recurrent"
+ ageOfOnset:
+ age: "P14Y"
+ evidence:
+ - evidenceCode:
+ id: "ECO:0000033"
+ label: "author statement supported by traceable reference"
+ reference:
+ id: "PMID:30808312"
+ description: "COL6A1 mutation leading to Bethlem myopathy with recurrent hematuria:\
+ \ a case report."
+ - type:
+ id: "HP:0001270"
+ label: "Motor delay"
+ severity:
+ id: "HP:0012825"
+ label: "Mild"
+ classOfOnset:
+ id: "HP:0011463"
+ label: "Childhood onset"
+ biosamples:
+ - id: "biosample-id"
+ individualId: "14 year-old boy"
+ description: "Muscle biopsy of 14 year-old boy"
+ sampledTissue:
+ id: "UBERON:0003403"
+ label: "skin of forearm"
+ taxonomy:
+ id: "NCBITaxon:9606"
+ label: "homo sapiens"
+ ageOfIndividualAtCollection:
+ age: "P14Y"
+ histologicalDiagnosis:
+ id: "NCIT:C38757"
+ label: "Negative Finding"
+ tumorProgression:
+ id: "NCIT:C3677"
+ label: "Benign Neoplasm"
+ tumorGrade:
+ id: "NCIT:C28076"
+ label: "Disease Grade Qualifier"
+ diagnosticMarkers:
+ - id: "NCIT:C68748"
+ label: "HER2/Neu Positive"
+ genes:
+ - id: "HGNC1:3688"
+ symbol: "FGFR1"
+ variants:
+ - hgvsAllele:
+ hgvs: "NM_001848.2:c.877G>A"
+ zygosity:
+ id: "GENO:0000135"
+ label: "heterozygous"
+ diseases:
+ - term:
+ id: "OMIM:101600"
+ label: "PFEIFFER SYNDROME"
+ classOfOnset:
+ id: "HP:0003577"
+ label: "Congenital onset"
+ htsFiles:
+ - uri: "file://data/genomes/P000001C"
+ description: "Whole genome sequencing VCF output"
+ htsFormat: "VCF"
+ genomeAssembly: "GRCh38.p13"
+ individualToSampleIdentifiers:
+ "14 year-old boy": "P000001C"
+ metaData:
+ created: "2022-10-03T16:39:04.000123456Z"
+ createdBy: "Peter R."
+ submittedBy: "PhenopacketLab"
+ resources:
+ - id: "hp"
+ name: "human phenotype ontology"
+ url: "http://purl.obolibrary.org/obo/hp.owl"
+ version: "2018-03-08"
+ namespacePrefix: "HP"
+ iriPrefix: "http://purl.obolibrary.org/obo/HP_"
+ - id: "geno"
+ name: "Genotype Ontology"
+ url: "http://purl.obolibrary.org/obo/geno.owl"
+ version: "19-03-2018"
+ namespacePrefix: "GENO"
+ iriPrefix: "http://purl.obolibrary.org/obo/GENO_"
+ - id: "pubmed"
+ name: "PubMed"
+ namespacePrefix: "PMID"
+ iriPrefix: "https://www.ncbi.nlm.nih.gov/pubmed/"
+ - id: "ncit"
+ name: "NCI Thesaurus"
+ url: "http://purl.obolibrary.org/obo/ncit.owl"
+ version: "20-03-2020"
+ namespacePrefix: "NCIT"
+ iriPrefix: "http://purl.obolibrary.org/obo/NCIT_"
+ phenopacketSchemaVersion: "1.0.0"
+ externalReferences:
+ - id: "PMID:30808312"
+ description: "COL6A1 mutation leading to Bethlem myopathy with recurrent hematuria:\
+ \ a case report."
+- subject:
+ id: "MOTHER"
+ sex: "FEMALE"
+- subject:
+ id: "FATHER"
+ sex: "MALE"
+htsFiles:
+- uri: "file://data/genomes/FAM000001"
+ description: "Whole genome sequencing VCF output"
+ htsFormat: "VCF"
+ genomeAssembly: "GRCh38.p13"
+ individualToSampleIdentifiers:
+ "14 year-old boy": "P000001C"
+ MOTHER: "P000001M"
+ FATHER: "P000001F"
+metaData:
+ created: "2022-10-03T16:39:04.000123456Z"
+ createdBy: "Peter R."
+ submittedBy: "PhenopacketLab"
+ resources:
+ - id: "hp"
+ name: "human phenotype ontology"
+ url: "http://purl.obolibrary.org/obo/hp.owl"
+ version: "2018-03-08"
+ namespacePrefix: "HP"
+ iriPrefix: "http://purl.obolibrary.org/obo/HP_"
+ - id: "geno"
+ name: "Genotype Ontology"
+ url: "http://purl.obolibrary.org/obo/geno.owl"
+ version: "19-03-2018"
+ namespacePrefix: "GENO"
+ iriPrefix: "http://purl.obolibrary.org/obo/GENO_"
+ - id: "pubmed"
+ name: "PubMed"
+ namespacePrefix: "PMID"
+ iriPrefix: "https://www.ncbi.nlm.nih.gov/pubmed/"
+ - id: "ncit"
+ name: "NCI Thesaurus"
+ url: "http://purl.obolibrary.org/obo/ncit.owl"
+ version: "20-03-2020"
+ namespacePrefix: "NCIT"
+ iriPrefix: "http://purl.obolibrary.org/obo/NCIT_"
+ phenopacketSchemaVersion: "1.0.0"
+ externalReferences:
+ - id: "PMID:30808312"
+ description: "COL6A1 mutation leading to Bethlem myopathy with recurrent hematuria:\
+ \ a case report."
diff --git a/phenopacket-tools-io/src/test/resources/org/phenopackets/phenopackettools/io/v1/family.json b/phenopacket-tools-io/src/test/resources/org/phenopackets/phenopackettools/io/v1/family.json
new file mode 100644
index 00000000..b7ad2d73
--- /dev/null
+++ b/phenopacket-tools-io/src/test/resources/org/phenopackets/phenopackettools/io/v1/family.json
@@ -0,0 +1,268 @@
+{
+ "id": "comprehensive-family-id",
+ "proband": {
+ "id": "comprehensive-phenopacket-id",
+ "subject": {
+ "id": "14 year-old boy",
+ "alternateIds": ["boy", "patient", "proband"],
+ "dateOfBirth": "1970-01-02T10:17:36.000000100Z",
+ "ageAtCollection": {
+ "age": "P14Y"
+ },
+ "sex": "MALE",
+ "karyotypicSex": "XY",
+ "taxonomy": {
+ "id": "NCBITaxon:9606",
+ "label": "homo sapiens"
+ }
+ },
+ "phenotypicFeatures": [{
+ "type": {
+ "id": "HP:0001558",
+ "label": "Decreased fetal movement"
+ },
+ "classOfOnset": {
+ "id": "HP:0011461",
+ "label": "Fetal onset"
+ },
+ "evidence": [{
+ "evidenceCode": {
+ "id": "ECO:0000033",
+ "label": "author statement supported by traceable reference"
+ },
+ "reference": {
+ "id": "PMID:30808312",
+ "description": "COL6A1 mutation leading to Bethlem myopathy with recurrent hematuria: a case report."
+ }
+ }]
+ }, {
+ "type": {
+ "id": "HP:0031910",
+ "label": "Abnormal cranial nerve physiology"
+ },
+ "negated": true,
+ "evidence": [{
+ "evidenceCode": {
+ "id": "ECO:0000033",
+ "label": "author statement supported by traceable reference"
+ },
+ "reference": {
+ "id": "PMID:30808312",
+ "description": "COL6A1 mutation leading to Bethlem myopathy with recurrent hematuria: a case report."
+ }
+ }]
+ }, {
+ "type": {
+ "id": "HP:0011463",
+ "label": "Macroscopic hematuria"
+ },
+ "modifiers": [{
+ "id": "HP:0031796",
+ "label": "Recurrent"
+ }],
+ "ageOfOnset": {
+ "age": "P14Y"
+ },
+ "evidence": [{
+ "evidenceCode": {
+ "id": "ECO:0000033",
+ "label": "author statement supported by traceable reference"
+ },
+ "reference": {
+ "id": "PMID:30808312",
+ "description": "COL6A1 mutation leading to Bethlem myopathy with recurrent hematuria: a case report."
+ }
+ }]
+ }, {
+ "type": {
+ "id": "HP:0001270",
+ "label": "Motor delay"
+ },
+ "severity": {
+ "id": "HP:0012825",
+ "label": "Mild"
+ },
+ "classOfOnset": {
+ "id": "HP:0011463",
+ "label": "Childhood onset"
+ }
+ }],
+ "biosamples": [{
+ "id": "biosample-id",
+ "individualId": "14 year-old boy",
+ "description": "Muscle biopsy of 14 year-old boy",
+ "sampledTissue": {
+ "id": "UBERON:0003403",
+ "label": "skin of forearm"
+ },
+ "taxonomy": {
+ "id": "NCBITaxon:9606",
+ "label": "homo sapiens"
+ },
+ "ageOfIndividualAtCollection": {
+ "age": "P14Y"
+ },
+ "histologicalDiagnosis": {
+ "id": "NCIT:C38757",
+ "label": "Negative Finding"
+ },
+ "tumorProgression": {
+ "id": "NCIT:C3677",
+ "label": "Benign Neoplasm"
+ },
+ "tumorGrade": {
+ "id": "NCIT:C28076",
+ "label": "Disease Grade Qualifier"
+ },
+ "diagnosticMarkers": [{
+ "id": "NCIT:C68748",
+ "label": "HER2/Neu Positive"
+ }]
+ }],
+ "genes": [{
+ "id": "HGNC1:3688",
+ "symbol": "FGFR1"
+ }],
+ "variants": [{
+ "hgvsAllele": {
+ "hgvs": "NM_001848.2:c.877G\u003eA"
+ },
+ "zygosity": {
+ "id": "GENO:0000135",
+ "label": "heterozygous"
+ }
+ }],
+ "diseases": [{
+ "term": {
+ "id": "OMIM:101600",
+ "label": "PFEIFFER SYNDROME"
+ },
+ "classOfOnset": {
+ "id": "HP:0003577",
+ "label": "Congenital onset"
+ }
+ }],
+ "htsFiles": [{
+ "uri": "file://data/genomes/P000001C",
+ "description": "Whole genome sequencing VCF output",
+ "htsFormat": "VCF",
+ "genomeAssembly": "GRCh38.p13",
+ "individualToSampleIdentifiers": {
+ "14 year-old boy": "P000001C"
+ }
+ }],
+ "metaData": {
+ "created": "2022-10-03T16:39:04.000123456Z",
+ "createdBy": "Peter R.",
+ "submittedBy": "PhenopacketLab",
+ "resources": [{
+ "id": "hp",
+ "name": "human phenotype ontology",
+ "url": "http://purl.obolibrary.org/obo/hp.owl",
+ "version": "2018-03-08",
+ "namespacePrefix": "HP",
+ "iriPrefix": "http://purl.obolibrary.org/obo/HP_"
+ }, {
+ "id": "geno",
+ "name": "Genotype Ontology",
+ "url": "http://purl.obolibrary.org/obo/geno.owl",
+ "version": "19-03-2018",
+ "namespacePrefix": "GENO",
+ "iriPrefix": "http://purl.obolibrary.org/obo/GENO_"
+ }, {
+ "id": "pubmed",
+ "name": "PubMed",
+ "namespacePrefix": "PMID",
+ "iriPrefix": "https://www.ncbi.nlm.nih.gov/pubmed/"
+ }, {
+ "id": "ncit",
+ "name": "NCI Thesaurus",
+ "url": "http://purl.obolibrary.org/obo/ncit.owl",
+ "version": "20-03-2020",
+ "namespacePrefix": "NCIT",
+ "iriPrefix": "http://purl.obolibrary.org/obo/NCIT_"
+ }],
+ "phenopacketSchemaVersion": "1.0.0",
+ "externalReferences": [{
+ "id": "PMID:30808312",
+ "description": "COL6A1 mutation leading to Bethlem myopathy with recurrent hematuria: a case report."
+ }]
+ }
+ },
+ "relatives": [{
+ "subject": {
+ "id": "MOTHER",
+ "sex": "FEMALE"
+ }
+ }, {
+ "subject": {
+ "id": "FATHER",
+ "sex": "MALE"
+ }
+ }],
+ "pedigree": {
+ "persons": [{
+ "individualId": "14 year-old boy",
+ "paternalId": "FATHER",
+ "maternalId": "MOTHER",
+ "sex": "MALE",
+ "affectedStatus": "AFFECTED"
+ }, {
+ "individualId": "MOTHER",
+ "sex": "FEMALE",
+ "affectedStatus": "UNAFFECTED"
+ }, {
+ "individualId": "FATHER",
+ "sex": "MALE",
+ "affectedStatus": "UNAFFECTED"
+ }]
+ },
+ "htsFiles": [{
+ "uri": "file://data/genomes/FAM000001",
+ "description": "Whole genome sequencing VCF output",
+ "htsFormat": "VCF",
+ "genomeAssembly": "GRCh38.p13",
+ "individualToSampleIdentifiers": {
+ "14 year-old boy": "P000001C",
+ "MOTHER": "P000001M",
+ "FATHER": "P000001F"
+ }
+ }],
+ "metaData": {
+ "created": "2022-10-03T16:39:04.000123456Z",
+ "createdBy": "Peter R.",
+ "submittedBy": "PhenopacketLab",
+ "resources": [{
+ "id": "hp",
+ "name": "human phenotype ontology",
+ "url": "http://purl.obolibrary.org/obo/hp.owl",
+ "version": "2018-03-08",
+ "namespacePrefix": "HP",
+ "iriPrefix": "http://purl.obolibrary.org/obo/HP_"
+ }, {
+ "id": "geno",
+ "name": "Genotype Ontology",
+ "url": "http://purl.obolibrary.org/obo/geno.owl",
+ "version": "19-03-2018",
+ "namespacePrefix": "GENO",
+ "iriPrefix": "http://purl.obolibrary.org/obo/GENO_"
+ }, {
+ "id": "pubmed",
+ "name": "PubMed",
+ "namespacePrefix": "PMID",
+ "iriPrefix": "https://www.ncbi.nlm.nih.gov/pubmed/"
+ }, {
+ "id": "ncit",
+ "name": "NCI Thesaurus",
+ "url": "http://purl.obolibrary.org/obo/ncit.owl",
+ "version": "20-03-2020",
+ "namespacePrefix": "NCIT",
+ "iriPrefix": "http://purl.obolibrary.org/obo/NCIT_"
+ }],
+ "phenopacketSchemaVersion": "1.0.0",
+ "externalReferences": [{
+ "id": "PMID:30808312",
+ "description": "COL6A1 mutation leading to Bethlem myopathy with recurrent hematuria: a case report."
+ }]
+ }
+}
\ No newline at end of file
diff --git a/phenopacket-tools-io/src/test/resources/org/phenopackets/phenopackettools/io/v1/family.pb b/phenopacket-tools-io/src/test/resources/org/phenopackets/phenopackettools/io/v1/family.pb
new file mode 100644
index 00000000..fb131057
--- /dev/null
+++ b/phenopacket-tools-io/src/test/resources/org/phenopackets/phenopackettools/io/v1/family.pb
@@ -0,0 +1,83 @@
+
+comprehensive-family-id
+comprehensive-phenopacket-id\
+14 year-old boyboypatientprobandd"
+P14Y08B
+NCBITaxon:9606homo sapiens&
+
+HP:0001558Decreased fetal movementJ
+
+HP:0011461Fetal onsetR
+@
+ECO:00000331author statement supported by traceable referencee
+
PMID:30808312TCOL6A1 mutation leading to Bethlem myopathy with recurrent hematuria: a case report./
+
+HP:0031910!Abnormal cranial nerve physiologyR
+@
+ECO:00000331author statement supported by traceable referencee
+
PMID:30808312TCOL6A1 mutation leading to Bethlem myopathy with recurrent hematuria: a case report.#
+
+HP:0011463Macroscopic hematuria*
+
+HP:0031796 Recurrent2
+P14YR
+@
+ECO:00000331author statement supported by traceable referencee
+
PMID:30808312TCOL6A1 mutation leading to Bethlem myopathy with recurrent hematuria: a case report.N
+
+HP:0001270Motor delay"
+
+HP:0012825MildJ
+
+HP:0011463Childhood onset"
+biosample-id14 year-old boy Muscle biopsy of 14 year-old boy"!
+UBERON:0003403skin of forearm2
+NCBITaxon:9606homo sapiens:
+P14YJ
+NCIT:C38757Negative FindingR
+
+NCIT:C3677Benign NeoplasmZ&
+NCIT:C28076Disease Grade Qualifierb
+NCIT:C68748HER2/Neu Positive*
+
+HGNC1:3688FGFR126NM_001848.2:c.877G>A2
+GENO:0000135heterozygous:B
+
+OMIM:101600PFEIFFER SYNDROME"
+
+HP:0003577Congenital onsetBm
+file://data/genomes/P000001C"Whole genome sequencing VCF output"
+GRCh38.p13*
+14 year-old boyP000001CJ
+
+Peter R.PhenopacketLab"y
+hphuman phenotype ontology%http://purl.obolibrary.org/obo/hp.owl"
+2018-03-08*HP2"http://purl.obolibrary.org/obo/HP_"z
+genoGenotype Ontology'http://purl.obolibrary.org/obo/geno.owl"
+19-03-2018*GENO2$http://purl.obolibrary.org/obo/GENO_"<
+pubmedPubMed*PMID2$https://www.ncbi.nlm.nih.gov/pubmed/"v
+ncit
NCI Thesaurus'http://purl.obolibrary.org/obo/ncit.owl"
+20-03-2020*NCIT2$http://purl.obolibrary.org/obo/NCIT_21.0.0:e
+
PMID:30808312TCOL6A1 mutation leading to Bethlem myopathy with recurrent hematuria: a case report.
+
+MOTHER0
+
+FATHER0"C
+%14 year-old boyFATHER"MOTHER(0
+MOTHER(0
+FATHER(0*
+file://data/genomes/FAM000001"Whole genome sequencing VCF output"
+GRCh38.p13*
+14 year-old boyP000001C*
+MOTHERP000001M*
+FATHERP000001F2
+
+Peter R.PhenopacketLab"y
+hphuman phenotype ontology%http://purl.obolibrary.org/obo/hp.owl"
+2018-03-08*HP2"http://purl.obolibrary.org/obo/HP_"z
+genoGenotype Ontology'http://purl.obolibrary.org/obo/geno.owl"
+19-03-2018*GENO2$http://purl.obolibrary.org/obo/GENO_"<
+pubmedPubMed*PMID2$https://www.ncbi.nlm.nih.gov/pubmed/"v
+ncit
NCI Thesaurus'http://purl.obolibrary.org/obo/ncit.owl"
+20-03-2020*NCIT2$http://purl.obolibrary.org/obo/NCIT_21.0.0:e
+
PMID:30808312TCOL6A1 mutation leading to Bethlem myopathy with recurrent hematuria: a case report.
\ No newline at end of file
diff --git a/phenopacket-tools-io/src/test/resources/org/phenopackets/phenopackettools/io/v1/family.yaml b/phenopacket-tools-io/src/test/resources/org/phenopackets/phenopackettools/io/v1/family.yaml
new file mode 100644
index 00000000..02f6a92c
--- /dev/null
+++ b/phenopacket-tools-io/src/test/resources/org/phenopackets/phenopackettools/io/v1/family.yaml
@@ -0,0 +1,209 @@
+id: "comprehensive-family-id"
+proband:
+ id: "comprehensive-phenopacket-id"
+ subject:
+ id: "14 year-old boy"
+ alternateIds:
+ - "boy"
+ - "patient"
+ - "proband"
+ dateOfBirth: "1970-01-02T10:17:36.000000100Z"
+ ageAtCollection:
+ age: "P14Y"
+ sex: "MALE"
+ karyotypicSex: "XY"
+ taxonomy:
+ id: "NCBITaxon:9606"
+ label: "homo sapiens"
+ phenotypicFeatures:
+ - type:
+ id: "HP:0001558"
+ label: "Decreased fetal movement"
+ classOfOnset:
+ id: "HP:0011461"
+ label: "Fetal onset"
+ evidence:
+ - evidenceCode:
+ id: "ECO:0000033"
+ label: "author statement supported by traceable reference"
+ reference:
+ id: "PMID:30808312"
+ description: "COL6A1 mutation leading to Bethlem myopathy with recurrent hematuria:\
+ \ a case report."
+ - type:
+ id: "HP:0031910"
+ label: "Abnormal cranial nerve physiology"
+ negated: true
+ evidence:
+ - evidenceCode:
+ id: "ECO:0000033"
+ label: "author statement supported by traceable reference"
+ reference:
+ id: "PMID:30808312"
+ description: "COL6A1 mutation leading to Bethlem myopathy with recurrent hematuria:\
+ \ a case report."
+ - type:
+ id: "HP:0011463"
+ label: "Macroscopic hematuria"
+ modifiers:
+ - id: "HP:0031796"
+ label: "Recurrent"
+ ageOfOnset:
+ age: "P14Y"
+ evidence:
+ - evidenceCode:
+ id: "ECO:0000033"
+ label: "author statement supported by traceable reference"
+ reference:
+ id: "PMID:30808312"
+ description: "COL6A1 mutation leading to Bethlem myopathy with recurrent hematuria:\
+ \ a case report."
+ - type:
+ id: "HP:0001270"
+ label: "Motor delay"
+ severity:
+ id: "HP:0012825"
+ label: "Mild"
+ classOfOnset:
+ id: "HP:0011463"
+ label: "Childhood onset"
+ biosamples:
+ - id: "biosample-id"
+ individualId: "14 year-old boy"
+ description: "Muscle biopsy of 14 year-old boy"
+ sampledTissue:
+ id: "UBERON:0003403"
+ label: "skin of forearm"
+ taxonomy:
+ id: "NCBITaxon:9606"
+ label: "homo sapiens"
+ ageOfIndividualAtCollection:
+ age: "P14Y"
+ histologicalDiagnosis:
+ id: "NCIT:C38757"
+ label: "Negative Finding"
+ tumorProgression:
+ id: "NCIT:C3677"
+ label: "Benign Neoplasm"
+ tumorGrade:
+ id: "NCIT:C28076"
+ label: "Disease Grade Qualifier"
+ diagnosticMarkers:
+ - id: "NCIT:C68748"
+ label: "HER2/Neu Positive"
+ genes:
+ - id: "HGNC1:3688"
+ symbol: "FGFR1"
+ variants:
+ - hgvsAllele:
+ hgvs: "NM_001848.2:c.877G>A"
+ zygosity:
+ id: "GENO:0000135"
+ label: "heterozygous"
+ diseases:
+ - term:
+ id: "OMIM:101600"
+ label: "PFEIFFER SYNDROME"
+ classOfOnset:
+ id: "HP:0003577"
+ label: "Congenital onset"
+ htsFiles:
+ - uri: "file://data/genomes/P000001C"
+ description: "Whole genome sequencing VCF output"
+ htsFormat: "VCF"
+ genomeAssembly: "GRCh38.p13"
+ individualToSampleIdentifiers:
+ "14 year-old boy": "P000001C"
+ metaData:
+ created: "2022-10-03T16:39:04.000123456Z"
+ createdBy: "Peter R."
+ submittedBy: "PhenopacketLab"
+ resources:
+ - id: "hp"
+ name: "human phenotype ontology"
+ url: "http://purl.obolibrary.org/obo/hp.owl"
+ version: "2018-03-08"
+ namespacePrefix: "HP"
+ iriPrefix: "http://purl.obolibrary.org/obo/HP_"
+ - id: "geno"
+ name: "Genotype Ontology"
+ url: "http://purl.obolibrary.org/obo/geno.owl"
+ version: "19-03-2018"
+ namespacePrefix: "GENO"
+ iriPrefix: "http://purl.obolibrary.org/obo/GENO_"
+ - id: "pubmed"
+ name: "PubMed"
+ namespacePrefix: "PMID"
+ iriPrefix: "https://www.ncbi.nlm.nih.gov/pubmed/"
+ - id: "ncit"
+ name: "NCI Thesaurus"
+ url: "http://purl.obolibrary.org/obo/ncit.owl"
+ version: "20-03-2020"
+ namespacePrefix: "NCIT"
+ iriPrefix: "http://purl.obolibrary.org/obo/NCIT_"
+ phenopacketSchemaVersion: "1.0.0"
+ externalReferences:
+ - id: "PMID:30808312"
+ description: "COL6A1 mutation leading to Bethlem myopathy with recurrent hematuria:\
+ \ a case report."
+relatives:
+- subject:
+ id: "MOTHER"
+ sex: "FEMALE"
+- subject:
+ id: "FATHER"
+ sex: "MALE"
+pedigree:
+ persons:
+ - individualId: "14 year-old boy"
+ paternalId: "FATHER"
+ maternalId: "MOTHER"
+ sex: "MALE"
+ affectedStatus: "AFFECTED"
+ - individualId: "MOTHER"
+ sex: "FEMALE"
+ affectedStatus: "UNAFFECTED"
+ - individualId: "FATHER"
+ sex: "MALE"
+ affectedStatus: "UNAFFECTED"
+htsFiles:
+- uri: "file://data/genomes/FAM000001"
+ description: "Whole genome sequencing VCF output"
+ htsFormat: "VCF"
+ genomeAssembly: "GRCh38.p13"
+ individualToSampleIdentifiers:
+ "14 year-old boy": "P000001C"
+ MOTHER: "P000001M"
+ FATHER: "P000001F"
+metaData:
+ created: "2022-10-03T16:39:04.000123456Z"
+ createdBy: "Peter R."
+ submittedBy: "PhenopacketLab"
+ resources:
+ - id: "hp"
+ name: "human phenotype ontology"
+ url: "http://purl.obolibrary.org/obo/hp.owl"
+ version: "2018-03-08"
+ namespacePrefix: "HP"
+ iriPrefix: "http://purl.obolibrary.org/obo/HP_"
+ - id: "geno"
+ name: "Genotype Ontology"
+ url: "http://purl.obolibrary.org/obo/geno.owl"
+ version: "19-03-2018"
+ namespacePrefix: "GENO"
+ iriPrefix: "http://purl.obolibrary.org/obo/GENO_"
+ - id: "pubmed"
+ name: "PubMed"
+ namespacePrefix: "PMID"
+ iriPrefix: "https://www.ncbi.nlm.nih.gov/pubmed/"
+ - id: "ncit"
+ name: "NCI Thesaurus"
+ url: "http://purl.obolibrary.org/obo/ncit.owl"
+ version: "20-03-2020"
+ namespacePrefix: "NCIT"
+ iriPrefix: "http://purl.obolibrary.org/obo/NCIT_"
+ phenopacketSchemaVersion: "1.0.0"
+ externalReferences:
+ - id: "PMID:30808312"
+ description: "COL6A1 mutation leading to Bethlem myopathy with recurrent hematuria:\
+ \ a case report."
diff --git a/phenopacket-tools-io/src/test/resources/org/phenopackets/phenopackettools/io/v1/phenopacket.json b/phenopacket-tools-io/src/test/resources/org/phenopackets/phenopackettools/io/v1/phenopacket.json
new file mode 100644
index 00000000..e6848a9c
--- /dev/null
+++ b/phenopacket-tools-io/src/test/resources/org/phenopackets/phenopackettools/io/v1/phenopacket.json
@@ -0,0 +1,189 @@
+{
+ "id": "comprehensive-phenopacket-id",
+ "subject": {
+ "id": "14 year-old boy",
+ "alternateIds": ["boy", "patient", "proband"],
+ "dateOfBirth": "1970-01-02T10:17:36.000000100Z",
+ "ageAtCollection": {
+ "age": "P14Y"
+ },
+ "sex": "MALE",
+ "karyotypicSex": "XY",
+ "taxonomy": {
+ "id": "NCBITaxon:9606",
+ "label": "homo sapiens"
+ }
+ },
+ "phenotypicFeatures": [{
+ "type": {
+ "id": "HP:0001558",
+ "label": "Decreased fetal movement"
+ },
+ "classOfOnset": {
+ "id": "HP:0011461",
+ "label": "Fetal onset"
+ },
+ "evidence": [{
+ "evidenceCode": {
+ "id": "ECO:0000033",
+ "label": "author statement supported by traceable reference"
+ },
+ "reference": {
+ "id": "PMID:30808312",
+ "description": "COL6A1 mutation leading to Bethlem myopathy with recurrent hematuria: a case report."
+ }
+ }]
+ }, {
+ "type": {
+ "id": "HP:0031910",
+ "label": "Abnormal cranial nerve physiology"
+ },
+ "negated": true,
+ "evidence": [{
+ "evidenceCode": {
+ "id": "ECO:0000033",
+ "label": "author statement supported by traceable reference"
+ },
+ "reference": {
+ "id": "PMID:30808312",
+ "description": "COL6A1 mutation leading to Bethlem myopathy with recurrent hematuria: a case report."
+ }
+ }]
+ }, {
+ "type": {
+ "id": "HP:0011463",
+ "label": "Macroscopic hematuria"
+ },
+ "modifiers": [{
+ "id": "HP:0031796",
+ "label": "Recurrent"
+ }],
+ "ageOfOnset": {
+ "age": "P14Y"
+ },
+ "evidence": [{
+ "evidenceCode": {
+ "id": "ECO:0000033",
+ "label": "author statement supported by traceable reference"
+ },
+ "reference": {
+ "id": "PMID:30808312",
+ "description": "COL6A1 mutation leading to Bethlem myopathy with recurrent hematuria: a case report."
+ }
+ }]
+ }, {
+ "type": {
+ "id": "HP:0001270",
+ "label": "Motor delay"
+ },
+ "severity": {
+ "id": "HP:0012825",
+ "label": "Mild"
+ },
+ "classOfOnset": {
+ "id": "HP:0011463",
+ "label": "Childhood onset"
+ }
+ }],
+ "biosamples": [{
+ "id": "biosample-id",
+ "individualId": "14 year-old boy",
+ "description": "Muscle biopsy of 14 year-old boy",
+ "sampledTissue": {
+ "id": "UBERON:0003403",
+ "label": "skin of forearm"
+ },
+ "taxonomy": {
+ "id": "NCBITaxon:9606",
+ "label": "homo sapiens"
+ },
+ "ageOfIndividualAtCollection": {
+ "age": "P14Y"
+ },
+ "histologicalDiagnosis": {
+ "id": "NCIT:C38757",
+ "label": "Negative Finding"
+ },
+ "tumorProgression": {
+ "id": "NCIT:C3677",
+ "label": "Benign Neoplasm"
+ },
+ "tumorGrade": {
+ "id": "NCIT:C28076",
+ "label": "Disease Grade Qualifier"
+ },
+ "diagnosticMarkers": [{
+ "id": "NCIT:C68748",
+ "label": "HER2/Neu Positive"
+ }]
+ }],
+ "genes": [{
+ "id": "HGNC1:3688",
+ "symbol": "FGFR1"
+ }],
+ "variants": [{
+ "hgvsAllele": {
+ "hgvs": "NM_001848.2:c.877G\u003eA"
+ },
+ "zygosity": {
+ "id": "GENO:0000135",
+ "label": "heterozygous"
+ }
+ }],
+ "diseases": [{
+ "term": {
+ "id": "OMIM:101600",
+ "label": "PFEIFFER SYNDROME"
+ },
+ "classOfOnset": {
+ "id": "HP:0003577",
+ "label": "Congenital onset"
+ }
+ }],
+ "htsFiles": [{
+ "uri": "file://data/genomes/P000001C",
+ "description": "Whole genome sequencing VCF output",
+ "htsFormat": "VCF",
+ "genomeAssembly": "GRCh38.p13",
+ "individualToSampleIdentifiers": {
+ "14 year-old boy": "P000001C"
+ }
+ }],
+ "metaData": {
+ "created": "2022-10-03T16:39:04.000123456Z",
+ "createdBy": "Peter R.",
+ "submittedBy": "PhenopacketLab",
+ "resources": [{
+ "id": "hp",
+ "name": "human phenotype ontology",
+ "url": "http://purl.obolibrary.org/obo/hp.owl",
+ "version": "2018-03-08",
+ "namespacePrefix": "HP",
+ "iriPrefix": "http://purl.obolibrary.org/obo/HP_"
+ }, {
+ "id": "geno",
+ "name": "Genotype Ontology",
+ "url": "http://purl.obolibrary.org/obo/geno.owl",
+ "version": "19-03-2018",
+ "namespacePrefix": "GENO",
+ "iriPrefix": "http://purl.obolibrary.org/obo/GENO_"
+ }, {
+ "id": "pubmed",
+ "name": "PubMed",
+ "namespacePrefix": "PMID",
+ "iriPrefix": "https://www.ncbi.nlm.nih.gov/pubmed/"
+ }, {
+ "id": "ncit",
+ "name": "NCI Thesaurus",
+ "url": "http://purl.obolibrary.org/obo/ncit.owl",
+ "version": "20-03-2020",
+ "namespacePrefix": "NCIT",
+ "iriPrefix": "http://purl.obolibrary.org/obo/NCIT_"
+ }],
+ "phenopacketSchemaVersion": "1.0.0",
+ "externalReferences": [{
+ "id": "PMID:30808312",
+ "description": "COL6A1 mutation leading to Bethlem myopathy with recurrent hematuria: a case report."
+ }]
+ }
+}
\ No newline at end of file
diff --git a/phenopacket-tools-io/src/test/resources/org/phenopackets/phenopackettools/io/v1/phenopacket.pb b/phenopacket-tools-io/src/test/resources/org/phenopackets/phenopackettools/io/v1/phenopacket.pb
new file mode 100644
index 00000000..8fe66cc2
--- /dev/null
+++ b/phenopacket-tools-io/src/test/resources/org/phenopackets/phenopackettools/io/v1/phenopacket.pb
@@ -0,0 +1,60 @@
+
+comprehensive-phenopacket-id\
+14 year-old boyboypatientprobandd"
+P14Y08B
+NCBITaxon:9606homo sapiens&
+
+HP:0001558Decreased fetal movementJ
+
+HP:0011461Fetal onsetR
+@
+ECO:00000331author statement supported by traceable referencee
+
PMID:30808312TCOL6A1 mutation leading to Bethlem myopathy with recurrent hematuria: a case report./
+
+HP:0031910!Abnormal cranial nerve physiologyR
+@
+ECO:00000331author statement supported by traceable referencee
+
PMID:30808312TCOL6A1 mutation leading to Bethlem myopathy with recurrent hematuria: a case report.#
+
+HP:0011463Macroscopic hematuria*
+
+HP:0031796 Recurrent2
+P14YR
+@
+ECO:00000331author statement supported by traceable referencee
+
PMID:30808312TCOL6A1 mutation leading to Bethlem myopathy with recurrent hematuria: a case report.N
+
+HP:0001270Motor delay"
+
+HP:0012825MildJ
+
+HP:0011463Childhood onset"
+biosample-id14 year-old boy Muscle biopsy of 14 year-old boy"!
+UBERON:0003403skin of forearm2
+NCBITaxon:9606homo sapiens:
+P14YJ
+NCIT:C38757Negative FindingR
+
+NCIT:C3677Benign NeoplasmZ&
+NCIT:C28076Disease Grade Qualifierb
+NCIT:C68748HER2/Neu Positive*
+
+HGNC1:3688FGFR126NM_001848.2:c.877G>A2
+GENO:0000135heterozygous:B
+
+OMIM:101600PFEIFFER SYNDROME"
+
+HP:0003577Congenital onsetBm
+file://data/genomes/P000001C"Whole genome sequencing VCF output"
+GRCh38.p13*
+14 year-old boyP000001CJ
+
+Peter R.PhenopacketLab"y
+hphuman phenotype ontology%http://purl.obolibrary.org/obo/hp.owl"
+2018-03-08*HP2"http://purl.obolibrary.org/obo/HP_"z
+genoGenotype Ontology'http://purl.obolibrary.org/obo/geno.owl"
+19-03-2018*GENO2$http://purl.obolibrary.org/obo/GENO_"<
+pubmedPubMed*PMID2$https://www.ncbi.nlm.nih.gov/pubmed/"v
+ncit
NCI Thesaurus'http://purl.obolibrary.org/obo/ncit.owl"
+20-03-2020*NCIT2$http://purl.obolibrary.org/obo/NCIT_21.0.0:e
+
PMID:30808312TCOL6A1 mutation leading to Bethlem myopathy with recurrent hematuria: a case report.
\ No newline at end of file
diff --git a/phenopacket-tools-io/src/test/resources/org/phenopackets/phenopackettools/io/v1/phenopacket.yaml b/phenopacket-tools-io/src/test/resources/org/phenopackets/phenopackettools/io/v1/phenopacket.yaml
new file mode 100644
index 00000000..bafcc88b
--- /dev/null
+++ b/phenopacket-tools-io/src/test/resources/org/phenopackets/phenopackettools/io/v1/phenopacket.yaml
@@ -0,0 +1,146 @@
+id: "comprehensive-phenopacket-id"
+subject:
+ id: "14 year-old boy"
+ alternateIds:
+ - "boy"
+ - "patient"
+ - "proband"
+ dateOfBirth: "1970-01-02T10:17:36.000000100Z"
+ ageAtCollection:
+ age: "P14Y"
+ sex: "MALE"
+ karyotypicSex: "XY"
+ taxonomy:
+ id: "NCBITaxon:9606"
+ label: "homo sapiens"
+phenotypicFeatures:
+- type:
+ id: "HP:0001558"
+ label: "Decreased fetal movement"
+ classOfOnset:
+ id: "HP:0011461"
+ label: "Fetal onset"
+ evidence:
+ - evidenceCode:
+ id: "ECO:0000033"
+ label: "author statement supported by traceable reference"
+ reference:
+ id: "PMID:30808312"
+ description: "COL6A1 mutation leading to Bethlem myopathy with recurrent hematuria:\
+ \ a case report."
+- type:
+ id: "HP:0031910"
+ label: "Abnormal cranial nerve physiology"
+ negated: true
+ evidence:
+ - evidenceCode:
+ id: "ECO:0000033"
+ label: "author statement supported by traceable reference"
+ reference:
+ id: "PMID:30808312"
+ description: "COL6A1 mutation leading to Bethlem myopathy with recurrent hematuria:\
+ \ a case report."
+- type:
+ id: "HP:0011463"
+ label: "Macroscopic hematuria"
+ modifiers:
+ - id: "HP:0031796"
+ label: "Recurrent"
+ ageOfOnset:
+ age: "P14Y"
+ evidence:
+ - evidenceCode:
+ id: "ECO:0000033"
+ label: "author statement supported by traceable reference"
+ reference:
+ id: "PMID:30808312"
+ description: "COL6A1 mutation leading to Bethlem myopathy with recurrent hematuria:\
+ \ a case report."
+- type:
+ id: "HP:0001270"
+ label: "Motor delay"
+ severity:
+ id: "HP:0012825"
+ label: "Mild"
+ classOfOnset:
+ id: "HP:0011463"
+ label: "Childhood onset"
+biosamples:
+- id: "biosample-id"
+ individualId: "14 year-old boy"
+ description: "Muscle biopsy of 14 year-old boy"
+ sampledTissue:
+ id: "UBERON:0003403"
+ label: "skin of forearm"
+ taxonomy:
+ id: "NCBITaxon:9606"
+ label: "homo sapiens"
+ ageOfIndividualAtCollection:
+ age: "P14Y"
+ histologicalDiagnosis:
+ id: "NCIT:C38757"
+ label: "Negative Finding"
+ tumorProgression:
+ id: "NCIT:C3677"
+ label: "Benign Neoplasm"
+ tumorGrade:
+ id: "NCIT:C28076"
+ label: "Disease Grade Qualifier"
+ diagnosticMarkers:
+ - id: "NCIT:C68748"
+ label: "HER2/Neu Positive"
+genes:
+- id: "HGNC1:3688"
+ symbol: "FGFR1"
+variants:
+- hgvsAllele:
+ hgvs: "NM_001848.2:c.877G>A"
+ zygosity:
+ id: "GENO:0000135"
+ label: "heterozygous"
+diseases:
+- term:
+ id: "OMIM:101600"
+ label: "PFEIFFER SYNDROME"
+ classOfOnset:
+ id: "HP:0003577"
+ label: "Congenital onset"
+htsFiles:
+- uri: "file://data/genomes/P000001C"
+ description: "Whole genome sequencing VCF output"
+ htsFormat: "VCF"
+ genomeAssembly: "GRCh38.p13"
+ individualToSampleIdentifiers:
+ "14 year-old boy": "P000001C"
+metaData:
+ created: "2022-10-03T16:39:04.000123456Z"
+ createdBy: "Peter R."
+ submittedBy: "PhenopacketLab"
+ resources:
+ - id: "hp"
+ name: "human phenotype ontology"
+ url: "http://purl.obolibrary.org/obo/hp.owl"
+ version: "2018-03-08"
+ namespacePrefix: "HP"
+ iriPrefix: "http://purl.obolibrary.org/obo/HP_"
+ - id: "geno"
+ name: "Genotype Ontology"
+ url: "http://purl.obolibrary.org/obo/geno.owl"
+ version: "19-03-2018"
+ namespacePrefix: "GENO"
+ iriPrefix: "http://purl.obolibrary.org/obo/GENO_"
+ - id: "pubmed"
+ name: "PubMed"
+ namespacePrefix: "PMID"
+ iriPrefix: "https://www.ncbi.nlm.nih.gov/pubmed/"
+ - id: "ncit"
+ name: "NCI Thesaurus"
+ url: "http://purl.obolibrary.org/obo/ncit.owl"
+ version: "20-03-2020"
+ namespacePrefix: "NCIT"
+ iriPrefix: "http://purl.obolibrary.org/obo/NCIT_"
+ phenopacketSchemaVersion: "1.0.0"
+ externalReferences:
+ - id: "PMID:30808312"
+ description: "COL6A1 mutation leading to Bethlem myopathy with recurrent hematuria:\
+ \ a case report."
diff --git a/phenopacket-tools-io/src/test/resources/org/phenopackets/phenopackettools/io/v2/cohort.json b/phenopacket-tools-io/src/test/resources/org/phenopackets/phenopackettools/io/v2/cohort.json
new file mode 100644
index 00000000..e59170ac
--- /dev/null
+++ b/phenopacket-tools-io/src/test/resources/org/phenopackets/phenopackettools/io/v2/cohort.json
@@ -0,0 +1,294 @@
+{
+ "id": "comprehensive-cohort-id",
+ "description": "A description of the example cohort.",
+ "members": [{
+ "id": "comprehensive-phenopacket-id",
+ "subject": {
+ "id": "14 year-old boy",
+ "alternateIds": ["boy", "patient", "proband"],
+ "dateOfBirth": "1970-01-02T10:17:36.000000100Z",
+ "timeAtLastEncounter": {
+ "age": {
+ "iso8601duration": "P14Y"
+ }
+ },
+ "sex": "MALE",
+ "karyotypicSex": "XY",
+ "taxonomy": {
+ "id": "NCBITaxon:9606",
+ "label": "homo sapiens"
+ }
+ },
+ "phenotypicFeatures": [{
+ "type": {
+ "id": "HP:0001558",
+ "label": "Decreased fetal movement"
+ },
+ "onset": {
+ "ontologyClass": {
+ "id": "HP:0011461",
+ "label": "Fetal onset"
+ }
+ },
+ "evidence": [{
+ "evidenceCode": {
+ "id": "ECO:0000033",
+ "label": "author statement supported by traceable reference"
+ },
+ "reference": {
+ "id": "PMID:30808312",
+ "description": "COL6A1 mutation leading to Bethlem myopathy with recurrent hematuria: a case report."
+ }
+ }]
+ }, {
+ "type": {
+ "id": "HP:0031910",
+ "label": "Abnormal cranial nerve physiology"
+ },
+ "excluded": true,
+ "evidence": [{
+ "evidenceCode": {
+ "id": "ECO:0000033",
+ "label": "author statement supported by traceable reference"
+ },
+ "reference": {
+ "id": "PMID:30808312",
+ "description": "COL6A1 mutation leading to Bethlem myopathy with recurrent hematuria: a case report."
+ }
+ }]
+ }, {
+ "type": {
+ "id": "HP:0011463",
+ "label": "Macroscopic hematuria"
+ },
+ "modifiers": [{
+ "id": "HP:0031796",
+ "label": "Recurrent"
+ }],
+ "onset": {
+ "age": {
+ "iso8601duration": "P14Y"
+ }
+ },
+ "evidence": [{
+ "evidenceCode": {
+ "id": "ECO:0000033",
+ "label": "author statement supported by traceable reference"
+ },
+ "reference": {
+ "id": "PMID:30808312",
+ "description": "COL6A1 mutation leading to Bethlem myopathy with recurrent hematuria: a case report."
+ }
+ }]
+ }, {
+ "type": {
+ "id": "HP:0001270",
+ "label": "Motor delay"
+ },
+ "severity": {
+ "id": "HP:0012825",
+ "label": "Mild"
+ },
+ "onset": {
+ "ontologyClass": {
+ "id": "HP:0011463",
+ "label": "Childhood onset"
+ }
+ }
+ }],
+ "biosamples": [{
+ "id": "biosample-id",
+ "individualId": "14 year-old boy",
+ "description": "Muscle biopsy of 14 year-old boy",
+ "sampledTissue": {
+ "id": "UBERON:0003403",
+ "label": "skin of forearm"
+ },
+ "taxonomy": {
+ "id": "NCBITaxon:9606",
+ "label": "homo sapiens"
+ },
+ "timeOfCollection": {
+ "age": {
+ "iso8601duration": "P14Y"
+ }
+ },
+ "histologicalDiagnosis": {
+ "id": "NCIT:C38757",
+ "label": "Negative Finding"
+ },
+ "tumorProgression": {
+ "id": "NCIT:C3677",
+ "label": "Benign Neoplasm"
+ },
+ "tumorGrade": {
+ "id": "NCIT:C28076",
+ "label": "Disease Grade Qualifier"
+ },
+ "diagnosticMarkers": [{
+ "id": "NCIT:C68748",
+ "label": "HER2/Neu Positive"
+ }],
+ "materialSample": {
+ "id": "EFO:0009655",
+ "label": "abnormal sample"
+ }
+ }],
+ "interpretations": [{
+ "id": "comprehensive-phenopacket-id",
+ "progressStatus": "SOLVED",
+ "diagnosis": {
+ "disease": {
+ "id": "OMIM:101600",
+ "label": "PFEIFFER SYNDROME"
+ },
+ "genomicInterpretations": [{
+ "subjectOrBiosampleId": "14 year-old boy",
+ "interpretationStatus": "CAUSATIVE",
+ "variantInterpretation": {
+ "variationDescriptor": {
+ "expressions": [{
+ "syntax": "hgvs",
+ "value": "NM_001848.2:c.877G\u003eA"
+ }],
+ "allelicState": {
+ "id": "GENO:0000135",
+ "label": "heterozygous"
+ }
+ }
+ }
+ }]
+ }
+ }],
+ "diseases": [{
+ "term": {
+ "id": "OMIM:101600",
+ "label": "PFEIFFER SYNDROME"
+ },
+ "onset": {
+ "ontologyClass": {
+ "id": "HP:0003577",
+ "label": "Congenital onset"
+ }
+ }
+ }],
+ "files": [{
+ "uri": "file://data/genomes/P000001C",
+ "individualToFileIdentifiers": {
+ "14 year-old boy": "P000001C"
+ },
+ "fileAttributes": {
+ "genomeAssembly": "GRCh38.p13",
+ "fileFormat": "vcf",
+ "description": "Whole genome sequencing VCF output"
+ }
+ }],
+ "metaData": {
+ "created": "2022-10-03T16:39:04.000123456Z",
+ "createdBy": "Peter R.",
+ "submittedBy": "PhenopacketLab",
+ "resources": [{
+ "id": "hp",
+ "name": "human phenotype ontology",
+ "url": "http://purl.obolibrary.org/obo/hp.owl",
+ "version": "2018-03-08",
+ "namespacePrefix": "HP",
+ "iriPrefix": "http://purl.obolibrary.org/obo/HP_"
+ }, {
+ "id": "geno",
+ "name": "Genotype Ontology",
+ "url": "http://purl.obolibrary.org/obo/geno.owl",
+ "version": "19-03-2018",
+ "namespacePrefix": "GENO",
+ "iriPrefix": "http://purl.obolibrary.org/obo/GENO_"
+ }, {
+ "id": "pubmed",
+ "name": "PubMed",
+ "namespacePrefix": "PMID",
+ "iriPrefix": "https://www.ncbi.nlm.nih.gov/pubmed/"
+ }, {
+ "id": "ncit",
+ "name": "NCI Thesaurus",
+ "url": "http://purl.obolibrary.org/obo/ncit.owl",
+ "version": "20-03-2020",
+ "namespacePrefix": "NCIT",
+ "iriPrefix": "http://purl.obolibrary.org/obo/NCIT_"
+ }],
+ "phenopacketSchemaVersion": "2.0.0",
+ "externalReferences": [{
+ "id": "PMID:30808312",
+ "description": "COL6A1 mutation leading to Bethlem myopathy with recurrent hematuria: a case report."
+ }]
+ }
+ }, {
+ "subject": {
+ "id": "MOTHER",
+ "dateOfBirth": "1970-01-01T00:00:00Z",
+ "timeAtLastEncounter": {
+ },
+ "sex": "FEMALE",
+ "taxonomy": {
+ }
+ }
+ }, {
+ "subject": {
+ "id": "FATHER",
+ "dateOfBirth": "1970-01-01T00:00:00Z",
+ "timeAtLastEncounter": {
+ },
+ "sex": "MALE",
+ "taxonomy": {
+ }
+ }
+ }],
+ "files": [{
+ "uri": "file://data/genomes/FAM000001",
+ "individualToFileIdentifiers": {
+ "14 year-old boy": "P000001C",
+ "MOTHER": "P000001M",
+ "FATHER": "P000001F"
+ },
+ "fileAttributes": {
+ "genomeAssembly": "GRCh38.p13",
+ "fileFormat": "vcf",
+ "description": "Whole genome sequencing VCF output"
+ }
+ }],
+ "metaData": {
+ "created": "2022-10-03T16:39:04.000123456Z",
+ "createdBy": "Peter R.",
+ "submittedBy": "PhenopacketLab",
+ "resources": [{
+ "id": "hp",
+ "name": "human phenotype ontology",
+ "url": "http://purl.obolibrary.org/obo/hp.owl",
+ "version": "2018-03-08",
+ "namespacePrefix": "HP",
+ "iriPrefix": "http://purl.obolibrary.org/obo/HP_"
+ }, {
+ "id": "geno",
+ "name": "Genotype Ontology",
+ "url": "http://purl.obolibrary.org/obo/geno.owl",
+ "version": "19-03-2018",
+ "namespacePrefix": "GENO",
+ "iriPrefix": "http://purl.obolibrary.org/obo/GENO_"
+ }, {
+ "id": "pubmed",
+ "name": "PubMed",
+ "namespacePrefix": "PMID",
+ "iriPrefix": "https://www.ncbi.nlm.nih.gov/pubmed/"
+ }, {
+ "id": "ncit",
+ "name": "NCI Thesaurus",
+ "url": "http://purl.obolibrary.org/obo/ncit.owl",
+ "version": "20-03-2020",
+ "namespacePrefix": "NCIT",
+ "iriPrefix": "http://purl.obolibrary.org/obo/NCIT_"
+ }],
+ "phenopacketSchemaVersion": "2.0.0",
+ "externalReferences": [{
+ "id": "PMID:30808312",
+ "description": "COL6A1 mutation leading to Bethlem myopathy with recurrent hematuria: a case report."
+ }]
+ }
+}
\ No newline at end of file
diff --git a/phenopacket-tools-io/src/test/resources/org/phenopackets/phenopackettools/io/v2/cohort.pb b/phenopacket-tools-io/src/test/resources/org/phenopackets/phenopackettools/io/v2/cohort.pb
new file mode 100644
index 00000000..0679cfa0
Binary files /dev/null and b/phenopacket-tools-io/src/test/resources/org/phenopackets/phenopackettools/io/v2/cohort.pb differ
diff --git a/phenopacket-tools-io/src/test/resources/org/phenopackets/phenopackettools/io/v2/cohort.yaml b/phenopacket-tools-io/src/test/resources/org/phenopackets/phenopackettools/io/v2/cohort.yaml
new file mode 100644
index 00000000..55e7969d
--- /dev/null
+++ b/phenopacket-tools-io/src/test/resources/org/phenopackets/phenopackettools/io/v2/cohort.yaml
@@ -0,0 +1,222 @@
+id: "comprehensive-cohort-id"
+description: "A description of the example cohort."
+members:
+- id: "comprehensive-phenopacket-id"
+ subject:
+ id: "14 year-old boy"
+ alternateIds:
+ - "boy"
+ - "patient"
+ - "proband"
+ dateOfBirth: "1970-01-02T10:17:36.000000100Z"
+ timeAtLastEncounter:
+ age:
+ iso8601duration: "P14Y"
+ sex: "MALE"
+ karyotypicSex: "XY"
+ taxonomy:
+ id: "NCBITaxon:9606"
+ label: "homo sapiens"
+ phenotypicFeatures:
+ - type:
+ id: "HP:0001558"
+ label: "Decreased fetal movement"
+ onset:
+ ontologyClass:
+ id: "HP:0011461"
+ label: "Fetal onset"
+ evidence:
+ - evidenceCode:
+ id: "ECO:0000033"
+ label: "author statement supported by traceable reference"
+ reference:
+ id: "PMID:30808312"
+ description: "COL6A1 mutation leading to Bethlem myopathy with recurrent hematuria:\
+ \ a case report."
+ - type:
+ id: "HP:0031910"
+ label: "Abnormal cranial nerve physiology"
+ excluded: true
+ evidence:
+ - evidenceCode:
+ id: "ECO:0000033"
+ label: "author statement supported by traceable reference"
+ reference:
+ id: "PMID:30808312"
+ description: "COL6A1 mutation leading to Bethlem myopathy with recurrent hematuria:\
+ \ a case report."
+ - type:
+ id: "HP:0011463"
+ label: "Macroscopic hematuria"
+ modifiers:
+ - id: "HP:0031796"
+ label: "Recurrent"
+ onset:
+ age:
+ iso8601duration: "P14Y"
+ evidence:
+ - evidenceCode:
+ id: "ECO:0000033"
+ label: "author statement supported by traceable reference"
+ reference:
+ id: "PMID:30808312"
+ description: "COL6A1 mutation leading to Bethlem myopathy with recurrent hematuria:\
+ \ a case report."
+ - type:
+ id: "HP:0001270"
+ label: "Motor delay"
+ severity:
+ id: "HP:0012825"
+ label: "Mild"
+ onset:
+ ontologyClass:
+ id: "HP:0011463"
+ label: "Childhood onset"
+ biosamples:
+ - id: "biosample-id"
+ individualId: "14 year-old boy"
+ description: "Muscle biopsy of 14 year-old boy"
+ sampledTissue:
+ id: "UBERON:0003403"
+ label: "skin of forearm"
+ taxonomy:
+ id: "NCBITaxon:9606"
+ label: "homo sapiens"
+ timeOfCollection:
+ age:
+ iso8601duration: "P14Y"
+ histologicalDiagnosis:
+ id: "NCIT:C38757"
+ label: "Negative Finding"
+ tumorProgression:
+ id: "NCIT:C3677"
+ label: "Benign Neoplasm"
+ tumorGrade:
+ id: "NCIT:C28076"
+ label: "Disease Grade Qualifier"
+ diagnosticMarkers:
+ - id: "NCIT:C68748"
+ label: "HER2/Neu Positive"
+ materialSample:
+ id: "EFO:0009655"
+ label: "abnormal sample"
+ interpretations:
+ - id: "comprehensive-phenopacket-id"
+ progressStatus: "SOLVED"
+ diagnosis:
+ disease:
+ id: "OMIM:101600"
+ label: "PFEIFFER SYNDROME"
+ genomicInterpretations:
+ - subjectOrBiosampleId: "14 year-old boy"
+ interpretationStatus: "CAUSATIVE"
+ variantInterpretation:
+ variationDescriptor:
+ expressions:
+ - syntax: "hgvs"
+ value: "NM_001848.2:c.877G>A"
+ allelicState:
+ id: "GENO:0000135"
+ label: "heterozygous"
+ diseases:
+ - term:
+ id: "OMIM:101600"
+ label: "PFEIFFER SYNDROME"
+ onset:
+ ontologyClass:
+ id: "HP:0003577"
+ label: "Congenital onset"
+ files:
+ - uri: "file://data/genomes/P000001C"
+ individualToFileIdentifiers:
+ "14 year-old boy": "P000001C"
+ fileAttributes:
+ genomeAssembly: "GRCh38.p13"
+ fileFormat: "vcf"
+ description: "Whole genome sequencing VCF output"
+ metaData:
+ created: "2022-10-03T16:39:04.000123456Z"
+ createdBy: "Peter R."
+ submittedBy: "PhenopacketLab"
+ resources:
+ - id: "hp"
+ name: "human phenotype ontology"
+ url: "http://purl.obolibrary.org/obo/hp.owl"
+ version: "2018-03-08"
+ namespacePrefix: "HP"
+ iriPrefix: "http://purl.obolibrary.org/obo/HP_"
+ - id: "geno"
+ name: "Genotype Ontology"
+ url: "http://purl.obolibrary.org/obo/geno.owl"
+ version: "19-03-2018"
+ namespacePrefix: "GENO"
+ iriPrefix: "http://purl.obolibrary.org/obo/GENO_"
+ - id: "pubmed"
+ name: "PubMed"
+ namespacePrefix: "PMID"
+ iriPrefix: "https://www.ncbi.nlm.nih.gov/pubmed/"
+ - id: "ncit"
+ name: "NCI Thesaurus"
+ url: "http://purl.obolibrary.org/obo/ncit.owl"
+ version: "20-03-2020"
+ namespacePrefix: "NCIT"
+ iriPrefix: "http://purl.obolibrary.org/obo/NCIT_"
+ phenopacketSchemaVersion: "2.0.0"
+ externalReferences:
+ - id: "PMID:30808312"
+ description: "COL6A1 mutation leading to Bethlem myopathy with recurrent hematuria:\
+ \ a case report."
+- subject:
+ id: "MOTHER"
+ dateOfBirth: "1970-01-01T00:00:00Z"
+ timeAtLastEncounter: {}
+ sex: "FEMALE"
+ taxonomy: {}
+- subject:
+ id: "FATHER"
+ dateOfBirth: "1970-01-01T00:00:00Z"
+ timeAtLastEncounter: {}
+ sex: "MALE"
+ taxonomy: {}
+files:
+- uri: "file://data/genomes/FAM000001"
+ individualToFileIdentifiers:
+ "14 year-old boy": "P000001C"
+ MOTHER: "P000001M"
+ FATHER: "P000001F"
+ fileAttributes:
+ genomeAssembly: "GRCh38.p13"
+ fileFormat: "vcf"
+ description: "Whole genome sequencing VCF output"
+metaData:
+ created: "2022-10-03T16:39:04.000123456Z"
+ createdBy: "Peter R."
+ submittedBy: "PhenopacketLab"
+ resources:
+ - id: "hp"
+ name: "human phenotype ontology"
+ url: "http://purl.obolibrary.org/obo/hp.owl"
+ version: "2018-03-08"
+ namespacePrefix: "HP"
+ iriPrefix: "http://purl.obolibrary.org/obo/HP_"
+ - id: "geno"
+ name: "Genotype Ontology"
+ url: "http://purl.obolibrary.org/obo/geno.owl"
+ version: "19-03-2018"
+ namespacePrefix: "GENO"
+ iriPrefix: "http://purl.obolibrary.org/obo/GENO_"
+ - id: "pubmed"
+ name: "PubMed"
+ namespacePrefix: "PMID"
+ iriPrefix: "https://www.ncbi.nlm.nih.gov/pubmed/"
+ - id: "ncit"
+ name: "NCI Thesaurus"
+ url: "http://purl.obolibrary.org/obo/ncit.owl"
+ version: "20-03-2020"
+ namespacePrefix: "NCIT"
+ iriPrefix: "http://purl.obolibrary.org/obo/NCIT_"
+ phenopacketSchemaVersion: "2.0.0"
+ externalReferences:
+ - id: "PMID:30808312"
+ description: "COL6A1 mutation leading to Bethlem myopathy with recurrent hematuria:\
+ \ a case report."
diff --git a/phenopacket-tools-io/src/test/resources/org/phenopackets/phenopackettools/io/v2/family.json b/phenopacket-tools-io/src/test/resources/org/phenopackets/phenopackettools/io/v2/family.json
new file mode 100644
index 00000000..b29f296b
--- /dev/null
+++ b/phenopacket-tools-io/src/test/resources/org/phenopackets/phenopackettools/io/v2/family.json
@@ -0,0 +1,311 @@
+{
+ "id": "comprehensive-family-id",
+ "proband": {
+ "id": "comprehensive-phenopacket-id",
+ "subject": {
+ "id": "14 year-old boy",
+ "alternateIds": ["boy", "patient", "proband"],
+ "dateOfBirth": "1970-01-02T10:17:36.000000100Z",
+ "timeAtLastEncounter": {
+ "age": {
+ "iso8601duration": "P14Y"
+ }
+ },
+ "sex": "MALE",
+ "karyotypicSex": "XY",
+ "taxonomy": {
+ "id": "NCBITaxon:9606",
+ "label": "homo sapiens"
+ }
+ },
+ "phenotypicFeatures": [{
+ "type": {
+ "id": "HP:0001558",
+ "label": "Decreased fetal movement"
+ },
+ "onset": {
+ "ontologyClass": {
+ "id": "HP:0011461",
+ "label": "Fetal onset"
+ }
+ },
+ "evidence": [{
+ "evidenceCode": {
+ "id": "ECO:0000033",
+ "label": "author statement supported by traceable reference"
+ },
+ "reference": {
+ "id": "PMID:30808312",
+ "description": "COL6A1 mutation leading to Bethlem myopathy with recurrent hematuria: a case report."
+ }
+ }]
+ }, {
+ "type": {
+ "id": "HP:0031910",
+ "label": "Abnormal cranial nerve physiology"
+ },
+ "excluded": true,
+ "evidence": [{
+ "evidenceCode": {
+ "id": "ECO:0000033",
+ "label": "author statement supported by traceable reference"
+ },
+ "reference": {
+ "id": "PMID:30808312",
+ "description": "COL6A1 mutation leading to Bethlem myopathy with recurrent hematuria: a case report."
+ }
+ }]
+ }, {
+ "type": {
+ "id": "HP:0011463",
+ "label": "Macroscopic hematuria"
+ },
+ "modifiers": [{
+ "id": "HP:0031796",
+ "label": "Recurrent"
+ }],
+ "onset": {
+ "age": {
+ "iso8601duration": "P14Y"
+ }
+ },
+ "evidence": [{
+ "evidenceCode": {
+ "id": "ECO:0000033",
+ "label": "author statement supported by traceable reference"
+ },
+ "reference": {
+ "id": "PMID:30808312",
+ "description": "COL6A1 mutation leading to Bethlem myopathy with recurrent hematuria: a case report."
+ }
+ }]
+ }, {
+ "type": {
+ "id": "HP:0001270",
+ "label": "Motor delay"
+ },
+ "severity": {
+ "id": "HP:0012825",
+ "label": "Mild"
+ },
+ "onset": {
+ "ontologyClass": {
+ "id": "HP:0011463",
+ "label": "Childhood onset"
+ }
+ }
+ }],
+ "biosamples": [{
+ "id": "biosample-id",
+ "individualId": "14 year-old boy",
+ "description": "Muscle biopsy of 14 year-old boy",
+ "sampledTissue": {
+ "id": "UBERON:0003403",
+ "label": "skin of forearm"
+ },
+ "taxonomy": {
+ "id": "NCBITaxon:9606",
+ "label": "homo sapiens"
+ },
+ "timeOfCollection": {
+ "age": {
+ "iso8601duration": "P14Y"
+ }
+ },
+ "histologicalDiagnosis": {
+ "id": "NCIT:C38757",
+ "label": "Negative Finding"
+ },
+ "tumorProgression": {
+ "id": "NCIT:C3677",
+ "label": "Benign Neoplasm"
+ },
+ "tumorGrade": {
+ "id": "NCIT:C28076",
+ "label": "Disease Grade Qualifier"
+ },
+ "diagnosticMarkers": [{
+ "id": "NCIT:C68748",
+ "label": "HER2/Neu Positive"
+ }],
+ "materialSample": {
+ "id": "EFO:0009655",
+ "label": "abnormal sample"
+ }
+ }],
+ "interpretations": [{
+ "id": "comprehensive-phenopacket-id",
+ "progressStatus": "SOLVED",
+ "diagnosis": {
+ "disease": {
+ "id": "OMIM:101600",
+ "label": "PFEIFFER SYNDROME"
+ },
+ "genomicInterpretations": [{
+ "subjectOrBiosampleId": "14 year-old boy",
+ "interpretationStatus": "CAUSATIVE",
+ "variantInterpretation": {
+ "variationDescriptor": {
+ "expressions": [{
+ "syntax": "hgvs",
+ "value": "NM_001848.2:c.877G\u003eA"
+ }],
+ "allelicState": {
+ "id": "GENO:0000135",
+ "label": "heterozygous"
+ }
+ }
+ }
+ }]
+ }
+ }],
+ "diseases": [{
+ "term": {
+ "id": "OMIM:101600",
+ "label": "PFEIFFER SYNDROME"
+ },
+ "onset": {
+ "ontologyClass": {
+ "id": "HP:0003577",
+ "label": "Congenital onset"
+ }
+ }
+ }],
+ "files": [{
+ "uri": "file://data/genomes/P000001C",
+ "individualToFileIdentifiers": {
+ "14 year-old boy": "P000001C"
+ },
+ "fileAttributes": {
+ "genomeAssembly": "GRCh38.p13",
+ "fileFormat": "vcf",
+ "description": "Whole genome sequencing VCF output"
+ }
+ }],
+ "metaData": {
+ "created": "2022-10-03T16:39:04.000123456Z",
+ "createdBy": "Peter R.",
+ "submittedBy": "PhenopacketLab",
+ "resources": [{
+ "id": "hp",
+ "name": "human phenotype ontology",
+ "url": "http://purl.obolibrary.org/obo/hp.owl",
+ "version": "2018-03-08",
+ "namespacePrefix": "HP",
+ "iriPrefix": "http://purl.obolibrary.org/obo/HP_"
+ }, {
+ "id": "geno",
+ "name": "Genotype Ontology",
+ "url": "http://purl.obolibrary.org/obo/geno.owl",
+ "version": "19-03-2018",
+ "namespacePrefix": "GENO",
+ "iriPrefix": "http://purl.obolibrary.org/obo/GENO_"
+ }, {
+ "id": "pubmed",
+ "name": "PubMed",
+ "namespacePrefix": "PMID",
+ "iriPrefix": "https://www.ncbi.nlm.nih.gov/pubmed/"
+ }, {
+ "id": "ncit",
+ "name": "NCI Thesaurus",
+ "url": "http://purl.obolibrary.org/obo/ncit.owl",
+ "version": "20-03-2020",
+ "namespacePrefix": "NCIT",
+ "iriPrefix": "http://purl.obolibrary.org/obo/NCIT_"
+ }],
+ "phenopacketSchemaVersion": "2.0.0",
+ "externalReferences": [{
+ "id": "PMID:30808312",
+ "description": "COL6A1 mutation leading to Bethlem myopathy with recurrent hematuria: a case report."
+ }]
+ }
+ },
+ "relatives": [{
+ "subject": {
+ "id": "MOTHER",
+ "dateOfBirth": "1970-01-01T00:00:00Z",
+ "timeAtLastEncounter": {
+ },
+ "sex": "FEMALE",
+ "taxonomy": {
+ }
+ }
+ }, {
+ "subject": {
+ "id": "FATHER",
+ "dateOfBirth": "1970-01-01T00:00:00Z",
+ "timeAtLastEncounter": {
+ },
+ "sex": "MALE",
+ "taxonomy": {
+ }
+ }
+ }],
+ "pedigree": {
+ "persons": [{
+ "individualId": "14 year-old boy",
+ "paternalId": "FATHER",
+ "maternalId": "MOTHER",
+ "sex": "MALE",
+ "affectedStatus": "AFFECTED"
+ }, {
+ "individualId": "MOTHER",
+ "sex": "FEMALE",
+ "affectedStatus": "UNAFFECTED"
+ }, {
+ "individualId": "FATHER",
+ "sex": "MALE",
+ "affectedStatus": "UNAFFECTED"
+ }]
+ },
+ "files": [{
+ "uri": "file://data/genomes/FAM000001",
+ "individualToFileIdentifiers": {
+ "14 year-old boy": "P000001C",
+ "MOTHER": "P000001M",
+ "FATHER": "P000001F"
+ },
+ "fileAttributes": {
+ "genomeAssembly": "GRCh38.p13",
+ "fileFormat": "vcf",
+ "description": "Whole genome sequencing VCF output"
+ }
+ }],
+ "metaData": {
+ "created": "2022-10-03T16:39:04.000123456Z",
+ "createdBy": "Peter R.",
+ "submittedBy": "PhenopacketLab",
+ "resources": [{
+ "id": "hp",
+ "name": "human phenotype ontology",
+ "url": "http://purl.obolibrary.org/obo/hp.owl",
+ "version": "2018-03-08",
+ "namespacePrefix": "HP",
+ "iriPrefix": "http://purl.obolibrary.org/obo/HP_"
+ }, {
+ "id": "geno",
+ "name": "Genotype Ontology",
+ "url": "http://purl.obolibrary.org/obo/geno.owl",
+ "version": "19-03-2018",
+ "namespacePrefix": "GENO",
+ "iriPrefix": "http://purl.obolibrary.org/obo/GENO_"
+ }, {
+ "id": "pubmed",
+ "name": "PubMed",
+ "namespacePrefix": "PMID",
+ "iriPrefix": "https://www.ncbi.nlm.nih.gov/pubmed/"
+ }, {
+ "id": "ncit",
+ "name": "NCI Thesaurus",
+ "url": "http://purl.obolibrary.org/obo/ncit.owl",
+ "version": "20-03-2020",
+ "namespacePrefix": "NCIT",
+ "iriPrefix": "http://purl.obolibrary.org/obo/NCIT_"
+ }],
+ "phenopacketSchemaVersion": "2.0.0",
+ "externalReferences": [{
+ "id": "PMID:30808312",
+ "description": "COL6A1 mutation leading to Bethlem myopathy with recurrent hematuria: a case report."
+ }]
+ }
+}
\ No newline at end of file
diff --git a/phenopacket-tools-io/src/test/resources/org/phenopackets/phenopackettools/io/v2/family.pb b/phenopacket-tools-io/src/test/resources/org/phenopackets/phenopackettools/io/v2/family.pb
new file mode 100644
index 00000000..cb79a8f1
Binary files /dev/null and b/phenopacket-tools-io/src/test/resources/org/phenopackets/phenopackettools/io/v2/family.pb differ
diff --git a/phenopacket-tools-io/src/test/resources/org/phenopackets/phenopackettools/io/v2/family.yaml b/phenopacket-tools-io/src/test/resources/org/phenopackets/phenopackettools/io/v2/family.yaml
new file mode 100644
index 00000000..02e2b78e
--- /dev/null
+++ b/phenopacket-tools-io/src/test/resources/org/phenopackets/phenopackettools/io/v2/family.yaml
@@ -0,0 +1,235 @@
+id: "comprehensive-family-id"
+proband:
+ id: "comprehensive-phenopacket-id"
+ subject:
+ id: "14 year-old boy"
+ alternateIds:
+ - "boy"
+ - "patient"
+ - "proband"
+ dateOfBirth: "1970-01-02T10:17:36.000000100Z"
+ timeAtLastEncounter:
+ age:
+ iso8601duration: "P14Y"
+ sex: "MALE"
+ karyotypicSex: "XY"
+ taxonomy:
+ id: "NCBITaxon:9606"
+ label: "homo sapiens"
+ phenotypicFeatures:
+ - type:
+ id: "HP:0001558"
+ label: "Decreased fetal movement"
+ onset:
+ ontologyClass:
+ id: "HP:0011461"
+ label: "Fetal onset"
+ evidence:
+ - evidenceCode:
+ id: "ECO:0000033"
+ label: "author statement supported by traceable reference"
+ reference:
+ id: "PMID:30808312"
+ description: "COL6A1 mutation leading to Bethlem myopathy with recurrent hematuria:\
+ \ a case report."
+ - type:
+ id: "HP:0031910"
+ label: "Abnormal cranial nerve physiology"
+ excluded: true
+ evidence:
+ - evidenceCode:
+ id: "ECO:0000033"
+ label: "author statement supported by traceable reference"
+ reference:
+ id: "PMID:30808312"
+ description: "COL6A1 mutation leading to Bethlem myopathy with recurrent hematuria:\
+ \ a case report."
+ - type:
+ id: "HP:0011463"
+ label: "Macroscopic hematuria"
+ modifiers:
+ - id: "HP:0031796"
+ label: "Recurrent"
+ onset:
+ age:
+ iso8601duration: "P14Y"
+ evidence:
+ - evidenceCode:
+ id: "ECO:0000033"
+ label: "author statement supported by traceable reference"
+ reference:
+ id: "PMID:30808312"
+ description: "COL6A1 mutation leading to Bethlem myopathy with recurrent hematuria:\
+ \ a case report."
+ - type:
+ id: "HP:0001270"
+ label: "Motor delay"
+ severity:
+ id: "HP:0012825"
+ label: "Mild"
+ onset:
+ ontologyClass:
+ id: "HP:0011463"
+ label: "Childhood onset"
+ biosamples:
+ - id: "biosample-id"
+ individualId: "14 year-old boy"
+ description: "Muscle biopsy of 14 year-old boy"
+ sampledTissue:
+ id: "UBERON:0003403"
+ label: "skin of forearm"
+ taxonomy:
+ id: "NCBITaxon:9606"
+ label: "homo sapiens"
+ timeOfCollection:
+ age:
+ iso8601duration: "P14Y"
+ histologicalDiagnosis:
+ id: "NCIT:C38757"
+ label: "Negative Finding"
+ tumorProgression:
+ id: "NCIT:C3677"
+ label: "Benign Neoplasm"
+ tumorGrade:
+ id: "NCIT:C28076"
+ label: "Disease Grade Qualifier"
+ diagnosticMarkers:
+ - id: "NCIT:C68748"
+ label: "HER2/Neu Positive"
+ materialSample:
+ id: "EFO:0009655"
+ label: "abnormal sample"
+ interpretations:
+ - id: "comprehensive-phenopacket-id"
+ progressStatus: "SOLVED"
+ diagnosis:
+ disease:
+ id: "OMIM:101600"
+ label: "PFEIFFER SYNDROME"
+ genomicInterpretations:
+ - subjectOrBiosampleId: "14 year-old boy"
+ interpretationStatus: "CAUSATIVE"
+ variantInterpretation:
+ variationDescriptor:
+ expressions:
+ - syntax: "hgvs"
+ value: "NM_001848.2:c.877G>A"
+ allelicState:
+ id: "GENO:0000135"
+ label: "heterozygous"
+ diseases:
+ - term:
+ id: "OMIM:101600"
+ label: "PFEIFFER SYNDROME"
+ onset:
+ ontologyClass:
+ id: "HP:0003577"
+ label: "Congenital onset"
+ files:
+ - uri: "file://data/genomes/P000001C"
+ individualToFileIdentifiers:
+ "14 year-old boy": "P000001C"
+ fileAttributes:
+ genomeAssembly: "GRCh38.p13"
+ fileFormat: "vcf"
+ description: "Whole genome sequencing VCF output"
+ metaData:
+ created: "2022-10-03T16:39:04.000123456Z"
+ createdBy: "Peter R."
+ submittedBy: "PhenopacketLab"
+ resources:
+ - id: "hp"
+ name: "human phenotype ontology"
+ url: "http://purl.obolibrary.org/obo/hp.owl"
+ version: "2018-03-08"
+ namespacePrefix: "HP"
+ iriPrefix: "http://purl.obolibrary.org/obo/HP_"
+ - id: "geno"
+ name: "Genotype Ontology"
+ url: "http://purl.obolibrary.org/obo/geno.owl"
+ version: "19-03-2018"
+ namespacePrefix: "GENO"
+ iriPrefix: "http://purl.obolibrary.org/obo/GENO_"
+ - id: "pubmed"
+ name: "PubMed"
+ namespacePrefix: "PMID"
+ iriPrefix: "https://www.ncbi.nlm.nih.gov/pubmed/"
+ - id: "ncit"
+ name: "NCI Thesaurus"
+ url: "http://purl.obolibrary.org/obo/ncit.owl"
+ version: "20-03-2020"
+ namespacePrefix: "NCIT"
+ iriPrefix: "http://purl.obolibrary.org/obo/NCIT_"
+ phenopacketSchemaVersion: "2.0.0"
+ externalReferences:
+ - id: "PMID:30808312"
+ description: "COL6A1 mutation leading to Bethlem myopathy with recurrent hematuria:\
+ \ a case report."
+relatives:
+- subject:
+ id: "MOTHER"
+ dateOfBirth: "1970-01-01T00:00:00Z"
+ timeAtLastEncounter: {}
+ sex: "FEMALE"
+ taxonomy: {}
+- subject:
+ id: "FATHER"
+ dateOfBirth: "1970-01-01T00:00:00Z"
+ timeAtLastEncounter: {}
+ sex: "MALE"
+ taxonomy: {}
+pedigree:
+ persons:
+ - individualId: "14 year-old boy"
+ paternalId: "FATHER"
+ maternalId: "MOTHER"
+ sex: "MALE"
+ affectedStatus: "AFFECTED"
+ - individualId: "MOTHER"
+ sex: "FEMALE"
+ affectedStatus: "UNAFFECTED"
+ - individualId: "FATHER"
+ sex: "MALE"
+ affectedStatus: "UNAFFECTED"
+files:
+- uri: "file://data/genomes/FAM000001"
+ individualToFileIdentifiers:
+ "14 year-old boy": "P000001C"
+ MOTHER: "P000001M"
+ FATHER: "P000001F"
+ fileAttributes:
+ genomeAssembly: "GRCh38.p13"
+ fileFormat: "vcf"
+ description: "Whole genome sequencing VCF output"
+metaData:
+ created: "2022-10-03T16:39:04.000123456Z"
+ createdBy: "Peter R."
+ submittedBy: "PhenopacketLab"
+ resources:
+ - id: "hp"
+ name: "human phenotype ontology"
+ url: "http://purl.obolibrary.org/obo/hp.owl"
+ version: "2018-03-08"
+ namespacePrefix: "HP"
+ iriPrefix: "http://purl.obolibrary.org/obo/HP_"
+ - id: "geno"
+ name: "Genotype Ontology"
+ url: "http://purl.obolibrary.org/obo/geno.owl"
+ version: "19-03-2018"
+ namespacePrefix: "GENO"
+ iriPrefix: "http://purl.obolibrary.org/obo/GENO_"
+ - id: "pubmed"
+ name: "PubMed"
+ namespacePrefix: "PMID"
+ iriPrefix: "https://www.ncbi.nlm.nih.gov/pubmed/"
+ - id: "ncit"
+ name: "NCI Thesaurus"
+ url: "http://purl.obolibrary.org/obo/ncit.owl"
+ version: "20-03-2020"
+ namespacePrefix: "NCIT"
+ iriPrefix: "http://purl.obolibrary.org/obo/NCIT_"
+ phenopacketSchemaVersion: "2.0.0"
+ externalReferences:
+ - id: "PMID:30808312"
+ description: "COL6A1 mutation leading to Bethlem myopathy with recurrent hematuria:\
+ \ a case report."
diff --git a/phenopacket-tools-io/src/test/resources/org/phenopackets/phenopackettools/io/v2/phenopacket.json b/phenopacket-tools-io/src/test/resources/org/phenopackets/phenopackettools/io/v2/phenopacket.json
new file mode 100644
index 00000000..89d29db4
--- /dev/null
+++ b/phenopacket-tools-io/src/test/resources/org/phenopackets/phenopackettools/io/v2/phenopacket.json
@@ -0,0 +1,220 @@
+{
+ "id": "comprehensive-phenopacket-id",
+ "subject": {
+ "id": "14 year-old boy",
+ "alternateIds": ["boy", "patient", "proband"],
+ "dateOfBirth": "1970-01-02T10:17:36.000000100Z",
+ "timeAtLastEncounter": {
+ "age": {
+ "iso8601duration": "P14Y"
+ }
+ },
+ "sex": "MALE",
+ "karyotypicSex": "XY",
+ "taxonomy": {
+ "id": "NCBITaxon:9606",
+ "label": "homo sapiens"
+ }
+ },
+ "phenotypicFeatures": [{
+ "type": {
+ "id": "HP:0001558",
+ "label": "Decreased fetal movement"
+ },
+ "onset": {
+ "ontologyClass": {
+ "id": "HP:0011461",
+ "label": "Fetal onset"
+ }
+ },
+ "evidence": [{
+ "evidenceCode": {
+ "id": "ECO:0000033",
+ "label": "author statement supported by traceable reference"
+ },
+ "reference": {
+ "id": "PMID:30808312",
+ "description": "COL6A1 mutation leading to Bethlem myopathy with recurrent hematuria: a case report."
+ }
+ }]
+ }, {
+ "type": {
+ "id": "HP:0031910",
+ "label": "Abnormal cranial nerve physiology"
+ },
+ "excluded": true,
+ "evidence": [{
+ "evidenceCode": {
+ "id": "ECO:0000033",
+ "label": "author statement supported by traceable reference"
+ },
+ "reference": {
+ "id": "PMID:30808312",
+ "description": "COL6A1 mutation leading to Bethlem myopathy with recurrent hematuria: a case report."
+ }
+ }]
+ }, {
+ "type": {
+ "id": "HP:0011463",
+ "label": "Macroscopic hematuria"
+ },
+ "modifiers": [{
+ "id": "HP:0031796",
+ "label": "Recurrent"
+ }],
+ "onset": {
+ "age": {
+ "iso8601duration": "P14Y"
+ }
+ },
+ "evidence": [{
+ "evidenceCode": {
+ "id": "ECO:0000033",
+ "label": "author statement supported by traceable reference"
+ },
+ "reference": {
+ "id": "PMID:30808312",
+ "description": "COL6A1 mutation leading to Bethlem myopathy with recurrent hematuria: a case report."
+ }
+ }]
+ }, {
+ "type": {
+ "id": "HP:0001270",
+ "label": "Motor delay"
+ },
+ "severity": {
+ "id": "HP:0012825",
+ "label": "Mild"
+ },
+ "onset": {
+ "ontologyClass": {
+ "id": "HP:0011463",
+ "label": "Childhood onset"
+ }
+ }
+ }],
+ "biosamples": [{
+ "id": "biosample-id",
+ "individualId": "14 year-old boy",
+ "description": "Muscle biopsy of 14 year-old boy",
+ "sampledTissue": {
+ "id": "UBERON:0003403",
+ "label": "skin of forearm"
+ },
+ "taxonomy": {
+ "id": "NCBITaxon:9606",
+ "label": "homo sapiens"
+ },
+ "timeOfCollection": {
+ "age": {
+ "iso8601duration": "P14Y"
+ }
+ },
+ "histologicalDiagnosis": {
+ "id": "NCIT:C38757",
+ "label": "Negative Finding"
+ },
+ "tumorProgression": {
+ "id": "NCIT:C3677",
+ "label": "Benign Neoplasm"
+ },
+ "tumorGrade": {
+ "id": "NCIT:C28076",
+ "label": "Disease Grade Qualifier"
+ },
+ "diagnosticMarkers": [{
+ "id": "NCIT:C68748",
+ "label": "HER2/Neu Positive"
+ }],
+ "materialSample": {
+ "id": "EFO:0009655",
+ "label": "abnormal sample"
+ }
+ }],
+ "interpretations": [{
+ "id": "comprehensive-phenopacket-id",
+ "progressStatus": "SOLVED",
+ "diagnosis": {
+ "disease": {
+ "id": "OMIM:101600",
+ "label": "PFEIFFER SYNDROME"
+ },
+ "genomicInterpretations": [{
+ "subjectOrBiosampleId": "14 year-old boy",
+ "interpretationStatus": "CAUSATIVE",
+ "variantInterpretation": {
+ "variationDescriptor": {
+ "expressions": [{
+ "syntax": "hgvs",
+ "value": "NM_001848.2:c.877G\u003eA"
+ }],
+ "allelicState": {
+ "id": "GENO:0000135",
+ "label": "heterozygous"
+ }
+ }
+ }
+ }]
+ }
+ }],
+ "diseases": [{
+ "term": {
+ "id": "OMIM:101600",
+ "label": "PFEIFFER SYNDROME"
+ },
+ "onset": {
+ "ontologyClass": {
+ "id": "HP:0003577",
+ "label": "Congenital onset"
+ }
+ }
+ }],
+ "files": [{
+ "uri": "file://data/genomes/P000001C",
+ "individualToFileIdentifiers": {
+ "14 year-old boy": "P000001C"
+ },
+ "fileAttributes": {
+ "genomeAssembly": "GRCh38.p13",
+ "fileFormat": "vcf",
+ "description": "Whole genome sequencing VCF output"
+ }
+ }],
+ "metaData": {
+ "created": "2022-10-03T16:39:04.000123456Z",
+ "createdBy": "Peter R.",
+ "submittedBy": "PhenopacketLab",
+ "resources": [{
+ "id": "hp",
+ "name": "human phenotype ontology",
+ "url": "http://purl.obolibrary.org/obo/hp.owl",
+ "version": "2018-03-08",
+ "namespacePrefix": "HP",
+ "iriPrefix": "http://purl.obolibrary.org/obo/HP_"
+ }, {
+ "id": "geno",
+ "name": "Genotype Ontology",
+ "url": "http://purl.obolibrary.org/obo/geno.owl",
+ "version": "19-03-2018",
+ "namespacePrefix": "GENO",
+ "iriPrefix": "http://purl.obolibrary.org/obo/GENO_"
+ }, {
+ "id": "pubmed",
+ "name": "PubMed",
+ "namespacePrefix": "PMID",
+ "iriPrefix": "https://www.ncbi.nlm.nih.gov/pubmed/"
+ }, {
+ "id": "ncit",
+ "name": "NCI Thesaurus",
+ "url": "http://purl.obolibrary.org/obo/ncit.owl",
+ "version": "20-03-2020",
+ "namespacePrefix": "NCIT",
+ "iriPrefix": "http://purl.obolibrary.org/obo/NCIT_"
+ }],
+ "phenopacketSchemaVersion": "2.0.0",
+ "externalReferences": [{
+ "id": "PMID:30808312",
+ "description": "COL6A1 mutation leading to Bethlem myopathy with recurrent hematuria: a case report."
+ }]
+ }
+}
\ No newline at end of file
diff --git a/phenopacket-tools-io/src/test/resources/org/phenopackets/phenopackettools/io/v2/phenopacket.pb b/phenopacket-tools-io/src/test/resources/org/phenopackets/phenopackettools/io/v2/phenopacket.pb
new file mode 100644
index 00000000..3acd16a2
--- /dev/null
+++ b/phenopacket-tools-io/src/test/resources/org/phenopackets/phenopackettools/io/v2/phenopacket.pb
@@ -0,0 +1,71 @@
+
+comprehensive-phenopacket-id^
+14 year-old boyboypatientprobandd"
+
+P14Y08J
+NCBITaxon:9606homo sapiens&
+
+HP:0001558Decreased fetal movement2
+
+HP:0011461Fetal onsetB
+@
+ECO:00000331author statement supported by traceable referencee
+
PMID:30808312TCOL6A1 mutation leading to Bethlem myopathy with recurrent hematuria: a case report./
+
+HP:0031910!Abnormal cranial nerve physiologyB
+@
+ECO:00000331author statement supported by traceable referencee
+
PMID:30808312TCOL6A1 mutation leading to Bethlem myopathy with recurrent hematuria: a case report.#
+
+HP:0011463Macroscopic hematuria*
+
+HP:0031796 Recurrent2
+
+P14YB
+@
+ECO:00000331author statement supported by traceable referencee
+
PMID:30808312TCOL6A1 mutation leading to Bethlem myopathy with recurrent hematuria: a case report.P
+
+HP:0001270Motor delay"
+
+HP:0012825Mild2
+
+HP:0011463Childhood onset*
+biosample-id14 year-old boy" Muscle biopsy of 14 year-old boy*!
+UBERON:0003403skin of forearmJ
+NCBITaxon:9606homo sapiensR
+
+P14YZ
+NCIT:C38757Negative Findingb
+
+NCIT:C3677Benign Neoplasmj&
+NCIT:C28076Disease Grade Qualifier
+NCIT:C68748HER2/Neu Positive
+EFO:0009655abnormal sample2
+comprehensive-phenopacket-idw
+
+OMIM:101600PFEIFFER SYNDROMES
+14 year-old boy"><2
+hgvsNM_001848.2:c.877G>Ar
+GENO:0000135heterozygous:D
+
+OMIM:101600PFEIFFER SYNDROME
+
+HP:0003577Congenital onsetR
+file://data/genomes/P000001C
+14 year-old boyP000001C
+genomeAssembly
+GRCh38.p13
+
+fileFormatvcf1
+description"Whole genome sequencing VCF outputZ
+
+Peter R.PhenopacketLab"y
+hphuman phenotype ontology%http://purl.obolibrary.org/obo/hp.owl"
+2018-03-08*HP2"http://purl.obolibrary.org/obo/HP_"z
+genoGenotype Ontology'http://purl.obolibrary.org/obo/geno.owl"
+19-03-2018*GENO2$http://purl.obolibrary.org/obo/GENO_"<
+pubmedPubMed*PMID2$https://www.ncbi.nlm.nih.gov/pubmed/"v
+ncit
NCI Thesaurus'http://purl.obolibrary.org/obo/ncit.owl"
+20-03-2020*NCIT2$http://purl.obolibrary.org/obo/NCIT_22.0.0:e
+
PMID:30808312TCOL6A1 mutation leading to Bethlem myopathy with recurrent hematuria: a case report.
\ No newline at end of file
diff --git a/phenopacket-tools-io/src/test/resources/org/phenopackets/phenopackettools/io/v2/phenopacket.yaml b/phenopacket-tools-io/src/test/resources/org/phenopackets/phenopackettools/io/v2/phenopacket.yaml
new file mode 100644
index 00000000..2a5986de
--- /dev/null
+++ b/phenopacket-tools-io/src/test/resources/org/phenopackets/phenopackettools/io/v2/phenopacket.yaml
@@ -0,0 +1,165 @@
+id: "comprehensive-phenopacket-id"
+subject:
+ id: "14 year-old boy"
+ alternateIds:
+ - "boy"
+ - "patient"
+ - "proband"
+ dateOfBirth: "1970-01-02T10:17:36.000000100Z"
+ timeAtLastEncounter:
+ age:
+ iso8601duration: "P14Y"
+ sex: "MALE"
+ karyotypicSex: "XY"
+ taxonomy:
+ id: "NCBITaxon:9606"
+ label: "homo sapiens"
+phenotypicFeatures:
+- type:
+ id: "HP:0001558"
+ label: "Decreased fetal movement"
+ onset:
+ ontologyClass:
+ id: "HP:0011461"
+ label: "Fetal onset"
+ evidence:
+ - evidenceCode:
+ id: "ECO:0000033"
+ label: "author statement supported by traceable reference"
+ reference:
+ id: "PMID:30808312"
+ description: "COL6A1 mutation leading to Bethlem myopathy with recurrent hematuria:\
+ \ a case report."
+- type:
+ id: "HP:0031910"
+ label: "Abnormal cranial nerve physiology"
+ excluded: true
+ evidence:
+ - evidenceCode:
+ id: "ECO:0000033"
+ label: "author statement supported by traceable reference"
+ reference:
+ id: "PMID:30808312"
+ description: "COL6A1 mutation leading to Bethlem myopathy with recurrent hematuria:\
+ \ a case report."
+- type:
+ id: "HP:0011463"
+ label: "Macroscopic hematuria"
+ modifiers:
+ - id: "HP:0031796"
+ label: "Recurrent"
+ onset:
+ age:
+ iso8601duration: "P14Y"
+ evidence:
+ - evidenceCode:
+ id: "ECO:0000033"
+ label: "author statement supported by traceable reference"
+ reference:
+ id: "PMID:30808312"
+ description: "COL6A1 mutation leading to Bethlem myopathy with recurrent hematuria:\
+ \ a case report."
+- type:
+ id: "HP:0001270"
+ label: "Motor delay"
+ severity:
+ id: "HP:0012825"
+ label: "Mild"
+ onset:
+ ontologyClass:
+ id: "HP:0011463"
+ label: "Childhood onset"
+biosamples:
+- id: "biosample-id"
+ individualId: "14 year-old boy"
+ description: "Muscle biopsy of 14 year-old boy"
+ sampledTissue:
+ id: "UBERON:0003403"
+ label: "skin of forearm"
+ taxonomy:
+ id: "NCBITaxon:9606"
+ label: "homo sapiens"
+ timeOfCollection:
+ age:
+ iso8601duration: "P14Y"
+ histologicalDiagnosis:
+ id: "NCIT:C38757"
+ label: "Negative Finding"
+ tumorProgression:
+ id: "NCIT:C3677"
+ label: "Benign Neoplasm"
+ tumorGrade:
+ id: "NCIT:C28076"
+ label: "Disease Grade Qualifier"
+ diagnosticMarkers:
+ - id: "NCIT:C68748"
+ label: "HER2/Neu Positive"
+ materialSample:
+ id: "EFO:0009655"
+ label: "abnormal sample"
+interpretations:
+- id: "comprehensive-phenopacket-id"
+ progressStatus: "SOLVED"
+ diagnosis:
+ disease:
+ id: "OMIM:101600"
+ label: "PFEIFFER SYNDROME"
+ genomicInterpretations:
+ - subjectOrBiosampleId: "14 year-old boy"
+ interpretationStatus: "CAUSATIVE"
+ variantInterpretation:
+ variationDescriptor:
+ expressions:
+ - syntax: "hgvs"
+ value: "NM_001848.2:c.877G>A"
+ allelicState:
+ id: "GENO:0000135"
+ label: "heterozygous"
+diseases:
+- term:
+ id: "OMIM:101600"
+ label: "PFEIFFER SYNDROME"
+ onset:
+ ontologyClass:
+ id: "HP:0003577"
+ label: "Congenital onset"
+files:
+- uri: "file://data/genomes/P000001C"
+ individualToFileIdentifiers:
+ "14 year-old boy": "P000001C"
+ fileAttributes:
+ genomeAssembly: "GRCh38.p13"
+ fileFormat: "vcf"
+ description: "Whole genome sequencing VCF output"
+metaData:
+ created: "2022-10-03T16:39:04.000123456Z"
+ createdBy: "Peter R."
+ submittedBy: "PhenopacketLab"
+ resources:
+ - id: "hp"
+ name: "human phenotype ontology"
+ url: "http://purl.obolibrary.org/obo/hp.owl"
+ version: "2018-03-08"
+ namespacePrefix: "HP"
+ iriPrefix: "http://purl.obolibrary.org/obo/HP_"
+ - id: "geno"
+ name: "Genotype Ontology"
+ url: "http://purl.obolibrary.org/obo/geno.owl"
+ version: "19-03-2018"
+ namespacePrefix: "GENO"
+ iriPrefix: "http://purl.obolibrary.org/obo/GENO_"
+ - id: "pubmed"
+ name: "PubMed"
+ namespacePrefix: "PMID"
+ iriPrefix: "https://www.ncbi.nlm.nih.gov/pubmed/"
+ - id: "ncit"
+ name: "NCI Thesaurus"
+ url: "http://purl.obolibrary.org/obo/ncit.owl"
+ version: "20-03-2020"
+ namespacePrefix: "NCIT"
+ iriPrefix: "http://purl.obolibrary.org/obo/NCIT_"
+ phenopacketSchemaVersion: "2.0.0"
+ externalReferences:
+ - id: "PMID:30808312"
+ description: "COL6A1 mutation leading to Bethlem myopathy with recurrent hematuria:\
+ \ a case report."
diff --git a/phenopacket-tools-test/pom.xml b/phenopacket-tools-test/pom.xml
index 36a9d7bf..d0f5ce0e 100644
--- a/phenopacket-tools-test/pom.xml
+++ b/phenopacket-tools-test/pom.xml
@@ -5,7 +5,7 @@
phenopacket-tools
org.phenopackets.phenopackettools
- 0.4.6
+ 0.4.7
4.0.0
diff --git a/phenopacket-tools-util/pom.xml b/phenopacket-tools-util/pom.xml
index 575e8e8f..7bb365de 100644
--- a/phenopacket-tools-util/pom.xml
+++ b/phenopacket-tools-util/pom.xml
@@ -5,10 +5,18 @@
phenopacket-tools
org.phenopackets.phenopackettools
- 0.4.6
+ 0.4.7
4.0.0
phenopacket-tools-util
+
+
+ org.phenopackets.phenopackettools
+ phenopacket-tools-core
+ ${project.parent.version}
+
+
+
\ No newline at end of file
diff --git a/phenopacket-tools-util/src/main/java/module-info.java b/phenopacket-tools-util/src/main/java/module-info.java
index 9d20effb..6c8793a5 100644
--- a/phenopacket-tools-util/src/main/java/module-info.java
+++ b/phenopacket-tools-util/src/main/java/module-info.java
@@ -1,3 +1,9 @@
+/**
+ * A module with utility functions.
+ */
module org.phenopackets.phenopackettools.util {
+ requires transitive org.phenopackets.phenopackettools.core;
+ requires org.slf4j;
+
exports org.phenopackets.phenopackettools.util.format;
}
\ No newline at end of file
diff --git a/phenopacket-tools-util/src/main/java/org/phenopackets/phenopackettools/util/format/ElementSniffException.java b/phenopacket-tools-util/src/main/java/org/phenopackets/phenopackettools/util/format/ElementSniffException.java
new file mode 100644
index 00000000..b90545f0
--- /dev/null
+++ b/phenopacket-tools-util/src/main/java/org/phenopackets/phenopackettools/util/format/ElementSniffException.java
@@ -0,0 +1,25 @@
+package org.phenopackets.phenopackettools.util.format;
+
+public class ElementSniffException extends SniffException {
+
+ public ElementSniffException() {
+ super();
+ }
+
+ public ElementSniffException(String message) {
+ super(message);
+ }
+
+ public ElementSniffException(String message, Throwable cause) {
+ super(message, cause);
+ }
+
+ public ElementSniffException(Throwable cause) {
+ super(cause);
+ }
+
+ protected ElementSniffException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) {
+ super(message, cause, enableSuppression, writableStackTrace);
+ }
+
+}
diff --git a/phenopacket-tools-util/src/main/java/org/phenopackets/phenopackettools/util/format/ElementSniffer.java b/phenopacket-tools-util/src/main/java/org/phenopackets/phenopackettools/util/format/ElementSniffer.java
new file mode 100644
index 00000000..84c33dcb
--- /dev/null
+++ b/phenopacket-tools-util/src/main/java/org/phenopackets/phenopackettools/util/format/ElementSniffer.java
@@ -0,0 +1,86 @@
+package org.phenopackets.phenopackettools.util.format;
+
+import org.phenopackets.phenopackettools.core.PhenopacketElement;
+import org.phenopackets.phenopackettools.core.PhenopacketFormat;
+import org.phenopackets.phenopackettools.core.PhenopacketSchemaVersion;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.IOException;
+import java.io.InputStream;
+
+/**
+ * Make an educated guess regarding which top-level element of Phenopacket schema is represented in the provided
+ * {@code byte[]} or {@link InputStream}.
+ */
+public class ElementSniffer {
+
+ // Remove SLF4J from module-info if we omit logging.
+ private static final Logger LOGGER = LoggerFactory.getLogger(ElementSniffer.class);
+
+ /**
+ * The number of bytes used for element sniffing.
+ */
+ static final int BUFFER_SIZE = 32;
+
+ private ElementSniffer() {
+ }
+
+ /**
+ * Make an educated guess of {@link PhenopacketElement} present in given {@code input}.
+ *
+ * @param input an {@link InputStream} that supports {@link InputStream#mark(int)}.
+ * @param format the {@code payload} format
+ * @return the sniffed {@link PhenopacketElement}.
+ * @throws IOException in case an error occurs while reading the {@code input}.
+ * @throws SniffException if there are not enough bytes available in the {@code input} of if the {@code input} does not
+ * support {@link InputStream#mark(int)}.
+ */
+ public static PhenopacketElement sniff(InputStream input,
+ PhenopacketSchemaVersion schemaVersion,
+ PhenopacketFormat format) throws IOException, SniffException {
+ return sniff(Util.getFirstBytesAndReset(input, BUFFER_SIZE), schemaVersion, format);
+ }
+
+ /**
+ * Make an educated guess of {@link PhenopacketElement} based on given {@code payload}.
+ *
+ * @param payload buffer with at least the first {@link #BUFFER_SIZE} bytes of the input.
+ * @param format the {@code payload} format
+ * @return the sniffed {@link PhenopacketElement}.
+ * @throws ElementSniffException if {@code payload} contains less than {@link #BUFFER_SIZE} bytes.
+ */
+ public static PhenopacketElement sniff(byte[] payload,
+ PhenopacketSchemaVersion schemaVersion,
+ PhenopacketFormat format) throws ElementSniffException {
+ if (payload.length < BUFFER_SIZE)
+ throw new ElementSniffException("Need at least %d bytes to sniff but got %d".formatted(BUFFER_SIZE, payload.length));
+
+ return switch (format) {
+ case PROTOBUF -> sniffProtobuf(payload, schemaVersion);
+ case JSON -> sniffJson(payload, schemaVersion);
+ case YAML -> sniffYaml(payload, schemaVersion);
+ };
+ }
+
+ private static PhenopacketElement sniffProtobuf(byte[] payload, PhenopacketSchemaVersion schemaVersion) {
+ // TODO - implement
+ LOGGER.debug("Sniffing is not yet implemented, assuming {}", PhenopacketElement.PHENOPACKET);
+ return PhenopacketElement.PHENOPACKET;
+ }
+
+ private static PhenopacketElement sniffJson(byte[] payload, PhenopacketSchemaVersion schemaVersion) {
+ // TODO - implement
+ // TODO - reconsider the sniffing workflow. In case of loosely defined formats like JSON and YAML,
+ // the fields can be in any order and we may not get enough information.
+ // Is it OK to throw upon sniffing failure or an Optional is enough?
+ LOGGER.debug("Sniffing is not yet implemented, assuming {}", PhenopacketElement.PHENOPACKET);
+ return PhenopacketElement.PHENOPACKET;
+ }
+
+ private static PhenopacketElement sniffYaml(byte[] payload, PhenopacketSchemaVersion schemaVersion) {
+ // TODO - implement
+ LOGGER.debug("Sniffing is not yet implemented, assuming {}", PhenopacketElement.PHENOPACKET);
+ return PhenopacketElement.PHENOPACKET;
+ }
+}
diff --git a/phenopacket-tools-util/src/main/java/org/phenopackets/phenopackettools/util/format/FormatSniffException.java b/phenopacket-tools-util/src/main/java/org/phenopackets/phenopackettools/util/format/FormatSniffException.java
index 3cc3a382..c33b276d 100644
--- a/phenopacket-tools-util/src/main/java/org/phenopackets/phenopackettools/util/format/FormatSniffException.java
+++ b/phenopacket-tools-util/src/main/java/org/phenopackets/phenopackettools/util/format/FormatSniffException.java
@@ -3,7 +3,7 @@
/**
* An exception thrown when sniffing of the top-level element of Phenopacket schema cannot be performed.
*/
-public class FormatSniffException extends Exception {
+public class FormatSniffException extends ElementSniffException {
public FormatSniffException() {
super();
diff --git a/phenopacket-tools-util/src/main/java/org/phenopackets/phenopackettools/util/format/FormatSniffer.java b/phenopacket-tools-util/src/main/java/org/phenopackets/phenopackettools/util/format/FormatSniffer.java
index 0d4984d6..9d6b7376 100644
--- a/phenopacket-tools-util/src/main/java/org/phenopackets/phenopackettools/util/format/FormatSniffer.java
+++ b/phenopacket-tools-util/src/main/java/org/phenopackets/phenopackettools/util/format/FormatSniffer.java
@@ -1,5 +1,7 @@
package org.phenopackets.phenopackettools.util.format;
+import org.phenopackets.phenopackettools.core.PhenopacketFormat;
+
import java.io.IOException;
import java.io.InputStream;
@@ -48,21 +50,7 @@ public static PhenopacketFormat sniff(byte[] payload) throws FormatSniffExceptio
* @throws FormatSniffException if there are not enough bytes available in the {@code input} of if the {@code input} does not
* support {@link InputStream#mark(int)}.
*/
- public static PhenopacketFormat sniff(InputStream input) throws IOException, FormatSniffException {
- if (input.markSupported()) {
- byte[] buffer = new byte[BUFFER_SIZE];
- input.mark(BUFFER_SIZE);
- int read = input.read(buffer);
- if (read < BUFFER_SIZE) {
- // We explode because there are not enough bytes available for format sniffing.
- String message = read < 0
- ? "The stream must not be at the end"
- : "Need at least %d bytes to sniff the format but only %d was available".formatted(BUFFER_SIZE, read);
- throw new FormatSniffException(message);
- }
- input.reset();
- return sniff(buffer);
- } else
- throw new FormatSniffException("The provided InputStream does not support `mark()`");
+ public static PhenopacketFormat sniff(InputStream input) throws IOException, SniffException {
+ return sniff(Util.getFirstBytesAndReset(input, BUFFER_SIZE));
}
}
diff --git a/phenopacket-tools-util/src/main/java/org/phenopackets/phenopackettools/util/format/SniffException.java b/phenopacket-tools-util/src/main/java/org/phenopackets/phenopackettools/util/format/SniffException.java
new file mode 100644
index 00000000..fae81f92
--- /dev/null
+++ b/phenopacket-tools-util/src/main/java/org/phenopackets/phenopackettools/util/format/SniffException.java
@@ -0,0 +1,30 @@
+package org.phenopackets.phenopackettools.util.format;
+
+import org.phenopackets.phenopackettools.core.PhenopacketToolsException;
+
+/**
+ * A checked exception thrown in case of encountering some content sniffing issues.
+ */
+public class SniffException extends PhenopacketToolsException {
+
+ public SniffException() {
+ super();
+ }
+
+ public SniffException(String message) {
+ super(message);
+ }
+
+ public SniffException(String message, Throwable cause) {
+ super(message, cause);
+ }
+
+ public SniffException(Throwable cause) {
+ super(cause);
+ }
+
+ protected SniffException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) {
+ super(message, cause, enableSuppression, writableStackTrace);
+ }
+
+}
diff --git a/phenopacket-tools-util/src/main/java/org/phenopackets/phenopackettools/util/format/Util.java b/phenopacket-tools-util/src/main/java/org/phenopackets/phenopackettools/util/format/Util.java
index 603769b1..9deb1fcd 100644
--- a/phenopacket-tools-util/src/main/java/org/phenopackets/phenopackettools/util/format/Util.java
+++ b/phenopacket-tools-util/src/main/java/org/phenopackets/phenopackettools/util/format/Util.java
@@ -31,4 +31,22 @@ static boolean looksLikeYaml(byte[] payload) {
}
}
+ static byte[] getFirstBytesAndReset(InputStream input, int nBytes) throws SniffException, IOException {
+ if (input.markSupported()) {
+ byte[] buffer = new byte[nBytes];
+ input.mark(nBytes);
+ int read = input.read(buffer);
+ if (read < nBytes) {
+ // We explode because there are not enough bytes available for format sniffing.
+ String message = read < 0
+ ? "The stream must not be at the end"
+ : "Need at least %d bytes to sniff the format but only %d was available".formatted(nBytes, read);
+ throw new SniffException(message);
+ }
+ input.reset();
+ return buffer;
+ } else
+ throw new SniffException("The provided InputStream does not support `mark()`");
+
+ }
}
diff --git a/phenopacket-tools-util/src/main/java/org/phenopackets/phenopackettools/util/format/package-info.java b/phenopacket-tools-util/src/main/java/org/phenopackets/phenopackettools/util/format/package-info.java
index fecd687b..9e9da40d 100644
--- a/phenopacket-tools-util/src/main/java/org/phenopackets/phenopackettools/util/format/package-info.java
+++ b/phenopacket-tools-util/src/main/java/org/phenopackets/phenopackettools/util/format/package-info.java
@@ -1,4 +1,5 @@
/**
- * Defines the supported phenopacket formats and utility methods for working with the formats.
+ * Defines utility methods for working with {@link org.phenopackets.phenopackettools.core.PhenopacketElement}s
+ * and {@link org.phenopackets.phenopackettools.core.PhenopacketFormat}s.
*/
package org.phenopackets.phenopackettools.util.format;
\ No newline at end of file
diff --git a/phenopacket-tools-util/src/test/java/org/phenopackets/phenopackettools/util/format/ElementSnifferTest.java b/phenopacket-tools-util/src/test/java/org/phenopackets/phenopackettools/util/format/ElementSnifferTest.java
new file mode 100644
index 00000000..22a095cd
--- /dev/null
+++ b/phenopacket-tools-util/src/test/java/org/phenopackets/phenopackettools/util/format/ElementSnifferTest.java
@@ -0,0 +1,11 @@
+package org.phenopackets.phenopackettools.util.format;
+
+import static org.junit.jupiter.api.Assertions.*;
+import static org.hamcrest.MatcherAssert.*;
+import static org.hamcrest.Matchers.*;
+
+public class ElementSnifferTest {
+
+ // TODO - implement
+
+}
\ No newline at end of file
diff --git a/phenopacket-tools-util/src/test/java/org/phenopackets/phenopackettools/util/format/FormatSnifferTest.java b/phenopacket-tools-util/src/test/java/org/phenopackets/phenopackettools/util/format/FormatSnifferTest.java
index 5a1200fb..3321e094 100644
--- a/phenopacket-tools-util/src/test/java/org/phenopackets/phenopackettools/util/format/FormatSnifferTest.java
+++ b/phenopacket-tools-util/src/test/java/org/phenopackets/phenopackettools/util/format/FormatSnifferTest.java
@@ -2,6 +2,7 @@
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.CsvSource;
+import org.phenopackets.phenopackettools.core.PhenopacketFormat;
import java.io.BufferedInputStream;
import java.io.IOException;
diff --git a/phenopacket-tools-validator-core/pom.xml b/phenopacket-tools-validator-core/pom.xml
index 4e848013..97b8b750 100644
--- a/phenopacket-tools-validator-core/pom.xml
+++ b/phenopacket-tools-validator-core/pom.xml
@@ -7,7 +7,7 @@
org.phenopackets.phenopackettools
phenopacket-tools
- 0.4.6
+ 0.4.7
phenopacket-tools-validator-core
@@ -16,6 +16,11 @@
Validator utilities for phenopackets
+
+ org.phenopackets.phenopackettools
+ phenopacket-tools-core
+ ${project.parent.version}
+
org.phenopackets
phenopacket-schema
diff --git a/phenopacket-tools-validator-core/src/main/java/module-info.java b/phenopacket-tools-validator-core/src/main/java/module-info.java
index 1abaea74..0c5824ee 100644
--- a/phenopacket-tools-validator-core/src/main/java/module-info.java
+++ b/phenopacket-tools-validator-core/src/main/java/module-info.java
@@ -1,3 +1,6 @@
+/**
+ * Defines the base APIs for phenopacket validation.
+ */
module org.phenopackets.phenopackettools.validator.core {
exports org.phenopackets.phenopackettools.validator.core;
@@ -6,6 +9,7 @@
exports org.phenopackets.phenopackettools.validator.core.phenotype;
exports org.phenopackets.phenopackettools.validator.core.writer;
+ requires org.phenopackets.phenopackettools.core;
requires org.monarchinitiative.phenol.core;
requires org.phenopackets.schema;
diff --git a/phenopacket-tools-validator-core/src/main/java/org/phenopackets/phenopackettools/validator/core/ConversionException.java b/phenopacket-tools-validator-core/src/main/java/org/phenopackets/phenopackettools/validator/core/ConversionException.java
index d8f13cc4..c5d76cb8 100644
--- a/phenopacket-tools-validator-core/src/main/java/org/phenopackets/phenopackettools/validator/core/ConversionException.java
+++ b/phenopacket-tools-validator-core/src/main/java/org/phenopackets/phenopackettools/validator/core/ConversionException.java
@@ -1,9 +1,19 @@
package org.phenopackets.phenopackettools.validator.core;
+import org.phenopackets.phenopackettools.core.PhenopacketToolsException;
+
/**
- * An {@link Exception} that is thrown in case the provided data has incorrect format.
+ * A {@link PhenopacketToolsException} that is thrown by {@link org.phenopackets.phenopackettools.validator.core.convert.PhenopacketConverter}
+ * in case the provided data has incorrect format.
+ *
+ * This can happen if e.g. the {@code payload} to
+ * {@link org.phenopackets.phenopackettools.validator.core.convert.PhenopacketConverter#toJson(byte[])}
+ * is not valid JSON.
+ *
+ * {@code ConversionException} implements {@link ValidationResult} so that it can be reported
+ * by a {@link PhenopacketValidator}.
*/
-public class ConversionException extends Exception implements ValidationResult {
+public class ConversionException extends PhenopacketToolsException implements ValidationResult {
private static final String VALIDATION_CATEGORY = "input";
diff --git a/phenopacket-tools-validator-core/src/main/java/org/phenopackets/phenopackettools/validator/core/InputError.java b/phenopacket-tools-validator-core/src/main/java/org/phenopackets/phenopackettools/validator/core/InputError.java
deleted file mode 100644
index 0d93f1e4..00000000
--- a/phenopacket-tools-validator-core/src/main/java/org/phenopackets/phenopackettools/validator/core/InputError.java
+++ /dev/null
@@ -1,27 +0,0 @@
-package org.phenopackets.phenopackettools.validator.core;
-
-/**
- * {@link ValidationResult} returned when encountering a format error.
- * @param message message to present the user.
- */
-record InputError(String message) implements ValidationResult {
-
- private static final String VALIDATION_CATEGORY = "input";
-
-
- @Override
- public ValidatorInfo validatorInfo() {
- return ValidatorInfo.inputValidator();
- }
-
- @Override
- public ValidationLevel level() {
- return ValidationLevel.ERROR;
- }
-
- @Override
- public String category() {
- return VALIDATION_CATEGORY;
- }
-
-}
diff --git a/phenopacket-tools-validator-core/src/main/java/org/phenopackets/phenopackettools/validator/core/PhenopacketValidator.java b/phenopacket-tools-validator-core/src/main/java/org/phenopackets/phenopackettools/validator/core/PhenopacketValidator.java
index dda03f96..6d38d083 100644
--- a/phenopacket-tools-validator-core/src/main/java/org/phenopackets/phenopackettools/validator/core/PhenopacketValidator.java
+++ b/phenopacket-tools-validator-core/src/main/java/org/phenopackets/phenopackettools/validator/core/PhenopacketValidator.java
@@ -5,21 +5,21 @@
import java.util.List;
/**
- * {@link PhenopacketValidator} validates a top-level component of Phenopacket schema.
- *
- * The top-level component must be one of the following types:
- *
- * - {@link org.phenopackets.schema.v2.Phenopacket}
- * - {@link org.phenopackets.schema.v2.Family}
- * - {@link org.phenopackets.schema.v2.Cohort}
- *
+ * {@link PhenopacketValidator} represents a single step of the validation workflow.
+ * The validator checks a top-level component of Phenopacket Schema.
*
- * @param type of the top-level component.
+ * @param type of the top-level element of the Phenopacket Schema.
*/
public interface PhenopacketValidator {
+ /**
+ * @return description of the validator and the validation logic.
+ */
ValidatorInfo validatorInfo();
+ /**
+ * Validate the {@code component} and summarize the results into a {@link List} of {@link ValidationResult}s.
+ */
List validate(T component);
}
diff --git a/phenopacket-tools-validator-core/src/main/java/org/phenopackets/phenopackettools/validator/core/ValidationLevel.java b/phenopacket-tools-validator-core/src/main/java/org/phenopackets/phenopackettools/validator/core/ValidationLevel.java
index 87fc622b..6958be59 100644
--- a/phenopacket-tools-validator-core/src/main/java/org/phenopackets/phenopackettools/validator/core/ValidationLevel.java
+++ b/phenopacket-tools-validator-core/src/main/java/org/phenopackets/phenopackettools/validator/core/ValidationLevel.java
@@ -1,5 +1,8 @@
package org.phenopackets.phenopackettools.validator.core;
+/**
+ * {@code ValidationLevel} represents a severity level for {@link ValidationResult}.
+ */
public enum ValidationLevel {
/**
diff --git a/phenopacket-tools-validator-core/src/main/java/org/phenopackets/phenopackettools/validator/core/ValidationResult.java b/phenopacket-tools-validator-core/src/main/java/org/phenopackets/phenopackettools/validator/core/ValidationResult.java
index d4e77eb3..f8193cd6 100644
--- a/phenopacket-tools-validator-core/src/main/java/org/phenopackets/phenopackettools/validator/core/ValidationResult.java
+++ b/phenopacket-tools-validator-core/src/main/java/org/phenopackets/phenopackettools/validator/core/ValidationResult.java
@@ -1,19 +1,31 @@
package org.phenopackets.phenopackettools.validator.core;
+/**
+ * {@code ValidationResult} contains results of a single validation step performed by a {@link PhenopacketValidator}.
+ */
public interface ValidationResult {
+ /**
+ * Create a {@link ValidationLevel#WARNING} result from given data.
+ */
static ValidationResult warning(ValidatorInfo validatorInfo,
String category,
String message) {
return of(validatorInfo, ValidationLevel.WARNING, category, message);
}
+ /**
+ * Create a {@link ValidationLevel#ERROR} result from given data.
+ */
static ValidationResult error(ValidatorInfo validatorInfo,
String category,
String message) {
return of(validatorInfo, ValidationLevel.ERROR, category, message);
}
+ /**
+ * Create a {@code ValidationResult} from given data.
+ */
static ValidationResult of(ValidatorInfo validatorInfo,
ValidationLevel level,
String category,
diff --git a/phenopacket-tools-validator-core/src/main/java/org/phenopackets/phenopackettools/validator/core/ValidationResults.java b/phenopacket-tools-validator-core/src/main/java/org/phenopackets/phenopackettools/validator/core/ValidationResults.java
index debd46ac..da4aa633 100644
--- a/phenopacket-tools-validator-core/src/main/java/org/phenopackets/phenopackettools/validator/core/ValidationResults.java
+++ b/phenopacket-tools-validator-core/src/main/java/org/phenopackets/phenopackettools/validator/core/ValidationResults.java
@@ -4,9 +4,12 @@
import java.util.List;
/**
- * {@link ValidationResults} contain validation results for one Phenopacket schema top-level element
- * (phenopacket, family, or cohort).
- * The results contain info regarding which validators were run and the issues found during the validation.
+ * {@code ValidationResults} contain validation results for one Phenopacket schema top-level element
+ * ({@link org.phenopackets.schema.v2.Phenopacket}, {@link org.phenopackets.schema.v2.Family},
+ * or {@link org.phenopackets.schema.v2.Cohort}).
+ *
+ * The results contain info regarding which validators were run ({@link #validators()}) and the issues found during
+ * the validation ({@link #validationResults()}).
*/
public interface ValidationResults {
@@ -42,6 +45,9 @@ default boolean isValid() {
return validationResults().isEmpty();
}
+ /**
+ * A builder for creating {@link ValidationResults}.
+ */
class Builder {
private final List validators = new ArrayList<>();
diff --git a/phenopacket-tools-validator-core/src/main/java/org/phenopackets/phenopackettools/validator/core/ValidationWorkflowDispatcher.java b/phenopacket-tools-validator-core/src/main/java/org/phenopackets/phenopackettools/validator/core/ValidationWorkflowDispatcher.java
index 4591051a..e90b9baf 100644
--- a/phenopacket-tools-validator-core/src/main/java/org/phenopackets/phenopackettools/validator/core/ValidationWorkflowDispatcher.java
+++ b/phenopacket-tools-validator-core/src/main/java/org/phenopackets/phenopackettools/validator/core/ValidationWorkflowDispatcher.java
@@ -5,21 +5,96 @@
import org.phenopackets.schema.v2.Phenopacket;
/**
- * {@link ValidationWorkflowDispatcher} exposes endpoints for validating top-level elements of Phenopacket schema
+ * {@link ValidationWorkflowDispatcher} exposes endpoints for validating top-level elements of Phenopacket Schema
* and dispatches the data into the appropriate {@link ValidationWorkflowRunner}.
*/
public interface ValidationWorkflowDispatcher {
+ static ValidationWorkflowDispatcher of(ValidationWorkflowRunner phenopacketValidationRunner,
+ ValidationWorkflowRunner familyValidationRunner,
+ ValidationWorkflowRunner cohortValidationRunner) {
+ return new ValidationWorkflowDispatcherImpl(phenopacketValidationRunner, familyValidationRunner, cohortValidationRunner);
+ }
+
+ /**
+ * Validate a phenopacket starting from a pile of bytes.
+ *
+ * @param bytes that can represent a phenopacket in either
+ * of {@link org.phenopackets.phenopackettools.core.PhenopacketFormat}s.
+ * @return validation results.
+ */
ValidationResults validatePhenopacket(byte[] bytes);
+
+ /**
+ * Validate a phenopacket starting from a string.
+ *
+ * @param string that can represent a phenopacket either
+ * in {@link org.phenopackets.phenopackettools.core.PhenopacketFormat#JSON}
+ * or {@link org.phenopackets.phenopackettools.core.PhenopacketFormat#YAML} format.
+ * @return validation results.
+ */
ValidationResults validatePhenopacket(String string);
+
+ /**
+ * Validate a phenopacket starting from a protobuf object.
+ *
+ * @param phenopacket to be validated.
+ * @return validation results.
+ */
ValidationResults validatePhenopacket(Phenopacket phenopacket);
+ /**
+ * Validate a family starting from a pile of bytes.
+ *
+ * @param bytes that can represent a family in either
+ * of {@link org.phenopackets.phenopackettools.core.PhenopacketFormat}s.
+ * @return validation results.
+ */
ValidationResults validateFamily(byte[] bytes);
+
+ /**
+ * Validate a family starting from a string.
+ *
+ * @param string that can represent a family either
+ * in {@link org.phenopackets.phenopackettools.core.PhenopacketFormat#JSON}
+ * or {@link org.phenopackets.phenopackettools.core.PhenopacketFormat#YAML} format.
+ * @return validation results.
+ */
ValidationResults validateFamily(String string);
+
+ /**
+ * Validate a family starting from a protobuf object.
+ *
+ * @param family to be validated.
+ * @return validation results.
+ */
ValidationResults validateFamily(Family family);
+ /**
+ * Validate a cohort starting from a pile of bytes.
+ *
+ * @param bytes that can represent a cohort in either
+ * of {@link org.phenopackets.phenopackettools.core.PhenopacketFormat}s.
+ * @return validation results.
+ */
ValidationResults validateCohort(byte[] bytes);
+
+ /**
+ * Validate a cohort starting from a string.
+ *
+ * @param string that can represent a cohort either
+ * in {@link org.phenopackets.phenopackettools.core.PhenopacketFormat#JSON}
+ * or {@link org.phenopackets.phenopackettools.core.PhenopacketFormat#YAML} format.
+ * @return validation results.
+ */
ValidationResults validateCohort(String string);
+
+ /**
+ * Validate a cohort starting from a protobuf object.
+ *
+ * @param cohort to be validated.
+ * @return validation results.
+ */
ValidationResults validateCohort(Cohort cohort);
}
diff --git a/phenopacket-tools-validator-core/src/main/java/org/phenopackets/phenopackettools/validator/core/ValidationWorkflowDispatcherImpl.java b/phenopacket-tools-validator-core/src/main/java/org/phenopackets/phenopackettools/validator/core/ValidationWorkflowDispatcherImpl.java
index 184399c8..c15a29fc 100644
--- a/phenopacket-tools-validator-core/src/main/java/org/phenopackets/phenopackettools/validator/core/ValidationWorkflowDispatcherImpl.java
+++ b/phenopacket-tools-validator-core/src/main/java/org/phenopackets/phenopackettools/validator/core/ValidationWorkflowDispatcherImpl.java
@@ -10,24 +10,23 @@
* A {@link ValidationWorkflowDispatcher} implementation that uses 3 {@link ValidationWorkflowRunner} to validate
* top-level elements of the Phenopacket schema.
*/
-public class ValidationWorkflowDispatcherImpl implements ValidationWorkflowDispatcher {
+class ValidationWorkflowDispatcherImpl implements ValidationWorkflowDispatcher {
private final ValidationWorkflowRunner phenopacketValidationRunner;
private final ValidationWorkflowRunner familyValidationRunner;
private final ValidationWorkflowRunner cohortValidationRunner;
-
- public ValidationWorkflowDispatcherImpl(ValidationWorkflowRunner phenopacketValidationRunner,
- ValidationWorkflowRunner familyValidationRunner,
- ValidationWorkflowRunner cohortValidationRunner) {
+ ValidationWorkflowDispatcherImpl(ValidationWorkflowRunner phenopacketValidationRunner,
+ ValidationWorkflowRunner familyValidationRunner,
+ ValidationWorkflowRunner cohortValidationRunner) {
this.phenopacketValidationRunner = Objects.requireNonNull(phenopacketValidationRunner);
this.familyValidationRunner = Objects.requireNonNull(familyValidationRunner);
this.cohortValidationRunner = Objects.requireNonNull(cohortValidationRunner);
}
@Override
- public ValidationResults validatePhenopacket(Phenopacket phenopacket) {
- return phenopacketValidationRunner.validate(phenopacket);
+ public ValidationResults validatePhenopacket(byte[] bytes) {
+ return phenopacketValidationRunner.validate(bytes);
}
@Override
@@ -36,13 +35,13 @@ public ValidationResults validatePhenopacket(String string) {
}
@Override
- public ValidationResults validatePhenopacket(byte[] bytes) {
- return phenopacketValidationRunner.validate(bytes);
+ public ValidationResults validatePhenopacket(Phenopacket phenopacket) {
+ return phenopacketValidationRunner.validate(phenopacket);
}
@Override
- public ValidationResults validateFamily(Family family) {
- return familyValidationRunner.validate(family);
+ public ValidationResults validateFamily(byte[] bytes) {
+ return familyValidationRunner.validate(bytes);
}
@Override
@@ -51,13 +50,13 @@ public ValidationResults validateFamily(String string) {
}
@Override
- public ValidationResults validateFamily(byte[] bytes) {
- return familyValidationRunner.validate(bytes);
+ public ValidationResults validateFamily(Family family) {
+ return familyValidationRunner.validate(family);
}
@Override
- public ValidationResults validateCohort(Cohort cohort) {
- return cohortValidationRunner.validate(cohort);
+ public ValidationResults validateCohort(byte[] bytes) {
+ return cohortValidationRunner.validate(bytes);
}
@Override
@@ -66,8 +65,8 @@ public ValidationResults validateCohort(String string) {
}
@Override
- public ValidationResults validateCohort(byte[] bytes) {
- return cohortValidationRunner.validate(bytes);
+ public ValidationResults validateCohort(Cohort cohort) {
+ return cohortValidationRunner.validate(cohort);
}
}
diff --git a/phenopacket-tools-validator-core/src/main/java/org/phenopackets/phenopackettools/validator/core/ValidationWorkflowRunner.java b/phenopacket-tools-validator-core/src/main/java/org/phenopackets/phenopackettools/validator/core/ValidationWorkflowRunner.java
index 82a7448d..9671cbc7 100644
--- a/phenopacket-tools-validator-core/src/main/java/org/phenopackets/phenopackettools/validator/core/ValidationWorkflowRunner.java
+++ b/phenopacket-tools-validator-core/src/main/java/org/phenopackets/phenopackettools/validator/core/ValidationWorkflowRunner.java
@@ -8,16 +8,26 @@
import java.util.List;
/**
- * {@link ValidationWorkflowRunner} validates selected top-level element of the Phenopacket schema.
+ * {@link ValidationWorkflowRunner} validates selected top-level element of the Phenopacket Schema.
*
* The validation is performed on 3 input types: {@link #validate(MessageOrBuilder)} validates an existing top-level
- * element, {@link #validate(String)} validates input formatted in JSON format,
- * and {@link #validate(byte[])} validates bytes that can be either in JSON or Protobuf binary exchange format.
+ * element, {@link #validate(String)} validates input formatted either
+ * in {@link org.phenopackets.phenopackettools.core.PhenopacketFormat#JSON}
+ * or {@link org.phenopackets.phenopackettools.core.PhenopacketFormat#YAML},
+ * and {@link #validate(byte[])} validates a pile of bytes that can be in either
+ * of the {@link org.phenopackets.phenopackettools.core.PhenopacketFormat}s.
*
* Validator provides a list with {@link ValidatorInfo} that describes validations
* done by the {@link ValidationWorkflowRunner}.
+ *
+ * The validation is generally done in 2 phases, syntax and semantic phases.
+ * The syntax phase checks if the building blocks meet the requirements independently
+ * (e.g. all required fields are defined for a {@link org.phenopackets.schema.v2.core.Resource}).
+ * The semantic validation checks for presence of errors in the context of the entire top-level element
+ * (e.g. a phenopacket contains an HPO term but an HPO {@link org.phenopackets.schema.v2.core.Resource} is missing
+ * in {@link org.phenopackets.schema.v2.core.MetaData}).
*
- * @param type of the top-level element of the Phenopacket schema.
+ * @param type of the top-level element of the Phenopacket Schema.
*/
public interface ValidationWorkflowRunner {
@@ -27,10 +37,30 @@ public interface ValidationWorkflowRunner {
*/
List validators();
+ /**
+ * Validate a top-level element starting from a pile of bytes.
+ *
+ * @param payload top-level element in one of the {@link org.phenopackets.phenopackettools.core.PhenopacketFormat}s.
+ * @return the validation results.
+ */
ValidationResults validate(byte[] payload);
- ValidationResults validate(String json);
+ /**
+ * Validate a top-level element starting from a string.
+ *
+ * @param value top-level element in either {@link org.phenopackets.phenopackettools.core.PhenopacketFormat#JSON}
+ * or {@link org.phenopackets.phenopackettools.core.PhenopacketFormat#YAML}.
+ * @return the validation results.
+ */
+ // TODO - include YAML validation.
+ ValidationResults validate(String value);
+ /**
+ * Validate a top-level element starting from a protobuf item.
+ *
+ * @param item the top-level element as protobuf item.
+ * @return the validation results.
+ */
ValidationResults validate(T item);
default ValidationResults validate(InputStream is) throws IOException {
diff --git a/phenopacket-tools-validator-core/src/main/java/org/phenopackets/phenopackettools/validator/core/ValidationWorkflowRunnerBuilder.java b/phenopacket-tools-validator-core/src/main/java/org/phenopackets/phenopackettools/validator/core/ValidationWorkflowRunnerBuilder.java
new file mode 100644
index 00000000..31d6263f
--- /dev/null
+++ b/phenopacket-tools-validator-core/src/main/java/org/phenopackets/phenopackettools/validator/core/ValidationWorkflowRunnerBuilder.java
@@ -0,0 +1,72 @@
+package org.phenopackets.phenopackettools.validator.core;
+
+import com.google.protobuf.MessageOrBuilder;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * The base builder for constructing {@link ValidationWorkflowRunner}. The builder keeps track of
+ * the syntax and semantic validators.
+ *
+ * @param type of the top-level element of the Phenopacket Schema.
+ */
+public abstract class ValidationWorkflowRunnerBuilder {
+
+ protected final List> syntaxValidators = new ArrayList<>();
+ protected final List> semanticValidators = new ArrayList<>();
+
+ /**
+ * Add a syntax validator.
+ *
+ * @param syntaxValidator the syntax validator
+ * @return the builder
+ */
+ public ValidationWorkflowRunnerBuilder addSyntaxValidator(PhenopacketValidator syntaxValidator) {
+ this.syntaxValidators.add(syntaxValidator);
+ return this;
+ }
+
+ /**
+ * Add syntax validators in bulk.
+ *
+ * @param validators the syntax validators
+ * @return the builder
+ */
+ public ValidationWorkflowRunnerBuilder addAllSyntaxValidators(List> validators) {
+ // A slightly more efficient implementation comparing to the default method on the interface.
+ this.syntaxValidators.addAll(validators);
+ return this;
+ }
+
+ /**
+ * Add a semantic validator.
+ *
+ * @param semanticValidator the semantic validator
+ * @return the builder
+ */
+ public ValidationWorkflowRunnerBuilder addSemanticValidator(PhenopacketValidator semanticValidator) {
+ this.semanticValidators.add(semanticValidator);
+ return this;
+ }
+
+ /**
+ * Add semantic validators in bulk.
+ *
+ * @param validators the semantic validators
+ * @return the builder
+ */
+ public ValidationWorkflowRunnerBuilder addAllSemanticValidators(List> validators) {
+ // A slightly more efficient implementation comparing to the default method on the interface.
+ this.semanticValidators.addAll(validators);
+ return this;
+ }
+
+ /**
+ * Finish building of the {@link ValidationWorkflowRunner}.
+ *
+ * @return the runner
+ */
+ public abstract ValidationWorkflowRunner build();
+
+}
diff --git a/phenopacket-tools-validator-core/src/main/java/org/phenopackets/phenopackettools/validator/core/ValidatorInfo.java b/phenopacket-tools-validator-core/src/main/java/org/phenopackets/phenopackettools/validator/core/ValidatorInfo.java
index f0d199d1..3714b4ae 100644
--- a/phenopacket-tools-validator-core/src/main/java/org/phenopackets/phenopackettools/validator/core/ValidatorInfo.java
+++ b/phenopacket-tools-validator-core/src/main/java/org/phenopackets/phenopackettools/validator/core/ValidatorInfo.java
@@ -1,22 +1,12 @@
package org.phenopackets.phenopackettools.validator.core;
/**
- * Information regarding validator.
+ * A description of a {@link PhenopacketValidator}.
*/
public interface ValidatorInfo {
- static ValidatorInfo genericJsonSchema() {
- return ValidatorInfoDefault.GENERIC;
- }
-
- /**
- * This class implements additional validation of a phenopacket that is intended to be used
- * for HPO rare disease phenotyping. By assumption, the phenopacket will have been first
- * checked against the {@link ValidatorInfo#genericJsonSchema()} specification. This class performs validation with the
- * file {@code hpo-rare-disease-schema.json}.
- */
- static ValidatorInfo rareDiseaseValidation() {
- return ValidatorInfoDefault.RARE_DISEASE_VALIDATOR;
+ static ValidatorInfo baseSyntaxValidation() {
+ return ValidatorInfoDefault.BASE;
}
/**
diff --git a/phenopacket-tools-validator-core/src/main/java/org/phenopackets/phenopackettools/validator/core/ValidatorInfoDefault.java b/phenopacket-tools-validator-core/src/main/java/org/phenopackets/phenopackettools/validator/core/ValidatorInfoDefault.java
index 5ec46e0c..ce2d44e9 100644
--- a/phenopacket-tools-validator-core/src/main/java/org/phenopackets/phenopackettools/validator/core/ValidatorInfoDefault.java
+++ b/phenopacket-tools-validator-core/src/main/java/org/phenopackets/phenopackettools/validator/core/ValidatorInfoDefault.java
@@ -3,9 +3,8 @@
record ValidatorInfoDefault(String validatorId,
String validatorName,
String description) implements ValidatorInfo {
- // TODO - add descriptions
- static final ValidatorInfoDefault GENERIC = new ValidatorInfoDefault("GENERIC", "Validation of a generic Phenopacket", "");
- static final ValidatorInfoDefault RARE_DISEASE_VALIDATOR = new ValidatorInfoDefault("RARE_DISEASE_VALIDATOR", "Validation of rare disease Phenopacket constraints", "");
- static final ValidatorInfoDefault INPUT_VALIDATOR = new ValidatorInfoDefault("Input", "Input of phenopacket data", "Validation of data format");
+
+ static final ValidatorInfoDefault BASE = new ValidatorInfoDefault("BaseValidator", "Base syntax validator", "The base syntax validation of a phenopacket, family, or cohort");
+ static final ValidatorInfoDefault INPUT_VALIDATOR = new ValidatorInfoDefault("InputValidator", "Data format validator", "The validator for checking data format issues (e.g. presence of a required field in JSON document)");
}
diff --git a/phenopacket-tools-validator-core/src/main/java/org/phenopackets/phenopackettools/validator/core/convert/BaseConverter.java b/phenopacket-tools-validator-core/src/main/java/org/phenopackets/phenopackettools/validator/core/convert/BaseConverter.java
index 8b5ffc80..5713bb52 100644
--- a/phenopacket-tools-validator-core/src/main/java/org/phenopackets/phenopackettools/validator/core/convert/BaseConverter.java
+++ b/phenopacket-tools-validator-core/src/main/java/org/phenopackets/phenopackettools/validator/core/convert/BaseConverter.java
@@ -4,18 +4,19 @@
import com.google.protobuf.MessageOrBuilder;
import com.google.protobuf.util.JsonFormat;
import org.phenopackets.phenopackettools.validator.core.PhenopacketFormatConverter;
+import org.phenopackets.phenopackettools.validator.core.except.PhenopacketValidatorRuntimeException;
abstract class BaseConverter implements PhenopacketFormatConverter {
- protected final JsonFormat.Parser parser = JsonFormat.parser();
- protected final JsonFormat.Printer printer = JsonFormat.printer();
+ protected static final JsonFormat.Parser parser = JsonFormat.parser();
+ protected static final JsonFormat.Printer printer = JsonFormat.printer();
@Override
public String toJson(T item) {
try {
return printer.print(item);
} catch (InvalidProtocolBufferException e) {
- throw new RuntimeException(e);
+ throw new PhenopacketValidatorRuntimeException(e);
}
}
diff --git a/phenopacket-tools-validator-core/src/main/java/org/phenopackets/phenopackettools/validator/core/convert/package-info.java b/phenopacket-tools-validator-core/src/main/java/org/phenopackets/phenopackettools/validator/core/convert/package-info.java
new file mode 100644
index 00000000..ec95fd57
--- /dev/null
+++ b/phenopacket-tools-validator-core/src/main/java/org/phenopackets/phenopackettools/validator/core/convert/package-info.java
@@ -0,0 +1,5 @@
+/**
+ * A module-private package with {@link org.phenopackets.phenopackettools.validator.core.PhenopacketFormatConverter}
+ * implementations.
+ */
+package org.phenopackets.phenopackettools.validator.core.convert;
\ No newline at end of file
diff --git a/phenopacket-tools-validator-core/src/main/java/org/phenopackets/phenopackettools/validator/core/except/PhenopacketValidatorException.java b/phenopacket-tools-validator-core/src/main/java/org/phenopackets/phenopackettools/validator/core/except/PhenopacketValidatorException.java
index c55bc234..a4ea709a 100644
--- a/phenopacket-tools-validator-core/src/main/java/org/phenopackets/phenopackettools/validator/core/except/PhenopacketValidatorException.java
+++ b/phenopacket-tools-validator-core/src/main/java/org/phenopackets/phenopackettools/validator/core/except/PhenopacketValidatorException.java
@@ -1,6 +1,8 @@
package org.phenopackets.phenopackettools.validator.core.except;
-public class PhenopacketValidatorException extends Exception {
+import org.phenopackets.phenopackettools.core.PhenopacketToolsException;
+
+public class PhenopacketValidatorException extends PhenopacketToolsException {
public PhenopacketValidatorException() {
super();
}
diff --git a/phenopacket-tools-validator-core/src/main/java/org/phenopackets/phenopackettools/validator/core/except/PhenopacketValidatorRuntimeException.java b/phenopacket-tools-validator-core/src/main/java/org/phenopackets/phenopackettools/validator/core/except/PhenopacketValidatorRuntimeException.java
index 663d3e15..30f728d0 100644
--- a/phenopacket-tools-validator-core/src/main/java/org/phenopackets/phenopackettools/validator/core/except/PhenopacketValidatorRuntimeException.java
+++ b/phenopacket-tools-validator-core/src/main/java/org/phenopackets/phenopackettools/validator/core/except/PhenopacketValidatorRuntimeException.java
@@ -1,6 +1,8 @@
package org.phenopackets.phenopackettools.validator.core.except;
-public class PhenopacketValidatorRuntimeException extends RuntimeException {
+import org.phenopackets.phenopackettools.core.PhenopacketToolsRuntimeException;
+
+public class PhenopacketValidatorRuntimeException extends PhenopacketToolsRuntimeException {
public PhenopacketValidatorRuntimeException() {
super();
diff --git a/phenopacket-tools-validator-core/src/main/java/org/phenopackets/phenopackettools/validator/core/except/package-info.java b/phenopacket-tools-validator-core/src/main/java/org/phenopackets/phenopackettools/validator/core/except/package-info.java
new file mode 100644
index 00000000..bbd53a90
--- /dev/null
+++ b/phenopacket-tools-validator-core/src/main/java/org/phenopackets/phenopackettools/validator/core/except/package-info.java
@@ -0,0 +1,4 @@
+/**
+ * A package with exceptions that can be thrown by the validation code.
+ */
+package org.phenopackets.phenopackettools.validator.core.except;
\ No newline at end of file
diff --git a/phenopacket-tools-validator-core/src/main/java/org/phenopackets/phenopackettools/validator/core/metadata/BaseMetaDataValidator.java b/phenopacket-tools-validator-core/src/main/java/org/phenopackets/phenopackettools/validator/core/metadata/BaseMetaDataValidator.java
index 4aee2f6d..8a053cec 100644
--- a/phenopacket-tools-validator-core/src/main/java/org/phenopackets/phenopackettools/validator/core/metadata/BaseMetaDataValidator.java
+++ b/phenopacket-tools-validator-core/src/main/java/org/phenopackets/phenopackettools/validator/core/metadata/BaseMetaDataValidator.java
@@ -18,8 +18,8 @@ abstract class BaseMetaDataValidator implements Phen
private static final ValidatorInfo VALIDATOR_INFO = ValidatorInfo.of(
"MetaDataValidator",
- "MetaDataValidator for Phenopacket, Family, and Cohort",
- "Validate that the MetaData section includes information about all ontologies used");
+ "MetaData validator",
+ "Validate that the MetaData section describes all used ontologies");
@Override
public ValidatorInfo validatorInfo() {
diff --git a/phenopacket-tools-validator-core/src/main/java/org/phenopackets/phenopackettools/validator/core/package-info.java b/phenopacket-tools-validator-core/src/main/java/org/phenopackets/phenopackettools/validator/core/package-info.java
new file mode 100644
index 00000000..c82ea30b
--- /dev/null
+++ b/phenopacket-tools-validator-core/src/main/java/org/phenopackets/phenopackettools/validator/core/package-info.java
@@ -0,0 +1,41 @@
+/**
+ * The package provides APIs and default implementations of phenopacket validation.
+ *
+ *
Actors
+ * This section describes the actors of the validation workflow (the classes for representing behavior
+ * for "doing stuff"), starting from the basic elements.
+ *
+ * {@link org.phenopackets.phenopackettools.validator.core.PhenopacketFormatConverters} is a static factory class
+ * for providing {@link org.phenopackets.phenopackettools.validator.core.PhenopacketFormatConverter}s to convert
+ * the top-level elements of Phenopacket Schema between the supported
+ * {@link org.phenopackets.phenopackettools.core.PhenopacketFormat}s.
+ *
+ * {@link org.phenopackets.phenopackettools.validator.core.PhenopacketValidator} represents a single step
+ * of the validation workflow.
+ *
+ * {@link org.phenopackets.phenopackettools.validator.core.ValidationWorkflowRunner} applies
+ * the {@link org.phenopackets.phenopackettools.validator.core.PhenopacketValidator}s of the validation workflow in
+ * the correct order, ensuring the base validation is always run as first.
+ * The {@link org.phenopackets.phenopackettools.validator.core.ValidationWorkflowRunner} validates
+ * a top-level element.
+ *
+ * The {@link org.phenopackets.phenopackettools.validator.core.ValidationWorkflowDispatcher} exposes methods
+ * for validating all top-level elements of the Phenopacket Schema.
+ *
+ *
Value objects
+ * The package includes stateful objects with no complex behavior starting from the most complex objects.
+ *
+ * The {@link org.phenopackets.phenopackettools.validator.core.ValidationWorkflowRunner}
+ * and {@link org.phenopackets.phenopackettools.validator.core.ValidationWorkflowDispatcher} return
+ * {@link org.phenopackets.phenopackettools.validator.core.ValidationResults}, a container with results
+ * of the validation workflow.
+ *
+ * {@link org.phenopackets.phenopackettools.validator.core.ValidationResult} contains results of
+ * a single validation step.
+ *
+ * {@link org.phenopackets.phenopackettools.validator.core.ValidatorInfo} describes
+ * the {@link org.phenopackets.phenopackettools.validator.core.PhenopacketValidator}.
+ *
+ * {@link org.phenopackets.phenopackettools.validator.core.ValidationLevel}
+ */
+package org.phenopackets.phenopackettools.validator.core;
\ No newline at end of file
diff --git a/phenopacket-tools-validator-core/src/main/java/org/phenopackets/phenopackettools/validator/core/phenotype/BaseHpoPhenotypeValidator.java b/phenopacket-tools-validator-core/src/main/java/org/phenopackets/phenopackettools/validator/core/phenotype/BaseHpoPhenotypeValidator.java
deleted file mode 100644
index dad25e54..00000000
--- a/phenopacket-tools-validator-core/src/main/java/org/phenopackets/phenopackettools/validator/core/phenotype/BaseHpoPhenotypeValidator.java
+++ /dev/null
@@ -1,68 +0,0 @@
-package org.phenopackets.phenopackettools.validator.core.phenotype;
-
-import com.google.protobuf.MessageOrBuilder;
-import org.monarchinitiative.phenol.base.PhenolRuntimeException;
-import org.monarchinitiative.phenol.ontology.data.Ontology;
-import org.monarchinitiative.phenol.ontology.data.TermId;
-import org.phenopackets.phenopackettools.validator.core.*;
-import org.phenopackets.schema.v2.core.PhenotypicFeature;
-
-import java.util.Objects;
-import java.util.stream.Stream;
-
-abstract class BaseHpoPhenotypeValidator implements PhenopacketValidator {
-
- private static final ValidatorInfo VALIDATOR_INFO = ValidatorInfo.of(
- "HpoPhenotypeValidator",
- "HPO phenotypic feature validator",
- "Validate that HPO terms are well formatted, present, and non-obsolete based on the provided HPO");
- private static final String INVALID_TERM_ID = "Invalid TermId";
- private static final String OBSOLETED_TERM_ID = "Obsoleted TermId";
-
- private final Ontology hpo;
- private final String hpoVersion;
-
- public BaseHpoPhenotypeValidator(Ontology hpo) {
- this.hpo = Objects.requireNonNull(hpo);
- this.hpoVersion = this.hpo.getMetaInfo().getOrDefault("data-version", "HPO");
- }
-
- @Override
- public ValidatorInfo validatorInfo() {
- return VALIDATOR_INFO;
- }
-
- protected Stream extends ValidationResult> checkPhenotypeFeature(String individualId, PhenotypicFeature feature) {
- TermId termId;
- try {
- termId = TermId.of(feature.getType().getId());
- } catch (PhenolRuntimeException e) {
- // Should not really happen if JsonSchema validators are run upstream, but let's stay safe.
- String msg = "The %s found in '%s' is not a valid value".formatted(feature.getType().getId(), individualId);
- return Stream.of(
- ValidationResult.error(VALIDATOR_INFO, INVALID_TERM_ID, msg)
- );
- }
-
- // Check if the HPO contains the term.
- if (!hpo.containsTerm(termId)) {
- String msg = "%s in '%s' not found in %s".formatted(termId.getValue(), individualId, hpoVersion);
- return Stream.of(
- ValidationResult.error(VALIDATOR_INFO, INVALID_TERM_ID, msg)
- );
- }
-
- // Check if the `termId` is a primary ID. // If not, this is a warning.
- TermId primaryId = hpo.getPrimaryTermId(termId);
- if (!primaryId.equals(termId)) {
- String msg = "Using obsoleted id (%s) instead of current primary id (%s) in '%s'"
- .formatted(termId.getValue(), primaryId.getValue(), individualId);
- return Stream.of(
- ValidationResult.warning(VALIDATOR_INFO, OBSOLETED_TERM_ID, msg)
- );
- }
-
- return Stream.empty();
- }
-
-}
diff --git a/phenopacket-tools-validator-core/src/main/java/org/phenopackets/phenopackettools/validator/core/phenotype/HpoOrganSystems.java b/phenopacket-tools-validator-core/src/main/java/org/phenopackets/phenopackettools/validator/core/phenotype/HpoOrganSystems.java
new file mode 100644
index 00000000..22554677
--- /dev/null
+++ b/phenopacket-tools-validator-core/src/main/java/org/phenopackets/phenopackettools/validator/core/phenotype/HpoOrganSystems.java
@@ -0,0 +1,51 @@
+package org.phenopackets.phenopackettools.validator.core.phenotype;
+
+import org.monarchinitiative.phenol.ontology.data.TermId;
+
+/**
+ * A class with constants that correspond to the upper-level HPO organ-system phenotypic abnormalities.
+ *
+ * The constants can be used together with the
+ * {@link org.phenopackets.phenopackettools.validator.core.phenotype.HpoPhenotypeValidators.OrganSystem} validators,
+ * which enforce that a phenopacket contains at least one term from a set of organ systems (observed or excluded).
+ *
+ * Note that users can also use any HPO term in this way -- the validator will enforce that the phenopacket
+ * has an HPO term that descends from it, but the most common use cases are these organ-level terms.
+ *
+ *
+ * Ontology hpo = ...; // get the ontology
+ * var requiredOrganSystems = Set.of(BLOOD, CARDIOVASCULAR, SKELETAL);
+ * var validator = HpoPhenotypeValidators.OrganSystem.phenopacketHpoOrganSystemValidator(hpo, requiredOrganSystems);
+ *
+ */
+public class HpoOrganSystems {
+
+ public static final TermId ABNORMAL_CELLULAR = TermId.of("HP:0025354");
+ public static final TermId BLOOD = TermId.of("HP:0001871");
+ public static final TermId CONNECTIVE_TISSUE = TermId.of("HP:0003549");
+ public static final TermId HEAD_AND_NECK = TermId.of("HP:0000152");
+ public static final TermId LIMBS = TermId.of("HP:0040064");
+ public static final TermId METABOLISM = TermId.of("HP:0001939");
+ public static final TermId PRENATAL = TermId.of("HP:0001197");
+ public static final TermId BREAST = TermId.of("HP:0000769");
+ public static final TermId CARDIOVASCULAR = TermId.of("HP:0001626");
+ public static final TermId DIGESTIVE = TermId.of("HP:0025031");
+ public static final TermId EAR = TermId.of("HP:0000598");
+ public static final TermId ENDOCRINE = TermId.of("HP:0000818");
+ public static final TermId EYE = TermId.of("HP:0000478");
+ public static final TermId GENITOURINARY = TermId.of("HP:0000119");
+ public static final TermId IMMUNOLOGY = TermId.of("HP:0002715");
+ public static final TermId INTEGUMENT = TermId.of("HP:0001574");
+ public static final TermId MUSCLE = TermId.of("HP:0003011");
+ public static final TermId NERVOUS_SYSTEM = TermId.of("HP:0000707");
+ public static final TermId RESPIRATORY = TermId.of("HP:0002086");
+ public static final TermId SKELETAL = TermId.of("HP:0000924");
+ public static final TermId THORACIC_CAVITY = TermId.of("HP:0045027");
+ public static final TermId VOICE = TermId.of("HP:0001608");
+ public static final TermId CONSTITUTIONAL = TermId.of("HP:0025142");
+ public static final TermId GROWTH = TermId.of("HP:0001507");
+ public static final TermId NEOPLASM = TermId.of("HP:0002664");
+
+ private HpoOrganSystems() {
+ }
+}
diff --git a/phenopacket-tools-validator-core/src/main/java/org/phenopackets/phenopackettools/validator/core/phenotype/HpoPhenotypeValidators.java b/phenopacket-tools-validator-core/src/main/java/org/phenopackets/phenopackettools/validator/core/phenotype/HpoPhenotypeValidators.java
index 1dbd6f54..07c56347 100644
--- a/phenopacket-tools-validator-core/src/main/java/org/phenopackets/phenopackettools/validator/core/phenotype/HpoPhenotypeValidators.java
+++ b/phenopacket-tools-validator-core/src/main/java/org/phenopackets/phenopackettools/validator/core/phenotype/HpoPhenotypeValidators.java
@@ -1,9 +1,21 @@
package org.phenopackets.phenopackettools.validator.core.phenotype;
import org.monarchinitiative.phenol.ontology.data.Ontology;
+import org.monarchinitiative.phenol.ontology.data.TermId;
import org.phenopackets.phenopackettools.validator.core.PhenopacketValidator;
+import org.phenopackets.phenopackettools.validator.core.phenotype.ancestry.CohortHpoAncestryValidator;
+import org.phenopackets.phenopackettools.validator.core.phenotype.ancestry.FamilyHpoAncestryValidator;
+import org.phenopackets.phenopackettools.validator.core.phenotype.ancestry.PhenopacketHpoAncestryValidator;
+import org.phenopackets.phenopackettools.validator.core.phenotype.orgsys.CohortHpoOrganSystemValidator;
+import org.phenopackets.phenopackettools.validator.core.phenotype.orgsys.FamilyHpoOrganSystemValidator;
+import org.phenopackets.phenopackettools.validator.core.phenotype.orgsys.PhenopacketHpoOrganSystemValidator;
+import org.phenopackets.phenopackettools.validator.core.phenotype.primary.CohortHpoPhenotypeValidator;
+import org.phenopackets.phenopackettools.validator.core.phenotype.primary.FamilyHpoPhenotypeValidator;
+import org.phenopackets.phenopackettools.validator.core.phenotype.primary.PhenopacketHpoPhenotypeValidator;
import org.phenopackets.schema.v2.*;
+import java.util.Collection;
+
/**
* Static factory class for getting {@link PhenopacketValidator}s for top-level Phenopacket schema components.
*/
@@ -17,27 +29,179 @@ private HpoPhenotypeValidators() {
* Get {@link PhenopacketValidator} to validate {@link Phenopacket} using provided {@link Ontology}.
*
* @param hpo HPO ontology
+ * @deprecated use {@link Primary#phenopacketHpoPhenotypeValidator(Ontology)} instead
*/
+ // TODO - remove prior v1
+ @Deprecated(forRemoval = true)
public static PhenopacketValidator phenopacketHpoPhenotypeValidator(Ontology hpo) {
- return new PhenopacketHpoPhenotypeValidator(hpo);
+ return Primary.phenopacketHpoPhenotypeValidator(hpo);
}
/**
- * Get {@link PhenopacketValidator} to validate {@link Family} using provided {@link Ontology}.
+ * Get {@link PhenopacketValidator} for validate {@link Family} using provided {@link Ontology}.
*
* @param hpo HPO ontology
+ * @deprecated use {@link Primary#familyHpoPhenotypeValidator(Ontology)} instead
*/
+ // TODO - remove prior v1
+ @Deprecated(forRemoval = true)
public static PhenopacketValidator familyHpoPhenotypeValidator(Ontology hpo) {
- return new FamilyHpoPhenotypeValidator(hpo);
+ return Primary.familyHpoPhenotypeValidator(hpo);
}
/**
- * Get {@link PhenopacketValidator} to validate {@link Cohort} using provided {@link Ontology}.
+ * Get {@link PhenopacketValidator} for performing primary validation {@link Cohort} using provided {@link Ontology},
+ * as described in {@link org.phenopackets.phenopackettools.validator.core.phenotype.primary.AbstractHpoPhenotypeValidator}.
*
* @param hpo HPO ontology
+ * @deprecated use {@link Primary#cohortHpoPhenotypeValidator(Ontology)} instead
*/
+ // TODO - remove prior v1
+ @Deprecated(forRemoval = true)
public static PhenopacketValidator cohortHpoPhenotypeValidator(Ontology hpo) {
- return new CohortHpoPhenotypeValidator(hpo);
+ return Primary.cohortHpoPhenotypeValidator(hpo);
+ }
+
+ /**
+ * A static factory class for providing {@link org.phenopackets.phenopackettools.validator.core.PhenopacketValidator}s
+ * that check if HPO terms of the Phenopacket schema elements are present in
+ * a given {@link org.monarchinitiative.phenol.ontology.data.Ontology} and if the terms are non-obsolete.
+ */
+ public static class Primary {
+ /**
+ * Get {@link PhenopacketValidator} to validate {@link Phenopacket} using provided {@link Ontology}.
+ *
+ * @param hpo HPO ontology
+ */
+ public static PhenopacketValidator phenopacketHpoPhenotypeValidator(Ontology hpo) {
+ return new PhenopacketHpoPhenotypeValidator(hpo);
+ }
+
+ /**
+ * Get {@link PhenopacketValidator} for validate {@link Family} using provided {@link Ontology}.
+ *
+ * @param hpo HPO ontology
+ */
+ public static PhenopacketValidator familyHpoPhenotypeValidator(Ontology hpo) {
+ return new FamilyHpoPhenotypeValidator(hpo);
+ }
+
+ /**
+ * Get {@link PhenopacketValidator} for performing primary validation {@link Cohort} using provided {@link Ontology},
+ * as described in {@link org.phenopackets.phenopackettools.validator.core.phenotype.primary.AbstractHpoPhenotypeValidator}.
+ *
+ * @param hpo HPO ontology
+ */
+ public static PhenopacketValidator cohortHpoPhenotypeValidator(Ontology hpo) {
+ return new CohortHpoPhenotypeValidator(hpo);
+ }
+ }
+
+ /**
+ * A static factory class for providing validators for pointing out violations of the annotation propagation rule.
+ *
+ * The validator checks observed and excluded phenotype terms. The observed terms are checked for a presence of
+ * an observed or an excluded ancestor, and a presence of such ancestor is pointed out as an error.
+ * For instance, Abnormality of finger or "NOT" Abnormality of finger must not be present
+ * in a patient annotated by Arachnodactyly. The most specific term (Arachnodactyly) must be used.
+ *
+ * For the excluded terms, the validator checks for presence of an excluded children. Here, the least specific term
+ * must be used. For instance, "NOT" Arachnodactyly must not be present in a patient annotated
+ * with "NOT" Abnormality of finger. Only the "NOT" Abnormality of finger must be used.
+ */
+ public static class Ancestry {
+
+ private Ancestry() {
+ }
+
+ /**
+ * Get {@link PhenopacketValidator} to validate ancestry {@link Phenopacket} using provided {@link Ontology}.
+ *
+ * @param hpo HPO ontology
+ */
+ public static PhenopacketValidator phenopacketHpoAncestryValidator(Ontology hpo) {
+ return new PhenopacketHpoAncestryValidator(hpo);
+ }
+
+ /**
+ * Get {@link PhenopacketValidator} to validate ancestry {@link Family} using provided {@link Ontology}.
+ *
+ * @param hpo HPO ontology
+ */
+ public static PhenopacketValidator familyHpoAncestryValidator(Ontology hpo) {
+ return new FamilyHpoAncestryValidator(hpo);
+ }
+
+ /**
+ * Get {@link PhenopacketValidator} to validate ancestry {@link Cohort} using provided {@link Ontology}.
+ *
+ * @param hpo HPO ontology
+ */
+ public static PhenopacketValidator cohortHpoAncestryValidator(Ontology hpo) {
+ return new CohortHpoAncestryValidator(hpo);
+ }
+ }
+
+ /**
+ * A static factory class for providing validators for checking annotation of organ systems.
+ *
+ * The validators check if each phenopacket or family/cohort member have annotation
+ * for an organ system represented by a top-level HPO term
+ * (e.g. Abnormality of limbs).
+ * The annotation comprises either one or more observed descendants
+ * (e.g. Arachnodactyly),
+ * or excluded top-level HPO term
+ * (NOT Abnormality of limbs).
+ *
+ */
+ public static class OrganSystem {
+ private OrganSystem() {
+ }
+
+ /**
+ * Get {@link PhenopacketValidator} to validate annotation of organ systems in a {@link Phenopacket}
+ * using provided {@link Ontology} and a collection of organ system {@link TermId}s.
+ *
+ * NOTE: the organ system {@link TermId} that is absent from the {@link Ontology} is disregarded
+ * and not used for validation.
+ *
+ * @param hpo HPO ontology
+ * @param organSystemTermIds a collection of HPO {@link TermId}s corresponding to organ systems.
+ */
+ public static PhenopacketValidator phenopacketHpoOrganSystemValidator(Ontology hpo,
+ Collection organSystemTermIds) {
+ return new PhenopacketHpoOrganSystemValidator(hpo, organSystemTermIds);
+ }
+
+ /**
+ * Get {@link PhenopacketValidator} to validate annotation of organ systems in a {@link Family}
+ * using provided {@link Ontology} and a collection of organ system {@link TermId}s.
+ *
+ * NOTE: the organ system {@link TermId} that is absent from the {@link Ontology} is disregarded
+ * and not used for validation.
+ *
+ * @param hpo HPO ontology
+ * @param organSystemTermIds a collection of HPO {@link TermId}s corresponding to organ systems.
+ */
+ public static PhenopacketValidator familyHpoOrganSystemValidator(Ontology hpo,
+ Collection organSystemTermIds) {
+ return new FamilyHpoOrganSystemValidator(hpo, organSystemTermIds);
+ }
+
+ /**
+ * Get {@link PhenopacketValidator} to validate annotation of organ systems in a {@link Cohort}
+ * using provided {@link Ontology} and a collection of organ system {@link TermId}s.
+ *
+ * NOTE: the organ system {@link TermId} that is absent from the {@link Ontology} is disregarded
+ * and not used for validation.
+ *
+ * @param hpo HPO ontology
+ * @param organSystemTermIds a collection of HPO {@link TermId}s corresponding to organ systems.
+ */
+ public static PhenopacketValidator cohortHpoOrganSystemValidator(Ontology hpo,
+ Collection organSystemTermIds) {
+ return new CohortHpoOrganSystemValidator(hpo, organSystemTermIds);
+ }
}
}
diff --git a/phenopacket-tools-validator-core/src/main/java/org/phenopackets/phenopackettools/validator/core/phenotype/ancestry/AbstractHpoAncestryValidator.java b/phenopacket-tools-validator-core/src/main/java/org/phenopackets/phenopackettools/validator/core/phenotype/ancestry/AbstractHpoAncestryValidator.java
new file mode 100644
index 00000000..4da56932
--- /dev/null
+++ b/phenopacket-tools-validator-core/src/main/java/org/phenopackets/phenopackettools/validator/core/phenotype/ancestry/AbstractHpoAncestryValidator.java
@@ -0,0 +1,133 @@
+package org.phenopackets.phenopackettools.validator.core.phenotype.ancestry;
+
+import com.google.protobuf.MessageOrBuilder;
+import org.monarchinitiative.phenol.ontology.algo.OntologyAlgorithm;
+import org.monarchinitiative.phenol.ontology.data.Ontology;
+import org.monarchinitiative.phenol.ontology.data.Term;
+import org.monarchinitiative.phenol.ontology.data.TermId;
+import org.phenopackets.phenopackettools.validator.core.ValidationResult;
+import org.phenopackets.phenopackettools.validator.core.ValidatorInfo;
+import org.phenopackets.phenopackettools.validator.core.phenotype.base.BaseHpoValidator;
+import org.phenopackets.phenopackettools.validator.core.phenotype.util.PhenotypicFeaturesByExclusionStatus;
+import org.phenopackets.phenopackettools.validator.core.phenotype.util.Util;
+import org.phenopackets.schema.v2.PhenopacketOrBuilder;
+import org.phenopackets.schema.v2.core.PhenotypicFeature;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.*;
+import java.util.stream.Stream;
+
+/**
+ * A class for pointing out violations of the annotation propagation rule.
+ *
+ * The validator checks observed and excluded phenotype terms. The observed terms are checked for a presence of
+ * an observed or an excluded ancestor, and a presence of such ancestor is pointed out as an error.
+ * For instance, Abnormality of finger or "NOT" Abnormality of finger must not be present
+ * in a patient annotated by Arachnodactyly. The most specific term (Arachnodactyly) must be used.
+ *
+ * For the excluded terms, the validator checks for presence of an excluded children. Here, the least specific term
+ * must be used. For instance, "NOT" Arachnodactyly must not be present in a patient annotated
+ * with "NOT" Abnormality of finger. Only the "NOT" Abnormality of finger must be used.
+ */
+public abstract class AbstractHpoAncestryValidator extends BaseHpoValidator {
+
+ private static final Logger LOGGER = LoggerFactory.getLogger(AbstractHpoAncestryValidator.class);
+
+ private static final ValidatorInfo VALIDATOR_INFO = ValidatorInfo.of(
+ "HpoAncestryValidator",
+ "HPO ancestry phenotypic feature validator",
+ "Validate that phenopacket does not contain an HPO term and its ancestor based on the provided HPO");
+ private static final String APR_VIOLATION = "Violation of the annotation propagation rule";
+ private static final String UNKNOWN = "UNKNOWN_NAME";
+
+ AbstractHpoAncestryValidator(Ontology hpo) {
+ super(hpo);
+ }
+
+ @Override
+ public ValidatorInfo validatorInfo() {
+ return VALIDATOR_INFO;
+ }
+
+ @Override
+ public List validate(T component) {
+ return extractPhenopackets(component)
+ .flatMap(pp -> validatePhenopacketPhenotypicFeatures(pp.getId(), pp.getPhenotypicFeaturesList()))
+ .toList();
+ }
+
+ protected abstract Stream extends PhenopacketOrBuilder> extractPhenopackets(T message);
+
+ private Stream validatePhenopacketPhenotypicFeatures(String id, List phenotypicFeatures) {
+ PhenotypicFeaturesByExclusionStatus featuresByExclusion = Util.partitionByExclusionStatus(phenotypicFeatures);
+
+ Stream.Builder results = Stream.builder();
+
+ // Check that the component does not contain both observed term and its ancestor.
+
+ for (TermId observed : featuresByExclusion.observedPhenotypicFeatures()) {
+ if (isObsoleteTermId(observed)) {
+ LOGGER.debug("Ignoring unknown/obsolete term ID {}", observed.getValue());
+ continue;
+ }
+
+ for (TermId ancestor : OntologyAlgorithm.getAncestorTerms(hpo, observed, false)) {
+ if (featuresByExclusion.observedPhenotypicFeatures().contains(ancestor))
+ results.add(constructResultForAnObservedTerm(id, observed, ancestor, false));
+ if (featuresByExclusion.excludedPhenotypicFeatures().contains(ancestor))
+ results.add(constructResultForAnObservedTerm(id, observed, ancestor, true));
+ }
+ }
+
+ // Check that the component does not have negated descendant
+ for (TermId excluded : featuresByExclusion.excludedPhenotypicFeatures()) {
+ if (isObsoleteTermId(excluded)) {
+ LOGGER.debug("Ignoring unknown/obsolete term ID {}", excluded.getValue());
+ continue;
+ }
+
+ for (TermId child : OntologyAlgorithm.getDescendents(hpo, excluded)) {
+ if (child.equals(excluded))
+ // skip the parent term
+ continue;
+ if (featuresByExclusion.excludedPhenotypicFeatures().contains(child))
+ results.add(constructResultForAnExcludedTerm(id, excluded, child));
+ }
+ }
+
+ return results.build();
+ }
+
+ private boolean isObsoleteTermId(TermId termId) {
+ return hpo.getObsoleteTermIds().contains(termId);
+ }
+
+ private ValidationResult constructResultForAnObservedTerm(String id, TermId observedId, TermId ancestorId, boolean ancestorIsExcluded) {
+ Term observedTerm = hpo.getTermMap().get(observedId);
+ String observedTermName = observedTerm == null ? UNKNOWN : observedTerm.getName();
+ Term ancestorTerm = hpo.getTermMap().get(ancestorId);
+ String ancestorTermName = ancestorTerm == null ? UNKNOWN : ancestorTerm.getName();
+ String message;
+ if (ancestorIsExcluded)
+ message = "Phenotypic features of %s must not contain both an observed term (%s, %s) and an excluded ancestor (%s, %s)".formatted(
+ id, observedTermName, observedId.getValue(), ancestorTermName, ancestorId.getValue());
+ else
+ message = "Phenotypic features of %s must not contain both an observed term (%s, %s) and an observed ancestor (%s, %s)".formatted(
+ id, observedTermName, observedId.getValue(), ancestorTermName, ancestorId.getValue());
+
+ return ValidationResult.error(VALIDATOR_INFO, APR_VIOLATION, message);
+ }
+
+ private ValidationResult constructResultForAnExcludedTerm(String id, TermId excluded, TermId child) {
+ Term excludedTerm = hpo.getTermMap().get(excluded);
+ String excludedTermName = excludedTerm == null ? UNKNOWN : excludedTerm.getName();
+ Term childTerm = hpo.getTermMap().get(child);
+ String childTermName = childTerm == null ? UNKNOWN : childTerm.getName();
+ String message = "Phenotypic features of %s must not contain both an excluded term (%s, %s) and an excluded child (%s, %s)".formatted(
+ id, excludedTermName, excluded.getValue(), childTermName, child.getValue());
+
+ return ValidationResult.error(VALIDATOR_INFO, APR_VIOLATION, message);
+ }
+
+}
diff --git a/phenopacket-tools-validator-core/src/main/java/org/phenopackets/phenopackettools/validator/core/phenotype/ancestry/CohortHpoAncestryValidator.java b/phenopacket-tools-validator-core/src/main/java/org/phenopackets/phenopackettools/validator/core/phenotype/ancestry/CohortHpoAncestryValidator.java
new file mode 100644
index 00000000..c68a3517
--- /dev/null
+++ b/phenopacket-tools-validator-core/src/main/java/org/phenopackets/phenopackettools/validator/core/phenotype/ancestry/CohortHpoAncestryValidator.java
@@ -0,0 +1,19 @@
+package org.phenopackets.phenopackettools.validator.core.phenotype.ancestry;
+
+import org.monarchinitiative.phenol.ontology.data.Ontology;
+import org.phenopackets.schema.v2.CohortOrBuilder;
+import org.phenopackets.schema.v2.PhenopacketOrBuilder;
+
+import java.util.stream.Stream;
+
+public class CohortHpoAncestryValidator extends AbstractHpoAncestryValidator {
+
+ public CohortHpoAncestryValidator(Ontology hpo) {
+ super(hpo);
+ }
+
+ @Override
+ protected Stream extends PhenopacketOrBuilder> extractPhenopackets(CohortOrBuilder message) {
+ return message.getMembersList().stream();
+ }
+}
diff --git a/phenopacket-tools-validator-core/src/main/java/org/phenopackets/phenopackettools/validator/core/phenotype/ancestry/FamilyHpoAncestryValidator.java b/phenopacket-tools-validator-core/src/main/java/org/phenopackets/phenopackettools/validator/core/phenotype/ancestry/FamilyHpoAncestryValidator.java
new file mode 100644
index 00000000..66ba7e88
--- /dev/null
+++ b/phenopacket-tools-validator-core/src/main/java/org/phenopackets/phenopackettools/validator/core/phenotype/ancestry/FamilyHpoAncestryValidator.java
@@ -0,0 +1,26 @@
+package org.phenopackets.phenopackettools.validator.core.phenotype.ancestry;
+
+import org.monarchinitiative.phenol.ontology.data.Ontology;
+import org.phenopackets.schema.v2.FamilyOrBuilder;
+import org.phenopackets.schema.v2.Phenopacket;
+import org.phenopackets.schema.v2.PhenopacketOrBuilder;
+
+import java.util.stream.Stream;
+
+public class FamilyHpoAncestryValidator extends AbstractHpoAncestryValidator {
+
+ public FamilyHpoAncestryValidator(Ontology hpo) {
+ super(hpo);
+ }
+
+ @Override
+ protected Stream extends PhenopacketOrBuilder> extractPhenopackets(FamilyOrBuilder message) {
+ Stream.Builder builder = Stream.builder();
+ builder.accept(message.getProband());
+
+ for (Phenopacket relative : message.getRelativesList())
+ builder.add(relative);
+
+ return builder.build();
+ }
+}
diff --git a/phenopacket-tools-validator-core/src/main/java/org/phenopackets/phenopackettools/validator/core/phenotype/ancestry/PhenopacketHpoAncestryValidator.java b/phenopacket-tools-validator-core/src/main/java/org/phenopackets/phenopackettools/validator/core/phenotype/ancestry/PhenopacketHpoAncestryValidator.java
new file mode 100644
index 00000000..b23ca49a
--- /dev/null
+++ b/phenopacket-tools-validator-core/src/main/java/org/phenopackets/phenopackettools/validator/core/phenotype/ancestry/PhenopacketHpoAncestryValidator.java
@@ -0,0 +1,19 @@
+package org.phenopackets.phenopackettools.validator.core.phenotype.ancestry;
+
+import org.monarchinitiative.phenol.ontology.data.Ontology;
+import org.phenopackets.schema.v2.PhenopacketOrBuilder;
+
+import java.util.stream.Stream;
+
+public class PhenopacketHpoAncestryValidator extends AbstractHpoAncestryValidator {
+
+ public PhenopacketHpoAncestryValidator(Ontology hpo) {
+ super(hpo);
+ }
+
+ @Override
+ protected Stream extends PhenopacketOrBuilder> extractPhenopackets(PhenopacketOrBuilder message) {
+ return Stream.of(message);
+ }
+
+}
diff --git a/phenopacket-tools-validator-core/src/main/java/org/phenopackets/phenopackettools/validator/core/phenotype/ancestry/package-info.java b/phenopacket-tools-validator-core/src/main/java/org/phenopackets/phenopackettools/validator/core/phenotype/ancestry/package-info.java
new file mode 100644
index 00000000..234f7f14
--- /dev/null
+++ b/phenopacket-tools-validator-core/src/main/java/org/phenopackets/phenopackettools/validator/core/phenotype/ancestry/package-info.java
@@ -0,0 +1,6 @@
+/**
+ * The package contains validators that point out violations of the annotation propagation rule.
+ *
+ * @see org.phenopackets.phenopackettools.validator.core.phenotype.HpoPhenotypeValidators.Ancestry
+ */
+package org.phenopackets.phenopackettools.validator.core.phenotype.ancestry;
\ No newline at end of file
diff --git a/phenopacket-tools-validator-core/src/main/java/org/phenopackets/phenopackettools/validator/core/phenotype/base/BaseHpoValidator.java b/phenopacket-tools-validator-core/src/main/java/org/phenopackets/phenopackettools/validator/core/phenotype/base/BaseHpoValidator.java
new file mode 100644
index 00000000..6e824235
--- /dev/null
+++ b/phenopacket-tools-validator-core/src/main/java/org/phenopackets/phenopackettools/validator/core/phenotype/base/BaseHpoValidator.java
@@ -0,0 +1,39 @@
+package org.phenopackets.phenopackettools.validator.core.phenotype.base;
+
+import com.google.protobuf.MessageOrBuilder;
+import org.monarchinitiative.phenol.ontology.data.Ontology;
+import org.phenopackets.phenopackettools.validator.core.PhenopacketValidator;
+import org.phenopackets.schema.v2.PhenopacketOrBuilder;
+
+import java.util.Objects;
+
+public abstract class BaseHpoValidator implements PhenopacketValidator {
+
+ protected final Ontology hpo;
+ protected final String hpoVersion;
+
+ protected BaseHpoValidator(Ontology hpo) {
+ this.hpo = Objects.requireNonNull(hpo);
+ // TODO - can be replaced by this.hpo.version() in the most recent phenol versions.
+ this.hpoVersion = this.hpo.getMetaInfo().getOrDefault("data-version", "HPO");
+ }
+
+ protected static String summarizePhenopacketAndIndividualId(PhenopacketOrBuilder phenopacket) {
+ // Build a string like / but only if one/other are present.
+ StringBuilder builder = new StringBuilder();
+ String phenopacketId = phenopacket.getId();
+ String individualId = phenopacket.getSubject().getId();
+ if (!phenopacketId.isBlank() || !individualId.isBlank()) {
+ builder.append(" in ");
+ if (!phenopacketId.isBlank())
+ builder.append(phenopacketId);
+
+ if (!individualId.isBlank()) {
+ if (!phenopacketId.isBlank())
+ builder.append("/");
+ builder.append(individualId);
+ }
+ }
+ return builder.toString();
+ }
+}
diff --git a/phenopacket-tools-validator-core/src/main/java/org/phenopackets/phenopackettools/validator/core/phenotype/base/package-info.java b/phenopacket-tools-validator-core/src/main/java/org/phenopackets/phenopackettools/validator/core/phenotype/base/package-info.java
new file mode 100644
index 00000000..eb9cb4de
--- /dev/null
+++ b/phenopacket-tools-validator-core/src/main/java/org/phenopackets/phenopackettools/validator/core/phenotype/base/package-info.java
@@ -0,0 +1,5 @@
+/**
+ * Shared bits of all {@link org.phenopackets.phenopackettools.validator.core.PhenopacketValidator}s
+ * that use HPO {@link org.monarchinitiative.phenol.ontology.data.Ontology} in validation.
+ */
+package org.phenopackets.phenopackettools.validator.core.phenotype.base;
\ No newline at end of file
diff --git a/phenopacket-tools-validator-core/src/main/java/org/phenopackets/phenopackettools/validator/core/phenotype/orgsys/AbstractOrganSystemValidator.java b/phenopacket-tools-validator-core/src/main/java/org/phenopackets/phenopackettools/validator/core/phenotype/orgsys/AbstractOrganSystemValidator.java
new file mode 100644
index 00000000..2c1fff7c
--- /dev/null
+++ b/phenopacket-tools-validator-core/src/main/java/org/phenopackets/phenopackettools/validator/core/phenotype/orgsys/AbstractOrganSystemValidator.java
@@ -0,0 +1,109 @@
+package org.phenopackets.phenopackettools.validator.core.phenotype.orgsys;
+
+import com.google.protobuf.MessageOrBuilder;
+import org.monarchinitiative.phenol.ontology.algo.OntologyAlgorithm;
+import org.monarchinitiative.phenol.ontology.data.Ontology;
+import org.monarchinitiative.phenol.ontology.data.Term;
+import org.monarchinitiative.phenol.ontology.data.TermId;
+import org.phenopackets.phenopackettools.validator.core.ValidationResult;
+import org.phenopackets.phenopackettools.validator.core.ValidatorInfo;
+import org.phenopackets.phenopackettools.validator.core.phenotype.base.BaseHpoValidator;
+import org.phenopackets.phenopackettools.validator.core.phenotype.util.PhenotypicFeaturesByExclusionStatus;
+import org.phenopackets.phenopackettools.validator.core.phenotype.util.Util;
+import org.phenopackets.schema.v2.PhenopacketOrBuilder;
+import org.phenopackets.schema.v2.core.PhenotypicFeature;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.*;
+import java.util.function.Predicate;
+import java.util.stream.Stream;
+
+/**
+ * The base class for an organ system validator to check if each phenopacket or family/cohort member have annotation
+ * for an organ system represented by a top-level HPO term
+ * (e.g. Abnormality of limbs).
+ * The annotation comprises either one or more observed descendants
+ * (e.g. Arachnodactyly),
+ * or excluded top-level HPO term
+ * (NOT Abnormality of limbs).
+ */
+public abstract class AbstractOrganSystemValidator extends BaseHpoValidator {
+
+ private static final Logger LOGGER = LoggerFactory.getLogger(AbstractOrganSystemValidator.class);
+
+ private static final ValidatorInfo VALIDATOR_INFO = ValidatorInfo.of(
+ "HpoOrganSystemValidator",
+ "HPO organ system validator",
+ "Validate annotation of selected organ systems");
+
+ private static final String MISSING_ORGAN_SYSTEM_CATEGORY = "Missing organ system annotation";
+
+ protected final List organSystemTermIds;
+
+ protected AbstractOrganSystemValidator(Ontology hpo,
+ Collection organSystemTermIds) {
+ super(hpo);
+ this.organSystemTermIds = Objects.requireNonNull(organSystemTermIds).stream()
+ .distinct()
+ .filter(organSystemTermIdIsInOntology(hpo))
+ .sorted()
+ .toList();
+ }
+
+ private static Predicate organSystemTermIdIsInOntology(Ontology hpo) {
+ return organSystemTermId -> {
+ if (hpo.containsTerm(organSystemTermId)) {
+ return true;
+ } else {
+ LOGGER.warn("{} is not present in the ontology", organSystemTermId.getValue());
+ return false;
+ }
+ };
+ }
+
+ @Override
+ public ValidatorInfo validatorInfo() {
+ return VALIDATOR_INFO;
+ }
+
+ @Override
+ public List validate(T component) {
+ return getPhenopackets(component)
+ .flatMap(p -> checkPhenotypicFeatures(p, p.getPhenotypicFeaturesList()))
+ .toList();
+ }
+
+ protected abstract Stream extends PhenopacketOrBuilder> getPhenopackets(T component);
+
+ private Stream checkPhenotypicFeatures(PhenopacketOrBuilder phenopacket, List features) {
+ PhenotypicFeaturesByExclusionStatus featuresByExclusion = Util.partitionByExclusionStatus(features);
+
+ Stream.Builder results = Stream.builder();
+ // Check we have at least one phenotypeFeature (pf) that is a descendant of given organSystemId
+ // and report otherwise.
+ organSystemLoop:
+ for (TermId organSystemId : organSystemTermIds) {
+ // Check if the organ system abnormality has been specifically excluded.
+ if (featuresByExclusion.excludedPhenotypicFeatures().contains(organSystemId))
+ continue; // Yes, it was. Let's check the next organ system
+
+ // Check if we have at least one observed annotation for the organ system.
+ for (TermId pf : featuresByExclusion.observedPhenotypicFeatures()) {
+ if (OntologyAlgorithm.existsPath(hpo, pf, organSystemId)) {
+ continue organSystemLoop; // It only takes one termId to annotate an organ system.
+ }
+ }
+
+ // The organSystemId is neither annotated nor excluded. We report a validation error.
+ Term organSystem = hpo.getTermMap().get(organSystemId);
+ ValidationResult result = ValidationResult.error(VALIDATOR_INFO,
+ MISSING_ORGAN_SYSTEM_CATEGORY,
+ "Missing annotation for %s [%s]%s"
+ .formatted(organSystem.getName(), organSystem.id().getValue(), summarizePhenopacketAndIndividualId(phenopacket)));
+ results.add(result);
+ }
+
+ return results.build();
+ }
+}
diff --git a/phenopacket-tools-validator-core/src/main/java/org/phenopackets/phenopackettools/validator/core/phenotype/orgsys/CohortHpoOrganSystemValidator.java b/phenopacket-tools-validator-core/src/main/java/org/phenopackets/phenopackettools/validator/core/phenotype/orgsys/CohortHpoOrganSystemValidator.java
new file mode 100644
index 00000000..96bea28b
--- /dev/null
+++ b/phenopacket-tools-validator-core/src/main/java/org/phenopackets/phenopackettools/validator/core/phenotype/orgsys/CohortHpoOrganSystemValidator.java
@@ -0,0 +1,23 @@
+package org.phenopackets.phenopackettools.validator.core.phenotype.orgsys;
+
+import org.monarchinitiative.phenol.ontology.data.Ontology;
+import org.monarchinitiative.phenol.ontology.data.TermId;
+import org.phenopackets.schema.v2.CohortOrBuilder;
+import org.phenopackets.schema.v2.PhenopacketOrBuilder;
+
+import java.util.Collection;
+import java.util.stream.Stream;
+
+public class CohortHpoOrganSystemValidator extends AbstractOrganSystemValidator {
+
+ public CohortHpoOrganSystemValidator(Ontology hpo, Collection organSystemTermIds) {
+ super(hpo, organSystemTermIds);
+ }
+
+ @Override
+ protected Stream extends PhenopacketOrBuilder> getPhenopackets(CohortOrBuilder component) {
+ return component.getMembersOrBuilderList().stream();
+ }
+
+
+}
diff --git a/phenopacket-tools-validator-core/src/main/java/org/phenopackets/phenopackettools/validator/core/phenotype/orgsys/FamilyHpoOrganSystemValidator.java b/phenopacket-tools-validator-core/src/main/java/org/phenopackets/phenopackettools/validator/core/phenotype/orgsys/FamilyHpoOrganSystemValidator.java
new file mode 100644
index 00000000..7edf20df
--- /dev/null
+++ b/phenopacket-tools-validator-core/src/main/java/org/phenopackets/phenopackettools/validator/core/phenotype/orgsys/FamilyHpoOrganSystemValidator.java
@@ -0,0 +1,25 @@
+package org.phenopackets.phenopackettools.validator.core.phenotype.orgsys;
+
+import org.monarchinitiative.phenol.ontology.data.Ontology;
+import org.monarchinitiative.phenol.ontology.data.TermId;
+import org.phenopackets.schema.v2.FamilyOrBuilder;
+import org.phenopackets.schema.v2.PhenopacketOrBuilder;
+
+import java.util.Collection;
+import java.util.stream.Stream;
+
+public class FamilyHpoOrganSystemValidator extends AbstractOrganSystemValidator {
+
+ public FamilyHpoOrganSystemValidator(Ontology hpo, Collection organSystemTermIds) {
+ super(hpo, organSystemTermIds);
+ }
+
+ @Override
+ protected Stream extends PhenopacketOrBuilder> getPhenopackets(FamilyOrBuilder component) {
+ return Stream.concat(
+ Stream.of(component.getProband()),
+ component.getRelativesList().stream()
+ );
+ }
+
+}
diff --git a/phenopacket-tools-validator-core/src/main/java/org/phenopackets/phenopackettools/validator/core/phenotype/orgsys/PhenopacketHpoOrganSystemValidator.java b/phenopacket-tools-validator-core/src/main/java/org/phenopackets/phenopackettools/validator/core/phenotype/orgsys/PhenopacketHpoOrganSystemValidator.java
new file mode 100644
index 00000000..d4db9e81
--- /dev/null
+++ b/phenopacket-tools-validator-core/src/main/java/org/phenopackets/phenopackettools/validator/core/phenotype/orgsys/PhenopacketHpoOrganSystemValidator.java
@@ -0,0 +1,21 @@
+package org.phenopackets.phenopackettools.validator.core.phenotype.orgsys;
+
+import org.monarchinitiative.phenol.ontology.data.Ontology;
+import org.monarchinitiative.phenol.ontology.data.TermId;
+import org.phenopackets.schema.v2.PhenopacketOrBuilder;
+
+import java.util.Collection;
+import java.util.stream.Stream;
+
+public class PhenopacketHpoOrganSystemValidator extends AbstractOrganSystemValidator {
+
+ public PhenopacketHpoOrganSystemValidator(Ontology hpo,
+ Collection organSystemTerms) {
+ super(hpo, organSystemTerms);
+ }
+
+ @Override
+ protected Stream extends PhenopacketOrBuilder> getPhenopackets(PhenopacketOrBuilder component) {
+ return Stream.of(component);
+ }
+}
diff --git a/phenopacket-tools-validator-core/src/main/java/org/phenopackets/phenopackettools/validator/core/phenotype/package-info.java b/phenopacket-tools-validator-core/src/main/java/org/phenopackets/phenopackettools/validator/core/phenotype/package-info.java
new file mode 100644
index 00000000..7c983548
--- /dev/null
+++ b/phenopacket-tools-validator-core/src/main/java/org/phenopackets/phenopackettools/validator/core/phenotype/package-info.java
@@ -0,0 +1,14 @@
+/**
+ * Package with off-the-shelf validators that work with Human Phenotype Ontology (HPO).
+ *
+ * The validators are exposed via a static factory class, there is a method for getting a validator for each top-level
+ * Phenopacket Schema component.
+ *
+ * The package includes a utility class with HPO {@link org.monarchinitiative.phenol.ontology.data.TermId}s
+ * that correspond to organ systems
+ * (e.g. {@link org.phenopackets.phenopackettools.validator.core.phenotype.HpoOrganSystems#EYE} for
+ * Abnormality of the eye) that can be used
+ * in combination with
+ * {@link org.phenopackets.phenopackettools.validator.core.phenotype.HpoPhenotypeValidators.OrganSystem} validators.
+ */
+package org.phenopackets.phenopackettools.validator.core.phenotype;
\ No newline at end of file
diff --git a/phenopacket-tools-validator-core/src/main/java/org/phenopackets/phenopackettools/validator/core/phenotype/primary/AbstractHpoPhenotypeValidator.java b/phenopacket-tools-validator-core/src/main/java/org/phenopackets/phenopackettools/validator/core/phenotype/primary/AbstractHpoPhenotypeValidator.java
new file mode 100644
index 00000000..d75cfc7f
--- /dev/null
+++ b/phenopacket-tools-validator-core/src/main/java/org/phenopackets/phenopackettools/validator/core/phenotype/primary/AbstractHpoPhenotypeValidator.java
@@ -0,0 +1,69 @@
+package org.phenopackets.phenopackettools.validator.core.phenotype.primary;
+
+import com.google.protobuf.MessageOrBuilder;
+import org.monarchinitiative.phenol.base.PhenolRuntimeException;
+import org.monarchinitiative.phenol.ontology.data.Ontology;
+import org.monarchinitiative.phenol.ontology.data.TermId;
+import org.phenopackets.phenopackettools.validator.core.*;
+import org.phenopackets.phenopackettools.validator.core.phenotype.base.BaseHpoValidator;
+import org.phenopackets.schema.v2.PhenopacketOrBuilder;
+import org.phenopackets.schema.v2.core.PhenotypicFeature;
+
+import java.util.stream.Stream;
+
+public abstract class AbstractHpoPhenotypeValidator extends BaseHpoValidator {
+
+ private static final ValidatorInfo VALIDATOR_INFO = ValidatorInfo.of(
+ "HpoPhenotypeValidator",
+ "HPO phenotypic feature validator",
+ "Validate that HPO terms are well formatted, present, and non-obsolete based on the provided HPO");
+ private static final String INVALID_TERM_ID = "Invalid TermId";
+ private static final String OBSOLETED_TERM_ID = "Obsoleted TermId";
+
+ public AbstractHpoPhenotypeValidator(Ontology hpo) {
+ super(hpo);
+ }
+
+ @Override
+ public ValidatorInfo validatorInfo() {
+ return VALIDATOR_INFO;
+ }
+
+ protected Stream extends ValidationResult> checkPhenotypeFeature(PhenopacketOrBuilder phenopacket, PhenotypicFeature feature) {
+ TermId termId;
+ try {
+ termId = TermId.of(feature.getType().getId());
+ } catch (PhenolRuntimeException e) {
+ String idSummary = summarizePhenopacketAndIndividualId(phenopacket);
+ // Should not really happen if JsonSchema validators are run upstream, but let's stay safe.
+ String msg = "The %s found%s is not a valid term ID".formatted(feature.getType().getId(), idSummary);
+ return Stream.of(
+ ValidationResult.error(VALIDATOR_INFO, INVALID_TERM_ID, msg)
+ );
+ }
+ if (termId.getPrefix().equals("HP")) {
+ // Check if the HPO contains the term.
+ if (!hpo.containsTerm(termId)) {
+ String idSummary = summarizePhenopacketAndIndividualId(phenopacket);
+ String msg = "%s%s not found in %s".formatted(termId.getValue(), idSummary, hpoVersion);
+ return Stream.of(
+ ValidationResult.error(VALIDATOR_INFO, INVALID_TERM_ID, msg)
+ );
+ }
+
+ // Check if the `termId` is a primary ID. // If not, this is a warning.
+ TermId primaryId = hpo.getPrimaryTermId(termId);
+ if (!primaryId.equals(termId)) {
+ String idSummary = summarizePhenopacketAndIndividualId(phenopacket);
+ String msg = "Using obsolete id (%s) instead of current primary id (%s)%s".formatted(
+ termId.getValue(), primaryId.getValue(), idSummary);
+ return Stream.of(
+ ValidationResult.warning(VALIDATOR_INFO, OBSOLETED_TERM_ID, msg)
+ );
+ }
+ }
+
+ return Stream.empty();
+ }
+
+}
diff --git a/phenopacket-tools-validator-core/src/main/java/org/phenopackets/phenopackettools/validator/core/phenotype/CohortHpoPhenotypeValidator.java b/phenopacket-tools-validator-core/src/main/java/org/phenopackets/phenopackettools/validator/core/phenotype/primary/CohortHpoPhenotypeValidator.java
similarity index 73%
rename from phenopacket-tools-validator-core/src/main/java/org/phenopackets/phenopackettools/validator/core/phenotype/CohortHpoPhenotypeValidator.java
rename to phenopacket-tools-validator-core/src/main/java/org/phenopackets/phenopackettools/validator/core/phenotype/primary/CohortHpoPhenotypeValidator.java
index f72bbc70..0642f21e 100644
--- a/phenopacket-tools-validator-core/src/main/java/org/phenopackets/phenopackettools/validator/core/phenotype/CohortHpoPhenotypeValidator.java
+++ b/phenopacket-tools-validator-core/src/main/java/org/phenopackets/phenopackettools/validator/core/phenotype/primary/CohortHpoPhenotypeValidator.java
@@ -1,18 +1,17 @@
-package org.phenopackets.phenopackettools.validator.core.phenotype;
+package org.phenopackets.phenopackettools.validator.core.phenotype.primary;
import org.monarchinitiative.phenol.ontology.data.Ontology;
import org.phenopackets.phenopackettools.validator.core.ValidationResult;
import org.phenopackets.schema.v2.CohortOrBuilder;
import org.phenopackets.schema.v2.Phenopacket;
-import org.phenopackets.schema.v2.core.Individual;
import org.phenopackets.schema.v2.core.PhenotypicFeature;
import java.util.ArrayList;
import java.util.List;
-class CohortHpoPhenotypeValidator extends BaseHpoPhenotypeValidator {
+public class CohortHpoPhenotypeValidator extends AbstractHpoPhenotypeValidator {
- CohortHpoPhenotypeValidator(Ontology hpo) {
+ public CohortHpoPhenotypeValidator(Ontology hpo) {
super(hpo);
}
@@ -21,9 +20,8 @@ public List validate(CohortOrBuilder component) {
List results = new ArrayList<>();
for (Phenopacket member : component.getMembersList()) {
- Individual subject = member.getSubject();
for (PhenotypicFeature feature : member.getPhenotypicFeaturesList()) {
- checkPhenotypeFeature(subject.getId(), feature)
+ checkPhenotypeFeature(member, feature)
.forEach(results::add);
}
}
diff --git a/phenopacket-tools-validator-core/src/main/java/org/phenopackets/phenopackettools/validator/core/phenotype/FamilyHpoPhenotypeValidator.java b/phenopacket-tools-validator-core/src/main/java/org/phenopackets/phenopackettools/validator/core/phenotype/primary/FamilyHpoPhenotypeValidator.java
similarity index 72%
rename from phenopacket-tools-validator-core/src/main/java/org/phenopackets/phenopackettools/validator/core/phenotype/FamilyHpoPhenotypeValidator.java
rename to phenopacket-tools-validator-core/src/main/java/org/phenopackets/phenopackettools/validator/core/phenotype/primary/FamilyHpoPhenotypeValidator.java
index 1512f25b..65beef4e 100644
--- a/phenopacket-tools-validator-core/src/main/java/org/phenopackets/phenopackettools/validator/core/phenotype/FamilyHpoPhenotypeValidator.java
+++ b/phenopacket-tools-validator-core/src/main/java/org/phenopackets/phenopackettools/validator/core/phenotype/primary/FamilyHpoPhenotypeValidator.java
@@ -1,18 +1,17 @@
-package org.phenopackets.phenopackettools.validator.core.phenotype;
+package org.phenopackets.phenopackettools.validator.core.phenotype.primary;
import org.monarchinitiative.phenol.ontology.data.Ontology;
import org.phenopackets.phenopackettools.validator.core.ValidationResult;
import org.phenopackets.schema.v2.FamilyOrBuilder;
import org.phenopackets.schema.v2.Phenopacket;
-import org.phenopackets.schema.v2.core.Individual;
import org.phenopackets.schema.v2.core.PhenotypicFeature;
import java.util.ArrayList;
import java.util.List;
-class FamilyHpoPhenotypeValidator extends BaseHpoPhenotypeValidator {
+public class FamilyHpoPhenotypeValidator extends AbstractHpoPhenotypeValidator {
- FamilyHpoPhenotypeValidator(Ontology hpo) {
+ public FamilyHpoPhenotypeValidator(Ontology hpo) {
super(hpo);
}
@@ -23,18 +22,16 @@ public List validate(FamilyOrBuilder component) {
// First check the proband.
{
Phenopacket proband = component.getProband();
- Individual subject = proband.getSubject();
for (PhenotypicFeature feature : proband.getPhenotypicFeaturesList()) {
- checkPhenotypeFeature(subject.getId(), feature)
+ checkPhenotypeFeature(proband, feature)
.forEach(results::add);
}
}
// Then the relatives.
for (Phenopacket relative : component.getRelativesList()) {
- Individual subject = relative.getSubject();
for (PhenotypicFeature feature : relative.getPhenotypicFeaturesList()) {
- checkPhenotypeFeature(subject.getId(), feature)
+ checkPhenotypeFeature(relative, feature)
.forEach(results::add);
}
}
diff --git a/phenopacket-tools-validator-core/src/main/java/org/phenopackets/phenopackettools/validator/core/phenotype/PhenopacketHpoPhenotypeValidator.java b/phenopacket-tools-validator-core/src/main/java/org/phenopackets/phenopackettools/validator/core/phenotype/primary/PhenopacketHpoPhenotypeValidator.java
similarity index 69%
rename from phenopacket-tools-validator-core/src/main/java/org/phenopackets/phenopackettools/validator/core/phenotype/PhenopacketHpoPhenotypeValidator.java
rename to phenopacket-tools-validator-core/src/main/java/org/phenopackets/phenopackettools/validator/core/phenotype/primary/PhenopacketHpoPhenotypeValidator.java
index c30b2b45..7b43a10e 100644
--- a/phenopacket-tools-validator-core/src/main/java/org/phenopackets/phenopackettools/validator/core/phenotype/PhenopacketHpoPhenotypeValidator.java
+++ b/phenopacket-tools-validator-core/src/main/java/org/phenopackets/phenopackettools/validator/core/phenotype/primary/PhenopacketHpoPhenotypeValidator.java
@@ -1,17 +1,16 @@
-package org.phenopackets.phenopackettools.validator.core.phenotype;
+package org.phenopackets.phenopackettools.validator.core.phenotype.primary;
import org.monarchinitiative.phenol.ontology.data.Ontology;
import org.phenopackets.phenopackettools.validator.core.ValidationResult;
import org.phenopackets.schema.v2.PhenopacketOrBuilder;
-import org.phenopackets.schema.v2.core.Individual;
import org.phenopackets.schema.v2.core.PhenotypicFeature;
import java.util.ArrayList;
import java.util.List;
-class PhenopacketHpoPhenotypeValidator extends BaseHpoPhenotypeValidator {
+public class PhenopacketHpoPhenotypeValidator extends AbstractHpoPhenotypeValidator {
- PhenopacketHpoPhenotypeValidator(Ontology hpo) {
+ public PhenopacketHpoPhenotypeValidator(Ontology hpo) {
super(hpo);
}
@@ -19,9 +18,8 @@ class PhenopacketHpoPhenotypeValidator extends BaseHpoPhenotypeValidator validate(PhenopacketOrBuilder component) {
List results = new ArrayList<>();
- Individual subject = component.getSubject();
for (PhenotypicFeature feature : component.getPhenotypicFeaturesList()) {
- checkPhenotypeFeature(subject.getId(), feature)
+ checkPhenotypeFeature(component, feature)
.forEach(results::add);
}
diff --git a/phenopacket-tools-validator-core/src/main/java/org/phenopackets/phenopackettools/validator/core/phenotype/primary/package-info.java b/phenopacket-tools-validator-core/src/main/java/org/phenopackets/phenopackettools/validator/core/phenotype/primary/package-info.java
new file mode 100644
index 00000000..c233fd38
--- /dev/null
+++ b/phenopacket-tools-validator-core/src/main/java/org/phenopackets/phenopackettools/validator/core/phenotype/primary/package-info.java
@@ -0,0 +1,7 @@
+/**
+ * The package of {@link org.phenopackets.phenopackettools.validator.core.PhenopacketValidator}s that perform
+ * primary validation of HPO terms.
+ *
+ * @see org.phenopackets.phenopackettools.validator.core.phenotype.HpoPhenotypeValidators.Primary
+ */
+package org.phenopackets.phenopackettools.validator.core.phenotype.primary;
\ No newline at end of file
diff --git a/phenopacket-tools-validator-core/src/main/java/org/phenopackets/phenopackettools/validator/core/phenotype/util/MaybeExcludedTermId.java b/phenopacket-tools-validator-core/src/main/java/org/phenopackets/phenopackettools/validator/core/phenotype/util/MaybeExcludedTermId.java
new file mode 100644
index 00000000..9b4eb0d4
--- /dev/null
+++ b/phenopacket-tools-validator-core/src/main/java/org/phenopackets/phenopackettools/validator/core/phenotype/util/MaybeExcludedTermId.java
@@ -0,0 +1,20 @@
+package org.phenopackets.phenopackettools.validator.core.phenotype.util;
+
+import org.monarchinitiative.phenol.base.PhenolRuntimeException;
+import org.monarchinitiative.phenol.ontology.data.TermId;
+import org.phenopackets.schema.v2.core.PhenotypicFeature;
+
+import java.util.Optional;
+
+record MaybeExcludedTermId(TermId termId, boolean excluded) {
+
+ static Optional fromPhenotypicFeature(PhenotypicFeature phenotypicFeature) {
+ TermId termId;
+ try {
+ termId = TermId.of(phenotypicFeature.getType().getId());
+ } catch (PhenolRuntimeException e) {
+ return Optional.empty();
+ }
+ return Optional.of(new MaybeExcludedTermId(termId, phenotypicFeature.getExcluded()));
+ }
+}
diff --git a/phenopacket-tools-validator-core/src/main/java/org/phenopackets/phenopackettools/validator/core/phenotype/util/PhenotypicFeaturesByExclusionStatus.java b/phenopacket-tools-validator-core/src/main/java/org/phenopackets/phenopackettools/validator/core/phenotype/util/PhenotypicFeaturesByExclusionStatus.java
new file mode 100644
index 00000000..a5abb10e
--- /dev/null
+++ b/phenopacket-tools-validator-core/src/main/java/org/phenopackets/phenopackettools/validator/core/phenotype/util/PhenotypicFeaturesByExclusionStatus.java
@@ -0,0 +1,9 @@
+package org.phenopackets.phenopackettools.validator.core.phenotype.util;
+
+import org.monarchinitiative.phenol.ontology.data.TermId;
+
+import java.util.Set;
+
+public record PhenotypicFeaturesByExclusionStatus(Set observedPhenotypicFeatures,
+ Set excludedPhenotypicFeatures) {
+}
diff --git a/phenopacket-tools-validator-core/src/main/java/org/phenopackets/phenopackettools/validator/core/phenotype/util/Util.java b/phenopacket-tools-validator-core/src/main/java/org/phenopackets/phenopackettools/validator/core/phenotype/util/Util.java
new file mode 100644
index 00000000..ffcf668b
--- /dev/null
+++ b/phenopacket-tools-validator-core/src/main/java/org/phenopackets/phenopackettools/validator/core/phenotype/util/Util.java
@@ -0,0 +1,42 @@
+package org.phenopackets.phenopackettools.validator.core.phenotype.util;
+
+import org.monarchinitiative.phenol.ontology.data.TermId;
+import org.phenopackets.schema.v2.core.PhenotypicFeature;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.Collection;
+import java.util.Map;
+import java.util.Optional;
+import java.util.Set;
+import java.util.function.Function;
+import java.util.stream.Collectors;
+
+public class Util {
+
+ private static final Logger LOGGER = LoggerFactory.getLogger(Util.class);
+
+ private Util() {
+ // static utility class
+ }
+
+ public static PhenotypicFeaturesByExclusionStatus partitionByExclusionStatus(Collection phenotypicFeatures) {
+ Map> byExclusion = phenotypicFeatures.stream()
+ .map(toMaybeObservedTermId())
+ .flatMap(Optional::stream)
+ // Use `partitioningBy` instead of `groupingBy` to ensure the map contains keys
+ // for both `true` and `false`. Then extract `TermId` and collect in a `Set`.
+ .collect(Collectors.partitioningBy(MaybeExcludedTermId::excluded,
+ Collectors.mapping(MaybeExcludedTermId::termId, Collectors.toSet())));
+ return new PhenotypicFeaturesByExclusionStatus(byExclusion.get(false), byExclusion.get(true));
+ }
+
+ private static Function> toMaybeObservedTermId() {
+ return pf -> MaybeExcludedTermId.fromPhenotypicFeature(pf)
+ .or(() -> {
+ // Let's log the malformed term.
+ LOGGER.warn("Skipping validation of malformed term ID {}", pf.getType().getId());
+ return Optional.empty();
+ });
+ }
+}
diff --git a/phenopacket-tools-validator-core/src/main/java/org/phenopackets/phenopackettools/validator/core/writer/package-info.java b/phenopacket-tools-validator-core/src/main/java/org/phenopackets/phenopackettools/validator/core/writer/package-info.java
new file mode 100644
index 00000000..07399df3
--- /dev/null
+++ b/phenopacket-tools-validator-core/src/main/java/org/phenopackets/phenopackettools/validator/core/writer/package-info.java
@@ -0,0 +1,4 @@
+/**
+ * A package with API for serialization of {@link org.phenopackets.phenopackettools.validator.core.ValidationResults}.
+ */
+package org.phenopackets.phenopackettools.validator.core.writer;
\ No newline at end of file
diff --git a/phenopacket-tools-validator-core/src/test/java/org/phenopackets/phenopackettools/validator/core/phenotype/AncestryHpoValidatorTest.java b/phenopacket-tools-validator-core/src/test/java/org/phenopackets/phenopackettools/validator/core/phenotype/AncestryHpoValidatorTest.java
new file mode 100644
index 00000000..aa5467d0
--- /dev/null
+++ b/phenopacket-tools-validator-core/src/test/java/org/phenopackets/phenopackettools/validator/core/phenotype/AncestryHpoValidatorTest.java
@@ -0,0 +1,177 @@
+package org.phenopackets.phenopackettools.validator.core.phenotype;
+
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Nested;
+import org.junit.jupiter.api.Test;
+import org.monarchinitiative.phenol.ontology.data.Ontology;
+import org.phenopackets.phenopackettools.validator.core.PhenopacketValidator;
+import org.phenopackets.phenopackettools.validator.core.TestData;
+import org.phenopackets.phenopackettools.validator.core.ValidationLevel;
+import org.phenopackets.phenopackettools.validator.core.ValidationResult;
+import org.phenopackets.schema.v2.*;
+import org.phenopackets.schema.v2.core.PhenotypicFeature;
+
+import java.util.List;
+
+import static org.phenopackets.phenopackettools.validator.core.phenotype.Utils.*;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.*;
+
+public class AncestryHpoValidatorTest {
+
+ private static final Ontology HPO = TestData.HPO;
+
+ @Nested
+ public class PhenopacketTest {
+
+ private PhenopacketValidator validator;
+
+ @BeforeEach
+ public void setUp() {
+ validator = HpoPhenotypeValidators.Ancestry.phenopacketHpoAncestryValidator(HPO);
+ }
+
+ @Test
+ public void testValidInput() {
+ // Has some Abnormality of finger but no Arachnodactyly.
+ Phenopacket pp = createPhenopacket(
+ "example-phenopacket", "example-subject",
+ createPhenotypicFeature("HP:0001167", "Abnormality of finger", false),
+ createPhenotypicFeature("HP:0001166", "Arachnodactyly", true)
+ ).build();
+
+ List results = validator.validate(pp);
+
+ assertThat(results, is(empty()));
+ }
+
+ @Test
+ public void testFailsIfTermAndAncestorIsObserved() {
+ // Has some Abnormality of finger and Arachnodactyly. Only Arachnodactyly should be present.
+ Phenopacket pp = createPhenopacket(
+ "example-phenopacket", "example-subject", createPhenotypicFeature("HP:0001167", "Abnormality of finger", false),
+ createPhenotypicFeature("HP:0001166", "Arachnodactyly", false)
+ ).build();
+
+ List results = validator.validate(pp);
+
+ assertThat(results, hasSize(1));
+ ValidationResult result = results.get(0);
+ assertThat(result.validatorInfo(), equalTo(validator.validatorInfo()));
+ assertThat(result.level(), equalTo(ValidationLevel.ERROR));
+ assertThat(result.category(), equalTo("Violation of the annotation propagation rule"));
+ assertThat(result.message(), equalTo("Phenotypic features of example-phenopacket must not contain both an observed term (Arachnodactyly, HP:0001166) and an observed ancestor (Abnormality of finger, HP:0001167)"));
+ }
+
+ @Test
+ public void testFailsIfTermAndAncestorIsExcluded() {
+ // Has neither Abnormality of finger nor Arachnodactyly. Only Abnormality of finger should be present.
+ Phenopacket pp = createPhenopacket(
+ "example-phenopacket", "example-subject", createPhenotypicFeature("HP:0001167", "Abnormality of finger", true),
+ createPhenotypicFeature("HP:0001166", "Arachnodactyly", true)
+ ).build();
+
+ List results = validator.validate(pp);
+
+ assertThat(results, hasSize(1));
+ ValidationResult result = results.get(0);
+ assertThat(result.level(), equalTo(ValidationLevel.ERROR));
+ assertThat(result.category(), equalTo("Violation of the annotation propagation rule"));
+ assertThat(result.message(), equalTo("Phenotypic features of example-phenopacket must not contain both an excluded term (Abnormality of finger, HP:0001167) and an excluded child (Arachnodactyly, HP:0001166)"));
+ }
+
+ @Test
+ public void testFailsIfTermIsPresentAndAncestorIsExcluded() {
+ // Has neither Abnormality of finger nor Arachnodactyly. Only Abnormality of finger should be present.
+ Phenopacket pp = createPhenopacket(
+ "example-phenopacket", "example-subject", createPhenotypicFeature("HP:0001167", "Abnormality of finger", true),
+ createPhenotypicFeature("HP:0001166", "Arachnodactyly", false)
+ ).build();
+
+ List results = validator.validate(pp);
+
+ assertThat(results, hasSize(1));
+ ValidationResult result = results.get(0);
+ assertThat(result.level(), equalTo(ValidationLevel.ERROR));
+ assertThat(result.category(), equalTo("Violation of the annotation propagation rule"));
+ assertThat(result.message(), equalTo("Phenotypic features of example-phenopacket must not contain both an observed term (Arachnodactyly, HP:0001166) and an excluded ancestor (Abnormality of finger, HP:0001167)"));
+ }
+ }
+
+ /**
+ * White-box testing - we know that the {@link PhenotypicFeature} is an attribute of a {@link Phenopacket}, so we
+ * test the validation logic extensively in {@link PhenopacketTest}. The {@link FamilyTest} test suite ensures
+ * there are not errors in valid input.
+ */
+ @Nested
+ public class FamilyTest {
+
+ private PhenopacketValidator validator;
+
+ @BeforeEach
+ public void setUp() {
+ validator = HpoPhenotypeValidators.Ancestry.familyHpoAncestryValidator(HPO);
+ }
+
+ @Test
+ public void testValidInput() {
+ Family family = Family.newBuilder()
+ .setProband(createPhenopacket("example-phenopacket", "example-subject",
+ createPhenotypicFeature("HP:0001167", "Abnormality of finger", false),
+ createPhenotypicFeature("HP:0001166", "Arachnodactyly", true))
+ .build())
+ .addRelatives(createPhenopacket("dad-phenopacket", "example-dad",
+ createPhenotypicFeature("HP:0001238", "Slender finger", false),
+ createPhenotypicFeature("HP:0100807", "Long fingers", false))
+ .build())
+ .addRelatives(createPhenopacket("mom-phenopacket", "example-mom",
+ createPhenotypicFeature("HP:0001238", "Slender finger", false),
+ createPhenotypicFeature("HP:0001166", "Arachnodactyly", true))
+ .build())
+ .build();
+
+ List results = validator.validate(family);
+
+ assertThat(results, is(empty()));
+ }
+ }
+
+ /**
+ * White-box testing (same as in {@link FamilyTest}) - we know that the {@link PhenotypicFeature}
+ * is an attribute of a {@link Phenopacket}, so we test the validation logic extensively
+ * in {@link PhenopacketTest}. The {@link CohortTest} test suite ensures there are not errors in valid input.
+ */
+ @Nested
+ public class CohortTest {
+
+ private PhenopacketValidator validator;
+
+ @BeforeEach
+ public void setUp() {
+ validator = HpoPhenotypeValidators.Ancestry.cohortHpoAncestryValidator(HPO);
+ }
+
+ @Test
+ public void testValidInput() {
+ Cohort cohort = Cohort.newBuilder()
+ .addMembers(createPhenopacket("joe-phenopacket", "example-subject",
+ createPhenotypicFeature("HP:0001167", "Abnormality of finger", false),
+ createPhenotypicFeature("HP:0001166", "Arachnodactyly", true))
+ .build())
+ .addMembers(createPhenopacket("jim-phenopacket", "example-jim",
+ createPhenotypicFeature("HP:0001238", "Slender finger", false),
+ createPhenotypicFeature("HP:0100807", "Long fingers", false))
+ .build())
+ .addMembers(createPhenopacket("jane-phenopacket", "example-jane",
+ createPhenotypicFeature("HP:0001238", "Slender finger", false),
+ createPhenotypicFeature("HP:0001166", "Arachnodactyly", true))
+ .build())
+ .build();
+
+ List results = validator.validate(cohort);
+
+ assertThat(results, is(empty()));
+ }
+ }
+
+}
diff --git a/phenopacket-tools-validator-core/src/test/java/org/phenopackets/phenopackettools/validator/core/phenotype/OrganSystemValidatorTest.java b/phenopacket-tools-validator-core/src/test/java/org/phenopackets/phenopackettools/validator/core/phenotype/OrganSystemValidatorTest.java
new file mode 100644
index 00000000..845fdc51
--- /dev/null
+++ b/phenopacket-tools-validator-core/src/test/java/org/phenopackets/phenopackettools/validator/core/phenotype/OrganSystemValidatorTest.java
@@ -0,0 +1,159 @@
+package org.phenopackets.phenopackettools.validator.core.phenotype;
+
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Nested;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.CsvSource;
+import org.monarchinitiative.phenol.ontology.data.Ontology;
+import org.monarchinitiative.phenol.ontology.data.TermId;
+import org.phenopackets.phenopackettools.validator.core.*;
+import org.phenopackets.schema.v2.*;
+import org.phenopackets.schema.v2.core.PhenotypicFeature;
+
+import java.util.List;
+import java.util.Set;
+
+import static org.phenopackets.phenopackettools.validator.core.phenotype.Utils.*;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.*;
+
+public class OrganSystemValidatorTest {
+
+ private static final Ontology HPO = TestData.HPO;
+ private static final Set ABNORMALITY_OF_LIMBS_ORGAN_SYSTEM = Set.of(TermId.of("HP:0040064"));
+ // Not a real organ system, but for the sake of testing...
+ private static final Set SLENDER_FINGER_ORGAN_SYSTEM = Set.of(TermId.of("HP:0001238"));
+
+ @Nested
+ public class PhenopacketTest {
+
+ private PhenopacketValidator abnormalityOfLimbValidator;
+ private PhenopacketValidator slenderFingerValidator;
+
+ @BeforeEach
+ public void setUp() {
+ abnormalityOfLimbValidator = HpoPhenotypeValidators.OrganSystem.phenopacketHpoOrganSystemValidator(HPO, ABNORMALITY_OF_LIMBS_ORGAN_SYSTEM);
+ slenderFingerValidator = HpoPhenotypeValidators.OrganSystem.phenopacketHpoOrganSystemValidator(HPO, SLENDER_FINGER_ORGAN_SYSTEM);
+ }
+
+ @Test
+ public void noValidationErrorsIfOrganSystemIsAnnotated() {
+ // Has Arachnodactyly.
+ Phenopacket pp = createPhenopacket(
+ "example-phenopacket", "example-subject",
+ createPhenotypicFeature("HP:0001166", "Arachnodactyly", false)
+ ).build();
+
+ List results = abnormalityOfLimbValidator.validate(pp);
+
+ assertThat(results, is(empty()));
+ }
+
+ @Test
+ public void noValidationErrorsIfOrganSystemAbnormalityIsExcluded() {
+ // Has Arachnodactyly.
+ Phenopacket pp = createPhenopacket(
+ "example-phenopacket", "example-subject",
+ createPhenotypicFeature("HP:0040064", "Abnormality of limbs", true)
+ ).build();
+
+ List results = abnormalityOfLimbValidator.validate(pp);
+
+ assertThat(results, is(empty()));
+ }
+
+ @ParameterizedTest
+ @CsvSource({
+ "true",
+ "false"
+ })
+ public void annotationAbsenceLeadsToAnError(boolean excluded) {
+ // Long fingers and Slender finger are siblings, hence no annotation here.
+ Phenopacket pp = createPhenopacket(
+ "example-phenopacket", "example-subject",
+ createPhenotypicFeature("HP:0100807", "Long fingers", excluded)
+ ).build();
+
+ List results = slenderFingerValidator.validate(pp);
+
+ assertThat(results, hasSize(1));
+ ValidationResult result = results.get(0);
+ assertThat(result.validatorInfo(), equalTo(slenderFingerValidator.validatorInfo()));
+ assertThat(result.level(), equalTo(ValidationLevel.ERROR));
+ assertThat(result.category(), equalTo("Missing organ system annotation"));
+ assertThat(result.message(), equalTo("Missing annotation for Slender finger [HP:0001238] in example-phenopacket/example-subject"));
+ }
+ }
+
+ /**
+ * White-box testing - we know that the {@link PhenotypicFeature} is an attribute of a {@link Phenopacket}, so we
+ * test the validation logic extensively in {@link OrganSystemValidatorTest.PhenopacketTest}.
+ * The {@link OrganSystemValidatorTest.FamilyTest} test suite ensures there are not errors in a valid input.
+ */
+ @Nested
+ public class FamilyTest {
+
+ private PhenopacketValidator abnormalityOfLimbValidator;
+
+ @BeforeEach
+ public void setUp() {
+ abnormalityOfLimbValidator = HpoPhenotypeValidators.OrganSystem.familyHpoOrganSystemValidator(HPO, ABNORMALITY_OF_LIMBS_ORGAN_SYSTEM);
+ }
+
+ @Test
+ public void testValidInput() {
+ Family family = Family.newBuilder()
+ .setProband(createPhenopacket("example-phenopacket", "example-subject",
+ createPhenotypicFeature("HP:0001166", "Arachnodactyly", false))
+ .build())
+ .addRelatives(createPhenopacket("dad-phenopacket", "example-dad",
+ createPhenotypicFeature("HP:0001238", "Slender finger", false))
+ .build())
+ .addRelatives(createPhenopacket("mom-phenopacket", "other-mom",
+ createPhenotypicFeature("HP:0100807", "Long fingers", false))
+ .build())
+ .build();
+
+ List results = abnormalityOfLimbValidator.validate(family);
+
+ assertThat(results, is(empty()));
+ }
+ }
+
+ /**
+ * White-box testing (same as in {@link OrganSystemValidatorTest.FamilyTest}) - we know that the {@link PhenotypicFeature}
+ * is an attribute of a {@link Phenopacket}, so we test the validation logic extensively
+ * in {@link OrganSystemValidatorTest.PhenopacketTest}.
+ * The {@link OrganSystemValidatorTest.CohortTest} test suite ensures there are not errors in valid input.
+ */
+ @Nested
+ public class CohortTest {
+
+ private PhenopacketValidator abnormalityOfLimbValidator;
+
+ @BeforeEach
+ public void setUp() {
+ abnormalityOfLimbValidator = HpoPhenotypeValidators.OrganSystem.cohortHpoOrganSystemValidator(HPO, ABNORMALITY_OF_LIMBS_ORGAN_SYSTEM);
+ }
+
+ @Test
+ public void testValidInput() {
+ Cohort cohort = Cohort.newBuilder()
+ .addMembers(createPhenopacket("joe-phenopacket", "example-subject",
+ createPhenotypicFeature("HP:0001166", "Arachnodactyly", false))
+ .build())
+ .addMembers(createPhenopacket("jim-phenopacket", "example-jim",
+ createPhenotypicFeature("HP:0001238", "Slender finger", false))
+ .build())
+ .addMembers(createPhenopacket("jane-phenopacket", "example-jane",
+ createPhenotypicFeature("HP:0100807", "Long fingers", false))
+ .build())
+ .build();
+
+ List results = abnormalityOfLimbValidator.validate(cohort);
+
+ assertThat(results, is(empty()));
+ }
+ }
+}
diff --git a/phenopacket-tools-validator-core/src/test/java/org/phenopackets/phenopackettools/validator/core/phenotype/HpoPhenotypeValidatorTest.java b/phenopacket-tools-validator-core/src/test/java/org/phenopackets/phenopackettools/validator/core/phenotype/PrimaryHpoPhenotypeValidatorTest.java
similarity index 93%
rename from phenopacket-tools-validator-core/src/test/java/org/phenopackets/phenopackettools/validator/core/phenotype/HpoPhenotypeValidatorTest.java
rename to phenopacket-tools-validator-core/src/test/java/org/phenopackets/phenopackettools/validator/core/phenotype/PrimaryHpoPhenotypeValidatorTest.java
index 822167ea..d85048d4 100644
--- a/phenopacket-tools-validator-core/src/test/java/org/phenopackets/phenopackettools/validator/core/phenotype/HpoPhenotypeValidatorTest.java
+++ b/phenopacket-tools-validator-core/src/test/java/org/phenopackets/phenopackettools/validator/core/phenotype/PrimaryHpoPhenotypeValidatorTest.java
@@ -5,30 +5,29 @@
import org.junit.jupiter.api.Nested;
import org.junit.jupiter.api.Test;
import org.monarchinitiative.phenol.ontology.data.Ontology;
+import org.phenopackets.phenopackettools.validator.core.PhenopacketValidator;
import org.phenopackets.phenopackettools.validator.core.TestData;
import org.phenopackets.phenopackettools.validator.core.ValidationLevel;
import org.phenopackets.phenopackettools.validator.core.ValidationResult;
-import org.phenopackets.schema.v2.Cohort;
-import org.phenopackets.schema.v2.Family;
-import org.phenopackets.schema.v2.Phenopacket;
+import org.phenopackets.schema.v2.*;
import java.util.List;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.*;
-public class HpoPhenotypeValidatorTest {
+public class PrimaryHpoPhenotypeValidatorTest {
private static final Ontology HPO = TestData.HPO;
@Nested
public class PhenopacketTest {
- private PhenopacketHpoPhenotypeValidator validator;
+ private PhenopacketValidator validator;
@BeforeEach
public void setUp() {
- validator = new PhenopacketHpoPhenotypeValidator(HPO);
+ validator = HpoPhenotypeValidators.Primary.phenopacketHpoPhenotypeValidator(HPO);
}
@Test
@@ -86,7 +85,7 @@ public void testMissingTermId() throws Exception {
ValidationResult result = results.get(0);
assertThat(result.level(), equalTo(ValidationLevel.ERROR));
assertThat(result.category(), equalTo("Invalid TermId"));
- assertThat(result.message(), equalTo("HP:0001182 in 'proband A' not found in http://purl.obolibrary.org/obo/hp/releases/2021-06-08/hp.json"));
+ assertThat(result.message(), equalTo("HP:0001182 in proband A not found in http://purl.obolibrary.org/obo/hp/releases/2021-06-08/hp.json"));
}
@Test
@@ -117,7 +116,7 @@ public void testObsoleteTermId() throws Exception {
ValidationResult result = results.get(0);
assertThat(result.level(), equalTo(ValidationLevel.WARNING));
assertThat(result.category(), equalTo("Obsoleted TermId"));
- assertThat(result.message(), equalTo("Using obsoleted id (HP:0001505) instead of current primary id (HP:0001166) in 'proband A'"));
+ assertThat(result.message(), equalTo("Using obsolete id (HP:0001505) instead of current primary id (HP:0001166) in proband A"));
}
@Test
@@ -143,7 +142,7 @@ public void testMistypedTermId() throws Exception {
ValidationResult result = results.get(0);
assertThat(result.level(), equalTo(ValidationLevel.ERROR));
assertThat(result.category(), equalTo("Invalid TermId"));
- assertThat(result.message(), equalTo("The HP_0100807 found in 'proband A' is not a valid value"));
+ assertThat(result.message(), equalTo("The HP_0100807 found in proband A is not a valid term ID"));
}
}
@@ -152,11 +151,11 @@ public void testMistypedTermId() throws Exception {
*/
@Nested
public class FamilyTest {
- private FamilyHpoPhenotypeValidator validator;
+ private PhenopacketValidator validator;
@BeforeEach
public void setUp() {
- validator = new FamilyHpoPhenotypeValidator(HPO);
+ validator = HpoPhenotypeValidators.Primary.familyHpoPhenotypeValidator(HPO);
}
@Test
@@ -248,7 +247,7 @@ public void testInvalidIdInProband() throws Exception {
ValidationResult result = results.get(0);
assertThat(result.level(), equalTo(ValidationLevel.ERROR));
assertThat(result.category(), equalTo("Invalid TermId"));
- assertThat(result.message(), equalTo("The HP_0001238 found in 'Flynn' is not a valid value"));
+ assertThat(result.message(), equalTo("The HP_0001238 found in Flynn is not a valid term ID"));
}
@Test
@@ -296,7 +295,7 @@ public void testInvalidIdInRelative() throws Exception {
ValidationResult result = results.get(0);
assertThat(result.level(), equalTo(ValidationLevel.ERROR));
assertThat(result.category(), equalTo("Invalid TermId"));
- assertThat(result.message(), equalTo("The HP_0001238 found in 'Walt' is not a valid value"));
+ assertThat(result.message(), equalTo("The HP_0001238 found in Walt is not a valid term ID"));
}
}
@@ -306,11 +305,11 @@ public void testInvalidIdInRelative() throws Exception {
@Nested
public class CohortTest {
- private CohortHpoPhenotypeValidator validator;
+ private PhenopacketValidator validator;
@BeforeEach
public void setUp() {
- validator = new CohortHpoPhenotypeValidator(HPO);
+ validator = HpoPhenotypeValidators.Primary.cohortHpoPhenotypeValidator(HPO);
}
@Test
@@ -384,7 +383,7 @@ public void testInvalidId() throws Exception {
ValidationResult result = results.get(0);
assertThat(result.level(), equalTo(ValidationLevel.ERROR));
assertThat(result.category(), equalTo("Invalid TermId"));
- assertThat(result.message(), equalTo("The HP_0001238 found in 'Thing 1' is not a valid value"));
+ assertThat(result.message(), equalTo("The HP_0001238 found in Thing 1 is not a valid term ID"));
}
}
diff --git a/phenopacket-tools-validator-core/src/test/java/org/phenopackets/phenopackettools/validator/core/phenotype/Utils.java b/phenopacket-tools-validator-core/src/test/java/org/phenopackets/phenopackettools/validator/core/phenotype/Utils.java
new file mode 100644
index 00000000..7b93bc68
--- /dev/null
+++ b/phenopacket-tools-validator-core/src/test/java/org/phenopackets/phenopackettools/validator/core/phenotype/Utils.java
@@ -0,0 +1,33 @@
+package org.phenopackets.phenopackettools.validator.core.phenotype;
+
+import org.phenopackets.schema.v2.Phenopacket;
+import org.phenopackets.schema.v2.core.Individual;
+import org.phenopackets.schema.v2.core.OntologyClass;
+import org.phenopackets.schema.v2.core.PhenotypicFeature;
+
+import java.util.Arrays;
+
+public class Utils {
+
+ static Phenopacket.Builder createPhenopacket(String phenopacketId,
+ String subjectId,
+ PhenotypicFeature... features) {
+ return Phenopacket.newBuilder()
+ .setId(phenopacketId)
+ .setSubject(Individual.newBuilder()
+ .setId(subjectId)
+ .build())
+ .addAllPhenotypicFeatures(Arrays.asList(features));
+ }
+
+ static PhenotypicFeature createPhenotypicFeature(String id, String label, boolean excluded) {
+ return PhenotypicFeature.newBuilder()
+ .setType(OntologyClass.newBuilder()
+ .setId(id)
+ .setLabel(label)
+ .build())
+ .setExcluded(excluded)
+ .build();
+ }
+
+}
diff --git a/phenopacket-tools-validator-jsonschema/pom.xml b/phenopacket-tools-validator-jsonschema/pom.xml
index 84f949d4..10ef5f3d 100644
--- a/phenopacket-tools-validator-jsonschema/pom.xml
+++ b/phenopacket-tools-validator-jsonschema/pom.xml
@@ -7,7 +7,7 @@
org.phenopackets.phenopackettools
phenopacket-tools
- 0.4.6
+ 0.4.7
phenopacket-tools-validator-jsonschema
diff --git a/phenopacket-tools-validator-jsonschema/src/main/java/module-info.java b/phenopacket-tools-validator-jsonschema/src/main/java/module-info.java
index 092a770f..df165e96 100644
--- a/phenopacket-tools-validator-jsonschema/src/main/java/module-info.java
+++ b/phenopacket-tools-validator-jsonschema/src/main/java/module-info.java
@@ -1,3 +1,13 @@
+/**
+ * Defines a {@link org.phenopackets.phenopackettools.validator.core.ValidationWorkflowRunner} with base
+ * validation backed by a JSON schema.
+ *
+ * The module provides {@link org.phenopackets.phenopackettools.validator.jsonschema.JsonSchemaValidationWorkflowRunner}
+ * an implementation of {@link org.phenopackets.phenopackettools.validator.core.ValidationWorkflowRunner}
+ * backed by a JSON schema validator.
+ *
+ * @see org.phenopackets.phenopackettools.validator.jsonschema.JsonSchemaValidationWorkflowRunner
+ */
module org.phenopackets.phenopackettools.validator.jsonschema {
requires org.phenopackets.phenopackettools.util;
requires transitive org.phenopackets.phenopackettools.validator.core;
diff --git a/phenopacket-tools-validator-jsonschema/src/main/java/org/phenopackets/phenopackettools/validator/jsonschema/ValidationWorkflowRunnerBuilder.java b/phenopacket-tools-validator-jsonschema/src/main/java/org/phenopackets/phenopackettools/validator/jsonschema/BaseValidationWorkflowRunnerBuilder.java
similarity index 85%
rename from phenopacket-tools-validator-jsonschema/src/main/java/org/phenopackets/phenopackettools/validator/jsonschema/ValidationWorkflowRunnerBuilder.java
rename to phenopacket-tools-validator-jsonschema/src/main/java/org/phenopackets/phenopackettools/validator/jsonschema/BaseValidationWorkflowRunnerBuilder.java
index 5d75fc92..d21ec40c 100644
--- a/phenopacket-tools-validator-jsonschema/src/main/java/org/phenopackets/phenopackettools/validator/jsonschema/ValidationWorkflowRunnerBuilder.java
+++ b/phenopacket-tools-validator-jsonschema/src/main/java/org/phenopackets/phenopackettools/validator/jsonschema/BaseValidationWorkflowRunnerBuilder.java
@@ -18,14 +18,14 @@
import java.util.List;
/**
- * A utility class that provides {@link JsonSchemaValidationWorkflowRunner.Builder} implementations for top-level
+ * A utility class that provides {@link JsonSchemaValidationWorkflowRunnerBuilder} implementations for top-level
* elements of Phenopacket schema.
*
* The class exists because we do not want to expose {@link JsonSchemaValidator} to the outside world.
*/
-abstract class ValidationWorkflowRunnerBuilder extends JsonSchemaValidationWorkflowRunner.Builder {
+abstract class BaseValidationWorkflowRunnerBuilder extends JsonSchemaValidationWorkflowRunnerBuilder {
- private static final Logger LOGGER = LoggerFactory.getLogger(ValidationWorkflowRunnerBuilder.class);
+ private static final Logger LOGGER = LoggerFactory.getLogger(BaseValidationWorkflowRunnerBuilder.class);
@Override
public JsonSchemaValidationWorkflowRunner build() {
@@ -33,6 +33,7 @@ public JsonSchemaValidationWorkflowRunner build() {
return new JsonSchemaValidationWorkflowRunner<>(getFormatConverter(),
getBaseRequirementsValidator(),
requirementValidators,
+ syntaxValidators,
semanticValidators);
}
@@ -56,7 +57,7 @@ private List readRequirementValidators(List schemaUrls
return requirementValidators;
}
- static class PhenopacketWorkflowRunnerBuilder extends ValidationWorkflowRunnerBuilder