From 8b32cda59ece4ed449c2bf32f6fd3c97014fd035 Mon Sep 17 00:00:00 2001 From: Nicholas Hathaway Date: Mon, 29 Jun 2026 15:48:33 -0700 Subject: [PATCH 1/2] fixed a left over old schema version where all fields were copies over, now checks for optionally ones; --- src/pmotools/pmo_engine/pmo_processor.py | 24 +++++++++++------- tests/test_pmo_engine/test_pmo_processor.py | 28 ++++++++++++++++++++- 2 files changed, 42 insertions(+), 10 deletions(-) diff --git a/src/pmotools/pmo_engine/pmo_processor.py b/src/pmotools/pmo_engine/pmo_processor.py index cef95fb..722cd7a 100644 --- a/src/pmotools/pmo_engine/pmo_processor.py +++ b/src/pmotools/pmo_engine/pmo_processor.py @@ -1073,23 +1073,29 @@ def extract_from_pmo_with_read_filter(pmodata, read_filter: float): pmo_out = { "pmo_header": copy.deepcopy(pmodata["pmo_header"]), "panel_info": copy.deepcopy(pmodata["panel_info"]), - "sequencing_info": copy.deepcopy(pmodata["sequencing_info"]), "target_info": copy.deepcopy(pmodata["target_info"]), "specimen_info": copy.deepcopy(pmodata["specimen_info"]), "library_sample_info": copy.deepcopy(pmodata["library_sample_info"]), - "project_info": copy.deepcopy(pmodata["project_info"]), - "targeted_genomes": copy.deepcopy(pmodata["targeted_genomes"]), "representative_microhaplotypes": copy.deepcopy( pmodata["representative_microhaplotypes"] ), - "bioinformatics_methods_info": copy.deepcopy( - pmodata["bioinformatics_methods_info"] - ), - "bioinformatics_run_info": copy.deepcopy( - pmodata["bioinformatics_run_info"] - ), "detected_microhaplotypes": [], } + # only copy optional sections if they are present (no longer required by the schema) + if "sequencing_info" in pmodata: + pmo_out["sequencing_info"] = copy.deepcopy(pmodata["sequencing_info"]) + if "project_info" in pmodata: + pmo_out["project_info"] = copy.deepcopy(pmodata["project_info"]) + if "targeted_genomes" in pmodata: + pmo_out["targeted_genomes"] = copy.deepcopy(pmodata["targeted_genomes"]) + if "bioinformatics_methods_info" in pmodata: + pmo_out["bioinformatics_methods_info"] = copy.deepcopy( + pmodata["bioinformatics_methods_info"] + ) + if "bioinformatics_run_info" in pmodata: + pmo_out["bioinformatics_run_info"] = copy.deepcopy( + pmodata["bioinformatics_run_info"] + ) # if has optional read_counts_by_stage then add as well # if does contain, @todo consider updating with new counts now that a filter has been applied diff --git a/tests/test_pmo_engine/test_pmo_processor.py b/tests/test_pmo_engine/test_pmo_processor.py index 2c4a41b..5ce6d31 100755 --- a/tests/test_pmo_engine/test_pmo_processor.py +++ b/tests/test_pmo_engine/test_pmo_processor.py @@ -1,5 +1,6 @@ #!/usr/bin/env python3 +import copy import os import tempfile import unittest @@ -317,10 +318,35 @@ def test_extract_from_pmo_with_read_filter(self): ) with open(output_fnp, "w") as f: json.dump(pmo_data_filtered, f) - self.assertEqual("879b1a0c62a77a8fcc910a21d8ec9de6", md5sum_of_fnp(output_fnp)) + self.assertEqual("59e24e7efd4cd7822acde70828ad6119", md5sum_of_fnp(output_fnp)) checker = PMOChecker(self.pmo_jsonschema_data) checker.validate_pmo_json(pmo_data_filtered) + def test_extract_from_pmo_with_read_filter_missing_optional_sections(self): + # the v1.1.0 schema relaxed the required fields, so a PMO may omit these + # optional sections; the extracted PMO must not copy them in as null/empty keys + optional_sections = [ + "sequencing_info", + "project_info", + "targeted_genomes", + "bioinformatics_methods_info", + "bioinformatics_run_info", + "read_counts_by_stage", + ] + stripped_pmo_data = copy.deepcopy(self.combined_pmo_data) + for section in optional_sections: + stripped_pmo_data.pop(section, None) + pmo_data_filtered = PMOProcessor.extract_from_pmo_with_read_filter( + stripped_pmo_data, 1000 + ) + for section in optional_sections: + self.assertNotIn(section, pmo_data_filtered) + # the relaxed required fields are a v1.1.0 change, so validate against v1.1.0 + checker = PMOChecker( + load_schema("portable_microhaplotype_object_v1.1.0.schema.json") + ) + checker.validate_pmo_json(pmo_data_filtered) + def test_filter_pmo_by_target_ids(self): pmo_data_select_targets = PMOProcessor.filter_pmo_by_target_ids( self.combined_pmo_data, {1, 10, 11, 55} From 6bb05646d7cf24f5420321730a70d6996bf61ef9 Mon Sep 17 00:00:00 2001 From: Nicholas Hathaway Date: Mon, 29 Jun 2026 16:45:40 -0700 Subject: [PATCH 2/2] small fixes for handling minimum format; added more robust unittests for the v1.1.0 files; --- src/pmotools/pmo_engine/pmo_exporter.py | 12 +++++-- src/pmotools/pmo_engine/pmo_processor.py | 4 +++ tests/data/minimum_Furstenau2025_PMO.json.gz | Bin 0 -> 18894 bytes tests/test_pmo_engine/test_pmo_exporter.py | 32 +++++++++++++++++++ tests/test_pmo_engine/test_pmo_processor.py | 28 ++++++++++++++++ 5 files changed, 74 insertions(+), 2 deletions(-) create mode 100644 tests/data/minimum_Furstenau2025_PMO.json.gz diff --git a/src/pmotools/pmo_engine/pmo_exporter.py b/src/pmotools/pmo_engine/pmo_exporter.py index 361ebd7..66238c7 100644 --- a/src/pmotools/pmo_engine/pmo_exporter.py +++ b/src/pmotools/pmo_engine/pmo_exporter.py @@ -121,10 +121,14 @@ def export_specimen_meta_table(pmodata, separator: str = ",") -> pd.DataFrame: for specimen in pmodata["specimen_info"]: export_row = {} for key, value in specimen.items(): - if "project_id" == key: + if "project_id" == key and "project_info" in pmodata: export_row["project_name"] = pmodata["project_info"][value][ "project_name" ] + elif "project_id" == key: + # project_info is optional as of schema v1.1.0; keep the raw id + # rather than failing to resolve a name that isn't available + export_row[key] = value elif PMOExporter._is_primitive(value): export_row[key] = value elif PMOExporter._is_primitive_list(value): @@ -145,10 +149,14 @@ def export_library_sample_meta_table(pmodata, separator: str = ",") -> pd.DataFr for library_sample in pmodata["library_sample_info"]: export_row = {} for key, value in library_sample.items(): - if "sequencing_info_id" == key: + if "sequencing_info_id" == key and "sequencing_info" in pmodata: export_row["sequencing_info_name"] = pmodata["sequencing_info"][ value ]["sequencing_info_name"] + elif "sequencing_info_id" == key: + # sequencing_info is optional as of schema v1.1.0; keep the raw id + # rather than failing to resolve a name that isn't available + export_row[key] = value elif "specimen_id" == key: export_row["specimen_name"] = pmodata["specimen_info"][value][ "specimen_name" diff --git a/src/pmotools/pmo_engine/pmo_processor.py b/src/pmotools/pmo_engine/pmo_processor.py index 722cd7a..c452916 100644 --- a/src/pmotools/pmo_engine/pmo_processor.py +++ b/src/pmotools/pmo_engine/pmo_processor.py @@ -20,6 +20,10 @@ def get_index_key_of_bioinformatics_run_names(pmodata): :return: a dictionary of indexes keyed by bioinformatics_run_name """ ret = {} + # bioinformatics_run_info is optional as of schema v1.1.0; a PMO without it + # simply has no run-name to index mapping + if "bioinformatics_run_info" not in pmodata: + return ret for idx, bioinformatics_run in enumerate(pmodata["bioinformatics_run_info"]): ret[bioinformatics_run["bioinformatics_run_name"]] = idx return ret diff --git a/tests/data/minimum_Furstenau2025_PMO.json.gz b/tests/data/minimum_Furstenau2025_PMO.json.gz new file mode 100644 index 0000000000000000000000000000000000000000..b87e2e68517c4deb2c2bc9584534191f3c60dde5 GIT binary patch literal 18894 zcmagE1yCHp_b$4)%i`_ zcOJk0|JJK_Z{2rq)i+)Jo$s8}Gd(?B(>=o+g^N4bf@6#R=oqnWnh*LDZ*kkl$$0La?9kVXX`K_%PUZH%Jn2cXNB&w)p94 zXKS70aoyzM_QJ8Q?Y1%_HplmQ(Y0s5z1QjL=|P*<<3z-W=yt$& z1ymc|W98aQnUfsRryRU8XM022ZTAloi;mKdhp}ys+r1=ht&az&%DrC4w!PD%w#Snl zlgF#xgV=|<`xsxDtEsRund9w)$H&vVpE7qRr;pcHtLrjL0XN5c%x!;~AFt~lukJ6b zA1`loJ(q9(M(~?Fv_0G}mn-j@zcOjPnqU?UxWAssSYCSk)arD)87zJ0aXNUBvPrij z?2p{{L%Q9OE#8tHTs`wRQnl}W>FjH=Chm`1z9n1!r`oxc5*lMdcG=3|ahFhY`K)9; zWuxU~|9vaRL50^&!^Xrz!coK4#9?|$OqBEizt2v??!;kfO3dIB@!e@SoH%Ssi6M|V z;QzGKa58Z?kP@T%MErIdE=~{UXJYJRjw<{Pp2)*(>-zl2FFO;7iu>kY7q$O4a$A0H z;W5)H%-Ck>dGLPcitm@*vTd*99La^lQ)zTN?S7ZMSnKHbUqr<1zliyNlmAvED*T?@ zD%R&y7Hf~h|5qZj<7s&N&HPP6NSyTdutLA^OFCkp^pp31@1j5gUDpL-fNm#oTrN zm7K92k3U#<57Q-#o@i|W6E#n^%ga{>4FsY0I0jFC@W-I(m|wD9TgrP+HtXz~Co($& zmw9pX>;6%u!d=Wn`piOpc(KjDlxn;Gh5r`$_#K|ZL+BIi9zq=-AT|#W`v-{a1LWg9 z#5VhMQgXY*dsz20vuI09bI3J2eOpp^#QVR6M(<(Q)6DHHv5_YsV_Q;m#QS6R=^x4M z{|I}TIa4#`IdNN>Hm6+nr(A}oT%S(41$ctYY?`6i$wwjAQ z4P?e7x5xhnLdGTk2j(S1{wFls)_M>Bzxe;OVLd7`lIxKDdO@Esf&s< zh9ul=mpm7SnNu=9>~W7Du~rC%mYHb3=Vsrr!Jab~;SJ>*n(EnV?2qjxnU2eRz1 zH#DMW)A}p#`Y?RUm+~pbNk&qo!2Vq_*D3ervXOs((FpUy)xs@Fz@yjcrRLG&_Ogus z&8O3ah_>&IInz(Z)k(1Q{R3} z^(1-VCV4C-d9-1E7?ZmD8gSKnd>s34m3aasv!$5w@9GDWQHo>}$lJ~G#SZ)8; zs6@rLT(SI`Oh!4#?4XciSe1k3%WF6>5jWuJ!kWzluD4 zUh4nxd)5_j_weu0@9JveRz`YBdYk#k8<3szkn!mIFxi}`F(LVK;!}FEw?Q&<9rN|+ z(u9u)pxOJdp0~cu%AoMH(%@sjl%#rnfV8@2+g|_qW!HJcis8cfvj2qOqW|8(W7hDS zM#ioWtsWHH-(|7_Mh+asvpxnm?*X7E7!!Hagt>-i2lwK9r`O}#J56>bj#u8;$?i;r zjpy^_)#-GV_ZI})=P{a_tm2Ee^U2>$m|M<8`7F`jzg&8^Cuig9^6}-BTdUo6l#ER4 zjMS^`ZGXSBkr_XO0NvAWi6gm%ESaJ0@7W)z=e_5$6`ePmjAlj*vo9CWYK(nAtFMJS zR6R0J33Dp%E(#Zoue`gBy|*15FTH=gtIzgc1Pr(RbjPg+W~cVB{+`&38qt+ba&TFews#J|^Snu6up4kZ0IoKTx%N+AE8b^W#^P`1^rQD zwQetG(CB^r;in{2sP;aOrpb&imjH>I22S3Evo&;m2QUGpz%K2iUyZ z^=7_j=H-|(UY8>oqu1d%ZSG=5jEz}!n{Eas9}Zp$*1nee(lgu0ySRCmb6)PTIHhUX zQL%S%(UTJ}a;?ETL&#?7K;L!qU}C;?YN|1j#Li7n->ft(At2>NXd(`v{$aCVw{qvc zw`g#Abb9K$`NqvZC(|T&&)4!p?CYtF6^Fe=6BFa2b2QlY{d~mVnu^DX8y%BafrFHn za|Lyuw`T+D}!E)m@K%u#*_?? zXq|TBFh8@~n{$3ga=hsvlmz-aVCwUH;g|}${Bcw|{7aVWXPhf<0_mCD0^Z;kZ+0vt zD6>R=Ha16U&iw8YOPOoJE7$I7ZuQ%WKy#YFoj&yy!>=6N?P&4Z0ho>BF#URIfKyb$ zAg2N{j1hes?!5i%la=5g)T8z~oz-imnQl1t$opAYQJkG7#wMh>MP0*^Vo&UsAK|qm zqG#L2FJa4_#nZX_(`G{Uw0Vb*>Q-=CVP$rq#Prp_t=L%6oR9vucdMr(kM5@rGdYhp z|1R77uGUkQWggdKGc-*M5)av`~BSZ;nsb_g@Fh{QRRctgx_L zu^n|D{>95q%At(p9h_z_%V0Xqz$9Q(@&|>a*TdMaAen~v&nbE_VlyVwHAd4-QbVfs zgXzl~mMv~%nq2!FtwYv(ztxvk7JXhJ9oIGVxQ@%EV81-=q#D%*@4xeWuR2{c=vnyL zx--^vyUpBYxNIctP}}5vr*YpiGh2h5jkQtE+DDa;^L0$lYC3@5y4l@u^JPkB$BZ*o zbTeAXc|Q!FcwyFJ-LKMUeCNPue8zL<%pnEpN8IJ%PpZOZ!{6B8F1h_!L9%xZ6CT;j zTO78tBiro^(A=H^RRSy-?fTWry{AswGPZkuEq;1FVP`yWqJKE>=Rm$8hw^5&TpV*RzCZVj$O!rr(C7|!#sM&?cm%p|Pp|CBNgH8FL zTl5;wR=`PlU$`qQz7>%HE7H2%KU4;^-X%m?6bj$KD&6Vs42s@VoS$tPq}02VpSsR8 zowZKC9BDt9tkqb|3EDL{XobFQg7Mq?rBrTTZ$f*T?UgqUMjOUY$!>V}{?4o~8y;`a zHCOieOC=_~ujV8gfNck>6%7I%-ia8QB9~`;MJpf09B^9M0KY!n`cNM!|1cJ>e@k+F zrj8*gCDXn`MCkNf_94THR3_~7GUALL2i#^6I1%!X&wf3_SlaLWs^l_T(xk%57~bPk zKP+mahtBVMok1B)0d^605U&+JeLu0d=(TwiQ1DNt>GAY^99gEn!mxEElAOn76n8QqtXp>7`$KD?bf5YG&;pv+nBW0qN zN&7kc&NGXSJhuZSj0ViF;;yG~1Y7!POJqvtZ?zY{`0gd-M1FL5f%YzCwlq$RCBmaY10u z3ARzC^HK~n<3C!leFTyDJm3TOGw3{t4lv*0n4b+1W31n}jYUI?uUc4}{+?#V(|+xryyLXyqyu zM@rwe@>~^+jMT@t8uyA##JbY@WU4NJQ#!I2EJx4Z#XdJWBHZ_J7h;;*R`Zi1sB8C)Cr^B+-5)iE95*%2 zvsyR#j3-w|ChJWm|9EJhrbxKoxM&w`^Ov@EUmf1$7bxuyUNk75Og}C{_$HDyv!gi8 z66-VTF!fFwCC--FU$*B#MCo->@mcu`Y$kTH$&TGF{f(BH5564mj~efs7zTE4z9_mi zd3SYLQWbq)S+-m|dtXt`>*xD3n?JZ=qQKB;P^jGO@nj&o=-^M&ba$r8Mw`6`@I@?a z?_;62t%>!r7vBVZQ}X?-V131x$tJ&vpCPs5x7qaY|M5^v~mRPq_aU*=|&Gn~m#dJ$*g18jeMtp5Bf15$9{el;JVEMWNJ^r)Zl$76HKF2r+8=a(@>Bl`S8ow_ zv@RdQ{Dx~jKqToPtU2`lWKi+dLA1@a59Bn1W-Rip#L$>4dwLGz*ra>RmSfIW%i^L# zg!`+O>!L#}*H;+ZHdga{g{|!A%Y6)ttuY$Q(u(JQ!o=3N>+lm)G>Sld<*-kDi~KaF zWg7EVcHiL{$r5_K9^Z@{X109W93%=15g$6zpY7E=+`9~27cX>cEW1>nr|p(2%<*8n zd}da_P-8Q$f_;>)3ag87cl{wwDG}RQ45k7qndM8sDC1 zINoe!*@`;)Oo%$t2H0xyy?wikRpa~NP|ze)^K?hY+VJG8+tqz>CV59^=jV^!#^&Gw z91ZHqA#Zx?yv~I&mpNmXC1qSA_M&E)v`hk;RKv1FfbeP)BM~BXfuXdQJ3lN>yLp?kgh67I@{60Y z!Vaus$!*5^o7dR!qB3&{A;z`B@*cnJ(~u<)^*zEbY8@`=l_e1`+fD|H3;nQKnt`VpYnqoYt}>|tHfe|Yn|Gmhj}vJFt2IJtTRP$^&eotIUWz~!zp4r zfufaytBmdT*ZwG_PiAF>H`8&tiC0bvT4m$4bk5S6U$(JF4%gMGWI*GV_ZML>vv1Cx zZ_$xSTaEF*nrl}35<=sxOw~q4Mo8RQMvmy1K$SJ`F6h# z8QRU)ly1{kEoDrhTQ9i@cYn1kk7&prU#nI4=@ettD3flvcV2DFTS+r?w{KJE$TYOG zH}=~^;in|s(0yY^jFnHwS8rd zz~IV2{NJ?&rxH#6u)~FZ>(hf@cXywhe;KdG3(GfNkk1n9Wu#1h_?eK&@A+Xc;-mMW zd(y{e+b=At`T3>`*H#1$R|=1W-ZK8tF>)tOP1ztZ2SnaBGHSLobmN-vCVEx8Vy@dFmDGY+)dtP zan1N;*JX3N#>0|&UA`MUw;nIq*jAZ)bh#WIDR|hq>&sFZ^%3i!iVd8A&i5Ght$k{O z*&Ay#6!^{gh^m-ZZH`-RnIS!^dw8Y3&u3j_GcEiPtSfN0FRB-<+nK9Ye>1W$X}4tT zX}?^vv?VH1_o2r6N7KQDg5CS)Yg@WC_FR?ay>t4p&_BCvclSF{%KvKIYv%#$HAGEa zc-?Z%UKk^uQy-`+mp{uTOD_0W8=Ptk?woP*`!#Nj=lt3mTxdOe+ps6EcXsj8uXVTL z?OVUc&J7(76AP_Dt6BFCK0n^+8M^7D{P=n8hNNj@z#15_A6iK!GyZt#GAL(nTkgSk zi6W4^|7o&<`K$dp>2L+TrF}O&ie$d`{wl>mOXkk)^m?l@$I;)Xt^W8>eDnT)+1-;g zNV`1h-?Woi`gC*qGKaZxrT6lvdBd^oeBu@dd;TcA3g8@t;uves#rY=safnH+%V4$#=028yPO0WYa z835H^1*SVD*2BnPSpWSG)e(fY9?ML`%Z5Ow`7G~DTn#)f7MtZfhzZ!_=4C>p$1O3AvNY5n5P(~IHm;Hk&LBvV#)mJ4d2MM3D zcrr(CvZB%jNoZp@1>)hzPDpr50UOE~gPit402IF(8W;-}WFRD`6_EzUuYL)AN+r8S zuNV&e@QHzTIFJLAlrRbu1jj5#1;MfQ$7DhFQsgum*|>z;7xr(-X{dNWWyv^%5`lDB z*x^R`=%^SRLYx3RF&H?Bxuv)CB)4Pgn^Gu;aJe9_LyLPGBIVF3rQH507zK}h0>4?S^e?t z0cx*eB7VnfrK<9k^?+W(RX5q=Nymh$+z`sV48Tx{wJlCEfz^$)Z=DEIy53N7xqr9> zIJ|O(1X0+Akq|X7`<;}j(rmX{XlMwSjbneo{iV9ntRO2Ki&yo}LoCQ6h;>yqH1yev zcpVzk=1pg-*m$Mq_J9pwPw3P$CQd@I$Z8xY7;`nQN*$sEW{3O%v+>5LaPs2*2usAH z4;-Q<#*^s!N#*1&q9zEBJ-$!^Pf;bR!O$E`os_^l)MQpaC_QkE_@2Bh>7qjAi!ZDjfp9_50*!Wz=Hw@JG(-FWKbFs55#YbiVFNVt?qxO zDTL%Cg{9wrAQYHEO#4E*mspTQWd@SE?0(z9G;_*MV0#Gsy%o#&B)Urm*u>?93K_m#jV~2q}F+?&qu?658t;kl2<>k7To9tD{ zpNYUBiU?{Z)n<|XK|C8m&MF!5I+p=K69+J;T%j4IY%$KD(V|l&hpltMIn+%pwR`dq z`cvspMKUb?Y#|wn-0#f;`HTZj_ui;P!s0o+hSNquECxIJbY^8L*u-u|{PBk{u!;+$ z(9ISj5u`=ld8gn^YaQ5t_E&YggNe7xCTT2Nj*beFDv_nYxh*STuyvbXQRDIU@onbb8L7pu~rPd4)&C{J$4RE#u>$`Ky8LGfwP}2IShb!pQqw}vU%cgYny(8`K_#?mSAj> zyk@DGb!%VQJ5dQ73q&tTT{*owN$a|3z!LqGqdb`b+}VEddYGV8MNWCS4pM zJw39oN=U5~?ihwkNDxf0(q2Wa*cHYo_mKqE+1jE~fW%)ObOM>eRlhOl2M*F}@l$wL zfz|{<;Hv;xLs?#X@2V=?+;i$^g7>n&LtoTm;xd{Vf?=F@ez{ZuN8w>E~9k` zFxJ|lB|cbi@Ral(Lcd>}AxZP%!qh{7&Mr8B&F?uYz+* zu)s4~oUk|gY~1$1uzL{qKItDIPS|JbaeRsPLhMO=3^{rhijVbbA456^u)?I`^=Vck zyCxrqiYQ|sT3`S#FILqSeCU}Cll*hT|;4RXJ{yUqF<;Y42FuU z8pL6TtA>T5g}nr4Rq3&1fpnL}(9X2NSmAq;@ftLv6S(1_=A3e!_{H?Sjfx;vxO%)Y zSF~0>dOS#PL`)mA7^6wif>U>uH0=+&q+AQ0c_-Rx7@B$K^mB6Z;m&X5*pt$LG*FUv z=nbTR+)6OEFHnl8H`GB5qBd)5`h((Q3bIB3uGk%l<}nW9v`F^n(XGgSF$5=S?vW@%vu3`@&$^2Q8k&$95`IIDrV*f^v6+%eLe zEKXvo04?k>csPMeSiPZBg5HW={G5b+^Jr-xxp?BU7+J9MdM+q;kCzmmeVjy=J2N({ zlL-(O#Bjcsab9txfz1tU7faST&%%i_`14N;#F#xL6;0j{YWeE8mxVLTmxKHk`DzwEsq*QHd#CIxqW(pa8+pY zCLuWxyXL+N2oGcxkHLMP&qR_?f~aC=>y#Ec_fj(CV~Q3$zYp{36*HVN&*fPZgg|Cr zBxa?!s?EzDm8jH{uE{b0Jyi6Xx*?%_!H(}U#}x9}I>V8;uC-n^)|n0v@Mrl@bNNuR za5B8N7izM)VzUG+sd;kI;lUIKggFBO^!vfQHF~2Kd zGKdJOG2?pu_K{mvxi?n$!hz}p2&+s{vu;((+HR&2k4gDXdb6vrv6Zj^zG5L8R#^yV zoV@jpdJ<-OR74eM@eB2Nqe5o$Y)UI@lxU+0_0uB>3oFazF$I&`FRouf|}PIdgaO&CyG-1HCnvHvuMiGr~tfq z@*_mziVS46d1HzqU7yYnfKG3^z#toz0@#z_C0C=YRojIE=&(DX=3%&n;-OqzgoW67 zIz=jx5Rfn@r@JkbGubDsxRR&*kdgj*P+ znHkRYIt7D7neq2Sty$`BXyiBi#K-T&d8KEf0y^=N?a-Ygpclc#P3YC!no_AVghTjU zpPES3|Ca4G9rG1cfcX|Fcpic;WuG2UuB099;E1XLgjEF_hUo;R+T4%bdniKqM0 zFF18#3Z+N#>Qd(VhQguSVK`rT+V6m##F(i^E1*)Y(&H4_P;FMa*DMN~JjbHRs&Tt% z_;*5U?=WchwYA=-fQRhHl)~h2@QGpSu`egpB2FWL!*11Y@O^tFrAnd<*<5i6SFG1RICo zTPKy$#}NV0xcIxDLB9>pRx245xYkeQDH#<@+CsBFo6$v1>J0~p>u-8lz7nJ=QKj_j z)5<-(hOsPE%NiBKWHRz(7x!M$8%AB>RvGQ!?eaMEnbY7;>UCupN1(e?TIzutZ2t%Z ziT_KBz1bBdxgx(cF|YRsM9+hLz~f9iNgM2vw5&7 zq^>A)1aoy_XaWuro%g%MIX%$DFwp0atY%zjuXjxMIariPW3pJ(cAeztEQ7iF1$Jxz z=x_!}=jT9WO*sp;*Fi{3)=d34jy}P7mIAid2iWos9Nf|J!91FR`KQ?SyAxVekHXR! z3Tp;Q5I%sI7jzU8k!Jd|Yg z?)tU*u;mUUVuOk>shDdeSnFAH!O1f|rC)RaY_2eMmfQpt=OrU#gmt%4*Uv6ALUSn% zIdw?QBw+qX9`=Gm_UlY&q~I%HhXOG07;4JJ#heOtBi5!VY}jaokB^B3F5?k9>?Z2t z9QmY(l2#94I^hC-sAr_E6v6_(R?M}?~*IawvxJ5Ya)(oqd zZpIj8b)$OVV5dL!@{*BkWy>@4n4TsXzp!nWE>3E>)wMB85+4_K~iGY~^qp;aoeT zht2a4?ITJ;pw{GXsG}qDj-4?#pB!5|sb6LsV^<7LI+fmzq3z0MRy+)03b)PLgcrZY zAb(@YQ%7x=8hOyoYH++lQwo2g@_meOLSY!^b6YvL{xAV33FzvLAe1vhQ=kVZ09A4j zjLy61+Bl`f(4}oAfzDR9kTnrW48Ufq>pt|vH<%Bt+7+{1MNY}&CXcN7s17RqsUF{E z-Fi~6rik<~OvLOO@>Et{sP_DdS2D7+)-ZQZ@s~EmN8(MfcFGuf?$Ee#YrW-@|M4ci z$$)01M@K^cOD*mJ=l(&76>qKiGl$sGkDQr1kFY=5#u5`#J0 zRlB1w^$Q-%K_iTFx`XnP$ea(LG@to0ZT%G{7f|e%{H;wY+>HHptFwzi7{<1RaB6E8 zP(%>0?OS4uWv{-_#mPeiYvx;=5PBXYK-Q7OP!ld2OP9&^iUcb-n^_Gz(^l&DJag6c`o+V_x)SXB4-5d^TDzI6 zg#PE^a!fVz#{DTB>%at0mO@p3-kg^7u0=t2%H(2yZk@Nj>i`~4%xQ)fNNY3qr&Sef z+t!cTjoZNb#ZU8I9!et0qOeyKpUNcdYRoN&aKm;?!_)jBRoc)cQHUx&)(~&GL&M*>2yrBmVn%q zlP+AAMAk@45FxBQG>PpNOrc=BPK_P>QGE=}9YkR)RK@wvb16unZc~7*s_)D%&ai$g z9PJcrUVk&eC8|&jLPd0R-G7Nzg;oKGj+IHAG(1dha$G?1au5?3w=`%SaFAQ~8G{3W z)6SR`)@LWr7cYbNEs-z=16Ox5giWv$T;I~yg~p-}ZnyU{8<@t3(fChAkejw_BTokt zS@pR+YYdIar9r4k6k>hWH>sh*K0zxbbUn$|PBkjFwZiUW^`k@{8opxNL$vZ26RHh> z{H5y6V2tPNLU*HJ!WG5<7DWx8K&KGOaE{wll^yI{QR28faUR5OnN~^3C)3X2=K{5N zKd`mig1)PqCr5shyBS<%Ll;&9>6L+|q)X6n%Y5njpf9j^$r(jDp}&(7%}cj}Up;mL z*V^?)Ecue+UJzQb=`PIvseWd3Ic|%YIg6()^Ai`W+5vJVL`IPL1{zQ(A}L2PN$VbQ ziX`J;a{5e4nShEHV1?h4I9kyl%0H1L&~gBIz7l0GLfve9ercFEaaNyF%O%XbYYtT6 zAP;Z|3PQlph7-UL-nX-r&2R_r8rA2wV(Ur* zWRrnADM^XAowFT9w9)u^E7ygw~Y_UW>c0 zZtu`Ri})s~Ll?*kfDNU1&mythtOBdaWzp<=l1>EPMOv1&5+XRA1|~DG{&IyEdv+v6 zvTk8VXaK^Mz-|e}WnH9MEY5zk0RK$cZ9Z9fw$W02_yHy_M2E8$J6}G>dVa!b|C`aq zWRJ!l*gl6$EIsMsfis+3&I5CQ0I0ZTU{K=zian@&VDL9apMLMb@{}QU#7RjhB`d)2 zH@g!#O=;Tea)=jqzkc^zKaqYXm#}jKTrSqRD!WR@Zbmv;Td!OcgC&u_LCt5h`pf%P z{TDsZzM?pvC0v4;PfXvxR8B*1NLSCwHVJA29e4%Ozn3vW9I=DS8AGGV5r`1l?-@ap zBD-J;j=I`8yf@lw>4|*#mCAtvnX?r?9Wc7Hzn7AXJ*S$P!iIC;__*@D8t)J{#Ov-u|Irz`LF47*gXB%Kc74)t^VSqljpTx)Dz?t-z2Rby|bs#IfX zw?_XANK~7;nwIzFwa$S%{C0~ z8F(D5`B>KMD3zv~TXqo59uIwICYYS~T_6T-)loTtnsi*fW2S5u!Y>kn0AqmhV@Q2b z7MOT=Q%-@ic)I3syg|4m?8G@>@qIMo7THR?kT`kNiW4d4PbA`4tDDxn?((B8J1Rbk z5QE9m5c#}a0z)C3FA-U4kRRUFLB}S@3psETloiFrK#7o*M97!4gA!5YG^K~KW@Jgd z<*(zQ1a6oF5K}O_4g|~}&3zYvg#jFc5%h*JCJs4ZyTN+k@F+kDh&L0!A0f*dEqhUA z(=JwcsNV0Qy($X{lgbt1i^1=`z;71e$!sWl1_&E$z+hcd4!0ct!6ldspg?u}XA65L z3=J2{gP2gwuQRkitb>j-$N>_^XP88kdxxf|oh8nOpac4oRZ&}Gax^&;Qg+Y>UJwK} zOU31(m+A%*CJp$qTCs)Fp^?yyda-hGhTeP;UjMZ;&&Q@hPm0N?@F9znO=XWQ(PL(y z1G}A8_5+-&#?-5dlVua}f(o-3=8bT?zGPf z=Dg3tXa)x@6=aw28}aHIiOaQ&i^dX^CBwt$ zGDd`UtuX*uhy$sG2ua_wjsq0~llbCD-ayB|()NP7>(9f^ntQ!JD~2W&pal2>dC__! z$lH^M5D|K5(5^Y%F?iJMjHw&A*gPMlk%S<|L20`o?L|kklgif~M)vK6* zMI{l2;u;h-+#&J>`v8Jp5iG($%E}c-M33qU4~rzCALL*hMv#$k&{J`WWF{er?96nC zBl^0+TNF4&_W2TjK~&VX$oehWL^9VqrpaPdM52rLJ6g~Lf%F<`Kj5-0Z794ZauPZZ zF|s`PQ4%f@LA*T-ZTb`V72#hpdJu69qfXs(RR5$=b7#cQo|e)hds;DMNj@ha3ObLZ-rupI;RRdRQeSLg z#zvz=dyEx4Lh^Yyph!fi__FzwD#Npy*BR)yFg_ zDw6b%B29ft_58RVSpro&`z{Sx=-Zj7GYE=u+xdlg-s%7=&T4iv`RBQ6C@M*`7z=Yk zYOKod+}5#sib8@GpJ?Vs{DtY9Q|^y1$B-XzlH;207PL&kcPbogs(9;C@rzDoKfY+m zwzzaTph52c^0wnMS&P~??1ux5Vgm2`(fk;Kn&1#LASto0>`$uPk6>*0I1vVMh>|KE zWI-#lAXMj_3T|gKmi2RZG4C_Xjs_;d)gLreL9< z)fGY#8hzO^GzrosrEj!#foaZa{S-7bbx+T)mJngpYG=@@>^EAfz(Q=C@XwFL;miCq z7tnM#K~ZczHF4YIweJ{*tmJ*OxhBAXsNT&VLDr#B2JzX9gYOl#)gMR)9I zs1kxo{VmHQBa9Msh9{OZbHq>*E=!b=bcETJ_q%SOq43~bp>`Gf2oBVkpcs*_ta$hO zzP%;KtGl~FH9pz5Lhq(7u zvP(k`4q%EA3@Pj3V1w{Rf>Qo9baJ>L`E>mx{y$i_==$5JpY^qvOW&m5%_gt$lh1h&crbmZ@2Q0SUVxLHqKPIJma#$QuSaY?$j>OmJWz zRd~2m`wSf-{7tSL3bDnhgo4XbE9Sb*;wHM$jNem4gtNC>;(q||BS)R8((s8e2JW40 zXvQ8X5aBIA&feA3)KO3;3iB^-5U3uzF8}A#`1fIzL%p3;u8)TgLc%ffyGUjZ5q+>u zbe;-R3z}8Y-Bg!RCfJ7R(-?wOkfo;)MxZGY(a?C9H4;W7;Sth@zIn+@*oT{=ooPYy zuc^a%l|m3m75)mZONsWNT^V#{K5~Td_j&TJfLe zb94GC@(l4DQ)qwceG(K+PBrSG8v+82bCGCJS(dS{GkK%tmhW{#oso+1(8@G~ln;cU z7uW6$&T$_ZOfn8lz)kZa)!5_&odB0i?k!ib)KGZ!thpt1Ts3A%(%J(>gjp=5!FKNt zp5jq)(PXCDe9GyNxv$n{v1{xkzEDT+*{?K;T}fGU z9-A%%^m4OBwwt3fOb4!vd9xk=6(aYVyjc4pa!k|16FQQF9+k(dW5<^sZkzoq$>K(f zsw7D~8%!Zzj(cUqR6^RiMvu--PXunml0APPS}+P2D97~W+s->HPBIg~loF`YTNl)O z)?ELQo5wIMYrjNgH8DJ|s%nx@x~H@AVakr&83rR&!DOH&4xvQN_=rR@;}b@_C7Jd~ zShXcMspcr5OvdM?YfnOL_d&wtfrp(Sq#maIxYU?aHy62+t*#*srTJa8Jy@_BK9r3h zD(XXpXNfb3*sE{AQB1k3ozJk}aG0UcVXr_U`@#a%t4}(6yBuy`cvuwCdu26yFbz+C z6_JHKSg0BfIRI4nCDU>CgK~wriHY7AWOVdZ4Q3+<*{6v}3F&!qpzXv&m?*Y*T*8Q4 zi0Q0^B4+p-=~RNE>W1Sz)kaLNN+1(IFA{e)teOt3|GUJ?m15FR=hfdQ1B&b{ zq)6-JB!LMxR6W-^SLw%fPDPbxKq!R6E{NwHv=c${>KdWABJc8x@y61qsf7hPIH^{p956|0Gf%Y*IP*@zUs+3NsC!^70^Ob?<6 zGB%JM&3lFoFV!Sf(|nw`0hw$Qf#KYOs{KM(R&vj>2Ucg>e+yz+K_v$bM#xj&$x6`- zvv+p&k*8f3Lp3lBA%nU=K27-=4#EDS{HH_imY|5w$pu>LqpAFQ$$e>HZ504u5yi3H zKK-YgfE-=c_32-_96Z6S)@%Z}wVD7_eKK!nb;Z;EO|yf@L@RnD%VtAfB<_x|3ATba zk95D(ToSJr>ulh1fmo1I4@B%eLSst+FD_=@xHI#tsERmv+up%7O9XRtkL@z7PjV`|LXN+6wZjRXWLi?|w^%Q?KkYP?TDhWa88dD%SYz3$aNuS#e~){2FX( zqp&6Y*vOFwy3dU?3Lqa#5O^$yECuHS zEI#!gGj!!MXp%Fmz=8(#y#wy5*$A}xT%3BD&-pqHD0{C2SCWy~g$dTxoo*_@EZw+q zK!Bz)dKCwT2#@-|E8T>v-_W+K4X>#NQJjoR4*t_@0B#?0HxA&WhRmeT`BLmg!jHVE zFIdm1gnW^A2^1HF=wS^j;W?pLf|!QOo(nM=W{PFEYcsz8p>zHEUF@ift{y96KyPQq zkb|JESucHlC~j8{kw~iSB)JyD%Aa&oW2bAc5AJnp(aSiT@j7y9#5&n3dOJ)+l(aLy zq2bviAM+E74M^C?s2cwMCVeNzN@oMGWa>GASWh#U^c3)Ghgl;$J~tc_2K{zgP4m*E z%r0oB%oaEYfB9-Trd&++fX!kgq}Kga8e?FRS9@TY`Q=GJrotcTC0|uk`=w4dyrRB%HXfmvIOG7Z^HVMekAc*37%+LaBh&QAHd)L zNL;T^{XO}dEv@=@D32qJY@0ONr1#p*`MZF-@U*kpH>$ZRan^;HVzRX{s&N=Pe&F|- ziW`j|$dgz(SqcDZaocL4KCJQ&?JKeUn_OCHcMTWc8{6~nVHI|uD9fTYVr7;T+&v(+FGAmRR_CXFN<@pbw8=kGs7H^ zW#m1Ap;an(Ovh&`;bdu-zOa+VI*pKnHDAeezt`gEz}zD89?*K#*O?k(-lmqsH`PUv z3Vbal42eO!CeyB)6qHwI^)4*NtIk2=!%D?BCu=8vnhCu}CVLke%?FV`;gRV-p8Y_@ z1frtXu*tphIbYW-hlHumrJ}r(L7Re8fXEi7ym!%R8ZX&I$hxg;$r9gW7Yl9*ksKis zO2qRyWKCWfdRZa<7_%jgt9_1ztfme1Mp&ouNdAQ0hXTd;EKuTb{1JOWwbggO)rfn{ zI;+KcpEj5}UTAw+2|XPlWMe|-kSt~fy=@%eU}#;iV6A^&jB?S-IBAcDQRUz~{H*6r zJ!nywXX8?&6r&v^7ZVFa0f$}4SiW`1Yar+_4L81oJ$DIi9uCY1G>^=bTZzm>*W36a zsl7ol)M3k0%1^rVLUV({Yzvu5`T@sqW24tLTkqYg5O=I%h{nV7`<-7)B!I+OBSLK_ zPDBMu`(OJSl`exSF__HNJc^1K`+X3;J?odCDqe#V$n)2%yHd|~rGT?8hNh~i3`eZx z2F|8~x+NveBal4Zy3Q{^`+6CBf=trnOSHGga+CEhg&fileAxBeX))=z+wW(8hlY3# zLXxmXf4lyU2Bd%tsaSBw1caha7=&0UXzH+()YN)^V{&_7u=#9DB{6>%g*Yd&`53?W zQMkKOnxz)@H;sZ~i8gz-|Ap=pF?uFvrVf`tw?@zaFQ*shK?%*9XVOI4X&XcWLfS=k zWq288Tmm89d~ch)ec9?$U)a_yRlHZjwqbC#@_!fZ5>-NTAT?+A(^XR@u$yN%zNZxK z@(t)bV6jcjr~3$c0Ua`*iulb!f-}E_`2q(g2pTiI%b-choyLs|4TQ0S&O1A6p9{rv zOA{cGIVI-1VXK6?PE_(pGa<*+%w&h#g+c-Qd>4Z{Tyi(qw|KJn3Bg=>JYIVhl}Q}b zyFw;2y!c{N5)Kdf+hJPCFS}QA6e5i%-I$AC)Av)Ig+~UL^>iYBGfi!a4LeV! zCpbVfmTM0mCk;FW4u8$wf5z5a?ml!B@w(yn2>8`8;aPqA-*DfKIWoJfTj>`~jF0?W3;-c$F-1;dEK}%|+N;tV zL#N1;t`nw?@`0#(1OU+>Te=)R7#O0r@%RMAz*}|GjDkE+W>f; zaZ6^bT)kGBtx(m~B8m`z4WyW4j?punni8VHr^l@uz>^kmLN~yZh6@A`yc_2RkAj>;Pvc57TR;%u^$jED;a3&hqeJ0o0)2fUsd|CLR&E z0jHx;rpCA~jnzxMB0SNMh)TCo9E%9o{O}zS5l$*Y5#h?*Amnr~9coHpC?Z^`;0t*u zB3!9Pib6z$EkIJCz+hl3B3$7c#^s@qaHSjx;83KvU;t5Eg{yvb>0l&G)xB1&BSOMT zgoFX(JFBrM7 z5L0`>keS*GW=Txd1AsW=66(QHCDa|lMhuuxM|iy90J7;grIn3Ll~x%LMdR}6J~4T3g^-x?=@wwV0L-ULX3D1{Y~FDBbUC;e z!#z`^2o4<}k>}s8#0ukhbD8>zNI7IM4Tp~(zibvQ90UKH)+Oq;X=Te;rohLGt z6$>!0!v#-b^k_ArWlF09tV(ct^r4(fE2-O|j7uxqzw!j=2=eSvPy^BZGvKstmM5dG zArfWO0SN(|Z)r$GXWEdN&a~EgqJ+9dl-c6W83y20gFB&TLTQ@pZHoel-GLO~Cs**|p6bvY=E=fHTbWEP9la&W39~B1$l&LuV zit5Bh9PF5i)13%UaXP|-754&&j;J^t(3cAq(z?J@oQ{xG0*MepC=S~Lyo+2!h^e=E za7t92?%VlRW8CD&ikobuHLoSAPDga+g$)MK&lI*Q5xfbPO3!hmRCsbMef{D` zTn|tHI>Y7C0lhdfP8c*+F5Oj9f=nkTuZBt=j!33EToX{hHqbNuY+v0)5?9Wh5u<^n zpB!7QFzXo1O{i=O49z z*amRb+<-buTmm|^2cC@}HH-yPD~Bk@V~hz1HGqm89$QRjiEJ^|D^CrN_nD}M2T+m@ z3Kq6!{}O(84{5QGQ~76%~O{^1NUHD{s< z9w&y#4^t2>g}B~gfMc2hV*w_DiV$$){4gEBpaf@!iI9lk0IDMDkpj*V@n|J#91Ec8 zt2>9_DtMd_Rq%}E?OE%ZZiy;*0M?eqvEi4X<2$OeUGp>WX3*YmD$1B$qCc|lK9_~;Ff-BHJ}Om?GcpxEBKp|ld{+m{aq8>$u@6+o zT9|j70E&cgg^xCjmE-iUt_{Fh$04)vJQ|JVFp8+=k#}qn+ zz!f?Ls?Z5xx(Az`*j~xi7&=*|(8&Yr*I~P6h(wKFg9f6|$pfT2iK@3CE_CuwxzxSu z{QwX}XHg83)=3PNdOwL}3eOyX*8@A3NjMs9IV!XTXpM%;3MNmK6$ETExcE$&nv6Tg zg|-kcG+bkD8<@}*K(d2uGY)8AhKrrD6{e)1x`QJp4~U##uQY_`9xM#ZOnW9`kHM`< zro(1ZqWv-)y`@R BqwoL# literal 0 HcmV?d00001 diff --git a/tests/test_pmo_engine/test_pmo_exporter.py b/tests/test_pmo_engine/test_pmo_exporter.py index 1984bfa..89c8dfa 100755 --- a/tests/test_pmo_engine/test_pmo_exporter.py +++ b/tests/test_pmo_engine/test_pmo_exporter.py @@ -1,4 +1,5 @@ #!/usr/bin/env python3 +import copy import gzip import hashlib import os @@ -38,6 +39,15 @@ def setUp(self): "rt", ) as f: self.minimum_fields_v1_1_0_pmo_data = json.load(f) + # a real, schema-valid v1.1.0 PMO with only the required top-level sections + with gzip.open( + os.path.join( + os.path.dirname(self.working_dir), + "data/minimum_Furstenau2025_PMO.json.gz", + ), + "rt", + ) as f: + self.minimum_v1_1_0_pmo_data = json.load(f) def tearDown(self): self.test_dir.cleanup() @@ -245,6 +255,28 @@ def test_export_library_sample_meta_table(self): md5sum_of_fnp(os.path.join(self.test_dir.name, "library_sample_table.csv")), ) + def test_export_meta_tables_without_optional_cross_ref_sections(self): + # project_info and sequencing_info are optional as of v1.1.0; the specimen and + # library_sample meta-table exporters must not raise a raw KeyError when a + # referencing id is present but the referenced optional section is absent + self.assertNotIn("project_info", self.minimum_v1_1_0_pmo_data) + self.assertNotIn("sequencing_info", self.minimum_v1_1_0_pmo_data) + # a clean minimal PMO (no dangling ids) should export fine + PMOExporter.export_specimen_meta_table(self.minimum_v1_1_0_pmo_data) + PMOExporter.export_library_sample_meta_table(self.minimum_v1_1_0_pmo_data) + + # inject the referencing ids without the optional sections (referential dangling) + # the raw id is kept rather than resolving an unavailable name + dangling = copy.deepcopy(self.minimum_v1_1_0_pmo_data) + dangling["specimen_info"][0]["project_id"] = 0 + dangling["library_sample_info"][0]["sequencing_info_id"] = 0 + spec_table = PMOExporter.export_specimen_meta_table(dangling) + self.assertIn("project_id", spec_table.columns) + self.assertNotIn("project_name", spec_table.columns) + library_table = PMOExporter.export_library_sample_meta_table(dangling) + self.assertIn("sequencing_info_id", library_table.columns) + self.assertNotIn("sequencing_info_name", library_table.columns) + def test_export_sequencing_info_meta_table(self): sequencing_info_table = PMOExporter.export_sequencing_info_meta_table( self.small_example_pmo_data diff --git a/tests/test_pmo_engine/test_pmo_processor.py b/tests/test_pmo_engine/test_pmo_processor.py index 5ce6d31..2a48ba8 100755 --- a/tests/test_pmo_engine/test_pmo_processor.py +++ b/tests/test_pmo_engine/test_pmo_processor.py @@ -1,6 +1,7 @@ #!/usr/bin/env python3 import copy +import gzip import os import tempfile import unittest @@ -37,6 +38,16 @@ def setUp(self): ) ) as f: self.minimum_pmo_data = json.load(f) + # a real, schema-valid v1.1.0 PMO that contains only the required top-level + # sections (no sequencing_info/project_info/targeted_genomes/bioinformatics_*/ + # read_counts_by_stage) to guard against unguarded optional-section access + with gzip.open( + os.path.join( + os.path.dirname(self.working_dir), + "data/minimum_Furstenau2025_PMO.json.gz", + ) + ) as f: + self.minimum_v1_1_0_pmo_data = json.load(f) def tearDown(self): self.test_dir.cleanup() @@ -347,6 +358,23 @@ def test_extract_from_pmo_with_read_filter_missing_optional_sections(self): ) checker.validate_pmo_json(pmo_data_filtered) + def test_bioinformatics_run_name_lookups_without_run_info(self): + # bioinformatics_run_info is optional as of v1.1.0; the run-name lookups + # must not raise a raw KeyError on a valid PMO that omits it + self.assertNotIn("bioinformatics_run_info", self.minimum_v1_1_0_pmo_data) + self.assertEqual( + {}, + PMOProcessor.get_index_key_of_bioinformatics_run_names( + self.minimum_v1_1_0_pmo_data + ), + ) + self.assertEqual( + [], + PMOProcessor.get_sorted_bioinformatics_run_names( + self.minimum_v1_1_0_pmo_data + ), + ) + def test_filter_pmo_by_target_ids(self): pmo_data_select_targets = PMOProcessor.filter_pmo_by_target_ids( self.combined_pmo_data, {1, 10, 11, 55}