rkd 0.1.0 → 0.2.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: bf6f9827a74bf7a6288f966175b91ae0e847098783dd69cb13782aa4322e760b
4
- data.tar.gz: 4eabbe9159f88e1fb2aed36b718d06fbff82772cd4abeff55b92f8647398b3f5
3
+ metadata.gz: 90fda4ea793f4c4ae3fb171d56b8919728b3e9f8a71d6b9ca636ee22ab6d648f
4
+ data.tar.gz: 0633b70cf3ba381369e6b1ec4c56d9540b545c965c847edb764bfad40e00d507
5
5
  SHA512:
6
- metadata.gz: b09cfc13a74066f8e21317481ae10bf948509e2caa370382886d4827179580870e48eff5374c9337a6a3d9b7986b10a13f767070f0e7e813e2639f89618c9d4c
7
- data.tar.gz: 46e9b716fea7db61e0243a7dc786f4548581a2801ea50636e914d69883d8bb6c0bc85897105e74898864ffb772e693a2d5366d3db2cc39604efaf1eb3acd0b4f
6
+ metadata.gz: 405e9a82dd92ffb06e1ee1144634e3dd41d7ec0044d6f9a76ad6ba29f90fab57acb2da49e73415680e2b784b0e95756b2505c56bc85a1980d9621013dfa46a06
7
+ data.tar.gz: 467289a5a7b19934bdb5f0ca48c4a56075c8ffd9b32783e327015907b04ed92701f5184f8da94d96460cf80c3d0fb76dda765c4d59a822118b514e26d038b0e4
data/.gitignore CHANGED
@@ -6,3 +6,4 @@
6
6
  /pkg/
7
7
  /spec/reports/
8
8
  /tmp/
