Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions docs/assay-schema.md
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,7 @@ Rules:
- `derived_from` lists the variant YAML files used by the interpretation
- `emits` is optional but recommended so report generators know which output columns to display and how to label them
- `logic` is optional; use `logic.description` and `logic.source.url` to document where the script's derivation rules came from
- Analysis rows may emit `notes` or `report_notes` as a reporting convention. HTML reports render those notes below the analysis table and omit them from the table columns; this avoids a manifest-level template language while still letting the script build human-readable text from computed values.

## Findings

Expand Down
2 changes: 2 additions & 0 deletions rust/Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 1 addition & 3 deletions rust/bioscript-cli/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,8 @@ bioscript-formats = { path = "../bioscript-formats" }
bioscript-runtime = { path = "../bioscript-runtime" }
bioscript-schema = { path = "../bioscript-schema" }
monty = { path = "../../monty/crates/monty" }
serde_json = "1.0.133"
serde_json = { version = "1.0.133", features = ["preserve_order"] }
serde_yaml = "0.9.34"

[dev-dependencies]
zip = { version = "2.2.0", default-features = false, features = ["deflate"] }

[lints.clippy]
Expand Down
12 changes: 8 additions & 4 deletions rust/bioscript-cli/src/cli_bootstrap.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,9 @@ use std::{
};

