Skip to content

Commit d46ae48

Browse files
committed
changes to . VCF value mapping
1 parent cee20ba commit d46ae48

4 files changed

Lines changed: 81 additions & 0 deletions

File tree

src/run_conversion.sh

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -159,6 +159,17 @@ count_triples_json() {
159159
echo "}"
160160
}
161161

162+
# Replace plain VCF null marker literals (`"."`) with typed null literals.
163+
# Ontology alignment:
164+
# "." -> "."^^vcfr:Null
165+
annotate_null_literals_nt() {
166+
local nt_path="$1"
167+
local tmp_path
168+
tmp_path="$(mktemp "${nt_path}.nullfix.XXXXXX")"
169+
sed -E 's/"\."([[:space:]]+\.)/"."^^<https:\/\/w3id.org\/vcf-rdfizer\/vocab#Null>\1/g' "$nt_path" > "$tmp_path"
170+
mv "$tmp_path" "$nt_path"
171+
}
172+
162173

163174
# Convert elapsed clock text from `time` output to numeric seconds.
164175
elapsed_to_seconds() {
@@ -266,6 +277,17 @@ else
266277
OUTPUT_PATH="$OUT_DIR/$OUT_NAME"
267278
fi
268279

280+
# Apply ontology-compliant null datatype annotation to produced RDF files.
281+
if [[ -f "$OUTPUT_PATH" ]]; then
282+
annotate_null_literals_nt "$OUTPUT_PATH"
283+
elif [[ -d "$OUTPUT_PATH" ]]; then
284+
shopt -s nullglob
285+
for RDF_NT in "$OUTPUT_PATH"/*.nt; do
286+
annotate_null_literals_nt "$RDF_NT"
287+
done
288+
shopt -u nullglob
289+
fi
290+
269291
OUT_SIZE=$(stat_size "$OUTPUT_PATH")
270292
TRIPLES_JSON=$(count_triples_json "$OUTPUT_PATH")
271293

test/test_run_conversion_unit.py

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -532,6 +532,61 @@ def test_run_conversion_aggregate_skips_duplicate_part_payloads(self):
532532
self.assertEqual(lines.count("<dup> <p> <o> ."), 1)
533533
self.assertEqual(lines.count("<uniq> <p> <o> ."), 1)
534534

535+
def test_run_conversion_rewrites_dot_literal_to_typed_null(self):
536+
"""Plain literal dot is normalized to ontology null typed literal in RDF output."""
537+
with tempfile.TemporaryDirectory() as td:
538+
tmp_path = Path(td)
539+
fake_bin = tmp_path / "bin"
540+
fake_bin.mkdir()
541+
make_executable(
542+
fake_bin / "java",
543+
"""#!/usr/bin/env bash
544+
set -euo pipefail
545+
if [[ "${1:-}" == "-version" ]]; then
546+
echo 'openjdk version "11.0.0"' >&2
547+
exit 0
548+
fi
549+
out=""
550+
while [[ $# -gt 0 ]]; do
551+
if [[ "$1" == "-o" ]]; then
552+
out="$2"
553+
shift 2
554+
continue
555+
fi
556+
shift
557+
done
558+
mkdir -p "$out"
559+
printf '<s> <p> "." .\\n' > "$out/part-000"
560+
""",
561+
)
562+
563+
out_dir = tmp_path / "out"
564+
metrics_dir = tmp_path / "metrics"
565+
rules = tmp_path / "rules.ttl"
566+
rules.write_text("@prefix ex: <http://example.org/> .\n")
567+
vcf = tmp_path / "input.vcf"
568+
vcf.write_text("##fileformat=VCFv4.2\n#CHROM\tPOS\n1\t5\n")
569+
570+
env = env_with_path(fake_bin)
571+
env.update(
572+
{
573+
"JAR": "fake.jar",
574+
"IN": str(rules),
575+
"IN_VCF": str(vcf),
576+
"OUT_DIR": str(out_dir),
577+
"OUT_NAME": "rdf",
578+
"LOGDIR": str(metrics_dir),
579+
"RUN_ID": "run-null-dot",
580+
"TIMESTAMP": "2026-01-01T00:00:00",
581+
}
582+
)
583+
584+
result = subprocess.run(["bash", str(SCRIPT)], env=env, capture_output=True, text=True)
585+
self.assertEqual(result.returncode, 0, msg=result.stderr)
586+
merged_nt = out_dir / "rdf" / "rdf.nt"
587+
text = merged_nt.read_text()
588+
self.assertIn('"."^^<https://w3id.org/vcf-rdfizer/vocab#Null> .', text)
589+
535590

536591
if __name__ == "__main__":
537592
unittest.main()

test/test_vcf_rdfizer_unit.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1100,6 +1100,8 @@ def fake_run(cmd, cwd=None, env=None):
11001100
self.assertEqual(len(commands), 2)
11011101
self.assertIn("/opt/vcf-rdfizer/vcf_as_tsv.sh", commands[0])
11021102
self.assertIn("/opt/vcf-rdfizer/run_conversion.sh", commands[1])
1103+
self.assertTrue(any(str(arg).endswith(":/data/in:ro") for arg in commands[1]))
1104+
self.assertTrue(any(str(arg).startswith("IN_VCF=/data/in/") for arg in commands[1]))
11031105

11041106
def test_main_full_mode_batch_layout_compresses_each_rml_part(self):
11051107
"""Batch layout compresses each part and prints one consolidated size summary."""

vcf_rdfizer.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2118,6 +2118,8 @@ def fail_current(stage: str, message: str):
21182118
run_cmd = [
21192119
*docker_run_base(),
21202120
"-v",
2121+
f"{str(input_mount_dir)}:/data/in:ro",
2122+
"-v",
21212123
f"{str(generated_rules_dir)}:/data/rules:ro",
21222124
"-v",
21232125
f"{str(tsv_dir)}:/data/tsv:ro",

0 commit comments

Comments
 (0)