Repository: Informasjonsforvaltning/fdk-rdf-parser

Issue:
ny partitiv relasjon fra skos-ap-no v2.0.3
https://data.norge.no/specification/skos-ap-no-begrep#Begrep-har-partitiv-begrepsrelasjon
https://data.norge.no/specification/skos-ap-no-begrep#Partitiv-begrepsrelasjon

- predikat for angivelse av partitiv relasjon endres fra `skosno:partitivRelasjon` til `skosno:hasPartitiveConceptRelation`
- type endres fra `skosno:PartitivRelasjon` til `skosno:PartitiveConceptRelation` (har ikke effekt i parse-kode)
- predikat for delbegrep endres fra `dct:hasPart` til `skosno:hasPartitiveConcept`
- predikat for helhetsbegrep endres fra `dct:isPartOf` til `skosno:hasComprehensiveConcept`
- predikat for inndelingskritrium endres fra `skosno:inndelingskriterium` til `dct:description`

````
<tre> a skos:Concept ;
   skosno:hasPartitiveConceptRelation [ a skosno:PartitiveConceptRelation ;
      skosno:hasPartitiveConcept <stamme> ;
      dct:description "anatomi"@nb ;
   ] .

<stamme> a skos:Concept ;
   skosno:hasPartitiveConceptRelation [ a skosno:PartitiveConceptRelation ;
      skosno:hasComprehensiveConcept <tre> ;
   ] .
````
er ekvivalent med dette fra gammel skos-ap-no:
````
<tre> a skos:Concept ;
   skosno:partitivRelasjon [ a skosno:PartitivRelasjon ;
      skosno:hasPart <stamme> ;
      skosno:inndelingskriterium "anatomi"@nb ;
   ] .

<stamme> a skos:Concept ;
   skosno:partitivRelasjon [ a skosno:PartitivRelasjon ;
      skosno:isPartOf <tre> ;
   ] .
````