use bioscript_formats::{
GenotypeLoadOptions, GenotypeSourceFormat, GenotypeStore, InspectOptions, PrepareRequest,
inspect_file, prepare_indexes, shell_flags,
GenotypeLoadOptions, GenotypeSourceFormat, GenotypeStore, InferredSex, InspectOptions,
PrepareRequest, SexDetectionConfidence, SexInference, inspect_file, prepare_indexes,
shell_flags,
};
use bioscript_runtime::{BioscriptRuntime, RuntimeConfig, StageTiming};
use bioscript_schema::{
Expand Down Expand Up @@ -46,6 +47,8 @@ fn run_cli() -> Result<(), String> {
normalize_loader_paths(&runtime_root, &mut options.loader);
let mut cli_timings = prepare_cli_indexes(&runtime_root, &mut options)?;

let script_path = prepare_package_entrypoint_from_arg(&runtime_root, &script_path)?;

if is_yaml_manifest(&script_path) {
run_cli_manifest(&runtime_root, &script_path, &options, &mut cli_timings)?;
} else {
Expand All @@ -54,7 +57,7 @@ fn run_cli() -> Result<(), String> {
Ok(())
}

const USAGE: &str = "usage: bioscript <script.py|manifest.yaml> [--root <dir>] [--input-file <path>] [--output-file <path>] [--participant-id <id>] [--trace-report <path>] [--timing-report <path>] [--filter key=value] [--input-format auto|text|zip|vcf|cram] [--input-index <path>] [--reference-file <path>] [--reference-index <path>] [--auto-index] [--cache-dir <path>] [--max-duration-ms N] [--max-memory-bytes N] [--max-allocations N] [--max-recursion-depth N]\n bioscript report <manifest.yaml> --input-file <path> [--input-file <path>...] --output-dir <dir> [--html] [--root <dir>] [--input-format auto|text|zip|vcf|cram]\n bioscript validate-variants <path> [--report <file>]\n bioscript validate-panels <path> [--report <file>]\n bioscript validate-assays <path> [--report <file>]\n bioscript prepare [--root <dir>] [--input-file <path>] [--reference-file <path>] [--input-format auto|text|zip|vcf|cram] [--cache-dir <path>]\n bioscript inspect <path> [--input-index <path>] [--reference-file <path>] [--reference-index <path>]";
const USAGE: &str = "usage: bioscript <script.py|manifest.yaml|package.zip|https://.../package.zip> [--root <dir>] [--input-file <path>] [--output-file <path>] [--participant-id <id>] [--trace-report <path>] [--timing-report <path>] [--filter key=value] [--input-format auto|text|zip|vcf|cram] [--input-index <path>] [--reference-file <path>] [--reference-index <path>] [--auto-index] [--cache-dir <path>] [--max-duration-ms N] [--max-memory-bytes N] [--max-allocations N] [--max-recursion-depth N]\n bioscript report <manifest.yaml|package.zip|https://.../package.zip> --input-file <path> [--input-file <path>...] --output-dir <dir> [--html] [--open] [--root <dir>] [--input-format auto|text|zip|vcf|cram] [--detect-sex] [--sample-sex male|female|unknown] [--analysis-max-duration-ms N]\n bioscript review <manifest.yaml|package.zip> --cases <cases.yaml> --output-dir <dir> [--html] [--root <dir>] [--filter key=value]\n bioscript import-package <package.zip|https://.../package.zip> [--root <dir>] [--output-dir <dir>]\n bioscript validate-variants <path> [--report <file>]\n bioscript validate-panels <path> [--report <file>]\n bioscript validate-assays <path> [--report <file>]\n bioscript prepare [--root <dir>] [--input-file <path>] [--reference-file <path>] [--input-format auto|text|zip|vcf|cram] [--cache-dir <path>]\n bioscript inspect <path> [--input-index <path>] [--reference-file <path>] [--reference-index <path>] [--detect-sex]";

struct CliOptions {
script_path: Option<PathBuf>,
Expand All @@ -78,6 +81,8 @@ fn dispatch_subcommand(args: &[String]) -> Result<bool, String> {
let rest = rest.to_vec();
match first.as_str() {
"report" => run_app_report(rest).map(|()| true),
"review" => run_review_report(rest).map(|()| true),
"import-package" => run_import_package(rest).map(|()| true),
"validate-variants" => run_validate_variants(rest).map(|()| true),
"validate-panels" => run_validate_panels(rest).map(|()| true),
"validate-assays" => run_validate_assays(rest).map(|()| true),
Expand Down Expand Up @@ -411,4 +416,3 @@ fn write_timing_report(path: &PathBuf, timings: &[StageTiming]) -> Result<(), St
fs::write(path, output)
.map_err(|err| format!("failed to write timing report {}: {err}", path.display()))
}

5 changes: 4 additions & 1 deletion rust/bioscript-cli/src/cli_commands.rs
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,9 @@ fn run_inspect(args: Vec<String>) -> Result<(), String> {
iter.next().ok_or("--reference-index requires a path")?,
));
}
"--detect-sex" => {
options.detect_sex = true;
}
other if path.is_none() => {
path = Some(PathBuf::from(other));
}
Expand All @@ -99,7 +102,7 @@ fn run_inspect(args: Vec<String>) -> Result<(), String> {

let Some(path) = path else {
return Err(
"usage: bioscript inspect <path> [--input-index <path>] [--reference-file <path>] [--reference-index <path>]"
"usage: bioscript inspect <path> [--input-index <path>] [--reference-file <path>] [--reference-index <path>] [--detect-sex]"
.to_owned(),
);
};
Expand Down
5 changes: 4 additions & 1 deletion rust/bioscript-cli/src/commands.rs
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,9 @@ pub(crate) fn run_inspect(args: Vec<String>) -> Result<(), String> {
iter.next().ok_or("--reference-index requires a path")?,
));
}
"--detect-sex" => {
options.detect_sex = true;
}
other if path.is_none() => {
path = Some(PathBuf::from(other));
}
Expand All @@ -105,7 +108,7 @@ pub(crate) fn run_inspect(args: Vec<String>) -> Result<(), String> {

let Some(path) = path else {
return Err(
"usage: bioscript inspect <path> [--input-index <path>] [--reference-file <path>] [--reference-index <path>]"
"usage: bioscript inspect <path> [--input-index <path>] [--reference-file <path>] [--reference-index <path>] [--detect-sex]"
.to_owned(),
);
};
Expand Down
3 changes: 3 additions & 0 deletions rust/bioscript-cli/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,11 @@
include!("cli_bootstrap.rs");
include!("cli_commands.rs");
include!("report_options.rs");
include!("package.rs");
include!("report_review.rs");
include!("report_execution.rs");
include!("report_observations.rs");
include!("report_findings.rs");
include!("report_matching.rs");
include!("report_output.rs");
include!("report_html.rs");
Expand Down
4 changes: 4 additions & 0 deletions rust/bioscript-cli/src/manifest.rs
Original file line number Diff line number Diff line change
Expand Up @@ -198,6 +198,10 @@ pub(crate) fn variant_row(
.depth
.map_or_else(String::new, |value| value.to_string()),
);
row.insert(
"raw_counts".to_owned(),
serde_json::to_string(&observation.raw_counts).unwrap_or_default(),
);
row.insert("evidence".to_owned(), observation.evidence.join(" | "));
row
}
Expand Down
38 changes: 28 additions & 10 deletions rust/bioscript-cli/src/manifest_runner.rs
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,15 @@ fn run_variant_manifest(
let input_file = input_file.ok_or("manifest execution requires --input-file")?;
let store = GenotypeStore::from_file_with_options(Path::new(input_file), loader)
.map_err(|err| err.to_string())?;
run_variant_manifest_with_store(runtime_root, manifest, &store, participant_id)
}

fn run_variant_manifest_with_store(
runtime_root: &Path,
manifest: &VariantManifest,
store: &GenotypeStore,
participant_id: Option<&str>,
) -> Result<BTreeMap<String, String>, String> {
let observation = store
.lookup_variant(&manifest.spec)
.map_err(|err| err.to_string())?;
Expand All @@ -103,6 +112,16 @@ fn run_panel_manifest(
let input_file = input_file.ok_or("manifest execution requires --input-file")?;
let store = GenotypeStore::from_file_with_options(Path::new(input_file), loader)
.map_err(|err| err.to_string())?;
run_panel_manifest_with_store(runtime_root, panel, &store, participant_id, filters)
}

fn run_panel_manifest_with_store(
runtime_root: &Path,
panel: &PanelManifest,
store: &GenotypeStore,
participant_id: Option<&str>,
filters: &[String],
) -> Result<Vec<BTreeMap<String, String>>, String> {
let mut rows = Vec::new();

for member in &panel.members {
Expand All @@ -115,23 +134,18 @@ fn run_panel_manifest(
if !matches_filters(&manifest, &resolved, filters) {
continue;
}
let observation = store
.lookup_variant(&manifest.spec)
.map_err(|err| err.to_string())?;
rows.push(variant_row(
rows.push(run_variant_manifest_with_store(
runtime_root,
&resolved,
&manifest.name,
&manifest.tags,
&observation,
&manifest,
store,
participant_id,
));
)?);
} else if member.kind == "assay" {
let assay = load_assay_manifest(&resolved)?;
rows.extend(run_assay_manifest_with_store(
runtime_root,
&assay,
&store,
store,
participant_id,
filters,
)?);
Expand Down Expand Up @@ -260,6 +274,10 @@ fn variant_row(
.depth
.map_or_else(String::new, |value| value.to_string()),
);
row.insert(
"raw_counts".to_owned(),
serde_json::to_string(&observation.raw_counts).unwrap_or_default(),
);
row.insert("evidence".to_owned(), observation.evidence.join(" | "));
row
}
Expand Down
Loading
Loading