@@ -366,6 +366,66 @@ def test_process_ht_work_no_mapping_yields_pages_unchanged(tmp_path):
366366 assert all ("image_path" not in p for p in result )
367367
368368
369+ def test_process_ht_work_missing_image_warns_for_page_with_text (tmp_path , caplog ):
370+ # page is aligned to a zip filename, but adding the image raises KeyError
371+ # (image absent from the zip); a page with text should warn and be yielded
372+ # without an image_path -- it must not be dropped
373+ htid_suffix = "12345678"
374+ work_id = f"test.{ htid_suffix } "
375+ _make_ht_zip (tmp_path , htid_suffix , PAGE_TEXTS , with_images = True )
376+ pages = [
377+ {"work_id" : work_id , "id" : f"{ work_id } .{ pid } " , "text" : text }
378+ for pid , text in PAGE_TEXTS .items ()
379+ ]
380+ with (
381+ patch (
382+ "corppa.utils.dataset_prep.align_pages" ,
383+ return_value = {f"{ work_id } .{ pid } " : pid for pid in PAGE_TEXTS },
384+ ),
385+ patch (
386+ "corppa.utils.dataset_prep.add_zip_file_to_tar" ,
387+ side_effect = KeyError ("missing" ),
388+ ),
389+ caplog .at_level ("WARNING" , logger = "corppa.utils.dataset_prep" ),
390+ ):
391+ with tarfile .open (tmp_path / "out.tar" , "w" ) as tar :
392+ result = list (process_ht_work (work_id , pages , tmp_path , tar ))
393+
394+ # every page is still yielded, none get an image_path
395+ assert [p ["id" ] for p in result ] == [p ["id" ] for p in pages ]
396+ assert all ("image_path" not in p for p in result )
397+ # pages with text warn about the missing image
398+ assert "not found in zipfile but page has text; skipping" in caplog .text
399+
400+
401+ def test_process_ht_work_missing_image_no_warn_for_empty_page (tmp_path , caplog ):
402+ # when add_zip_file_to_tar raises KeyError for a page with no text,
403+ # the page is yielded without an image_path and without a warning
404+ htid_suffix = "12345678"
405+ work_id = f"test.{ htid_suffix } "
406+ _make_ht_zip (tmp_path , htid_suffix , PAGE_TEXTS , with_images = True )
407+ # single page with only whitespace text
408+ pages = [{"work_id" : work_id , "id" : f"{ work_id } .00000001" , "text" : " " }]
409+ with (
410+ patch (
411+ "corppa.utils.dataset_prep.align_pages" ,
412+ return_value = {f"{ work_id } .00000001" : "00000001" },
413+ ),
414+ patch (
415+ "corppa.utils.dataset_prep.add_zip_file_to_tar" ,
416+ side_effect = KeyError ("missing" ),
417+ ),
418+ caplog .at_level ("WARNING" , logger = "corppa.utils.dataset_prep" ),
419+ ):
420+ with tarfile .open (tmp_path / "out.tar" , "w" ) as tar :
421+ result = list (process_ht_work (work_id , pages , tmp_path , tar ))
422+
423+ assert [p ["id" ] for p in result ] == [p ["id" ] for p in pages ]
424+ assert "image_path" not in result [0 ]
425+ # no warning for a blank page missing its image
426+ assert "not found in zipfile but page has text" not in caplog .text
427+
428+
369429# --- process_work (dispatch) ---
370430
371431
@@ -850,6 +910,46 @@ def test_main_continue_skips_completed_works(tmp_path, corpus_input):
850910 ]
851911
852912
913+ def test_main_continue_skips_completed_last_work (tmp_path , corpus_input , caplog ):
914+ # when the LAST work in the corpus is already completed, the end-of-loop
915+ # handler must count it as skipped (not reprocess it)
916+ image_dir = tmp_path / "images"
917+ image_dir .mkdir ()
918+ output_dir = tmp_path / "out"
919+ output_dir .mkdir ()
920+
921+ output_pages = output_dir / "ppa_pages.jsonl"
922+ output_tar = output_dir / "ppa_images.tar"
923+ # simulate a previous run that already completed workB (the last work)
924+ _write_corpus (
925+ output_pages ,
926+ [
927+ {"work_id" : "workB" , "id" : "workB.0001" , "text" : "b1" },
928+ {"work_id" : "workB" , "id" : "workB.0002" , "text" : "b2" },
929+ ],
930+ )
931+ with tarfile .open (output_tar , "w" ):
932+ pass
933+
934+ with caplog .at_level ("INFO" , logger = "corppa.utils.dataset_prep" ):
935+ _run_main (corpus_input , image_dir , output_dir , extra_args = ["--continue" ])
936+
937+ # workA is appended; workB (already present, and the last work) is not
938+ # duplicated
939+ written = list (orjsonl .stream (output_pages ))
940+ assert [p ["id" ] for p in written ] == [
941+ "workB.0001" ,
942+ "workB.0002" ,
943+ "workA.0001" ,
944+ "workA.0002" ,
945+ ]
946+ # the last work being skipped is reflected in the summary counts
947+ assert (
948+ "finished: 1 works processed (2 pages, 0 page images), "
949+ "1 works skipped (2 pages)" in caplog .text
950+ )
951+
952+
853953def test_main_continue_does_not_rename_existing_output (tmp_path , corpus_input ):
854954 image_dir = tmp_path / "images"
855955 image_dir .mkdir ()
@@ -912,6 +1012,31 @@ def test_main_without_continue_renames_existing_output(tmp_path, corpus_input):
9121012 ]
9131013
9141014
1015+ def test_main_without_continue_warns_and_overwrites_existing_archive (
1016+ tmp_path , corpus_input , caplog
1017+ ):
1018+ image_dir = tmp_path / "images"
1019+ image_dir .mkdir ()
1020+ output_dir = tmp_path / "out"
1021+ output_dir .mkdir ()
1022+
1023+ output_tar = output_dir / "ppa_images.tar"
1024+ # a leftover archive from a prior run, with a stale member to prove it is
1025+ # overwritten (mode "w") rather than appended to
1026+ with tarfile .open (output_tar , "w" ) as tar :
1027+ info = tarfile .TarInfo (name = "stale.txt" )
1028+ info .size = 0
1029+ tar .addfile (info )
1030+
1031+ with caplog .at_level ("WARNING" , logger = "corppa.utils.dataset_prep" ):
1032+ _run_main (corpus_input , image_dir , output_dir )
1033+
1034+ # existing archive is flagged and overwritten (no stale member remains)
1035+ assert "already exists, overwriting" in caplog .text
1036+ with tarfile .open (output_tar , "r" ) as tar :
1037+ assert "stale.txt" not in tar .getnames ()
1038+
1039+
9151040# --- graceful stop on signal ---
9161041
9171042
0 commit comments