Validation Package ceplas-03-biodata

Language: F#

ceplas; quality-arc; study; assay; raw data

v1.0.0 released on 08/20/2026

by Dominik Brilhaus; Heinrich Lukas Weil

5 Downloads

Validates the ARC's "biological" data (e.g. measured, raw or processed, datasets)


Install with arc-validate

 arc-validate package install ceplas-03-biodata --version 1.0.0

Include in a PLANTDataHUB CQC pipeline

validation_packages:
  - name: ceplas-03-biodata
    version: 1.0.0

Description

## Critical quality criteria

- ARC contains 'raw' data (e.g. raw dataset file or URL)

- ARC assay dataset file exists

// - ARC run data file exists

- Every data entity derives from a Source or Sample

- Every data entity is annotated with at least one of Characteristic, Parameter, Factor


Release notes

Release leveled validation packages.


Browse code for v1.0.0 (fsharp)
let [<Literal>]PACKAGE_METADATA = """(*
---
Name: ceplas-03-biodata
Summary: Validates the ARC's "biological" data (e.g. measured, raw or processed, datasets)
Description: |
    ## Critical quality criteria
    - ARC contains 'raw' data (e.g. raw dataset file or URL)
    - ARC assay dataset file exists
    // - ARC run data file exists
    - Every data entity derives from a Source or Sample
    - Every data entity is annotated with at least one of Characteristic, Parameter, Factor
MajorVersion: 1
MinorVersion: 0
PatchVersion: 0
Publish: true
Authors:
  - FullName: Dominik Brilhaus
    Email: brilhaus@hhu.de
    Affiliation: CEPLAS
    AffiliationLink: https://ceplas.eu
  - FullName: Heinrich Lukas Weil
    Email: weil@nfdi4plants.org
    Affiliation: RPTU Kaiserslautern
    AffiliationLink: http://rptu.de/startseite
Tags:
  - Name: ceplas
  - Name: quality-arc
  - Name: study
  - Name: assay
  - Name: "raw data"
ReleaseNotes: |
    Release leveled validation packages.
---
*)"""

#r "nuget: ARCExpect.Core, 7.0.0-alpha"
#r "nuget: ARCtrl.QueryModel, 3.0.0-alpha.4"
#r "nuget: Fable.SimpleHttp"

open ARCtrl
open ARCtrl.QueryModel
open Expecto
open ARCExpect
open System.IO
open Fable.SimpleHttp


let pathIsUrl (p: string) =
    p.StartsWith("http:") || p.StartsWith("https:")

type UrlResolution =
    | Resolves of statusCode: int
    | HttpError of statusCode: int
    | Malformed of error: string
    | Unreachable of error: string

let urlResolves (url: string) =
    async {
        match System.Uri.TryCreate(url, System.UriKind.Absolute) with
        | false, _ ->
            return Malformed "Invalid URL"

        | true, uri when uri.Scheme <> "http" && uri.Scheme <> "https" ->
            return Malformed $"Unsupported URL scheme: {uri.Scheme}"

        | true, _ ->
            try
                let! statusCode, _responseText = Http.get url

                if statusCode >= 200 && statusCode < 300 then
                    return Resolves statusCode
                else
                    return HttpError statusCode

            with ex ->
                return Unreachable ex.Message
    }


// Input:

let arcDir = Directory.GetCurrentDirectory()

////////////////////////

let arc =
    try ARC.load arcDir with
    | _ -> ARC(identifier = "placeholder")

arc.MakeDataFilesAbsolute()
arc.DataContextMapping()

// Validations

let criticalCases =     
    testList "criticalCases" [

        // TestCase Critical: ARC contains 'raw' data (e.g. raw dataset file or URL)
        // This includes any I/ONode of type Data (i.e. in study, assay or run)

        testCase "ARC contains data entities" <| fun _ ->
            
            Expect.isGreaterThan arc.ArcTables.Data.Count 0
                "ARC contains no data entities"

        // data entity should resolve
            // 1. annotation resolves local file
            // 2. if not local (./dataset), resolves URL

        // TestCase Critical: ARC assay dataset file exists

        for a in arc.Assays do
            for d in a.Data |> Seq.distinctBy (fun d -> d.Name) do

                let filePath = if d.FilePath = "" then d.Name else d.FilePath

                testCaseAsync $"Data path '{filePath}' of assay '{a.Identifier}' resolves to local file or folder or a URL" <| async {
                    if pathIsUrl filePath then
                        let! result = urlResolves filePath

                        match result with
                        | Resolves _ ->
                            ()

                        | HttpError statusCode ->
                            Expect.isLessThan statusCode 300
                                $"Url '{filePath}' in assay '{a.Identifier}' returned an unsuccessful HTTP status"

                        | Malformed error ->
                            Expect.isTrue false
                                $"Url '{filePath}' in assay '{a.Identifier}' is malformed: {error}"

                        | Unreachable error ->
                            Expect.isTrue false
                                $"Url '{filePath}' in assay '{a.Identifier}' could not be reached: {error}"

                    else
                        let p = d.DataContext.Value.GetAbsolutePathForAssay(a.Identifier)
                        let fullPath = Path.Combine(arcDir, p)

                        Expect.isTrue (File.Exists fullPath || Directory.Exists fullPath)
                            $"Data path '{filePath}' does not resolve to existing local file or folder and was not identified as URL"
                }

        // TestCase Critical: ARC run data file exists
        // TODO: currently not fully possible, since `GetAbsolutePathForRun` does not exist https://github.com/nfdi4plants/ARCtrl/issues/629


        for d in arc.ArcTables.Data do

        // TestCase Critical: Every data entity derives from a Source or Sample

            testCase $"Data entity '{d.Name}' derives from a Source or Sample"  <| fun _ ->

                let firstSamplesContainBlank =  d.FirstSamples |> List.exists (fun q -> q.Name = "")
                
                Expect.isFalse ((d.FirstSamples.IsEmpty || firstSamplesContainBlank) && d.Sources.Count = 0)
                    $"Data entity '{d.Name}' does not derive from a Source or Sample"
        
        // TestCase Critical: Every data entity is annotated with at least one of Characteristic, Parameter, Factor
            
            testCase $"Data entity '{d.Name}' contains at least one of Characteristic, Parameter, Factor"  <| fun _ ->
                Expect.isNonEmpty d.PreviousValues
                    $"Data entity '{d.Name}' is not associated with any annotation value"

    ]


// let nonCriticalCases =
//     testList "nonCriticalCases" [



//     ]

// Execution:
Setup.ValidationPackage(
    metadata = Setup.Metadata(
        PACKAGE_METADATA,
        AVPRIndex.Frontmatter.FrontmatterLanguage.FSharpFrontmatter
        ),
    CriticalValidationCases = [criticalCases],
    NonCriticalValidationCases = []
)
|> Execute.ValidationPipeline(
    basePath = arcDir
)

Available versions
Version Released on
1.0.0 08/20/2026