9
+ rkd*.gem
data/.gitlab-ci.yml ADDED
@@ -0,0 +1,37 @@
1
+ # This file is a template, and might need editing before it works on your project.
2
+ # You can copy and paste this template into a new `.gitlab-ci.yml` file.
3
+ # You should not add this template to an existing `.gitlab-ci.yml` file by using the `include:` keyword.
4
+ #
5
+ # To contribute improvements to CI/CD templates, please follow the Development guide at:
6
+ # https://docs.gitlab.com/ee/development/cicd/templates.html
7
+ # This specific template is located at:
8
+ # https://gitlab.com/gitlab-org/gitlab/-/blob/master/lib/gitlab/ci/templates/Ruby.gitlab-ci.yml
9
+
10
+ # Official language image. Look for the different tagged releases at:
11
+ # https://hub.docker.com/r/library/ruby/tags/
12
+ image: ruby:latest
13
+
14
+
15
+ # Cache gems in between builds
16
+ cache:
17
+ paths:
18
+ - vendor/ruby
19
+
20
+ # This is a basic example for a gem or script which doesn't use
21
+ # services such as redis or postgres
22
+ before_script:
23
+ - ruby -v # Print out ruby version for debugging
24
+ # Uncomment next line if your rails app needs a JS runtime:
25
+ # - apt-get update -q && apt-get install nodejs -yqq
26
+ - bundle config set --local deployment true # Install dependencies into ./vendor/ruby
27
+ - bundle install -j $(nproc)
28
+ - gem install simplecov
29
+
30
+ standardrb:
31
+ script:
32
+ - bundle exec standardrb
33
+
34
+
35
+ test:
36
+ script:
37
+ - rake
data/.ruby-version ADDED
@@ -0,0 +1 @@
1
+ 3.3.4
data/CHANGELOG.md ADDED
@@ -0,0 +1,15 @@
1
+ # 0.3.0 (in progress)
2
+
3
+ * RKD SPARQL API implemented
4
+ * Artist.find
5
+ * Artist#public_url (returning a human readable page on the RKD website)
6
+ * few Rails conveniences (that shouldn't hurt non-Rails solution)
7
+ * Artist#to_partial_path
8
+ * Institution
9
+ * Handling network errors
10
+
11
+ # 0.2.0
12
+
13
+ * RKD Search API implemented
14
+
15
+ # 0.1.0 Init
data/Gemfile CHANGED
@@ -1,6 +1,4 @@
1
1
  source "https://rubygems.org"
2
2
 
3
- git_source(:github) {|repo_name| "https://github.com/#{repo_name}" }
4
-
5
3
  # Specify your gem's dependencies in rkd.gemspec
6
4
  gemspec
data/Gemfile.lock ADDED
@@ -0,0 +1,74 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ rkd (0.2.0)
5
+
6
+ GEM
7
+ remote: https://rubygems.org/
8
+ specs:
9
+ ast (2.4.2)
10
+ docile (1.4.1)
11
+ json (2.7.2)
12
+ language_server-protocol (3.17.0.3)
13
+ lint_roller (1.1.0)
14
+ minitest (5.25.1)
15
+ parallel (1.26.3)
16
+ parser (3.3.5.0)
17
+ ast (~> 2.4.1)
18
+ racc
19
+ racc (1.8.1)
20
+ rainbow (3.1.1)
21
+ rake (10.5.0)
22
+ regexp_parser (2.9.2)
23
+ rexml (3.3.7)
24
+ rubocop (1.65.1)
25
+ json (~> 2.3)
26
+ language_server-protocol (>= 3.17.0)
27
+ parallel (~> 1.10)
28
+ parser (>= 3.3.0.2)
29
+ rainbow (>= 2.2.2, < 4.0)
30
+ regexp_parser (>= 2.4, < 3.0)
31
+ rexml (>= 3.2.5, < 4.0)
32
+ rubocop-ast (>= 1.31.1, < 2.0)
33
+ ruby-progressbar (~> 1.7)
34
+ unicode-display_width (>= 2.4.0, < 3.0)
35
+ rubocop-ast (1.32.3)
36
+ parser (>= 3.3.1.0)
37
+ rubocop-performance (1.21.1)
38
+ rubocop (>= 1.48.1, < 2.0)
39
+ rubocop-ast (>= 1.31.1, < 2.0)
40
+ ruby-progressbar (1.13.0)
41
+ simplecov (0.22.0)
42
+ docile (~> 1.1)
43
+ simplecov-html (~> 0.11)
44
+ simplecov_json_formatter (~> 0.1)
45
+ simplecov-html (0.13.1)
46
+ simplecov_json_formatter (0.1.4)
47
+ standard (1.40.0)
48
+ language_server-protocol (~> 3.17.0.2)
49
+ lint_roller (~> 1.0)
50
+ rubocop (~> 1.65.0)
51
+ standard-custom (~> 1.0.0)
52
+ standard-performance (~> 1.4)
53
+ standard-custom (1.0.2)
54
+ lint_roller (~> 1.0)
55
+ rubocop (~> 1.50)
56
+ standard-performance (1.4.0)
57
+ lint_roller (~> 1.1)
58
+ rubocop-performance (~> 1.21.0)
59
+ unicode-display_width (2.6.0)
60
+
61
+ PLATFORMS
62
+ arm64-darwin-23
63
+ ruby
64
+
65
+ DEPENDENCIES
66
+ bundler (> 1)
67
+ minitest (~> 5.0)
68
+ rake (~> 10.0)
69
+ rkd!
70
+ simplecov (~> 0.22)
71
+ standard (~> 1)
72
+
73
+ BUNDLED WITH
74
+ 2.5.18
data/README.md CHANGED
@@ -1,8 +1,6 @@
1
- # Rkd
1
+ # RKD (unofficial)
2
2
 
3
- Welcome to your new gem! In this directory, you'll find the files you need to be able to package up your Ruby library into a gem. Put your Ruby code in the file `lib/rkd`. To experiment with that code, run `bin/console` for an interactive prompt.
4
-
5
- TODO: Delete this and the text above, and describe your gem
3
+ This is the RKD gem. A gem that helps ruby-developers to interact with the vast collection of data from the RKD in a ruby-friendly manner.
6
4
 
7
5
  ## Installation
8
6
 
@@ -22,7 +20,35 @@ Or install it yourself as:
22
20
 
23
21
  ## Usage
24
22
 
25
- TODO: Write usage instructions here
23
+ ### Searching for an artist:
24
+
25
+ ```
26
+ artists = RKD::Artist.search("Rembrandt") # returns array of results
27
+ artist = artists.first # best hit :)
28
+ artist.enrich! # enrich the data from the sparql endpoints
29
+ ```
30
+
31
+ ### Just getting a single artist
32
+
33
+ ```
34
+ RKD::Artist.find(123)
35
+ ```
36
+
37
+ ### Retrieving data
38
+
39
+ Just browse the methods:
40
+
41
+ ```
42
+ artist.birth_place.geoname_id
43
+ artist.performances.work.first.place.lat, artist.performances.work.first.place.lon
44
+ artist.performances.work.first.place.geoname_id
45
+ artist.performances.work.first.date_span.begin # returns a RKD::ImpreciseDate or Date
46
+ artist.performances.study.map{|s| s.label}
47
+ artist.types # []
48
+ artist.name_variants
49
+ ```
50
+
51
+ It is all powered by a combination of a ElasticSearch and SPARQL API call, but now without the hassle of making these queries manually.
26
52
 
27
53
  ## Development
28
54
 
@@ -32,7 +58,7 @@ To install this gem onto your local machine, run `bundle exec rake install`. To
32
58
 
33
59
  ## Contributing
34
60
 
35
- Bug reports and pull requests are welcome on GitHub at https://github.com/[USERNAME]/rkd. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [Contributor Covenant](http://contributor-covenant.org) code of conduct.
61
+ Bug reports and pull requests are welcome on GitHub at https://gitlab.com/murb/rkd/. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [Contributor Covenant](http://contributor-covenant.org) code of conduct.
36
62
 
37
63
  ## License
38
64
 
@@ -40,4 +66,4 @@ The gem is available as open source under the terms of the [MIT License](https:/
40
66
 
41
67
  ## Code of Conduct
42
68
 
43
- Everyone interacting in the Rkd project’s codebases, issue trackers, chat rooms and mailing lists is expected to follow the [code of conduct](https://github.com/[USERNAME]/rkd/blob/master/CODE_OF_CONDUCT.md).
69
+ Everyone interacting in the Rkd project’s codebases, issue trackers, chat rooms and mailing lists is expected to follow the [code of conduct](https://gitlab.com/murb/rkd/-/blob/main/CODE_OF_CONDUCT.md).
data/Rakefile CHANGED
@@ -7,4 +7,4 @@ Rake::TestTask.new(:test) do |t|
7
7
  t.test_files = FileList["test/**/*_test.rb"]
8
8
  end
9
9
 
10
- task :default => :test
10
+ task default: :test
@@ -0,0 +1,38 @@
1
+ PREFIX rkd: <https://data.rkd.nl/def#>
2
+ PREFIX rdfs: <http://www.w3.org/2000/01/rdf-schema#>
3
+ PREFIX skos: <http://www.w3.org/2004/02/skos/core#>
4
+ PREFIX crm: <http://www.cidoc-crm.org/cidoc-crm/>
5
+ prefix la: <https://linked.art/ns/terms/>
6
+
7
+ SELECT ?property ?propertyLabel ?value ?symbolicContent ?prefLabel ?tookPlaceAt ?placePoint ?placeLabel ?placeGeoname ?timeSpan ?start ?end
8
+ WHERE {
9
+ <https://data.rkd.nl/artists/66219> ?property ?value.
10
+ FILTER (
11
+ !(?property in (crm:P67i_is_referred_to_by, crm:P1_is_identified_by, crm:P62i_is_depicted_by, la:member_of))
12
+ )
13
+ OPTIONAL {
14
+ ?property rdfs:label ?propertyLabel.
15
+ FILTER (lang(?propertyLabel) = "en")
16
+ }
17
+ OPTIONAL {
18
+ ?value crm:P190_has_symbolic_content ?symbolicContent
19
+ }
20
+ OPTIONAL {
21
+ ?value skos:prefLabel ?prefLabel
22
+ FILTER (lang(?prefLabel) = "en")
23
+ }
24
+ OPTIONAL {
25
+ ?value crm:P7_took_place_at ?tookPlaceAt.
26
+ ?value crm:P4_has_time-span ?timeSpan.
27
+ ?tookPlaceAt crm:P168_place_is_defined_by ?placePoint.
28
+ ?tookPlaceAt rdfs:label ?placeLabel.
29
+ ?tookPlaceAt skos:exactMatch ?placeGeoname.
30
+ ?timeSpan crm:P82a_begin_of_the_begin ?start.
31
+ ?timeSpan crm:P82b_end_of_the_end ?end.
32
+
33
+ FILTER
34
+ (?property in (crm:P100i_died_in, crm:P98i_was_born))
35
+ }
36
+
37
+
38
+ }
@@ -0,0 +1,63 @@
1
+ PREFIX rkd: <https://data.rkd.nl/def#>
2
+ PREFIX rdfs: <http://www.w3.org/2000/01/rdf-schema#>
3
+ PREFIX skos: <http://www.w3.org/2004/02/skos/core#>
4
+ PREFIX crm: <http://www.cidoc-crm.org/cidoc-crm/>
5
+ prefix la: <https://linked.art/ns/terms/>
6
+
7
+ SELECT ?property ?propertyLabel ?value ?symbolicContent ?prefLabel ?tookPlaceAt ?tookPlaceAtTimeSpan ?tookPlaceAtTimeSpanBegin ?tookPlaceAtTimeSpanEnd ?placePoint ?placeLabel ?placeGeoname ?timeSpan ?start ?end ?domain ?range ?studyLabel
8
+ WHERE {
9
+ <https://data.rkd.nl/artists/32439> ?property ?value.
10
+ FILTER (
11
+ !(?property in (crm:P67i_is_referred_to_by, crm:P1_is_identified_by, crm:P62i_is_depicted_by, la:member_of))
12
+ )
13
+ OPTIONAL {
14
+ ?property rdfs:label ?propertyLabel.
15
+ FILTER (lang(?propertyLabel) = "en")
16
+ }
17
+ OPTIONAL {
18
+ ?value crm:P190_has_symbolic_content ?symbolicContent
19
+ }
20
+ OPTIONAL {
21
+ ?value skos:prefLabel ?prefLabel
22
+ FILTER (lang(?prefLabel) = "en")
23
+ }
24
+ OPTIONAL {
25
+ ?value crm:P7_took_place_at ?tookPlaceAt.
26
+ ?value crm:P4_has_time-span ?timeSpan.
27
+ ?tookPlaceAt crm:P168_place_is_defined_by ?placePoint.
28
+ ?tookPlaceAt rdfs:label ?placeLabel.
29
+ ?tookPlaceAt skos:exactMatch ?placeGeoname.
30
+ ?timeSpan crm:P82a_begin_of_the_begin ?start.
31
+ ?timeSpan crm:P82b_end_of_the_end ?end.
32
+ # FILTER
33
+ # (
34
+ # #?property in (crm:P100i_died_in, crm:P98i_was_born, crm:P14i_performed)
35
+ # # &&
36
+ # # STRSTARTS(STR(?placeGeoname), "https://sws.geonames.org/")
37
+ # )
38
+ }
39
+ OPTIONAL {
40
+ ?value crm:P7_took_place_at ?tookPlaceAt.
41
+ ?tookPlaceAt rdfs:label ?placeLabel.
42
+ ?tookPlaceAt skos:exactMatch ?placeGeoname.
43
+
44
+
45
+ FILTER
46
+ (
47
+ ?property in (crm:P100i_died_in, crm:P98i_was_born, crm:P14i_performed)
48
+ &&
49
+ STRSTARTS(STR(?placeGeoname), "https://sws.geonames.org/")
50
+ )
51
+ }
52
+ OPTIONAL {
53
+ ?value crm:P01i_is_domain_of ?domain.
54
+ ?domain crm:P02_has_range ?range.
55
+ ?range skos:prefLabel ?studyLabel.
56
+ }
57
+ OPTIONAL {
58
+ ?value crm:P4_has_time-span ?tookPlaceAtTimeSpan.
59
+ ?tookPlaceAtTimeSpan crm:P82a_begin_of_the_begin ?tookPlaceAtTimeSpanBegin.
60
+ ?tookPlaceAtTimeSpan crm:P82b_end_of_the_end ?tookPlaceAtTimeSpanEnd.
61
+
62
+ }
63
+ }
@@ -0,0 +1,22 @@
1
+ PREFIX rkd: <https://data.rkd.nl/def#>
2
+ PREFIX rdfs: <http://www.w3.org/2000/01/rdf-schema#>
3
+ PREFIX skos: <http://www.w3.org/2004/02/skos/core#>
4
+ PREFIX crm: <http://www.cidoc-crm.org/cidoc-crm/>
5
+ prefix la: <https://linked.art/ns/terms/>
6
+ prefix schema: <https://schema.org/>
7
+ prefix thesaurus: <https://data.rkd.nl/thesaurus/>
8
+
9
+ SELECT DISTINCT ?property ?value ?workLocation ?placeGeoname ?somethingElse
10
+ WHERE {
11
+ <https://data.rkd.nl/artists/66219> ?property ?value
12
+ OPTIONAL {
13
+ ?value schema:workLocation ?workLocation .
14
+ FILTER(STRSTARTS(STR(?workLocation), "https://data.rkd.nl/thesaurus"))
15
+ }
16
+ OPTIONAL {
17
+ SERVICE <https://api.rkd.triply.cc/datasets/rkd/RKD-Knowledge-Graph/sparql> {
18
+ ?workLocation a skos:Concept .
19
+ ?workLocation skos:exactMatch ?placeGeoname
20
+ }
21
+ }
22
+ }
@@ -0,0 +1,53 @@
1
+ PREFIX rkd: <https://data.rkd.nl/def#>
2
+ PREFIX rdfs: <http://www.w3.org/2000/01/rdf-schema#>
3
+ PREFIX skos: <http://www.w3.org/2004/02/skos/core#>
4
+ PREFIX crm: <http://www.cidoc-crm.org/cidoc-crm/>
5
+ prefix la: <https://linked.art/ns/terms/>
6
+
7
+ SELECT ?property ?propertyLabel ?value ?symbolicContent ?prefLabel ?tookPlaceAt ?placePoint ?placeLabel ?placeGeoname ?timeSpan ?start ?end
8
+ WHERE {
9
+ <https://data.rkd.nl/artists/473628> ?property ?value.
10
+ FILTER (
11
+ !(?property in (crm:P67i_is_referred_to_by, crm:P1_is_identified_by, crm:P62i_is_depicted_by, la:member_of))
12
+ )
13
+ OPTIONAL {
14
+ ?property rdfs:label ?propertyLabel.
15
+ FILTER (lang(?propertyLabel) = "en")
16
+ }
17
+ OPTIONAL {
18
+ ?value crm:P190_has_symbolic_content ?symbolicContent
19
+ }
20
+ OPTIONAL {
21
+ ?value skos:prefLabel ?prefLabel
22
+ FILTER (lang(?prefLabel) = "en")
23
+ }
24
+ OPTIONAL {
25
+ ?value crm:P7_took_place_at ?tookPlaceAt.
26
+ ?value crm:P4_has_time-span ?timeSpan.
27
+ ?tookPlaceAt crm:P168_place_is_defined_by ?placePoint.
28
+ ?tookPlaceAt rdfs:label ?placeLabel.
29
+ ?tookPlaceAt skos:exactMatch ?placeGeoname.
30
+ ?timeSpan crm:P82a_begin_of_the_begin ?start.
31
+ ?timeSpan crm:P82b_end_of_the_end ?end.
32
+
33
+ FILTER
34
+ (
35
+ ?property in (crm:P100i_died_in, crm:P98i_was_born, crm:P14i_performed)
36
+ # &&
37
+ # STRSTARTS(STR(?placeGeoname), "https://sws.geonames.org/")
38
+ )
39
+ }
40
+ OPTIONAL {
41
+ ?value crm:P7_took_place_at ?tookPlaceAt.
42
+ ?tookPlaceAt rdfs:label ?placeLabel.
43
+ ?tookPlaceAt skos:exactMatch ?placeGeoname.
44
+
45
+
46
+ FILTER
47
+ (
48
+ ?property in (crm:P100i_died_in, crm:P98i_was_born, crm:P14i_performed)
49
+ &&
50
+ STRSTARTS(STR(?placeGeoname), "https://sws.geonames.org/")
51
+ )
52
+ }
53
+ }
@@ -0,0 +1,252 @@
1
+ module RKD
2
+ class Artist
3
+ class NotFoundError < StandardError
4
+ end
5
+
6
+ include ::RKD::Helpers::DateParsing
7
+
8
+ # @!attribute [r] id
9
+ # @return [String] the full identifier of the artist; typically the uri
10
+ # @!attribute [r] name
11
+ # @return [String] the name of the artist
12
+ # @!attribute [r] identifier
13
+ # @return [Integer] RKDid (basically the last part of the URI; which used to be the RKD Artist ID)
14
+ # @!attribute [r] gender
15
+ # @return [String] currently just returns binary forms; 'male' | 'female'
16
+ # @!attribute [r] birth_date
17
+ # @return [Date,RKD::ImpreciseDate] will return date when known, otherwise imprecise date
18
+ # @!attribute [r] death_date
19
+ # @return [Date,RKD::ImpreciseDate] will return date when known, otherwise imprecise date
20
+ # @!attribute [r] types
21
+ # @return [Array<RKD::Type>] the types associated with the artist
22
+ # @!attribute [r] updated_at
23
+ # @return [DateTime] the timestamp when the artist information was last updated
24
+ # @!attribute [r] birth_place
25
+ # @return [RKD::Place] the place where the artist was born
26
+ # @!attribute [r] death_place
27
+ # @return [RKD::Place] the place where the artist died
28
+ # @!attribute [r] name_variants
29
+ # @return [Array<String>] the different name variants of the artist
30
+ attr_reader :id, :name, :identifier, :gender, :birth_date, :death_date, :types, :updated_at, :birth_place, :death_place, :name_variants
31
+
32
+ class << self
33
+ # Searches for artists by name.
34
+ # @param name [String] the name of the artist to search for.
35
+ # @return [Array<RKD::Artist>] an array of artist objects matching the search criteria.
36
+ def search(name)
37
+ name = name.to_s.strip
38
+
39
+ return [] if name.empty?
40
+
41
+ Query::ElasticSearch.search(name, dataset: "artists").map do |artist|
42
+ new_from_search_result(artist)
43
+ end
44
+ end
45
+
46
+ # Finds an artist by identifier.
47
+ # @param identifier [Integer, String] the identifier of the artist to find. It will accept both artist's RKD URI as well as RKD Artist's numeric identifiers.
48
+ # @raise [NotFoundError] if no artist is found with the given identifier.
49
+ # @return [RKD::Artist] the artist object corresponding to the given identifier.
50
+ def find(identifier)
51
+ identifier = identifier.sub("https://data.rkd.nl/artists/", "") if identifier.is_a? String
52
+ raise NotFoundError.new("No artist with id #{identifier}") if identifier.to_i == 0
53
+ identifier = identifier.to_i
54
+
55
+ found_data = Query::ElasticSearch.find(identifier, dataset: "artists")
56
+ raise NotFoundError.new("No artist found with id #{identifier}") if found_data.nil?
57
+ artist = new_from_search_result(found_data)
58
+ artist.enrich!
59
+ artist
60
+ end
61
+
62
+ private
63
+
64
+ # Creates a new artist object from the search result.
65
+ # @param result [Hash] the search result data.
66
+ # @return [RKD::Artist] a new artist object populated with the search result data.
67
+ def new_from_search_result(result)
68
+ RKD::Artist.new(
69
+ id: result["@id"],
70
+ identifier: result["https://data rkd nl/search#identifier"].first.to_i,
71
+ name: result["https://data rkd nl/search#kunstenaarsnaam"]&.first || result["https://data rkd nl/search#creatorName"]&.first,
72
+ gender: result["https://data rkd nl/search#geslacht"]&.join(", "),
73
+ birth_date: [result["https://data rkd nl/search#geboortedatum_begin"]&.first, result["https://data rkd nl/search#geboortedatum_eind"]&.first].compact.max_by(&:length),
74
+ birth_place_desc: result["https://data rkd nl/search#geboorteplaats"]&.first,
75
+ death_date: [result["https://data rkd nl/search#sterfdatum_begin"]&.first, result["https://data rkd nl/search#sterfdatum_eind"]&.first].compact.max_by(&:length),
76
+ death_place_desc: result["https://data rkd nl/search#sterfplaats"]&.first,
77
+ name_variants: result["https://data rkd nl/search#spellingsvariant"] || []
78
+ )
79
+ end
80
+ end
81
+
82
+ def initialize(id: nil, name: nil, identifier: nil, gender: nil, birth_date: nil, death_date: nil, birth_place_desc: nil, death_place_desc: nil, name_variants: [])
83
+ @id = id
84
+ @name = name
85
+ @identifier = identifier
86
+ @gender = gender
87
+ self.birth_date = birth_date
88
+ self.death_date = death_date
89
+ @birth_place_desc = birth_place_desc
90
+ @death_place_desc = death_place_desc
91
+ @name_variants = name_variants
92
+ @performances = []
93
+ end
94
+
95
+ # @!method death_place_desc
96
+ # Returns the description of the place where the artist died.
97
+ # @return [String] the description of the death place.
98
+ def death_place_desc
99
+ @death_place_desc || death_place&.label
100
+ end
101
+
102
+ # @!method birth_place_desc
103
+ # Returns the description of the place where the artist was born.
104
+ # @return [String] the description of the birth place.
105
+ def birth_place_desc
106
+ @birth_place_desc || birth_place&.label
107
+ end
108
+
109
+ # @!method update(**key_values)
110
+ # Updates the artist's attributes with the given key-value pairs.
111
+ # @param key_values [Hash] the attributes to update.
112
+ def update(**key_values)
113
+ key_values.each do |key, value|
114
+ send(:"#{key}=", value)
115
+ end
116
+ end
117
+
118
+ # @!method performances
119
+ # Returns a collection of the artist's performances.
120
+ # @return [Performance::Collection] the collection of performances.
121
+ def performances
122
+ Performance::Collection.new(@performances)
123
+ end
124
+
125
+ # @!method public_url
126
+ # Returns the public URL of the artist's profile.
127
+ # @return [String] the public URL.
128
+ def public_url
129
+ "https://research.rkd.nl/nl/detail/https%3A%2F%2Fdata.rkd.nl%2Fartists%2F#{identifier}"
130
+ end
131
+
132
+ # @!method death_date=(date)
133
+ # Sets the death date of the artist.
134
+ # @param date [Date, String] the death date to set.
135
+ def death_date= date
136
+ @death_date = parse_date(date)
137
+ end
138
+
139
+ # @!method birth_date=(date)
140
+ # Sets the birth date of the artist.
141
+ # @param date [Date, String] the birth date to set.
142
+ def birth_date= date
143
+ @birth_date = parse_date(date)
144
+ end
145
+
146
+ # @!method to_partial_path
147
+ # Returns the partial path for rendering the artist in Rails views.
148
+ # @return [String] the partial path.
149
+ def to_partial_path
150
+ "rkd/artists/artist"
151
+ end
152
+
153
+ # @!method to_param
154
+ # Returns the identifier of the artist for use in URLs (mostly for rails)
155
+ # @return [String] the identifier.
156
+ def to_param
157
+ identifier
158
+ end
159
+
160
+ # @!method enrich!
161
+ # Enriches the artist's data by fetching additional information from the SPARQL endpoint
162
+ # @raise [RKD::Artist::NotFoundError] if no data is found for the artist.
163
+ def enrich!
164
+ json = Query::Sparql.find(identifier)
165
+
166
+ death_data = json.select { |a| a["property"] == "http://www.cidoc-crm.org/cidoc-crm/P100i_died_in" }
167
+ birth_data = json.select { |a| a["property"] == "http://www.cidoc-crm.org/cidoc-crm/P98i_was_born" } # different placeRefs
168
+
169
+ death_places_data = extract_places_data(death_data).first
170
+ birth_places_data = extract_places_data(birth_data).first
171
+
172
+ @death_place = Place.new(**death_places_data) if death_places_data
173
+ self.death_date = [death_data.first["begin"], death_data.first["end"]].compact.max_by(&:length) if death_data.first
174
+
175
+ @birth_place = Place.new(**birth_places_data) if birth_places_data
176
+ self.birth_date = [birth_data.first["begin"], birth_data.first["end"]].compact.max_by(&:length) if birth_data.first
177
+
178
+ @name = json.select { |row| row["property"] == "http://www.w3.org/2000/01/rdf-schema#label" }.map { |row| row["value"] }.first
179
+
180
+ performances_data = json.select { |row| row["property"] == "http://www.cidoc-crm.org/cidoc-crm/P14i_performed" }.group_by { |row| row["value"] }
181
+ @performances = performances_data.map do |perf_id, values|
182
+ parse_performance_data(perf_id, values)
183
+ end.compact
184
+
185
+ type_data = json.select { |a| a["property"] == "http://www.cidoc-crm.org/cidoc-crm/P2_has_type" }.map { |row| {label: row["prefLabel"], ref: row["value"]} }.select { |a| a[:label] }
186
+ @types = type_data.map { |type_datum| RKD::Type.new(**type_datum) }
187
+
188
+ unlabeled_types = json.select { |a| a["property"] == "http://www.cidoc-crm.org/cidoc-crm/P2_has_type" }.map { |row| {label: row["prefLabel"], ref: row["value"]} }.select { |a| !a[:label] }.map { |row| row[:ref] }
189
+
190
+ if unlabeled_types.delete("http://vocab.getty.edu/aat/300189559")
191
+ @gender = "male"
192
+ elsif unlabeled_types.delete("http://vocab.getty.edu/aat/300189557")
193
+ @gender = "female"
194
+ end
195
+
196
+ type_data = json.select { |a| a["property"] == "http://www.w3.org/1999/02/22-rdf-syntax-ns#type" }.map { |row| {label: row["value"].sub(/http:\/\/www\.cidoc-crm\.org\/cidoc-crm\/[A-E]\d+_/, "").sub("_", " "), ref: row["value"]} }.select { |a| a[:label] }
197
+ @types += type_data.map { |type_datum| RKD::Type.new(**type_datum) }
198
+
199
+ p unlabeled_types if unlabeled_types.any?
200
+
201
+ @updated_at = begin
202
+ DateTime.parse json.find { |a| a["property"] == "http://www.w3.org/ns/prov#generatedAtTime" }["value"]
203
+ rescue Date::Error
204
+ rescue NoMethodError
205
+ if json == []
206
+ raise RKD::Artist::NotFoundError, "Nothing found for id #{identifier}"
207
+ end
208
+ end
209
+ end
210
+
211
+ private
212
+
213
+ # details are not expected to be updated
214
+ attr_writer :id, :name, :identifier, :gender, :birth_place_desc, :death_place_desc
215
+
216
+ def extract_places_data(responses)
217
+ grouped_responses = responses.group_by { |a| [a["placePoint"], a["placeLabel"]] }
218
+ grouped_responses
219
+ .map { |k, v| [k, v.map { |a| a["placeRef"] }] }
220
+ .map { |r| {point: r[0][0], label: r[0][1], refs: r[1]} }
221
+ end
222
+
223
+ def parse_performance_data(perf_id, values)
224
+ if perf_id.start_with?("https://data.rkd.nl/artists/#{identifier}/study") || perf_id.start_with?("https://data.rkd.nl/artists/#{identifier}/educate")
225
+ label = values.first["studyLabel"] || values.first["referenceContent"]
226
+ return if label.nil?
227
+ institution = Institution.initialize_from_label(label)
228
+ institution&.id = values.map { |a| a["range"] }.compact.first
229
+ place = institution&.place || Place.new(label: values.first["studyLabel"])
230
+ comments = values.map { |a| a["referenceContent"] }.uniq
231
+ date_span = DateSpan.parse(comments.first)
232
+ RKD::Performance::Study.new(place:, date_span:, institution:, comments: comments.join("\n\n"))
233
+ elsif perf_id.start_with?("https://data.rkd.nl/artists/#{identifier}/award")
234
+ # ignoring for now
235
+ elsif perf_id.start_with?("https://data.rkd.nl/artists/#{identifier}/work")
236
+ place_data = extract_places_data(values).first
237
+ place = Place.new(**place_data)
238
+ ts_begin = values.first["tookPlaceAtTimeSpanBegin"]
239
+ ts_end = values.first["tookPlaceAtTimeSpanEnd"]
240
+ date_span = RKD::DateSpan.new(ts_begin, ts_end)
241
+ RKD::Performance::Work.new(place:, date_span:)
242
+ else
243
+ place_data = extract_places_data(values).first
244
+ place = Place.new(**place_data)
245
+ ts_begin = values.first["tookPlaceAtTimeSpanBegin"]
246
+ ts_end = values.first["tookPlaceAtTimeSpanEnd"]
247
+ date_span = RKD::DateSpan.new(ts_begin, ts_end)
248
+ RKD::Performance.new(place:, date_span:) unless place.nilly? || date_span.nilly?
249
+ end
250
+ end
251
+ end
252
+ end