@@ -405,3 +405,116 @@ type F1AccountingTests() =
405405
406406 find " /RECEIPT/ACTIONS[1]" accountedReceipt.Accounting
407407 |> expectUnsupported accountedReceipt.Accounting)
408+
409+ [<Fact>]
410+ member _. ``JATS front matter accounting resolves source and ArcIR output designations`` () =
411+ let path = TestFiles.fixture " paper-PRJDB5192.jats.xml"
412+ let bytes = File.ReadAllBytes path
413+ let xml = Encoding.UTF8.GetString bytes
414+ let revision = ArtifactRevision.ofBytes path None bytes
415+ let accounted = Ingest.paperWithAccounting revision ( Path.GetFileName path) bytes []
416+ let repeated = Ingest.paperWithAccounting revision ( Path.GetFileName path) bytes []
417+ let graph = INSDC.build [ accounted.Conversion ]
418+ let sourceDocument = XPointer.entityDoc xml
419+ let outputLocations = ArcIRJson.locations graph |> Set.ofSeq
420+
421+ Assert.Equal( accounted, repeated)
422+ Assert.NotEmpty accounted.Accounting.Entries
423+ Assert.Empty accounted.Accounting.Diagnostics.Diagnostics
424+
425+ for entry in accounted.Accounting.Entries do
426+ Assert.Equal( revision, entry.Input.Artifact)
427+ Assert.Equal( F1Accounting.XPointerConformsTo, entry.Input.Selector.ConformsTo)
428+ Assert.True(
429+ XPointer.resolve sourceDocument entry.Input.Selector.Value |> Option.isSome,
430+ $" JATS selector did not resolve: {entry.Input.Selector.Value}"
431+ )
432+
433+ match entry.Outcome with
434+ | FieldAccountingOutcome.Emitted outputs ->
435+ Assert.NotEmpty outputs
436+ for output in outputs do
437+ Assert.Contains( output, outputLocations)
438+ | FieldAccountingOutcome.Ignored reason -> Assert.False( String.IsNullOrWhiteSpace reason)
439+ | outcome -> failwithf " Expected emitted or reviewed-ignored JATS metadata, got %A " outcome
440+
441+ withTempDirectory ( fun directory ->
442+ assertQualifiedOutputsResolve directory " paper-state.arcir.json" graph accounted.Accounting
443+ |> ignore)
444+
445+ let title =
446+ accounted.Accounting.Entries
447+ |> List.find ( fun entry -> entry.Input.Selector.Value.Contains( " article-title" ))
448+
449+ match title.Outcome with
450+ | FieldAccountingOutcome.Emitted outputs ->
451+ Assert.True( outputs |> List.exists ( function ArcJsonLocation.ObjectAnnotationValue _ -> true | _ -> false ))
452+ | outcome -> failwithf " Expected the JATS title to be emitted, got %A " outcome
453+
454+ [<Fact>]
455+ member _. ``count header accounting uses byte - position cells and resolves every ArcIR output`` () =
456+ let path = TestFiles.fixture " counts-PRJDB5192.tsv"
457+ let bytes = File.ReadAllBytes path
458+ let revision = ArtifactRevision.ofBytes path None bytes
459+ let accounted = Ingest.countDataWithAccounting revision ( Path.GetFileName path) bytes
460+ let repeated = Ingest.countDataWithAccounting revision ( Path.GetFileName path) bytes
461+ let graph = INSDC.build [ accounted.Conversion ]
462+ let outputLocations = ArcIRJson.locations graph |> Set.ofSeq
463+ let selectorPattern =
464+ " ^#selector\\ (type=DataPositionSelector,start=(\\ d+),end=(\\ d+)\\ )$"
465+
466+ let selectedValue ( entry : FieldAccountingEntry ) =
467+ let matched =
468+ Text.RegularExpressions.Regex.Match( entry.Input.Selector.Value, selectorPattern)
469+
470+ Assert.True( matched.Success, $" Invalid data-position selector: {entry.Input.Selector.Value}" )
471+ let startPosition = Int32.Parse matched.Groups.[ 1 ]. Value
472+ let endPosition = Int32.Parse matched.Groups.[ 2 ]. Value
473+ Assert.InRange( startPosition, 0 , bytes.Length)
474+ Assert.InRange( endPosition, startPosition, bytes.Length)
475+ Encoding.UTF8.GetString( bytes, startPosition, endPosition - startPosition)
476+
477+ Assert.Equal( accounted, repeated)
478+ Assert.Equal( 3 , accounted.Accounting.Entries.Length)
479+ Assert.Empty accounted.Accounting.Diagnostics.Diagnostics
480+
481+ for entry in accounted.Accounting.Entries do
482+ Assert.Equal( revision, entry.Input.Artifact)
483+ Assert.Equal( IngestAccounting.DataPositionSelectorConformsTo, entry.Input.Selector.ConformsTo)
484+ Assert.False( String.IsNullOrWhiteSpace( selectedValue entry))
485+
486+ match entry.Outcome with
487+ | FieldAccountingOutcome.Emitted outputs ->
488+ Assert.NotEmpty outputs
489+ for output in outputs do
490+ Assert.Contains( output, outputLocations)
491+ | FieldAccountingOutcome.Ignored reason -> Assert.False( String.IsNullOrWhiteSpace reason)
492+ | outcome -> failwithf " Expected emitted or reviewed-ignored count header cell, got %A " outcome
493+
494+ let featureColumn =
495+ accounted.Accounting.Entries
496+ |> List.find ( fun entry -> selectedValue entry = " gene_id" )
497+
498+ match featureColumn.Outcome with
499+ | FieldAccountingOutcome.Ignored _ -> ()
500+ | outcome -> failwithf " Expected the feature identifier column to be intentionally ignored, got %A " outcome
501+
502+ let runColumn =
503+ accounted.Accounting.Entries
504+ |> List.find ( fun entry -> selectedValue entry = " DRR072834" )
505+
506+ match runColumn.Outcome with
507+ | FieldAccountingOutcome.Emitted outputs ->
508+ Assert.True( outputs |> List.exists ( function ArcJsonLocation.Relation _ -> true | _ -> false ))
509+ | outcome -> failwithf " Expected the run header to emit the column and linkage, got %A " outcome
510+
511+ withTempDirectory ( fun directory ->
512+ assertQualifiedOutputsResolve directory " count-state.arcir.json" graph accounted.Accounting
513+ |> ignore)
514+
515+ let mismatched = Array.copy bytes
516+ mismatched.[ 0 ] <- mismatched.[ 0 ] ^^^ 1 uy
517+
518+ Assert.Throws< ArgumentException>( fun () ->
519+ Ingest.countDataWithAccounting revision ( Path.GetFileName path) mismatched |> ignore)
520+ |> ignore
0 commit comments