Discussion:
codecov[bot]: ## [Codecov](https://app.codecov.io/gh/Informasjonsforvaltning/fdk-rdf-parser/pull/309?dropdown=coverage&src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Informasjonsforvaltning) Report
All modified and coverable lines are covered by tests :white_check_mark:
> Project coverage is 100.00%. Comparing base [(`e2ff215`)](https://app.codecov.io/gh/Informasjonsforvaltning/fdk-rdf-parser/commit/e2ff215db4d333aa5a7b2f6068aebc94261c9a16?dropdown=coverage&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Informasjonsforvaltning) to head [(`946d2fa`)](https://app.codecov.io/gh/Informasjonsforvaltning/fdk-rdf-parser/commit/946d2fa318d10312767b0f6634b3df1cfee2c867?dropdown=coverage&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Informasjonsforvaltning).


<details><summary>Additional details and impacted files</summary>


```diff
@@            Coverage Diff            @@
##              main      #309   +/-   ##
=========================================
  Coverage   100.00%   100.00%           
=========================================
  Files           89        89           
  Lines         2372      2377    +5     
  Branches       265       267    +2     
=========================================
+ Hits          2372      2377    +5     
```



</details>

[:umbrella: View full report in Codecov by Sentry](https://app.codecov.io/gh/Informasjonsforvaltning/fdk-rdf-parser/pull/309?dropdown=coverage&src=pr&el=continue&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Informasjonsforvaltning).   
:loudspeaker: Have feedback on the report? [Share it here](https://about.codecov.io/codecov-pr-comment-feedback/?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Informasjonsforvaltning).

github-actions[bot]: ```
Warning: poetry-plugin-export will not be installed by default in a future version of Poetry.
In order to avoid a breaking change and make your automation forward-compatible, please install poetry-plugin-export explicitly. See https://python-poetry.org/docs/plugins/#using-plugins for details on how to install a plugin.
To disable this warning run 'poetry config warnings.export false'.
The `--dev` option is deprecated, use the `--with dev` notation instead.
+==============================================================================+

                                   /$$$$$$            /$$
                                  /$$__  $$          | $$
               /$$$$$$$  /$$$$$$ | $$  \__//$$$$$$  /$$$$$$   /$$   /$$
              /$$_____/ |____  $$| $$$$   /$$__  $$|_  $$_/  | $$  | $$
             |  $$$$$$   /$$$$$$$| $$_/  | $$$$$$$$  | $$    | $$  | $$
              \____  $$ /$$__  $$| $$    | $$_____/  | $$ /$$| $$  | $$
              /$$$$$$$/|  $$$$$$$| $$    |  $$$$$$$  |  $$$$/|  $$$$$$$
             |_______/  \_______/|__/     \_______/   \___/   \____  $$
                                                              /$$  | $$
                                                             |  $$$$$$/
      by safetycli.com                                        \______/

+==============================================================================+

 REPORT 

  Safety is using PyUp's free open-source vulnerability database. This
data is 30 days old and limited. 
  For real-time enhanced vulnerability data, fix recommendations, severity
reporting, cybersecurity support, team and project policy management and more
sign up at https://pyup.io or email sales@pyup.io

  Safety v3.1.0 is scanning for Vulnerabilities...
  Scanning dependencies in your files:

  -> /tmp/tmpxiybd30l

  Using open-source vulnerability database
  Found and scanned 72 packages
  Timestamp 2024-05-28 10:23:48
  0 vulnerabilities reported
  0 vulnerabilities ignored

+==============================================================================+

 No known security vulnerabilities found.

+==============================================================================+

  Safety is using PyUp's free open-source vulnerability database. This
data is 30 days old and limited. 
  For real-time enhanced vulnerability data, fix recommendations, severity
reporting, cybersecurity support, team and project policy management and more
sign up at https://pyup.io or email sales@pyup.io

+==============================================================================+
```
<!-- Sticky Pull Request CommentSafety result -->

Patch:
diff --git a/src/fdk_rdf_parser/parse_functions/concept.py b/src/fdk_rdf_parser/parse_functions/concept.py
index cb07846..8bdcdc5 100644
--- a/src/fdk_rdf_parser/parse_functions/concept.py
+++ b/src/fdk_rdf_parser/parse_functions/concept.py
@@ -18,10 +18,7 @@ from rdflib.namespace import (
     SKOS,
 )
 
-from fdk_rdf_parser.classes import (
-    Concept,
-    Temporal,
-)
+from fdk_rdf_parser.classes import Concept
 from fdk_rdf_parser.classes.concept import (
     AssociativeRelation,
     Collection,
@@ -50,7 +47,7 @@ from fdk_rdf_parser.rdf_utils import (
 from .contactpoint import extract_contact_points
 from .harvest_meta_data import extract_meta_data
 from .publisher import extract_publisher
-from .temporal import extract_temporal
+from .temporal import extract_temporal_skos
 
 
 def parse_text_and_uri(graph: Graph, subject: URIRef) -> TextAndURI:
@@ -290,6 +287,22 @@ def extract_associative_relations(
 
 def parse_partitive_relation(
     graph: Graph, partitive_relation_ref: URIRef
+) -> PartitiveRelation:
+    return PartitiveRelation(
+        description=value_translations(
+            graph, partitive_relation_ref, DCTERMS.description
+        ),
+        hasPart=object_value(
+            graph, partitive_relation_ref, skosno_uri("hasPartitiveConcept")
+        ),
+        isPartOf=object_value(
+            graph, partitive_relation_ref, skosno_uri("hasComprehensiveConcept")
+        ),
+    )
+
+
+def parse_partitive_relation_deprecated(
+    graph: Graph, partitive_relation_ref: URIRef
 ) -> PartitiveRelation:
     return PartitiveRelation(
         description=value_translations(
@@ -304,17 +317,44 @@ def extract_partitive_relations(
     graph: Graph, concept_uri: URIRef
 ) -> Optional[List[PartitiveRelation]]:
     partitive_relations = []
-    for partitive_relation_ref in graph.objects(
-        concept_uri, skosno_uri("partitivRelasjon")
+    if has_value_on_predicate(
+        graph, concept_uri, skosno_uri("hasPartitiveConceptRelation")
     ):
-        partitive_relations.append(
-            parse_partitive_relation(graph, partitive_relation_ref)
-        )
+        for partitive_relation_ref in graph.objects(
+            concept_uri, skosno_uri("hasPartitiveConceptRelation")
+        ):
+            partitive_relations.append(
+                parse_partitive_relation(graph, partitive_relation_ref)
+            )
+    else:
+        for partitive_relation_ref in graph.objects(
+            concept_uri, skosno_uri("partitivRelasjon")
+        ):
+            partitive_relations.append(
+                parse_partitive_relation_deprecated(graph, partitive_relation_ref)
+            )
+
     return partitive_relations if len(partitive_relations) > 0 else None
 
 
 def parse_generic_relation(
     graph: Graph, generic_relation_ref: URIRef
+) -> GenericRelation:
+    return GenericRelation(
+        divisioncriterion=value_translations(
+            graph, generic_relation_ref, DCTERMS.description
+        ),
+        generalizes=object_value(
+            graph, generic_relation_ref, skosno_uri("hasSpecificConcept")
+        ),
+        specializes=object_value(
+            graph, generic_relation_ref, skosno_uri("hasGenericConcept")
+        ),
+    )
+
+
+def parse_generic_relation_deprecated(
+    graph: Graph, generic_relation_ref: URIRef
 ) -> GenericRelation:
     return GenericRelation(
         divisioncriterion=value_translations(
@@ -333,13 +373,35 @@ def extract_generic_relations(
     graph: Graph, concept_uri: URIRef
 ) -> Optional[List[GenericRelation]]:
     generic_relations = []
-    for generic_relation_ref in graph.objects(
-        concept_uri, skosno_uri("generiskRelasjon")
+
+    if has_value_on_predicate(
+        graph, concept_uri, skosno_uri("hasGenericConceptRelation")
     ):
-        generic_relations.append(parse_generic_relation(graph, generic_relation_ref))
+        for generic_relation_ref in graph.objects(
+            concept_uri, skosno_uri("hasGenericConceptRelation")
+        ):
+            generic_relations.append(
+                parse_generic_relation(graph, generic_relation_ref)
+            )
+    else:
+        for generic_relation_ref in graph.objects(
+            concept_uri, skosno_uri("generiskRelasjon")
+        ):
+            generic_relations.append(
+                parse_generic_relation_deprecated(graph, generic_relation_ref)
+            )
     return generic_relations if len(generic_relations) > 0 else None
 
 
+def extract_collection_label(
+    graph: Graph, collection_uri: URIRef
+) -> Optional[Dict[str, str]]:
+    if has_literal_value_on_predicate(graph, collection_uri, DCTERMS.title):
+        return value_translations(graph, collection_uri, DCTERMS.title)
+    else:
+        return value_translations(graph, collection_uri, RDFS.label)
+
+
 def parse_collection(graph: Graph, concept_record_uri: URIRef) -> Optional[Collection]:
     collection_record_uri = graph.value(concept_record_uri, DCTERMS.isPartOf)
 
@@ -352,7 +414,7 @@ def parse_collection(graph: Graph, concept_record_uri: URIRef) -> Optional[Colle
             return Collection(
                 id=object_value(graph, collection_record_uri, DCTERMS.identifier),
                 publisher=extract_publisher(graph, collection_uri),
-                label=value_translations(graph, collection_uri, RDFS.label),
+                label=extract_collection_label(graph, collection_uri),
                 uri=collection_uri.toPython(),
                 description=value_translations(
                     graph, collection_uri, DCTERMS.description
@@ -384,8 +446,8 @@ def extract_labels(
 def _parse_concept(
     graph: Graph, fdk_record_uri: URIRef, concept_uri: URIRef
 ) -> Concept:
-    concept_temporal_list = extract_temporal(graph, concept_uri)
-    concept_temporal = concept_temporal_list[0] if concept_temporal_list else Temporal()
+
+    concept_temporal = extract_temporal_skos(graph, concept_uri)
     contact_points = extract_contact_points(graph, concept_uri)
 
     pref_label_list = extract_labels(
diff --git a/src/fdk_rdf_parser/parse_functions/temporal.py b/src/fdk_rdf_parser/parse_functions/temporal.py
index 42e9280..c5c3dca 100644
--- a/src/fdk_rdf_parser/parse_functions/temporal.py
+++ b/src/fdk_rdf_parser/parse_functions/temporal.py
@@ -14,6 +14,7 @@ from fdk_rdf_parser.classes import Temporal
 from fdk_rdf_parser.rdf_utils import (
     date_value,
     dcat_uri,
+    euvoc_uri,
     owl_time_uri,
     resource_list,
     schema_uri,
@@ -59,6 +60,24 @@ def extract_temporal(graph: Graph, subject: URIRef) -> Optional[List[Temporal]]:
     return values if len(values) > 0 else None
 
 
+def extract_temporal_skos(graph: Graph, subject: URIRef) -> Temporal:
+    temporal = Temporal()
+
+    start_value = date_value(graph, subject, euvoc_uri("startDate"))
+    end_value = date_value(graph, subject, euvoc_uri("endDate"))
+
+    deprecated_resource = graph.value(subject, DCTERMS.temporal)
+    if start_value or end_value:
+        temporal = Temporal(startDate=start_value, endDate=end_value)
+    elif deprecated_resource is not None:
+        start_value = date_value(graph, deprecated_resource, schema_uri("startDate"))
+        end_value = date_value(graph, deprecated_resource, schema_uri("endDate"))
+
+        temporal = Temporal(startDate=start_value, endDate=end_value)
+
+    return temporal
+
+
 def extract_owl_time_instant(graph: Graph, subject: Any) -> Optional[str]:
     owl_date = date_value(graph, subject, owl_time_uri("inXSDDateTime"))
     owl_date = (


Test patch:
diff --git a/tests/test_concept.py b/tests/test_concept.py
index 0d0466a..0069d46 100644
--- a/tests/test_concept.py
+++ b/tests/test_concept.py
@@ -47,10 +47,11 @@ def test_parse_concepts(mock_reference_data_client: Mock) -> None:
 @prefix xkos:  <http://rdf-vocabulary.ddialliance.org/xkos#> .
 @prefix euvoc:  <http://publications.europa.eu/ontology/euvoc#> .
 @prefix uneskos: <http://purl.org/umu/uneskos#> .
+@prefix schema:  <http://schema.org/> .
 
 <https://registrering-begrep-api.staging.fellesdatakatalog.digdir.no/910258028>
         a               skos:Collection ;
-        rdfs:label      "Concept collection belonging to 910258028" ;
+        dct:title      "Concept collection belonging to 910258028" ;
         dct:identifier  "https://registrering-begrep-api.staging.fellesdatakatalog.digdir.no/910258028" ;
         dct:publisher   <https://data.brreg.no/enhetsregisteret/api/enheter/910258028> ;
         skos:member     <https://registrering-begrep-api.staging.fellesdatakatalog.digdir.no/910258028/c4ae179e-6a3a-42bc-85a2-1e32d75fc013> , <https://registrering-begrep-api.staging.fellesdatakatalog.digdir.no/910258028/3609b02d-72c5-47e0-a6b8-df0a503cf190> , <https://registrering-begrep-api.staging.fellesdatakatalog.digdir.no/910258028/1843b048-f9af-4665-8e53-3c001d0166c0> , <https://registrering-begrep-api.staging.fellesdatakatalog.digdir.no/910258028/9f25b5ad-8aa7-4233-853b-7434e20aeaef> , <https://registrering-begrep-api.staging.fellesdatakatalog.digdir.no/910258028/523ff894-638b-44a2-a4fd-3e96a5a8a5a3> .
@@ -65,6 +66,10 @@ def test_parse_concepts(mock_reference_data_client: Mock) -> None:
 
 <https://registrering-begrep-api.staging.fellesdatakatalog.digdir.no/910258028/1843b048-f9af-4665-8e53-3c001d0166c0>
         a                   skos:Concept ;
+        dct:temporal        [ a           dct:PeriodOfTime ;
+                                schema:startDate "2020-05-29"^^xsd:date;
+                                schema:endDate "2021-05-29"^^xsd:date;
+                                ] ;
         dct:created        "2023-02-17"^^xsd:date ;
         dct:identifier      "1843b048-f9af-4665-8e53-3c001d0166c0" ;
         dct:modified        "2019-12-16"^^xsd:date ;
@@ -119,9 +124,29 @@ def test_parse_concepts(mock_reference_data_client: Mock) -> None:
         dct:modified       "2020-09-08"^^xsd:date ;
         dct:publisher      <https://data.brreg.no/enhetsregisteret/api/enheter/910258028> ;
         dct:subject        [ ] ;
+        skosno:hasPartitiveConceptRelation [
+                                  rdf:type  skosno:PartitiveConceptRelation ;
+                                  dct:description  "Inndelingskriterium"@nb ;
+                                  skosno:hasPartitiveConcept     <http://begrepskatalogen/begrep/blad>
+                                ] ;
+        skosno:hasPartitiveConceptRelation [
+                                  rdf:type  skosno:PartitiveConceptRelation ;
+                                  dct:description  "Inndelingskriterium"@nb ;
+                                  skosno:hasComprehensiveConcept     <http://begrepskatalogen/begrep/tre>
+                                ] ;
         skosno:assosiativRelasjon    [ rdf:type                skosno:AssosiativRelasjon ;
                                        dct:description         "Beskrivelse"@nb ;
                                        skos:related  <http://begrepskatalogen/begrep/virksomhet> ] ;
+        skosno:hasGenericConceptRelation [
+                                  rdf:type  skosno:GenericConceptRelation ;
+                                  dct:description  "Inndelingskriterium"@nb ;
+                                  skosno:hasSpecificConcept    <http://begrepskatalogen/begrep/biffbestikk>
+                                ] ;
+        skosno:hasGenericConceptRelation [
+                                  rdf:type  skosno:GenericConceptRelation ;
+                                  dct:description  "Inndelingskriterium"@nb ;
+                                  skosno:hasGenericConcept    <http://begrepskatalogen/begrep/bestikk>
+                                ] ;
         euvoc:status       "status nb" , "status en"@en ;
         skosxl:altLabel    [ a                   skosxl:Label ;
                              skosxl:literalForm  "stabilisator"@nb
@@ -301,11 +326,6 @@ def test_parse_concepts(mock_reference_data_client: Mock) -> None:
                 ],
             ),
             definitions=[
-                Definition(
-                    text={"nb": "def-2"},
-                    targetGroup="https://data.norge.no/vocabulary/audience-type#public",
-                    sourceRelationship="https://data.norge.no/vocabulary/relationship-with-source-type#self-composed",
-                ),
                 Definition(
                     text={"nb": "def-1"},
                     sourceRelationship="https://data.norge.no/vocabulary/relationship-with-source-type#self-composed",
@@ -315,6 +335,11 @@ def test_parse_concepts(mock_reference_data_client: Mock) -> None:
                         )
                     ],
                 ),
+                Definition(
+                    text={"nb": "def-2"},
+                    targetGroup="https://data.norge.no/vocabulary/audience-type#public",
+                    sourceRelationship="https://data.norge.no/vocabulary/relationship-with-source-type#self-composed",
+                ),
             ],
             associativeRelation=[
                 AssociativeRelation(
@@ -343,6 +368,8 @@ def test_parse_concepts(mock_reference_data_client: Mock) -> None:
                 ),
             ],
             type="concept",
+            validFromIncluding="2020-05-29",
+            validToIncluding="2021-05-29",
         ),
         "https://registrering-begrep-api.staging.fellesdatakatalog.digdir.no/910258028/3609b02d-72c5-47e0-a6b8-df0a503cf190": Concept(
             id="fc8baf8d-6146-3b69-93c5-52bd41592c4e",
@@ -364,12 +391,32 @@ def test_parse_concepts(mock_reference_data_client: Mock) -> None:
             ),
             prefLabel={"nb": "midtbaneanker"},
             status={"nb": "status nb", "en": "status en"},
+            partitiveRelation=[
+                PartitiveRelation(
+                    description={"nb": "Inndelingskriterium"},
+                    hasPart="http://begrepskatalogen/begrep/blad",
+                ),
+                PartitiveRelation(
+                    description={"nb": "Inndelingskriterium"},
+                    isPartOf="http://begrepskatalogen/begrep/tre",
+                ),
+            ],
             associativeRelation=[
                 AssociativeRelation(
                     description={"nb": "Beskrivelse"},
                     related="http://begrepskatalogen/begrep/virksomhet",
                 ),
             ],
+            genericRelation=[
+                GenericRelation(
+                    divisioncriterion={"nb": "Inndelingskriterium"},
+                    generalizes="http://begrepskatalogen/begrep/biffbestikk",
+                ),
+                GenericRelation(
+                    divisioncriterion={"nb": "Inndelingskriterium"},
+                    specializes="http://begrepskatalogen/begrep/bestikk",
+                ),
+            ],
             altLabel=[{"nb": "stabilisator"}],
             definition=Definition(text={"nb": "ugyldig-audience-og-forholdtilkilde"}),
             definitions=[
@@ -634,7 +681,9 @@ def test_parse_concept(mock_reference_data_client: Mock) -> None:
             skosno:valueRange
                                   "test-nn"@nn,
                                   "test",
-                                  <https://range.com> .
+                                  <https://range.com> ;
+            euvoc:startDate       "2020-05-29"^^xsd:date;
+            euvoc:endDate         "2021-05-29"^^xsd:date .
 
 
     <http://publications.europa.eu/resource/authority/concept-status/CURRENT>
@@ -706,6 +755,8 @@ def test_parse_concept(mock_reference_data_client: Mock) -> None:
             )
         ],
         type="concept",
+        validFromIncluding="2020-05-29",
+        validToIncluding="2021-05-29",
     )
 
     result = parse_concept(src)