ftr_ruby 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/CHANGELOG.md +5 -0
- data/LICENSE.txt +21 -0
- data/README.md +179 -0
- data/Rakefile +12 -0
- data/lib/dcat_metadata.rb +235 -0
- data/lib/fdp_index.rb +168 -0
- data/lib/ftr_ruby/version.rb +5 -0
- data/lib/ftr_ruby.rb +24 -0
- data/lib/openapi.rb +77 -0
- data/lib/output.rb +181 -0
- data/lib/registertest.rb +23 -0
- data/lib/test_infrastructure.rb +47 -0
- data/sig/ftr_ruby.rbs +4 -0
- metadata +59 -0
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: def2bc2018d1d794afabd625f72f2bc5f78fb363dc4a06f87c2fd8cc2967f7e2
|
|
4
|
+
data.tar.gz: 6ae0b03ff20569a405c6f34f5e13fd84f6a9bfe9db9d6b67f536ca2cadb4d5e2
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: 1f8771dfbcb93530f56ad05f53f2606300f4d829e78d66976f971501bc62d39668eeb5afe67ad0db00adb56dd263964f4d934afc64359285ce889b8389279738
|
|
7
|
+
data.tar.gz: 13eaaa05362c0290c13377d1b7f7b2e427bc7e00caa5d83a082e02eac6c404ee4d7f41fcfad9486a5a6e527af57487a2156f9a790240701314b4ff957f434abe
|
data/CHANGELOG.md
ADDED
data/LICENSE.txt
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
The MIT License (MIT)
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 markwilkinson
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in
|
|
13
|
+
all copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|
21
|
+
THE SOFTWARE.
|
data/README.md
ADDED
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
# FtrRuby
|
|
2
|
+
|
|
3
|
+
**Ruby library for generating DCAT-compliant metadata and FAIR test outputs following the FTR Vocabulary**
|
|
4
|
+
|
|
5
|
+
`FtrRuby` provides two main classes for working with FAIR Tests in Ruby:
|
|
6
|
+
|
|
7
|
+
- `DCAT_Record` – Creates rich DCAT metadata describing a FAIR Test (as a `dcat:DataService` + `ftr:Test`)
|
|
8
|
+
- `Output` – Generates a standardized FAIR test execution result (as a `ftr:TestResult` with provenance)
|
|
9
|
+
|
|
10
|
+
The library uses the **TripleEasy** mixin for easy RDF triple creation and produces graphs compatible with DCAT, DQV, PROV, and the **FAIR Test Registry (FTR)** vocabulary.
|
|
11
|
+
|
|
12
|
+
---
|
|
13
|
+
|
|
14
|
+
## Features
|
|
15
|
+
|
|
16
|
+
- Full DCAT 2 metadata generation for FAIR Tests
|
|
17
|
+
- Standardized test result output with provenance (`prov:wasGeneratedBy`, `ftr:TestResult`, etc.)
|
|
18
|
+
- Automatic URL construction for test endpoints and identifiers
|
|
19
|
+
- Support for contact points, indicators, metrics, themes, and guidance
|
|
20
|
+
- JSON-LD output for easy consumption by registries and portals
|
|
21
|
+
- Ready for use in FAIR assessment platforms, OSTrails, and EOSC services
|
|
22
|
+
|
|
23
|
+
---
|
|
24
|
+
|
|
25
|
+
## Installation
|
|
26
|
+
|
|
27
|
+
If published as a gem:
|
|
28
|
+
|
|
29
|
+
```ruby
|
|
30
|
+
gem 'ftr_ruby'
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
Or install manually:
|
|
34
|
+
|
|
35
|
+
```bash
|
|
36
|
+
gem install ftr_ruby
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
For local development:
|
|
40
|
+
|
|
41
|
+
```ruby
|
|
42
|
+
require_relative 'lib/ftr_ruby'
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
---
|
|
46
|
+
|
|
47
|
+
## Usage
|
|
48
|
+
|
|
49
|
+
### 1. Documenting a FAIR Test (`DCAT_Record`)
|
|
50
|
+
|
|
51
|
+
```ruby
|
|
52
|
+
require 'ftr_ruby'
|
|
53
|
+
|
|
54
|
+
meta = {
|
|
55
|
+
testid: "ftr-rda-f1-01m",
|
|
56
|
+
testname: "FAIR Test F1-01M: Globally Unique Persistent Identifier",
|
|
57
|
+
description: "This test checks whether a digital object is identified by a globally unique persistent identifier.",
|
|
58
|
+
keywords: ["FAIR", "F1", "persistent identifier", "PID"],
|
|
59
|
+
creator: "https://orcid.org/0000-0001-2345-6789",
|
|
60
|
+
indicators: ["https://w3id.org/ftr/indicator/F1-01M"],
|
|
61
|
+
metric: "https://w3id.org/ftr/metric/F1-01M",
|
|
62
|
+
license: "https://creativecommons.org/licenses/by/4.0/",
|
|
63
|
+
testversion: "1.0.0",
|
|
64
|
+
protocol: "https",
|
|
65
|
+
host: "tests.ostrails.eu",
|
|
66
|
+
basePath: "api",
|
|
67
|
+
individuals: [{ "name" => "Mark Wilkinson", "email" => "mark.wilkinson@upm.es" }],
|
|
68
|
+
organizations: [{ "name" => "CBGP", "url" => "https://www.cbgp.upm.es" }]
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
record = FtrRuby::DCAT_Record.new(meta: meta)
|
|
72
|
+
graph = record.get_dcat
|
|
73
|
+
|
|
74
|
+
puts graph.dump(:turtle)
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
### 2. Generating Test Output (`Output`)
|
|
78
|
+
|
|
79
|
+
```ruby
|
|
80
|
+
require 'ftr_ruby'
|
|
81
|
+
|
|
82
|
+
# Meta comes from the same test definition used for DCAT_Record
|
|
83
|
+
meta = { ... } # same hash as above
|
|
84
|
+
|
|
85
|
+
output = FtrRuby::Output.new(
|
|
86
|
+
testedGUID: "https://doi.org/10.1234/example.dataset",
|
|
87
|
+
meta: meta
|
|
88
|
+
)
|
|
89
|
+
|
|
90
|
+
# Add test results and comments
|
|
91
|
+
output.score = "pass"
|
|
92
|
+
output.comments << "The resource has a valid persistent identifier."
|
|
93
|
+
output.comments << "Identifier resolves correctly."
|
|
94
|
+
|
|
95
|
+
# Optional: add guidance for non-passing cases
|
|
96
|
+
output.guidance = [
|
|
97
|
+
["https://example.org/fix-pid", "Register a persistent identifier"],
|
|
98
|
+
]
|
|
99
|
+
|
|
100
|
+
jsonld = output.createEvaluationResponse
|
|
101
|
+
puts jsonld
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
---
|
|
105
|
+
|
|
106
|
+
## Classes
|
|
107
|
+
|
|
108
|
+
### `FtrRuby::DCAT_Record`
|
|
109
|
+
|
|
110
|
+
Creates metadata describing the test itself.
|
|
111
|
+
|
|
112
|
+
- Builds a `dcat:DataService` + `ftr:Test`
|
|
113
|
+
- Automatically constructs endpoint URLs, landing pages, and identifiers
|
|
114
|
+
- Includes indicators, metrics, themes, license, contact points, etc.
|
|
115
|
+
|
|
116
|
+
See the class for full list of supported metadata fields.
|
|
117
|
+
|
|
118
|
+
### `FtrRuby::Output`
|
|
119
|
+
|
|
120
|
+
Represents the result of executing a FAIR test against a specific resource.
|
|
121
|
+
|
|
122
|
+
- Produces a `ftr:TestResult` linked to a `ftr:TestExecutionActivity`
|
|
123
|
+
- Includes score, summary, log/comments, guidance suggestions, and provenance
|
|
124
|
+
- Outputs as JSON-LD (with configurable prefixes)
|
|
125
|
+
- Automatically handles assessment target (the tested GUID)
|
|
126
|
+
|
|
127
|
+
**Key methods:**
|
|
128
|
+
|
|
129
|
+
- `new(testedGUID:, meta:)` – Initialize with the tested resource and test metadata
|
|
130
|
+
- `createEvaluationResponse` – Returns JSON-LD string of the full evaluation graph
|
|
131
|
+
|
|
132
|
+
---
|
|
133
|
+
|
|
134
|
+
## Vocabulary & Standards Used
|
|
135
|
+
|
|
136
|
+
- **DCAT** – Data Catalog Vocabulary (W3C)
|
|
137
|
+
- **DQV** – Data Quality Vocabulary
|
|
138
|
+
- **PROV** – Provenance Ontology
|
|
139
|
+
- **FTR** – FAIR Test Registry vocabulary (`https://w3id.org/ftr#`)
|
|
140
|
+
- **SIO** – Semanticscience Integrated Ontology
|
|
141
|
+
- **vCard** – Contact points
|
|
142
|
+
- Schema.org
|
|
143
|
+
|
|
144
|
+
---
|
|
145
|
+
|
|
146
|
+
## Project Structure
|
|
147
|
+
|
|
148
|
+
```
|
|
149
|
+
ftr_ruby/
|
|
150
|
+
├── lib/
|
|
151
|
+
│ └── ftr_ruby.rb
|
|
152
|
+
├── lib/ftr_ruby/
|
|
153
|
+
│ ├── dcat_record.rb
|
|
154
|
+
│ └── output.rb
|
|
155
|
+
└── README.md
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
---
|
|
159
|
+
|
|
160
|
+
## Contributing
|
|
161
|
+
|
|
162
|
+
Bug reports and pull requests are welcome on GitHub at:
|
|
163
|
+
https://github.com/markwilkinson/ftr_ruby
|
|
164
|
+
|
|
165
|
+
---
|
|
166
|
+
|
|
167
|
+
## License
|
|
168
|
+
|
|
169
|
+
This project is licensed under the [MIT License](LICENSE) (or specify your license).
|
|
170
|
+
|
|
171
|
+
---
|
|
172
|
+
|
|
173
|
+
## Acknowledgments
|
|
174
|
+
|
|
175
|
+
Developed in the context of the **OSTrails** project and the **FAIR Champion** initiative.
|
|
176
|
+
|
|
177
|
+
---
|
|
178
|
+
|
|
179
|
+
**Made with ❤️ for the FAIR community**
|
data/Rakefile
ADDED
|
@@ -0,0 +1,235 @@
|
|
|
1
|
+
##
|
|
2
|
+
# Module containing FAIR Test Registry (FTR) related classes.
|
|
3
|
+
module FtrRuby
|
|
4
|
+
##
|
|
5
|
+
# Represents a single FAIR Test as a DCAT-compliant DataService with additional
|
|
6
|
+
# FAIR-specific metadata.
|
|
7
|
+
#
|
|
8
|
+
# This class generates RDF metadata (in a DCAT + DQV + FTR vocabulary profile)
|
|
9
|
+
# describing a test that can be used to assess FAIR compliance of digital objects
|
|
10
|
+
# (typically datasets). The resulting graph follows the DCAT-AP style for Data Services,
|
|
11
|
+
# extended with FAIR Test Registry (FTR) semantics.
|
|
12
|
+
#
|
|
13
|
+
# == Usage
|
|
14
|
+
#
|
|
15
|
+
# meta = {
|
|
16
|
+
# testid: "ftr-rda-f1-01m",
|
|
17
|
+
# testname: "FAIR Test F1-01M: Persistent Identifier",
|
|
18
|
+
# description: "Checks whether the digital object has a globally unique persistent identifier...",
|
|
19
|
+
# keywords: ["FAIR", "persistent identifier", "F1"],
|
|
20
|
+
# creator: "https://orcid.org/0000-0001-2345-6789",
|
|
21
|
+
# indicators: ["https://w3id.org/ftr/indicator/F1-01M"],
|
|
22
|
+
# metric: "https://w3id.org/ftr/metric/F1-01M",
|
|
23
|
+
# license: "https://creativecommons.org/licenses/by/4.0/",
|
|
24
|
+
# testversion: "1.0",
|
|
25
|
+
# # ... other fields
|
|
26
|
+
# }
|
|
27
|
+
#
|
|
28
|
+
# record = FtrRuby::DCAT_Record.new(meta: meta)
|
|
29
|
+
# graph = record.get_dcat
|
|
30
|
+
#
|
|
31
|
+
class DCAT_Record
|
|
32
|
+
attr_accessor :identifier, :testname, :description, :keywords, :creator,
|
|
33
|
+
:indicators, :end_desc, :end_url, :dctype, :testid, :supportedby,
|
|
34
|
+
:license, :themes, :testversion, :implementations, :isapplicablefor, :applicationarea,
|
|
35
|
+
:organizations, :individuals, :protocol, :host, :basePath, :metric, :landingpage
|
|
36
|
+
|
|
37
|
+
require_relative "./output"
|
|
38
|
+
include TripleEasy # get the :"triplify" function
|
|
39
|
+
# triplify(s, p, o, repo, datatype: nil, context: nil, language: 'en')
|
|
40
|
+
|
|
41
|
+
##
|
|
42
|
+
# Creates a new DCAT_Record from metadata hash.
|
|
43
|
+
#
|
|
44
|
+
# @param meta [Hash] Metadata describing the FAIR test.
|
|
45
|
+
# @option meta [String] :testid Unique identifier for the test (used in URLs)
|
|
46
|
+
# @option meta [String] :testname Human-readable name/title of the test
|
|
47
|
+
# @option meta [String] :description Detailed description of what the test does
|
|
48
|
+
# @option meta [String, Array<String>] :keywords Keywords describing the test
|
|
49
|
+
# @option meta [String] :creator URI or literal identifying the creator
|
|
50
|
+
# @option meta [String, Array<String>] :indicators URIs of the FAIR indicators this test addresses
|
|
51
|
+
# @option meta [String] :metric URI of the metric this test implements
|
|
52
|
+
# @option meta [String] :license License URI for the test
|
|
53
|
+
# @option meta [String, Array<String>] :themes Thematic categories (DCAT themes)
|
|
54
|
+
# @option meta [String] :testversion Version of the test
|
|
55
|
+
# @option meta [Array<Hash>] :individuals List of contact individuals (name, email)
|
|
56
|
+
# @option meta [Array<Hash>] :organizations List of responsible organizations (name, url)
|
|
57
|
+
# @option meta [String] :protocol Protocol (http/https)
|
|
58
|
+
# @option meta [String] :host Hostname of the test service
|
|
59
|
+
# @option meta [String] :basePath Base path of the test service
|
|
60
|
+
#
|
|
61
|
+
# @note Several fields have sensible defaults (e.g. +dctype+, +supportedby+, +applicationarea+).
|
|
62
|
+
# The +end_url+ and +identifier+ are automatically constructed from +protocol+, +host+,
|
|
63
|
+
# +basePath+, and +testid+.
|
|
64
|
+
#
|
|
65
|
+
def initialize(meta:)
|
|
66
|
+
indics = [meta[:indicators]] unless meta[:indicators].is_a? Array
|
|
67
|
+
@indicators = indics
|
|
68
|
+
@testid = meta[:testid]
|
|
69
|
+
@testname = meta[:testname]
|
|
70
|
+
@metric = meta[:metric]
|
|
71
|
+
@description = meta[:description] || "No description provided"
|
|
72
|
+
@keywords = meta[:keywords] || []
|
|
73
|
+
@keywords = [@keywords] unless @keywords.is_a? Array
|
|
74
|
+
@creator = meta[:creator]
|
|
75
|
+
@end_desc = meta[:end_desc]
|
|
76
|
+
@end_url = meta[:end_url]
|
|
77
|
+
@dctype = meta[:dctype] || "http://edamontology.org/operation_2428"
|
|
78
|
+
@supportedby = meta[:supportedby] || ["https://tools.ostrails.eu/champion"]
|
|
79
|
+
@applicationarea = meta[:applicationarea] || ["http://www.fairsharing.org/ontology/subject/SRAO_0000401"]
|
|
80
|
+
@isapplicablefor = meta[:isapplicablefor] || ["https://schema.org/Dataset"]
|
|
81
|
+
@landingpage = meta[:landingPage] || @end_url
|
|
82
|
+
@license = meta[:license] || "No License"
|
|
83
|
+
@themes = meta[:themes] || []
|
|
84
|
+
@themes = [@themes] unless @themes.is_a? Array
|
|
85
|
+
@testversion = meta[:testversion] || "unversioned"
|
|
86
|
+
@organizations = meta[:organizations] || []
|
|
87
|
+
@individuals = meta[:individuals] || []
|
|
88
|
+
@protocol = meta[:protocol]
|
|
89
|
+
@host = meta[:host]
|
|
90
|
+
@basePath = meta[:basePath]
|
|
91
|
+
cleanhost = @host.gsub("/", "")
|
|
92
|
+
cleanpath = @basePath.gsub("/", "") # TODO: this needs to check only leading and trailing! NOt internal...
|
|
93
|
+
endpointpath = "assess/test"
|
|
94
|
+
@end_url = "#{protocol}://#{cleanhost}/#{cleanpath}/#{endpointpath}/#{testid}"
|
|
95
|
+
@end_desc = "#{protocol}://#{cleanhost}/#{cleanpath}/#{testid}/api"
|
|
96
|
+
@identifier = "#{protocol}://#{cleanhost}/#{cleanpath}/#{testid}"
|
|
97
|
+
|
|
98
|
+
unless @testid && @testname && @description && @creator && @end_desc && @end_url && @protocol && @host && @basePath
|
|
99
|
+
warn "this record is invalid - it is missing one of testid testname description creator end_desc end_url protocol host basePath"
|
|
100
|
+
end
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
##
|
|
104
|
+
# Returns an RDF::Graph containing the DCAT metadata for this test.
|
|
105
|
+
#
|
|
106
|
+
# The graph describes the test as both a +dcat:DataService+ and an +ftr:Test+.
|
|
107
|
+
# It includes:
|
|
108
|
+
#
|
|
109
|
+
# * Core DCAT properties (identifier, title, description, keywords, landing page, etc.)
|
|
110
|
+
# * FAIR-specific extensions via the FTR vocabulary
|
|
111
|
+
# * Contact points (individuals and organizations) using vCard
|
|
112
|
+
# * Link to the metric it implements (SIO)
|
|
113
|
+
# * Supported-by relationships, application areas, and applicability statements
|
|
114
|
+
#
|
|
115
|
+
# @return [RDF::Graph] RDF graph with the complete DCAT record
|
|
116
|
+
#
|
|
117
|
+
def get_dcat
|
|
118
|
+
schema = RDF::Vocab::SCHEMA
|
|
119
|
+
dcterms = RDF::Vocab::DC
|
|
120
|
+
xsd = RDF::Vocab::XSD
|
|
121
|
+
dcat = RDF::Vocab::DCAT
|
|
122
|
+
sio = RDF::Vocabulary.new("http://semanticscience.org/resource/")
|
|
123
|
+
ftr = RDF::Vocabulary.new("https://w3id.org/ftr#")
|
|
124
|
+
dqv = RDF::Vocabulary.new("http://www.w3.org/ns/dqv#")
|
|
125
|
+
vcard = RDF::Vocabulary.new("http://www.w3.org/2006/vcard/ns#")
|
|
126
|
+
dpv = RDF::Vocabulary.new("https://w3id.org/dpv#")
|
|
127
|
+
|
|
128
|
+
g = RDF::Graph.new
|
|
129
|
+
# me = "#{identifier}/about" # at the hackathon we decided that the test id would return the metadata
|
|
130
|
+
# so now there is no need for /about
|
|
131
|
+
me = "#{identifier}"
|
|
132
|
+
|
|
133
|
+
triplify(me, RDF.type, dcat.DataService, g)
|
|
134
|
+
triplify(me, RDF.type, ftr.Test, g)
|
|
135
|
+
|
|
136
|
+
# triplify tests and rejects anything that is empty or nil --> SAFE
|
|
137
|
+
# Test Unique Identifier dcterms:identifier Literal
|
|
138
|
+
triplify(me, dcterms.identifier, identifier.to_s, g, datatype: xsd.string)
|
|
139
|
+
|
|
140
|
+
# Title/Name of the test dcterms:title Literal
|
|
141
|
+
triplify(me, dcterms.title, testname, g)
|
|
142
|
+
|
|
143
|
+
# Description dcterms:description Literal
|
|
144
|
+
# descriptions.each do |d|
|
|
145
|
+
# triplify(me, dcterms.description, d, g)
|
|
146
|
+
# end
|
|
147
|
+
triplify(me, dcterms.description, description, g)
|
|
148
|
+
|
|
149
|
+
# Keywords dcat:keyword Literal
|
|
150
|
+
keywords.each do |kw|
|
|
151
|
+
triplify(me, dcat.keyword, kw, g)
|
|
152
|
+
end
|
|
153
|
+
|
|
154
|
+
# Test creator dcterms:creator dcat:Agent (URI)
|
|
155
|
+
triplify(me, dcterms.creator, creator, g)
|
|
156
|
+
|
|
157
|
+
# Dimension ftr:indicator
|
|
158
|
+
indicators.each do |ind|
|
|
159
|
+
triplify(me, dqv.inDimension, ind, g)
|
|
160
|
+
end
|
|
161
|
+
|
|
162
|
+
# API description dcat:endpointDescription rdfs:Resource
|
|
163
|
+
triplify(me, dcat.endpointDescription, end_desc, g)
|
|
164
|
+
|
|
165
|
+
# API URL dcat:endpointURL rdfs:Resource
|
|
166
|
+
triplify(me, dcat.endpointURL, end_url, g)
|
|
167
|
+
|
|
168
|
+
# API URL dcat:landingPage rdfs:Resource
|
|
169
|
+
triplify(me, dcat.landingPage, landingpage, g)
|
|
170
|
+
|
|
171
|
+
# Source of the test codemeta:hasSourceCode/schema:codeRepository/ doap:repository schema:SoftwareSourceCode/URL
|
|
172
|
+
# TODO
|
|
173
|
+
# FAIRChampion::Output.triplify(me, dcat.endpointDescription, end_desc, g)
|
|
174
|
+
|
|
175
|
+
# Functional Descriptor/Operation dcterms:type xsd:anyURI
|
|
176
|
+
triplify(me, dcterms.type, dctype, g)
|
|
177
|
+
|
|
178
|
+
# License dcterms:license xsd:anyURI
|
|
179
|
+
triplify(me, dcterms.license, license, g)
|
|
180
|
+
|
|
181
|
+
# Semantic Annotation dcat:theme xsd:anyURI
|
|
182
|
+
themes.each do |theme|
|
|
183
|
+
triplify(me, dcat.theme, theme, g)
|
|
184
|
+
end
|
|
185
|
+
|
|
186
|
+
# Version dcat:version rdfs:Literal
|
|
187
|
+
triplify(me, RDF::Vocab::DCAT.to_s + "version", testversion, g)
|
|
188
|
+
|
|
189
|
+
# # Version notes adms:versionNotes rdfs:Literal
|
|
190
|
+
# FAIRChampion::Output.triplify(me, dcat.version, version, g)
|
|
191
|
+
|
|
192
|
+
triplify(me, sio["SIO_000233"], metric, g) # is implementation of
|
|
193
|
+
triplify(metric, RDF.type, dqv.Metric, g) # is implementation of
|
|
194
|
+
|
|
195
|
+
# Responsible dcat:contactPoint dcat:Kind (includes Individual/Organization)
|
|
196
|
+
individuals.each do |i|
|
|
197
|
+
# i = {name: "Mark WAilkkinson", "email": "asmlkfj;askjf@a;lksdjfas"}
|
|
198
|
+
guid = SecureRandom.uuid
|
|
199
|
+
cp = "urn:fairchampion:testmetadata:individual#{guid}"
|
|
200
|
+
triplify(me, dcat.contactPoint, cp, g)
|
|
201
|
+
triplify(cp, RDF.type, vcard.Individual, g)
|
|
202
|
+
triplify(cp, vcard.fn, i["name"], g) if i["name"]
|
|
203
|
+
next unless i["email"]
|
|
204
|
+
|
|
205
|
+
email = i["email"].to_s
|
|
206
|
+
email = "mailto:#{email}" unless email =~ /mailto:/
|
|
207
|
+
triplify(cp, vcard.hasEmail, RDF::URI.new(email), g)
|
|
208
|
+
end
|
|
209
|
+
|
|
210
|
+
organizations.each do |o|
|
|
211
|
+
# i = {name: "CBGP", "url": "https://dbdsf.orhf"}
|
|
212
|
+
guid = SecureRandom.uuid
|
|
213
|
+
cp = "urn:fairchampion:testmetadata:org:#{guid}"
|
|
214
|
+
triplify(me, dcat.contactPoint, cp, g)
|
|
215
|
+
triplify(cp, RDF.type, vcard.Organization, g)
|
|
216
|
+
triplify(cp, vcard["organization-name"], o["name"], g)
|
|
217
|
+
triplify(cp, vcard.url, RDF::URI.new(o["url"].to_s), g)
|
|
218
|
+
end
|
|
219
|
+
|
|
220
|
+
supportedby.each do |tool|
|
|
221
|
+
triplify(me, ftr.supportedBy, tool, g)
|
|
222
|
+
triplify(tool, RDF.type, schema.SoftwareApplication, g)
|
|
223
|
+
end
|
|
224
|
+
|
|
225
|
+
applicationarea.each do |domain|
|
|
226
|
+
triplify(me, ftr.applicationArea, domain, g)
|
|
227
|
+
end
|
|
228
|
+
isapplicablefor.each do |digitalo|
|
|
229
|
+
triplify(me, dpv.isApplicableFor, digitalo, g)
|
|
230
|
+
end
|
|
231
|
+
|
|
232
|
+
g
|
|
233
|
+
end
|
|
234
|
+
end
|
|
235
|
+
end
|
data/lib/fdp_index.rb
ADDED
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
require "sparql/client"
|
|
2
|
+
require "sparql"
|
|
3
|
+
require "json/ld"
|
|
4
|
+
require "json/ld/preloaded"
|
|
5
|
+
require "rdf/trig"
|
|
6
|
+
require "rdf/raptor"
|
|
7
|
+
require "fileutils" # For directory creation
|
|
8
|
+
require "digest" # For hashing URLs to filenames
|
|
9
|
+
|
|
10
|
+
module FtrRuby
|
|
11
|
+
class FDPIndex
|
|
12
|
+
# Cache directory and expiry time (in seconds, e.g., 24 hours)
|
|
13
|
+
CACHE_DIR = File.join(Dir.pwd, "cache", "rdf_repositories")
|
|
14
|
+
CACHE_EXPIRY = 240 * 60 * 60 # 24 hours in seconds
|
|
15
|
+
|
|
16
|
+
def self.retrieve_tests_from_index(indexendpoint: "https://tools.ostrails.eu/repositories/fdpindex-fdp")
|
|
17
|
+
sparql = SPARQL::Client.new(indexendpoint)
|
|
18
|
+
|
|
19
|
+
fdpindexquery = <<EOQUERY
|
|
20
|
+
PREFIX rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#>
|
|
21
|
+
PREFIX rdfs: <http://www.w3.org/2000/01/rdf-schema#>
|
|
22
|
+
PREFIX dqv: <http://www.w3.org/ns/dqv#>
|
|
23
|
+
PREFIX dct: <http://purl.org/dc/terms/>
|
|
24
|
+
PREFIX dcat: <http://www.w3.org/ns/dcat#>
|
|
25
|
+
PREFIX sio: <http://semanticscience.org/resource/>
|
|
26
|
+
PREFIX dpv: <http://www.w3.org/ns/dpv#>
|
|
27
|
+
PREFIX ftr: <https://w3id.org/ftr#>
|
|
28
|
+
SELECT distinct ?sub ?identifier ?title ?description ?endpoint ?openapi ?dimension ?objects ?domain ?benchmark_or_metric WHERE {
|
|
29
|
+
?sub a <https://w3id.org/ftr#Test> ;
|
|
30
|
+
dct:title ?title ;
|
|
31
|
+
dct:description ?description ;
|
|
32
|
+
dct:identifier ?identifier .
|
|
33
|
+
OPTIONAL {?sub dpv:isApplicableFor ?objects }
|
|
34
|
+
OPTIONAL {?sub ftr:applicationArea ?domain }
|
|
35
|
+
OPTIONAL {?sub sio:SIO_000233 ?benchmark_or_metric } # implementation of#{" "}
|
|
36
|
+
OPTIONAL {?sub dcat:endpointDescription ?openapi }
|
|
37
|
+
OPTIONAL {?sub dcat:endpointURL ?endpoint }
|
|
38
|
+
OPTIONAL {?sub dqv:inDimension ?dimension }
|
|
39
|
+
}#{" "}
|
|
40
|
+
EOQUERY
|
|
41
|
+
|
|
42
|
+
alltests = []
|
|
43
|
+
|
|
44
|
+
begin
|
|
45
|
+
# Execute the query
|
|
46
|
+
results = sparql.query(fdpindexquery)
|
|
47
|
+
|
|
48
|
+
# Process the results
|
|
49
|
+
results.each_solution do |solution|
|
|
50
|
+
test_object = {
|
|
51
|
+
subj: solution[:sub]&.to_s,
|
|
52
|
+
identifier: solution[:identifier]&.to_s,
|
|
53
|
+
title: solution[:title]&.to_s,
|
|
54
|
+
description: solution[:description]&.to_s,
|
|
55
|
+
endpoint: solution[:endpoint]&.to_s,
|
|
56
|
+
openapi: solution[:openapi]&.to_s,
|
|
57
|
+
dimension: solution[:dimension]&.to_s,
|
|
58
|
+
objects: solution[:objects]&.to_s,
|
|
59
|
+
domain: solution[:domain]&.to_s,
|
|
60
|
+
benchmark_or_metric: solution[:benchmark_or_metric]&.to_s
|
|
61
|
+
}
|
|
62
|
+
alltests << test_object
|
|
63
|
+
end
|
|
64
|
+
rescue StandardError => e
|
|
65
|
+
puts "Error executing SPARQL query: #{e.message}"
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
alltests
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
def self.get_metrics_labels_for_tests(tests:)
|
|
72
|
+
labels = {}
|
|
73
|
+
cache = {} # In-memory cache for this request
|
|
74
|
+
|
|
75
|
+
# Ensure cache directory exists
|
|
76
|
+
FileUtils.mkdir_p(CACHE_DIR)
|
|
77
|
+
|
|
78
|
+
tests.each do |test|
|
|
79
|
+
metric = test[:benchmark_or_metric] # Assume required
|
|
80
|
+
warn "Processing metric: #{metric}"
|
|
81
|
+
|
|
82
|
+
# Generate a safe filename for the metric URL
|
|
83
|
+
cache_key = Digest::SHA256.hexdigest(metric)
|
|
84
|
+
cache_file = File.join(CACHE_DIR, "#{cache_key}.bin")
|
|
85
|
+
|
|
86
|
+
# Check in-memory cache first
|
|
87
|
+
if cache[metric]
|
|
88
|
+
repository = cache[metric]
|
|
89
|
+
else
|
|
90
|
+
# Try to load from disk cache
|
|
91
|
+
repository = load_from_cache(cache_file)
|
|
92
|
+
if repository
|
|
93
|
+
warn "Loaded #{metric} from cache"
|
|
94
|
+
else
|
|
95
|
+
# Cache miss: fetch from URL
|
|
96
|
+
warn "Fetching RDF for #{metric}"
|
|
97
|
+
repository = RDF::Repository.new
|
|
98
|
+
headers = { "Accept" => "application/ld+json" }
|
|
99
|
+
begin
|
|
100
|
+
RDF::Reader.open(metric, headers: headers) do |reader|
|
|
101
|
+
repository << reader
|
|
102
|
+
end
|
|
103
|
+
# Save to disk cache with timestamp
|
|
104
|
+
save_to_cache(cache_file, repository)
|
|
105
|
+
warn "Cached #{metric} to disk"
|
|
106
|
+
rescue StandardError => e
|
|
107
|
+
warn "Error fetching RDF for #{metric}: #{e.message}"
|
|
108
|
+
labels[metric] = "Unable to resolve #{metric} to RDF metadata"
|
|
109
|
+
next
|
|
110
|
+
end
|
|
111
|
+
end
|
|
112
|
+
cache[metric] = repository # Store in memory for this request
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
# SPARQL query to get label
|
|
116
|
+
fdpindexquery = <<-METRICLABEL
|
|
117
|
+
PREFIX dct: <http://purl.org/dc/terms/>
|
|
118
|
+
PREFIX schema: <http://schema.org/>
|
|
119
|
+
SELECT DISTINCT ?label WHERE {
|
|
120
|
+
{ ?sub dct:title ?label }
|
|
121
|
+
UNION
|
|
122
|
+
{ ?sub schema:name ?label }
|
|
123
|
+
}
|
|
124
|
+
METRICLABEL
|
|
125
|
+
|
|
126
|
+
# Parse and execute the SPARQL query
|
|
127
|
+
fdpindexquery = SPARQL.parse(fdpindexquery)
|
|
128
|
+
results = fdpindexquery.execute(repository)
|
|
129
|
+
|
|
130
|
+
# Assign the label (first result or fallback)
|
|
131
|
+
labels[metric] = if results&.first&.[](:label)&.to_s&.length&.positive?
|
|
132
|
+
results.first[:label].to_s
|
|
133
|
+
else
|
|
134
|
+
"Unnamed Metric"
|
|
135
|
+
end
|
|
136
|
+
end
|
|
137
|
+
|
|
138
|
+
labels
|
|
139
|
+
end
|
|
140
|
+
|
|
141
|
+
# Load RDF::Repository from disk cache if not expired
|
|
142
|
+
def self.load_from_cache(cache_file)
|
|
143
|
+
return nil unless File.exist?(cache_file)
|
|
144
|
+
|
|
145
|
+
# Read timestamp and serialized data
|
|
146
|
+
File.open(cache_file, "rb") do |file|
|
|
147
|
+
timestamp = Marshal.load(file)
|
|
148
|
+
if Time.now - timestamp < CACHE_EXPIRY
|
|
149
|
+
return Marshal.load(file) # Return cached RDF::Repository
|
|
150
|
+
end
|
|
151
|
+
end
|
|
152
|
+
nil # Cache expired or invalid
|
|
153
|
+
rescue StandardError => e
|
|
154
|
+
warn "Error loading cache from #{cache_file}: #{e.message}"
|
|
155
|
+
nil
|
|
156
|
+
end
|
|
157
|
+
|
|
158
|
+
# Save RDF::Repository to disk cache with timestamp
|
|
159
|
+
def self.save_to_cache(cache_file, repository)
|
|
160
|
+
File.open(cache_file, "wb") do |file|
|
|
161
|
+
Marshal.dump(Time.now, file) # Store timestamp
|
|
162
|
+
Marshal.dump(repository, file) # Store repository
|
|
163
|
+
end
|
|
164
|
+
rescue StandardError => e
|
|
165
|
+
warn "Error saving cache to #{cache_file}: #{e.message}"
|
|
166
|
+
end
|
|
167
|
+
end
|
|
168
|
+
end
|
data/lib/ftr_ruby.rb
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "ftr_ruby/version"
|
|
4
|
+
require "rest-client"
|
|
5
|
+
require "json"
|
|
6
|
+
require "sparql"
|
|
7
|
+
require "sparql/client"
|
|
8
|
+
require "linkeddata"
|
|
9
|
+
require "safe_yaml"
|
|
10
|
+
require "rdf/nquads"
|
|
11
|
+
require "cgi"
|
|
12
|
+
require "securerandom"
|
|
13
|
+
require "rdf/vocab"
|
|
14
|
+
require "triple_easy" # provides "triplify" top-level function
|
|
15
|
+
|
|
16
|
+
# lib/ftr_ruby.rb
|
|
17
|
+
|
|
18
|
+
require "dcat_metadata"
|
|
19
|
+
require "output"
|
|
20
|
+
|
|
21
|
+
module FtrRuby
|
|
22
|
+
class Error < StandardError; end
|
|
23
|
+
# Your code goes here...
|
|
24
|
+
end
|
data/lib/openapi.rb
ADDED
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
class OpenAPI
|
|
2
|
+
attr_accessor :title, :metric, :description, :indicator, :testid,
|
|
3
|
+
:organization, :org_url, :version, :creator,
|
|
4
|
+
:responsible_developer, :email, :developer_ORCiD, :protocol,
|
|
5
|
+
:host, :basePath, :path, :response_description, :schemas, :endpointpath
|
|
6
|
+
|
|
7
|
+
def initialize(meta:)
|
|
8
|
+
indics = [meta[:indicators]] unless meta[:indicators].is_a? Array
|
|
9
|
+
@testid = meta[:testid]
|
|
10
|
+
@title = meta[:testname]
|
|
11
|
+
@version = meta[:testversion]
|
|
12
|
+
@metric = meta[:metric]
|
|
13
|
+
@description = meta[:description]
|
|
14
|
+
@indicator = indics.first
|
|
15
|
+
@organization = meta[:organization]
|
|
16
|
+
@org_url = meta[:org_url]
|
|
17
|
+
@responsible_developer = meta[:responsible_developer]
|
|
18
|
+
@email = meta[:email]
|
|
19
|
+
@creator = meta[:creator]
|
|
20
|
+
@host = meta[:host]
|
|
21
|
+
@host = @host.gsub(%r{/$}, "") # remove trailing slash if present
|
|
22
|
+
@protocol = meta[:protocol].gsub(%r{[:/]}, "")
|
|
23
|
+
@basePath = meta[:basePath].gsub(%r{[:/]}, "")
|
|
24
|
+
@basePath = "/#{basePath}" unless basePath[0] == "/" # must start with a slash
|
|
25
|
+
@path = meta[:path]
|
|
26
|
+
@response_description = meta[:response_description]
|
|
27
|
+
@schemas = meta[:schemas]
|
|
28
|
+
@endpointpath = "assess/test"
|
|
29
|
+
@end_url = "#{protocol}://#{host}#{basePath}/#{endpointpath}/#{testid}" # basepath starts with /
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def get_api
|
|
33
|
+
<<~"EOF_EOF"
|
|
34
|
+
|
|
35
|
+
openapi: 3.0.0
|
|
36
|
+
info:
|
|
37
|
+
version: "#{version}"
|
|
38
|
+
title: "#{title}"
|
|
39
|
+
x-tests_metric: "#{metric}"
|
|
40
|
+
description: >-
|
|
41
|
+
"#{description}"
|
|
42
|
+
x-applies_to_principle: "#{indicator}"
|
|
43
|
+
contact:
|
|
44
|
+
x-organization: "#{organization}"
|
|
45
|
+
url: "#{org_url}"
|
|
46
|
+
name: "#{responsible_developer}"
|
|
47
|
+
x-role: responsible developer
|
|
48
|
+
email: "#{email}"
|
|
49
|
+
x-id: "#{creator}"
|
|
50
|
+
paths:
|
|
51
|
+
"/#{testid}":
|
|
52
|
+
post:
|
|
53
|
+
requestBody:
|
|
54
|
+
content:
|
|
55
|
+
application/json:
|
|
56
|
+
schema:
|
|
57
|
+
$ref: "#/components/schemas/schemas"
|
|
58
|
+
required: true
|
|
59
|
+
responses:
|
|
60
|
+
"200":
|
|
61
|
+
description: >-
|
|
62
|
+
#{response_description}
|
|
63
|
+
servers:
|
|
64
|
+
- url: "#{protocol}://#{host}/#{endpointpath}"
|
|
65
|
+
components:
|
|
66
|
+
schemas:
|
|
67
|
+
schemas:
|
|
68
|
+
required:
|
|
69
|
+
- resource_identifier
|
|
70
|
+
properties:
|
|
71
|
+
- resource_identifier:
|
|
72
|
+
type: string
|
|
73
|
+
description: the GUID being tested
|
|
74
|
+
|
|
75
|
+
EOF_EOF
|
|
76
|
+
end
|
|
77
|
+
end
|
data/lib/output.rb
ADDED
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
module FtrRuby
|
|
2
|
+
##
|
|
3
|
+
# Represents the output/result of executing a FAIR test against a specific resource.
|
|
4
|
+
#
|
|
5
|
+
# This class generates a provenance-rich RDF graph describing a single test execution,
|
|
6
|
+
# including the result score, log messages, summary, and optional guidance for improvement.
|
|
7
|
+
#
|
|
8
|
+
# The output follows the FAIR Test Registry (FTR) vocabulary and uses PROV for tracing
|
|
9
|
+
# the execution activity and the tested entity.
|
|
10
|
+
#
|
|
11
|
+
# @example
|
|
12
|
+
# output = FtrRuby::Output.new(
|
|
13
|
+
# testedGUID: "https://doi.org/10.5281/zenodo.1234567",
|
|
14
|
+
# meta: test_meta_hash
|
|
15
|
+
# )
|
|
16
|
+
# output.score = "pass"
|
|
17
|
+
# output.comments << "Resource has a resolvable persistent identifier."
|
|
18
|
+
# jsonld = output.createEvaluationResponse
|
|
19
|
+
#
|
|
20
|
+
class Output
|
|
21
|
+
include TripleEasy # get the :"triplify" function
|
|
22
|
+
# triplify(s, p, o, repo, datatype: nil, context: nil, language: 'en')
|
|
23
|
+
include RDF
|
|
24
|
+
|
|
25
|
+
attr_accessor :score, :testedGUID, :testid, :uniqueid, :name, :description, :license, :dt, :metric, :softwareid,
|
|
26
|
+
:version, :summary, :completeness, :comments, :guidance, :creator, :protocol, :host, :basePath, :api
|
|
27
|
+
|
|
28
|
+
OPUTPUT_VERSION = "1.1.1"
|
|
29
|
+
|
|
30
|
+
##
|
|
31
|
+
# Creates a new test output instance.
|
|
32
|
+
#
|
|
33
|
+
# @param testedGUID [String] The identifier (usually URI) of the resource being tested
|
|
34
|
+
# @param meta [Hash] Metadata about the test (same structure used by DCAT_Record)
|
|
35
|
+
#
|
|
36
|
+
def initialize(testedGUID:, meta:)
|
|
37
|
+
@score = "indeterminate"
|
|
38
|
+
@testedGUID = testedGUID
|
|
39
|
+
@uniqueid = "urn:fairtestoutput:" + SecureRandom.uuid
|
|
40
|
+
@name = meta[:testname]
|
|
41
|
+
@description = meta[:description]
|
|
42
|
+
@license = meta[:license] || "https://creativecommons.org/licenses/by/4.0/"
|
|
43
|
+
@dt = Time.now.iso8601
|
|
44
|
+
@metric = meta[:metric]
|
|
45
|
+
@version = meta[:testversion]
|
|
46
|
+
@summary = meta[:summary] || "Summary:"
|
|
47
|
+
@completeness = "100"
|
|
48
|
+
@comments = []
|
|
49
|
+
@guidance = meta.fetch(:guidance, [])
|
|
50
|
+
@creator = meta[:creator]
|
|
51
|
+
@protocol = meta[:protocol].gsub(%r{[:/]}, "")
|
|
52
|
+
@host = meta[:host].gsub(%r{[:/]}, "")
|
|
53
|
+
@basePath = meta[:basePath].gsub(%r{[:/]}, "")
|
|
54
|
+
@softwareid = "#{@protocol}://#{@host}/#{@basePath}/#{meta[:testid]}"
|
|
55
|
+
@api = "#{@softwareid}/api"
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
##
|
|
59
|
+
# Generates the full evaluation response as JSON-LD.
|
|
60
|
+
#
|
|
61
|
+
# Builds an RDF graph containing:
|
|
62
|
+
# * A `ftr:TestExecutionActivity`
|
|
63
|
+
# * A `ftr:TestResult` with score, summary, log, and guidance
|
|
64
|
+
# * Links to the tested entity and the test software
|
|
65
|
+
# * Provenance information
|
|
66
|
+
#
|
|
67
|
+
# @return [String] JSON-LD representation of the test result graph
|
|
68
|
+
#
|
|
69
|
+
def createEvaluationResponse
|
|
70
|
+
g = RDF::Graph.new
|
|
71
|
+
schema = RDF::Vocab::SCHEMA
|
|
72
|
+
xsd = RDF::Vocab::XSD
|
|
73
|
+
dct = RDF::Vocab::DC
|
|
74
|
+
prov = RDF::Vocab::PROV
|
|
75
|
+
dcat = RDF::Vocab::DCAT
|
|
76
|
+
dqv = RDF::Vocabulary.new("https://www.w3.org/TR/vocab-dqv/")
|
|
77
|
+
ftr = RDF::Vocabulary.new("https://w3id.org/ftr#")
|
|
78
|
+
sio = RDF::Vocabulary.new("http://semanticscience.org/resource/")
|
|
79
|
+
cwmo = RDF::Vocabulary.new("http://purl.org/cwmo/#")
|
|
80
|
+
|
|
81
|
+
add_newline_to_comments
|
|
82
|
+
|
|
83
|
+
if summary =~ /^Summary$/
|
|
84
|
+
summary = "Summary of test results: #{comments[-1]}"
|
|
85
|
+
summary ||= "Summary of test results: #{comments[-2]}"
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
executionid = "urn:ostrails:testexecutionactivity:" + SecureRandom.uuid
|
|
89
|
+
|
|
90
|
+
# tid = 'urn:ostrails:fairtestentity:' + SecureRandom.uuid
|
|
91
|
+
# The entity is no longer an anonymous node, it is the GUID Of the tested input
|
|
92
|
+
|
|
93
|
+
triplify(executionid, RDF.type, ftr.TestExecutionActivity, g)
|
|
94
|
+
triplify(executionid, prov.wasAssociatedWith, softwareid, g)
|
|
95
|
+
triplify(uniqueid, prov.wasGeneratedBy, executionid, g)
|
|
96
|
+
|
|
97
|
+
triplify(uniqueid, RDF.type, ftr.TestResult, g)
|
|
98
|
+
triplify(uniqueid, dct.identifier, uniqueid.to_s, g, datatype: xsd.string)
|
|
99
|
+
triplify(uniqueid, dct.title, "#{name} OUTPUT", g)
|
|
100
|
+
triplify(uniqueid, dct.description, "OUTPUT OF #{description}", g)
|
|
101
|
+
triplify(uniqueid, dct.license, license, g)
|
|
102
|
+
triplify(uniqueid, prov.value, score, g)
|
|
103
|
+
triplify(uniqueid, ftr.summary, summary, g)
|
|
104
|
+
triplify(uniqueid, RDF::Vocab::PROV.generatedAtTime, dt, g)
|
|
105
|
+
triplify(uniqueid, ftr.log, comments.join, g)
|
|
106
|
+
triplify(uniqueid, ftr.completion, completeness, g)
|
|
107
|
+
|
|
108
|
+
triplify(uniqueid, ftr.outputFromTest, softwareid, g)
|
|
109
|
+
triplify(softwareid, RDF.type, ftr.Test, g)
|
|
110
|
+
triplify(softwareid, RDF.type, schema.SoftwareApplication, g)
|
|
111
|
+
triplify(softwareid, RDF.type, dcat.DataService, g)
|
|
112
|
+
triplify(softwareid, dct.identifier, softwareid.to_s, g, datatype: xsd.string)
|
|
113
|
+
triplify(softwareid, dct.title, "#{name}", g)
|
|
114
|
+
triplify(softwareid, dct.description, description, g)
|
|
115
|
+
triplify(softwareid, dcat.endpointDescription, api, g) # returns yaml
|
|
116
|
+
triplify(softwareid, dcat.endpointURL, softwareid, g) # POST to execute
|
|
117
|
+
triplify(softwareid, "http://www.w3.org/ns/dcat#version", "#{version} OutputVersion:#{OPUTPUT_VERSION}", g) # dcat namespace in library has no version - dcat 2 not 3
|
|
118
|
+
triplify(softwareid, dct.license, "https://github.com/wilkinsonlab/FAIR-Core-Tests/blob/main/LICENSE", g)
|
|
119
|
+
triplify(softwareid, sio["SIO_000233"], metric, g) # implementation of
|
|
120
|
+
|
|
121
|
+
# deprecated after release 1.0
|
|
122
|
+
# triplify(uniqueid, prov.wasDerivedFrom, tid, g)
|
|
123
|
+
# triplify(executionid, prov.used, tid, g)
|
|
124
|
+
# triplify(tid, RDF.type, prov.Entity, g)
|
|
125
|
+
# triplify(tid, schema.identifier, testedGUID, g, xsd.string)
|
|
126
|
+
# triplify(tid, schema.url, testedGUID, g) if testedGUID =~ %r{^https?://}
|
|
127
|
+
testedguidnode = "urn:ostrails:testedidentifiernode:" + SecureRandom.uuid
|
|
128
|
+
|
|
129
|
+
begin
|
|
130
|
+
triplify(uniqueid, ftr.assessmentTarget, testedguidnode, g)
|
|
131
|
+
triplify(executionid, prov.used, testedguidnode, g)
|
|
132
|
+
triplify(testedguidnode, RDF.type, prov.Entity, g)
|
|
133
|
+
triplify(testedguidnode, dct.identifier, testedGUID, g, datatype: xsd.string)
|
|
134
|
+
rescue StandardError
|
|
135
|
+
triplify(uniqueid, ftr.assessmentTarget, "not a URI", g)
|
|
136
|
+
triplify(executionid, prov.used, "not a URI", g)
|
|
137
|
+
score = "fail"
|
|
138
|
+
end
|
|
139
|
+
|
|
140
|
+
unless score == "pass"
|
|
141
|
+
guidance.each do |advice, label|
|
|
142
|
+
adviceid = "urn:ostrails:testexecutionactivity:advice:" + SecureRandom.uuid
|
|
143
|
+
triplify(uniqueid, ftr.suggestion, adviceid, g)
|
|
144
|
+
triplify(adviceid, RDF.type, ftr.GuidanceContext, g)
|
|
145
|
+
triplify(adviceid, RDFS.label, label, g)
|
|
146
|
+
triplify(adviceid, dct.description, label, g)
|
|
147
|
+
triplify(adviceid, sio["SIO_000339"], RDF::URI.new(advice), g)
|
|
148
|
+
end
|
|
149
|
+
end
|
|
150
|
+
|
|
151
|
+
# g.dump(:jsonld)
|
|
152
|
+
w = RDF::Writer.for(:jsonld)
|
|
153
|
+
w.dump(g, nil, prefixes: {
|
|
154
|
+
xsd: RDF::Vocab::XSD,
|
|
155
|
+
prov: RDF::Vocab::PROV,
|
|
156
|
+
dct: RDF::Vocab::DC,
|
|
157
|
+
dcat: RDF::Vocab::DCAT,
|
|
158
|
+
ftr: ftr,
|
|
159
|
+
sio: sio,
|
|
160
|
+
schema: schema
|
|
161
|
+
})
|
|
162
|
+
end
|
|
163
|
+
|
|
164
|
+
class << self
|
|
165
|
+
attr_reader :comments
|
|
166
|
+
end
|
|
167
|
+
|
|
168
|
+
def self.clear_comments
|
|
169
|
+
@comments = []
|
|
170
|
+
end
|
|
171
|
+
|
|
172
|
+
def add_newline_to_comments
|
|
173
|
+
cleancomments = []
|
|
174
|
+
@comments.each do |c|
|
|
175
|
+
c += "\n" unless c =~ /\n$/
|
|
176
|
+
cleancomments << c
|
|
177
|
+
end
|
|
178
|
+
@comments = cleancomments
|
|
179
|
+
end
|
|
180
|
+
end
|
|
181
|
+
end
|
data/lib/registertest.rb
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
require "rest-client"
|
|
2
|
+
module FtrRuby
|
|
3
|
+
class Tests
|
|
4
|
+
def self.register_test(test_uri:)
|
|
5
|
+
warn "registering new test"
|
|
6
|
+
# curl -v -L -H "content-type: application/json"
|
|
7
|
+
# -d '{"clientUrl": "https://my.domain.org/path/to/DCAT/testdcat.ttl"}'
|
|
8
|
+
# https://tools.ostrails.eu/fdp-index-proxy/proxy
|
|
9
|
+
begin
|
|
10
|
+
response = RestClient::Request.execute({
|
|
11
|
+
method: :post,
|
|
12
|
+
url: "https://tools.ostrails.eu/fdp-index-proxy/proxy",
|
|
13
|
+
headers: { "Accept" => "application/json",
|
|
14
|
+
"Content-Type" => "application/json" },
|
|
15
|
+
payload: { "clientUrl": test_uri }.to_json
|
|
16
|
+
}).body
|
|
17
|
+
rescue StandardError => e
|
|
18
|
+
warn "response is #{response.inspect} error #{e.inspect}"
|
|
19
|
+
end
|
|
20
|
+
response
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
module FtrRuby
|
|
2
|
+
class TestInfra
|
|
3
|
+
# there is a need to map between a test and its registered Metric in FS. This will return the label for the test
|
|
4
|
+
# in principle, we cojuld return a more complex object, but all I need now is the label
|
|
5
|
+
def self.get_tests_metrics(tests:)
|
|
6
|
+
base_url = ENV["TEST_BASE_URL"] || "http://localhost:8282" # Default to local server
|
|
7
|
+
test_path = ENV["TEST_PATH"] || "community-tests" # Default to local server
|
|
8
|
+
labels = {}
|
|
9
|
+
landingpages = {}
|
|
10
|
+
tests.each do |testid|
|
|
11
|
+
warn "getting dcat for #{testid} #{base_url}/#{test_path}/#{testid}"
|
|
12
|
+
dcat = RestClient::Request.execute({
|
|
13
|
+
method: :get,
|
|
14
|
+
url: "#{base_url}/#{test_path}/#{testid}",
|
|
15
|
+
headers: { "Accept" => "application/json" }
|
|
16
|
+
}).body
|
|
17
|
+
parseddcat = JSON.parse(dcat)
|
|
18
|
+
# this next line should probably be done with SPARQL
|
|
19
|
+
# # TODO TODO TODO
|
|
20
|
+
jpath = JsonPath.new('[0]["http://semanticscience.org/resource/SIO_000233"][0]["@id"]') # is implementation of
|
|
21
|
+
metricurl = jpath.on(parseddcat).first
|
|
22
|
+
|
|
23
|
+
begin
|
|
24
|
+
g = RDF::Graph.load(metricurl, format: :turtle)
|
|
25
|
+
rescue StandardError => e
|
|
26
|
+
warn "DCAT Metric loading failed #{e.inspect}"
|
|
27
|
+
g = RDF::Graph.new
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
title = g.query([nil, RDF::Vocab::DC.title, nil])&.first&.object&.to_s
|
|
31
|
+
lp = g.query([nil, RDF::Vocab::DCAT.landingPage, nil])&.first&.object&.to_s
|
|
32
|
+
|
|
33
|
+
labels[testid] = if title != ""
|
|
34
|
+
title
|
|
35
|
+
else
|
|
36
|
+
"Metric label not available"
|
|
37
|
+
end
|
|
38
|
+
landingpages[testid] = if lp != ""
|
|
39
|
+
lp
|
|
40
|
+
else
|
|
41
|
+
""
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
[labels, landingpages]
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
end
|
data/sig/ftr_ruby.rbs
ADDED
metadata
ADDED
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: ftr_ruby
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: 0.1.0
|
|
5
|
+
platform: ruby
|
|
6
|
+
authors:
|
|
7
|
+
- markwilkinson
|
|
8
|
+
bindir: exe
|
|
9
|
+
cert_chain: []
|
|
10
|
+
date: 1980-01-02 00:00:00.000000000 Z
|
|
11
|
+
dependencies: []
|
|
12
|
+
description: Libraries supporting the FAIR Testing Resources Vocabulary - Tests and
|
|
13
|
+
test outputs.
|
|
14
|
+
email:
|
|
15
|
+
- mark.wilkinson@upm.es
|
|
16
|
+
executables: []
|
|
17
|
+
extensions: []
|
|
18
|
+
extra_rdoc_files: []
|
|
19
|
+
files:
|
|
20
|
+
- CHANGELOG.md
|
|
21
|
+
- LICENSE.txt
|
|
22
|
+
- README.md
|
|
23
|
+
- Rakefile
|
|
24
|
+
- lib/dcat_metadata.rb
|
|
25
|
+
- lib/fdp_index.rb
|
|
26
|
+
- lib/ftr_ruby.rb
|
|
27
|
+
- lib/ftr_ruby/version.rb
|
|
28
|
+
- lib/openapi.rb
|
|
29
|
+
- lib/output.rb
|
|
30
|
+
- lib/registertest.rb
|
|
31
|
+
- lib/test_infrastructure.rb
|
|
32
|
+
- sig/ftr_ruby.rbs
|
|
33
|
+
homepage: https://github.com/markwilkinson/FTR-Ruby/tree/master
|
|
34
|
+
licenses:
|
|
35
|
+
- MIT
|
|
36
|
+
metadata:
|
|
37
|
+
allowed_push_host: https://rubygems.org
|
|
38
|
+
homepage_uri: https://github.com/markwilkinson/FTR-Ruby/tree/master
|
|
39
|
+
source_code_uri: https://github.com/markwilkinson/FTR-Ruby/tree/master
|
|
40
|
+
documentation_uri: https://rubydoc.info/gems/ftr_ruby
|
|
41
|
+
bug_tracker_uri: https://github.com/markwilkinson/FTR-Ruby/issues
|
|
42
|
+
rdoc_options: []
|
|
43
|
+
require_paths:
|
|
44
|
+
- lib
|
|
45
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
46
|
+
requirements:
|
|
47
|
+
- - ">="
|
|
48
|
+
- !ruby/object:Gem::Version
|
|
49
|
+
version: 3.2.0
|
|
50
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
51
|
+
requirements:
|
|
52
|
+
- - ">="
|
|
53
|
+
- !ruby/object:Gem::Version
|
|
54
|
+
version: '0'
|
|
55
|
+
requirements: []
|
|
56
|
+
rubygems_version: 4.0.9
|
|
57
|
+
specification_version: 4
|
|
58
|
+
summary: Libraries supporting the FTR Vocabulary.
|
|
59
|
+
test_files: []
|