From 00aba6afcc6981752bef92d5f9a8caa00dfc0b62 Mon Sep 17 00:00:00 2001 From: "Xia, Ning" <ning.xia@tu-darmstadt.de> Date: Fri, 27 Dec 2024 16:19:57 +0100 Subject: [PATCH 01/16] init recommendation app --- project/recommendation/__init__.py | 0 project/recommendation/admin.py | 3 +++ project/recommendation/apps.py | 6 ++++++ project/recommendation/models.py | 3 +++ 4 files changed, 12 insertions(+) create mode 100644 project/recommendation/__init__.py create mode 100644 project/recommendation/admin.py create mode 100644 project/recommendation/apps.py create mode 100644 project/recommendation/models.py diff --git a/project/recommendation/__init__.py b/project/recommendation/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/project/recommendation/admin.py b/project/recommendation/admin.py new file mode 100644 index 0000000..8c38f3f --- /dev/null +++ b/project/recommendation/admin.py @@ -0,0 +1,3 @@ +from django.contrib import admin + +# Register your models here. diff --git a/project/recommendation/apps.py b/project/recommendation/apps.py new file mode 100644 index 0000000..fc75208 --- /dev/null +++ b/project/recommendation/apps.py @@ -0,0 +1,6 @@ +from django.apps import AppConfig + + +class DaliaConfig(AppConfig): + default_auto_field = "django.db.models.BigAutoField" + name = "project.recommendation" diff --git a/project/recommendation/models.py b/project/recommendation/models.py new file mode 100644 index 0000000..71a8362 --- /dev/null +++ b/project/recommendation/models.py @@ -0,0 +1,3 @@ +from django.db import models + +# Create your models here. -- GitLab From d34586709924a98279c63b200f1afe4f0beb396e Mon Sep 17 00:00:00 2001 From: "Xia, Ning" <ning.xia@tu-darmstadt.de> Date: Fri, 27 Dec 2024 16:22:30 +0100 Subject: [PATCH 02/16] added api model for suggestions of related content of a material --- project/recommendation/api_models/__init__.py | 0 project/recommendation/api_models/api_models.py | 7 +++++++ 2 files changed, 7 insertions(+) create mode 100644 project/recommendation/api_models/__init__.py create mode 100644 project/recommendation/api_models/api_models.py diff --git a/project/recommendation/api_models/__init__.py b/project/recommendation/api_models/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/project/recommendation/api_models/api_models.py b/project/recommendation/api_models/api_models.py new file mode 100644 index 0000000..9488658 --- /dev/null +++ b/project/recommendation/api_models/api_models.py @@ -0,0 +1,7 @@ +from dataclasses import dataclass +from typing import List + + +@dataclass +class SuggestedContents: + uris: List[str] -- GitLab From 6e373597402561aab43e44dd32c56d7669a15969 Mon Sep 17 00:00:00 2001 From: "Xia, Ning" <ning.xia@tu-darmstadt.de> Date: Fri, 27 Dec 2024 16:24:20 +0100 Subject: [PATCH 03/16] implemted query for suggested content, using just schema:isPartOf attribut --- project/recommendation/materials/__init__.py | 0 .../materials/suggested_content.py | 36 +++++++++++++++++++ 2 files changed, 36 insertions(+) create mode 100644 project/recommendation/materials/__init__.py create mode 100644 project/recommendation/materials/suggested_content.py diff --git a/project/recommendation/materials/__init__.py b/project/recommendation/materials/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/project/recommendation/materials/suggested_content.py b/project/recommendation/materials/suggested_content.py new file mode 100644 index 0000000..fc9c0b6 --- /dev/null +++ b/project/recommendation/materials/suggested_content.py @@ -0,0 +1,36 @@ +from uuid import UUID + +from rdflib import URIRef, Variable + +from project.dalia.query.utils import query_dalia_dataset +from project.dalia.query_builder.query_builder import QueryBuilder +from project.recommendation.api_models.api_models import ( + SuggestedContents, +) + + +def get_suggested_contents(uuid: UUID): + query = _query_for_suggested_content(uuid) + results = query_dalia_dataset(query) + response = SuggestedContents([str(result[0]) for result in results]) + + return response + + +def _query_for_suggested_content(uri: str): + var_material = Variable("material") + + query = ( + QueryBuilder() + .SELECT(var_material) + .WHERE( + ( + URIRef(f"https://id.dalia.education/learning-resource/{uri}"), + URIRef("https://schema.org/isPartOf"), + var_material, + ) + ) + .build() + ) + + return query -- GitLab From b12258e8ad1d150e1b929f7fcf9d7350d645f143 Mon Sep 17 00:00:00 2001 From: "Xia, Ning" <ning.xia@tu-darmstadt.de> Date: Fri, 27 Dec 2024 16:26:14 +0100 Subject: [PATCH 04/16] added serializer for suggested contents --- project/recommendation/serializers.py | 8 ++++++++ 1 file changed, 8 insertions(+) create mode 100644 project/recommendation/serializers.py diff --git a/project/recommendation/serializers.py b/project/recommendation/serializers.py new file mode 100644 index 0000000..d663a7b --- /dev/null +++ b/project/recommendation/serializers.py @@ -0,0 +1,8 @@ +from rest_framework_dataclasses.serializers import DataclassSerializer + +import project.recommendation.api_models.api_models as api_models + + +class SuggestedContentSerializer(DataclassSerializer): + class Meta: + dataclass = api_models.SuggestedContents -- GitLab From aaddc2b421d9f578f02a9fb3c7d1f221e0ca435b Mon Sep 17 00:00:00 2001 From: "Xia, Ning" <ning.xia@tu-darmstadt.de> Date: Fri, 27 Dec 2024 16:28:50 +0100 Subject: [PATCH 05/16] added view for suggested contents --- project/recommendation/views.py | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) create mode 100644 project/recommendation/views.py diff --git a/project/recommendation/views.py b/project/recommendation/views.py new file mode 100644 index 0000000..e29e695 --- /dev/null +++ b/project/recommendation/views.py @@ -0,0 +1,17 @@ +from uuid import UUID + +from rest_framework.request import Request +from rest_framework.response import Response +from rest_framework.views import APIView + +import project.recommendation.serializers as serializers +from project.recommendation.materials.suggested_content import get_suggested_contents + + +# endpoint /<uuid:material_id>/suggestions +class MaterialSuggestionsView(APIView): + def get(self, request: Request, material_id: UUID): + result_serializer = serializers.SuggestedContentSerializer( + get_suggested_contents(material_id) + ) + return Response(result_serializer.data) -- GitLab From dea0df1a7d3f21267f045f5ce7c817a8fb2aa5d6 Mon Sep 17 00:00:00 2001 From: "Xia, Ning" <ning.xia@tu-darmstadt.de> Date: Fri, 27 Dec 2024 16:29:58 +0100 Subject: [PATCH 06/16] added endpoint for suggested contents --- project/recommendation/urls.py | 15 +++++++++++++++ 1 file changed, 15 insertions(+) create mode 100644 project/recommendation/urls.py diff --git a/project/recommendation/urls.py b/project/recommendation/urls.py new file mode 100644 index 0000000..398d0ca --- /dev/null +++ b/project/recommendation/urls.py @@ -0,0 +1,15 @@ +""" +URL configuration for app 'recommendation'. +""" + +from django.urls import path + +from project.recommendation import views + +urlpatterns = [ + path( + "v1/<uuid:material_id>/suggestions", + views.MaterialSuggestionsView.as_view(), + name="material_suggestions", + ), +] -- GitLab From b5bccac420ab782742402ab43d7f072edb5f28cc Mon Sep 17 00:00:00 2001 From: "Xia, Ning" <ning.xia@tu-darmstadt.de> Date: Fri, 27 Dec 2024 16:30:43 +0100 Subject: [PATCH 07/16] added recommendation app in main config --- project/urls.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/project/urls.py b/project/urls.py index 41455dc..edc927f 100644 --- a/project/urls.py +++ b/project/urls.py @@ -17,7 +17,9 @@ Including another URLconf from django.urls import include, path from project.dalia import urls as dalia_urls +from project.recommendation import urls as recommendation_urls urlpatterns = [ path('api/dalia/', include(dalia_urls)), + path('api/dalia/recommendation/', include(recommendation_urls)), ] -- GitLab From 864a7d0cad1ee6fdfea603026f113ccf5e81ab1e Mon Sep 17 00:00:00 2001 From: "Xia, Ning" <ning.xia@tu-darmstadt.de> Date: Fri, 27 Dec 2024 16:32:32 +0100 Subject: [PATCH 08/16] added tests for recommendation query building --- tests/project/recommendation/__init__.py | 0 tests/project/recommendation/test_query.py | 32 ++++++++++++++++++++++ 2 files changed, 32 insertions(+) create mode 100644 tests/project/recommendation/__init__.py create mode 100644 tests/project/recommendation/test_query.py diff --git a/tests/project/recommendation/__init__.py b/tests/project/recommendation/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/project/recommendation/test_query.py b/tests/project/recommendation/test_query.py new file mode 100644 index 0000000..7aa3663 --- /dev/null +++ b/tests/project/recommendation/test_query.py @@ -0,0 +1,32 @@ +from project.recommendation.api_models.api_models import ( + RecommendationMaterialSuggestedContentRequest, + SuggestedContents, +) +from project.recommendation.materials.suggested_content import ( + _query_for_suggested_content, + get_suggested_contents, +) + + +def test_query_for_suggested_content(): + request = RecommendationMaterialSuggestedContentRequest( + uri="https://id.dalia.education/learning-resource/39ea23c6-a591-442f-afde-c3262e20f1e4" + ) + + print() + print("--- QUERY ---") + print(_query_for_suggested_content(request.uri)) + print("--- QUERY ---") + + +def test_get_suggested_content(): + request = RecommendationMaterialSuggestedContentRequest( + uri="https://id.dalia.education/learning-resource/39ea23c6-a591-442f-afde-c3262e20f1e4" + ) + + results = get_suggested_contents(request) + assert results == [ + SuggestedContents( + uri="https://av.tib.eu/series/1527/datenmanagement+in+der+chemie+videos+zum+acf+praktikum+an+der+rwth+aachen+university" + ) + ] -- GitLab From 931389b81a252f17966c76a4d1108f74d5d2b01c Mon Sep 17 00:00:00 2001 From: "Xia, Ning" <ning.xia@tu-darmstadt.de> Date: Sat, 28 Dec 2024 13:09:30 +0100 Subject: [PATCH 09/16] changed uri in query, now using constant defined in dalia app --- project/recommendation/materials/suggested_content.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/project/recommendation/materials/suggested_content.py b/project/recommendation/materials/suggested_content.py index fc9c0b6..892c6bf 100644 --- a/project/recommendation/materials/suggested_content.py +++ b/project/recommendation/materials/suggested_content.py @@ -4,6 +4,7 @@ from rdflib import URIRef, Variable from project.dalia.query.utils import query_dalia_dataset from project.dalia.query_builder.query_builder import QueryBuilder +from project.dalia.rdf.prefix import LEARNING_RESOURCE_BASE_URI from project.recommendation.api_models.api_models import ( SuggestedContents, ) @@ -17,7 +18,7 @@ def get_suggested_contents(uuid: UUID): return response -def _query_for_suggested_content(uri: str): +def _query_for_suggested_content(uuid: UUID): var_material = Variable("material") query = ( @@ -25,7 +26,7 @@ def _query_for_suggested_content(uri: str): .SELECT(var_material) .WHERE( ( - URIRef(f"https://id.dalia.education/learning-resource/{uri}"), + URIRef(f"{LEARNING_RESOURCE_BASE_URI}{uuid}"), URIRef("https://schema.org/isPartOf"), var_material, ) -- GitLab From 3be0232b8daedd54b2eb28ace641f56a2d78f979 Mon Sep 17 00:00:00 2001 From: "Xia, Ning" <ning.xia@tu-darmstadt.de> Date: Sat, 28 Dec 2024 13:09:54 +0100 Subject: [PATCH 10/16] updated tests --- tests/project/recommendation/test_query.py | 25 ++++++++-------------- 1 file changed, 9 insertions(+), 16 deletions(-) diff --git a/tests/project/recommendation/test_query.py b/tests/project/recommendation/test_query.py index 7aa3663..826c3cb 100644 --- a/tests/project/recommendation/test_query.py +++ b/tests/project/recommendation/test_query.py @@ -1,7 +1,6 @@ -from project.recommendation.api_models.api_models import ( - RecommendationMaterialSuggestedContentRequest, - SuggestedContents, -) +from uuid import UUID + +from project.recommendation.api_models.api_models import SuggestedContents from project.recommendation.materials.suggested_content import ( _query_for_suggested_content, get_suggested_contents, @@ -9,24 +8,18 @@ from project.recommendation.materials.suggested_content import ( def test_query_for_suggested_content(): - request = RecommendationMaterialSuggestedContentRequest( - uri="https://id.dalia.education/learning-resource/39ea23c6-a591-442f-afde-c3262e20f1e4" - ) + uuid = UUID("39ea23c6-a591-442f-afde-c3262e20f1e4") print() print("--- QUERY ---") - print(_query_for_suggested_content(request.uri)) + print(_query_for_suggested_content(uuid)) print("--- QUERY ---") def test_get_suggested_content(): - request = RecommendationMaterialSuggestedContentRequest( - uri="https://id.dalia.education/learning-resource/39ea23c6-a591-442f-afde-c3262e20f1e4" - ) + uuid = UUID("39ea23c6-a591-442f-afde-c3262e20f1e4") - results = get_suggested_contents(request) - assert results == [ - SuggestedContents( - uri="https://av.tib.eu/series/1527/datenmanagement+in+der+chemie+videos+zum+acf+praktikum+an+der+rwth+aachen+university" + results = get_suggested_contents(uuid) + assert results == SuggestedContents( + uris=["https://av.tib.eu/series/1527/datenmanagement+in+der+chemie+videos+zum+acf+praktikum+an+der+rwth+aachen+university"] ) - ] -- GitLab From cd7717deb450545dc63115b1ee07e4f8b5eed622 Mon Sep 17 00:00:00 2001 From: agneskleinhans <kleinhansagnes@gmail.com> Date: Fri, 17 Jan 2025 16:40:39 +0100 Subject: [PATCH 11/16] add query keywords --- .../materials/suggested_content.py | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/project/recommendation/materials/suggested_content.py b/project/recommendation/materials/suggested_content.py index 892c6bf..db5cc71 100644 --- a/project/recommendation/materials/suggested_content.py +++ b/project/recommendation/materials/suggested_content.py @@ -12,7 +12,10 @@ from project.recommendation.api_models.api_models import ( def get_suggested_contents(uuid: UUID): query = _query_for_suggested_content(uuid) - results = query_dalia_dataset(query) + results = [] + for q in query: + results += query_dalia_dataset(q) + #results = query_dalia_dataset(query[0]) response = SuggestedContents([str(result[0]) for result in results]) return response @@ -34,4 +37,16 @@ def _query_for_suggested_content(uuid: UUID): .build() ) - return query + + query2 = f""" + PREFIX schema: <https://schema.org/> + SELECT ?sub (COUNT(?sharedKeyword) AS ?keywordCount) WHERE {{ + <{LEARNING_RESOURCE_BASE_URI}{uuid}> schema:keywords ?sharedKeyword . + ?sub schema:keywords ?sharedKeyword . + FILTER(?sub != <{LEARNING_RESOURCE_BASE_URI}{uuid}>) + }} + GROUP BY ?sub + HAVING(?keywordCount >= 2) + """ + + return [query, query2] -- GitLab From e6f974eafb8cdabce2d81cd14065712201514275 Mon Sep 17 00:00:00 2001 From: flange <38500-flange@users.noreply.git.rwth-aachen.de> Date: Fri, 24 Jan 2025 15:48:45 +0100 Subject: [PATCH 12/16] change order of social_media list of communities --- .../dalia/query/communities/one_to_one_metadata.py | 14 ++++++++------ .../dalia/query/communities/test_communities.py | 4 ++-- 2 files changed, 10 insertions(+), 8 deletions(-) diff --git a/project/dalia/query/communities/one_to_one_metadata.py b/project/dalia/query/communities/one_to_one_metadata.py index 65d11f9..0cf04f1 100644 --- a/project/dalia/query/communities/one_to_one_metadata.py +++ b/project/dalia/query/communities/one_to_one_metadata.py @@ -53,14 +53,19 @@ def process_result_for_one_to_one_metadata_for_community(result) -> Community: def process_social_media_in_result(result): social_media = [] - if result.bluesky_handle: + + if result.zenodo_community_id: social_media.append( - SocialMedia(name="Bluesky", url="https://bsky.app/profile/" + str(result.bluesky_handle)) + SocialMedia(name="Zenodo", url="https://zenodo.org/communities/" + str(result.zenodo_community_id)) ) if result.youtube_channel_id: social_media.append( SocialMedia(name="YouTube", url="https://www.youtube.com/channel/" + str(result.youtube_channel_id)) ) + if result.bluesky_handle: + social_media.append( + SocialMedia(name="Bluesky", url="https://bsky.app/profile/" + str(result.bluesky_handle)) + ) if result.mastodon_address: server = str(result.mastodon_address).split("@")[-1] handle = str(result.mastodon_address).split("@")[0] @@ -71,10 +76,7 @@ def process_social_media_in_result(result): social_media.append( SocialMedia(name="LinkedIn", url="https://www.linkedin.com/company/" + str(result.linkedin_id)) ) - if result.zenodo_community_id: - social_media.append( - SocialMedia(name="Zenodo", url="https://zenodo.org/communities/" + str(result.zenodo_community_id)) - ) + return social_media diff --git a/tests/project/dalia/query/communities/test_communities.py b/tests/project/dalia/query/communities/test_communities.py index 71b2abb..812e89d 100644 --- a/tests/project/dalia/query/communities/test_communities.py +++ b/tests/project/dalia/query/communities/test_communities.py @@ -21,11 +21,11 @@ def test_get_on_CommunityView_returns_200_and_community_data_for_existing_commun image=None, url='https://www.nfdi4chem.de/', social_media=[ - SocialMedia(name='Bluesky', url='https://bsky.app/profile/nfdi4chem.de'), + SocialMedia(name='Zenodo', url='https://zenodo.org/communities/nfdi4chem'), SocialMedia(name='YouTube', url='https://www.youtube.com/channel/UCQlKQDjyYFzlUFrDfR9vVJg'), + SocialMedia(name='Bluesky', url='https://bsky.app/profile/nfdi4chem.de'), SocialMedia(name='Mastodon', url='https://nfdi.social/@NFDI4Chem'), SocialMedia(name='LinkedIn', url='https://www.linkedin.com/company/nfdi4chem'), - SocialMedia(name='Zenodo', url='https://zenodo.org/communities/nfdi4chem') ], about='NFDI4Chem is building an open and FAIR infrastructure for research data management in chemistry. The ' 'consortium consists of dedicated data producers and users from university and non-university research, ' -- GitLab From a21bfd99e65a89ac9aa9ba89292df12cee3b2d8b Mon Sep 17 00:00:00 2001 From: flange <38500-flange@users.noreply.git.rwth-aachen.de> Date: Thu, 30 Jan 2025 19:27:07 +0100 Subject: [PATCH 13/16] update facet items --- project/dalia/query/items/facets/facet_objects.py | 3 +++ project/dalia/rdf/namespace/hcrt.py | 1 + 2 files changed, 4 insertions(+) diff --git a/project/dalia/query/items/facets/facet_objects.py b/project/dalia/query/items/facets/facet_objects.py index 971a3ee..657c3b3 100644 --- a/project/dalia/query/items/facets/facet_objects.py +++ b/project/dalia/query/items/facets/facet_objects.py @@ -87,6 +87,7 @@ LEARNING_RESOURCE_TYPE_FACET = FacetObject( hcrt.experiment: "Experiment", MoDalia.Lecture: "Lecture", hcrt.lesson_plan: "Lesson Plan", + hcrt.other: "Other resource type", SCHEMA.PodcastSeries: "PodcastSeries", MoDalia.Poster: "Poster", hcrt.index: "Reference Work", @@ -108,7 +109,9 @@ LANGUAGE_FACET = FacetObject( predicate=DCTERMS.language, items={ Literal("en"): "English", + Literal("fr"): "French", Literal("de"): "German", + Literal("es"): "Spanish", }, selected_facet_initializer=Literal, ) diff --git a/project/dalia/rdf/namespace/hcrt.py b/project/dalia/rdf/namespace/hcrt.py index 0f96e60..5223948 100644 --- a/project/dalia/rdf/namespace/hcrt.py +++ b/project/dalia/rdf/namespace/hcrt.py @@ -16,6 +16,7 @@ educational_game = URIRef(NS + "educational_game") experiment = URIRef(NS + "experiment") lesson_plan = URIRef(NS + "lesson_plan") index = URIRef(NS + "index") +other = URIRef(NS + "other") slide = URIRef(NS + "slide") text = URIRef(NS + "text") textbook = URIRef(NS + "textbook") -- GitLab From ecfa388b249f37454d9c5b6cc3d1543b4dfb7c0d Mon Sep 17 00:00:00 2001 From: "Xia, Ning" <ning.xia@tu-darmstadt.de> Date: Fri, 7 Mar 2025 00:33:01 +0100 Subject: [PATCH 14/16] Get suggested content considering different ranking metrics --- .vscode/settings.json | 18 +++ .../materials/suggested_content.py | 136 +++++++++++++++--- tests/project/recommendation/test_query.py | 85 +++++++++-- 3 files changed, 207 insertions(+), 32 deletions(-) create mode 100644 .vscode/settings.json diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..b9398da --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,18 @@ +{ + "[python]": { + "editor.defaultFormatter": "charliermarsh.ruff", + "editor.formatOnSave": true, + "editor.rulers": [ + 88 + ], + "editor.renderWhitespace": "trailing", + "editor.codeActionsOnSave": { + "source.organizeImports.ruff": "explicit" + } + }, + "python.testing.pytestArgs": [ + "tests" + ], + "python.testing.unittestEnabled": false, + "python.testing.pytestEnabled": true +} \ No newline at end of file diff --git a/project/recommendation/materials/suggested_content.py b/project/recommendation/materials/suggested_content.py index db5cc71..89015a8 100644 --- a/project/recommendation/materials/suggested_content.py +++ b/project/recommendation/materials/suggested_content.py @@ -4,49 +4,147 @@ from rdflib import URIRef, Variable from project.dalia.query.utils import query_dalia_dataset from project.dalia.query_builder.query_builder import QueryBuilder +from project.dalia.rdf.namespace import SCHEMA, MoDalia, fabio from project.dalia.rdf.prefix import LEARNING_RESOURCE_BASE_URI from project.recommendation.api_models.api_models import ( SuggestedContents, ) -def get_suggested_contents(uuid: UUID): - query = _query_for_suggested_content(uuid) - results = [] - for q in query: - results += query_dalia_dataset(q) - #results = query_dalia_dataset(query[0]) - response = SuggestedContents([str(result[0]) for result in results]) +def _get_shared_keywords(uuid: UUID): + query = f""" + PREFIX schema: <https://schema.org/> + SELECT ?sub (COUNT(?sharedKeyword) AS ?keywordCount) WHERE {{ + <{LEARNING_RESOURCE_BASE_URI}{uuid}> schema:keywords ?sharedKeyword . + ?sub schema:keywords ?sharedKeyword . + FILTER(?sub != <{LEARNING_RESOURCE_BASE_URI}{uuid}>) + }} + GROUP BY ?sub + HAVING(?keywordCount >= 2) + """ - return response + results = query_dalia_dataset(query) + return [result[0] for result in results] -def _query_for_suggested_content(uuid: UUID): +def _get_is_part_of(uuid: UUID): var_material = Variable("material") + query = ( + QueryBuilder() + .SELECT(var_material) + .WHERE( + ( + URIRef(f"{LEARNING_RESOURCE_BASE_URI}{uuid}"), + URIRef(URIRef(SCHEMA.NS + "isPartOf")), + var_material, + ) + ) + .build() + ) + results = query_dalia_dataset(query) + return [result[0] for result in results] + +def _get_is_related_to(uuid: UUID): + var_material = Variable("material") query = ( QueryBuilder() .SELECT(var_material) .WHERE( ( URIRef(f"{LEARNING_RESOURCE_BASE_URI}{uuid}"), - URIRef("https://schema.org/isPartOf"), + URIRef(URIRef(MoDalia.NS + "isRelatedTo")), var_material, ) ) .build() ) + results = query_dalia_dataset(query) + return [result[0] for result in results] - query2 = f""" +def _get_is_based_on(uuid: UUID): + var_material = Variable("material") + query = ( + QueryBuilder() + .SELECT(var_material) + .WHERE( + ( + URIRef(f"{LEARNING_RESOURCE_BASE_URI}{uuid}"), + URIRef(URIRef(MoDalia.NS + "isBasedOn")), + var_material, + ) + ) + .build() + ) + results = query_dalia_dataset(query) + return [result[0] for result in results] + + +def _get_authors(uuid: UUID): + query = f""" + PREFIX arq: <http://jena.apache.org/ARQ/list#> PREFIX schema: <https://schema.org/> - SELECT ?sub (COUNT(?sharedKeyword) AS ?keywordCount) WHERE {{ - <{LEARNING_RESOURCE_BASE_URI}{uuid}> schema:keywords ?sharedKeyword . - ?sub schema:keywords ?sharedKeyword . - FILTER(?sub != <{LEARNING_RESOURCE_BASE_URI}{uuid}>) + PREFIX m4i: <http://w3id.org/nfdi4ing/metadata4ing#> + + SELECT DISTINCT ?lr WHERE {{ + <{LEARNING_RESOURCE_BASE_URI}{uuid}> schema:author ?list. + ?list arq:member ?member. + ?member a ?type. + ?member m4i:orcidId ?id. + ?lr schema:author ?newlist. + ?newlist arq:member ?newmember. + ?newmember m4i:orcidId ?id. + FILTER(?lr != <{LEARNING_RESOURCE_BASE_URI}{uuid}>) + FILTER(?type = schema:Person) }} - GROUP BY ?sub - HAVING(?keywordCount >= 2) - """ + LIMIT 10""" + results = query_dalia_dataset(query) + return [result[0] for result in results] + + +def _get_same_discipline(uuid: UUID): + var_material = Variable("material") + var_discipline = Variable("discipline") + query = ( + QueryBuilder() + .SELECT(var_material) + .WHERE( + ( + URIRef(f"{LEARNING_RESOURCE_BASE_URI}{uuid}"), + URIRef(URIRef(fabio.NS + "hasDiscipline")), + var_discipline, + ), + ( + var_material, + URIRef(URIRef(fabio.NS + "hasDiscipline")), + var_discipline, + ), + ) + .build() + ) + results = query_dalia_dataset(query) + return [result[0] for result in results] + + +RANKING = ( + _get_is_part_of, + _get_is_based_on, + _get_is_related_to, + _get_authors, + _get_shared_keywords, + _get_same_discipline, +) + + +def get_suggested_contents(uuid: UUID): + number_of_materials = 5 + results = set() + for i in range(len(RANKING)): + for result in RANKING[i](uuid): + if LEARNING_RESOURCE_BASE_URI == result[: len(LEARNING_RESOURCE_BASE_URI)]: + results.add(result) + if len(results) == number_of_materials: + return SuggestedContents(results) - return [query, query2] + return SuggestedContents(results) diff --git a/tests/project/recommendation/test_query.py b/tests/project/recommendation/test_query.py index 826c3cb..66dcc92 100644 --- a/tests/project/recommendation/test_query.py +++ b/tests/project/recommendation/test_query.py @@ -1,25 +1,84 @@ from uuid import UUID -from project.recommendation.api_models.api_models import SuggestedContents from project.recommendation.materials.suggested_content import ( - _query_for_suggested_content, + _get_authors, + _get_is_based_on, + _get_is_part_of, + _get_is_related_to, + _get_same_discipline, + _get_shared_keywords, get_suggested_contents, ) +# def test_query_for_suggested_content(): +# uuid = UUID("39ea23c6-a591-442f-afde-c3262e20f1e4") -def test_query_for_suggested_content(): - uuid = UUID("39ea23c6-a591-442f-afde-c3262e20f1e4") +# print() +# print("--- QUERY ---") +# print(_query_for_suggested_content(uuid)) +# print("--- QUERY ---") - print() - print("--- QUERY ---") - print(_query_for_suggested_content(uuid)) - print("--- QUERY ---") +# def test_get_suggested_content(): +# uuid = UUID("39ea23c6-a591-442f-afde-c3262e20f1e4") -def test_get_suggested_content(): - uuid = UUID("39ea23c6-a591-442f-afde-c3262e20f1e4") +# results = get_suggested_contents(uuid) +# assert results == SuggestedContents( +# uris=["https://av.tib.eu/series/1527/datenmanagement+in+der+chemie+videos+zum+acf+praktikum+an+der+rwth+aachen+university"] +# ) + +def test_get_is_part_of(): + uuid = UUID("39ea23c6-a591-442f-afde-c3262e20f1e4") + + print("get is part of") + results = _get_is_part_of(uuid) + print("\n".join(results)) + + +def test_get_is_related_to(): + uuid = UUID("39ea23c6-a591-442f-afde-c3262e20f1e4") + + print("get is based on") + results = _get_is_related_to(uuid) + print("\n".join(results)) + + +def test_get_is_based_on(): + uuid = UUID("39ea23c6-a591-442f-afde-c3262e20f1e4") + + print("get is related to") + results = _get_is_based_on(uuid) + print("\n".join(results)) + + +def test_get_shared_keywords(): + uuid = UUID("39ea23c6-a591-442f-afde-c3262e20f1e4") + + print("get shared keywords") + results = _get_shared_keywords(uuid) + print("\n".join(results)) + + +def test_get_authors(): + uuid = UUID("39ea23c6-a591-442f-afde-c3262e20f1e4") + + print("get authors") + results = _get_authors(uuid) + print("\n".join(results)) + + +def test_get_same_discipline(): + uuid = UUID("39ea23c6-a591-442f-afde-c3262e20f1e4") + + print("get same discipline") + results = _get_same_discipline(uuid) + print("\n".join(results)) + + +def test_get_suggested_contents(): + uuid = UUID("39ea23c6-a591-442f-afde-c3262e20f1e4") + + print("get suggested contents") results = get_suggested_contents(uuid) - assert results == SuggestedContents( - uris=["https://av.tib.eu/series/1527/datenmanagement+in+der+chemie+videos+zum+acf+praktikum+an+der+rwth+aachen+university"] - ) + print("\n".join(results.uris)) -- GitLab From 00a6fcd7b332ab70d75ea83cc24a4581e9c250c5 Mon Sep 17 00:00:00 2001 From: "Xia, Ning" <ning.xia@tu-darmstadt.de> Date: Mon, 14 Apr 2025 22:52:21 +0200 Subject: [PATCH 15/16] finished /items/{}/recommendations api end point according to the doc --- .vscode/settings.json | 3 +- .../recommendation/api_models/api_models.py | 6 ++-- .../materials/suggested_content.py | 36 ++++++++++++------- project/recommendation/urls.py | 4 +-- project/recommendation/views.py | 14 +++++--- 5 files changed, 40 insertions(+), 23 deletions(-) diff --git a/.vscode/settings.json b/.vscode/settings.json index b9398da..81b3e0a 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -11,7 +11,8 @@ } }, "python.testing.pytestArgs": [ - "tests" + "tests", + "-s" ], "python.testing.unittestEnabled": false, "python.testing.pytestEnabled": true diff --git a/project/recommendation/api_models/api_models.py b/project/recommendation/api_models/api_models.py index 9488658..9d34c50 100644 --- a/project/recommendation/api_models/api_models.py +++ b/project/recommendation/api_models/api_models.py @@ -1,7 +1,9 @@ from dataclasses import dataclass -from typing import List +from typing import List, Optional + +from project.dalia.api_models.api_models import Item @dataclass class SuggestedContents: - uris: List[str] + results: Optional[List[Item]] diff --git a/project/recommendation/materials/suggested_content.py b/project/recommendation/materials/suggested_content.py index 89015a8..6c5754e 100644 --- a/project/recommendation/materials/suggested_content.py +++ b/project/recommendation/materials/suggested_content.py @@ -1,11 +1,13 @@ +from typing import Set from uuid import UUID from rdflib import URIRef, Variable +from project.dalia.query.items.metadata.items import get_metadata_for_learning_resources from project.dalia.query.utils import query_dalia_dataset from project.dalia.query_builder.query_builder import QueryBuilder +from project.dalia.rdf.dalia_kb import _LEARNING_RESOURCE_BASE_URI from project.dalia.rdf.namespace import SCHEMA, MoDalia, fabio -from project.dalia.rdf.prefix import LEARNING_RESOURCE_BASE_URI from project.recommendation.api_models.api_models import ( SuggestedContents, ) @@ -15,9 +17,9 @@ def _get_shared_keywords(uuid: UUID): query = f""" PREFIX schema: <https://schema.org/> SELECT ?sub (COUNT(?sharedKeyword) AS ?keywordCount) WHERE {{ - <{LEARNING_RESOURCE_BASE_URI}{uuid}> schema:keywords ?sharedKeyword . + <{_LEARNING_RESOURCE_BASE_URI}{uuid}> schema:keywords ?sharedKeyword . ?sub schema:keywords ?sharedKeyword . - FILTER(?sub != <{LEARNING_RESOURCE_BASE_URI}{uuid}>) + FILTER(?sub != <{_LEARNING_RESOURCE_BASE_URI}{uuid}>) }} GROUP BY ?sub HAVING(?keywordCount >= 2) @@ -34,7 +36,7 @@ def _get_is_part_of(uuid: UUID): .SELECT(var_material) .WHERE( ( - URIRef(f"{LEARNING_RESOURCE_BASE_URI}{uuid}"), + URIRef(f"{_LEARNING_RESOURCE_BASE_URI}{uuid}"), URIRef(URIRef(SCHEMA.NS + "isPartOf")), var_material, ) @@ -52,7 +54,7 @@ def _get_is_related_to(uuid: UUID): .SELECT(var_material) .WHERE( ( - URIRef(f"{LEARNING_RESOURCE_BASE_URI}{uuid}"), + URIRef(f"{_LEARNING_RESOURCE_BASE_URI}{uuid}"), URIRef(URIRef(MoDalia.NS + "isRelatedTo")), var_material, ) @@ -70,7 +72,7 @@ def _get_is_based_on(uuid: UUID): .SELECT(var_material) .WHERE( ( - URIRef(f"{LEARNING_RESOURCE_BASE_URI}{uuid}"), + URIRef(f"{_LEARNING_RESOURCE_BASE_URI}{uuid}"), URIRef(URIRef(MoDalia.NS + "isBasedOn")), var_material, ) @@ -88,14 +90,14 @@ def _get_authors(uuid: UUID): PREFIX m4i: <http://w3id.org/nfdi4ing/metadata4ing#> SELECT DISTINCT ?lr WHERE {{ - <{LEARNING_RESOURCE_BASE_URI}{uuid}> schema:author ?list. + <{_LEARNING_RESOURCE_BASE_URI}{uuid}> schema:author ?list. ?list arq:member ?member. ?member a ?type. ?member m4i:orcidId ?id. ?lr schema:author ?newlist. ?newlist arq:member ?newmember. ?newmember m4i:orcidId ?id. - FILTER(?lr != <{LEARNING_RESOURCE_BASE_URI}{uuid}>) + FILTER(?lr != <{_LEARNING_RESOURCE_BASE_URI}{uuid}>) FILTER(?type = schema:Person) }} LIMIT 10""" @@ -111,7 +113,7 @@ def _get_same_discipline(uuid: UUID): .SELECT(var_material) .WHERE( ( - URIRef(f"{LEARNING_RESOURCE_BASE_URI}{uuid}"), + URIRef(f"{_LEARNING_RESOURCE_BASE_URI}{uuid}"), URIRef(URIRef(fabio.NS + "hasDiscipline")), var_discipline, ), @@ -137,14 +139,22 @@ RANKING = ( ) -def get_suggested_contents(uuid: UUID): +def get_suggested_contents_id(uuid: UUID) -> Set: number_of_materials = 5 results = set() for i in range(len(RANKING)): for result in RANKING[i](uuid): - if LEARNING_RESOURCE_BASE_URI == result[: len(LEARNING_RESOURCE_BASE_URI)]: + if ( + _LEARNING_RESOURCE_BASE_URI + == result[: len(_LEARNING_RESOURCE_BASE_URI)] + ): results.add(result) if len(results) == number_of_materials: - return SuggestedContents(results) + return results - return SuggestedContents(results) + return results + + +def get_suggested_contents(uuid: UUID) -> SuggestedContents: + ids = get_suggested_contents_id(uuid) + return SuggestedContents(get_metadata_for_learning_resources(list(ids))) diff --git a/project/recommendation/urls.py b/project/recommendation/urls.py index 398d0ca..2b33939 100644 --- a/project/recommendation/urls.py +++ b/project/recommendation/urls.py @@ -8,8 +8,8 @@ from project.recommendation import views urlpatterns = [ path( - "v1/<uuid:material_id>/suggestions", + "v1/item/<uuid:material_id>/recommendations", views.MaterialSuggestionsView.as_view(), - name="material_suggestions", + name="material_recommendations", ), ] diff --git a/project/recommendation/views.py b/project/recommendation/views.py index e29e695..52c245f 100644 --- a/project/recommendation/views.py +++ b/project/recommendation/views.py @@ -1,5 +1,6 @@ from uuid import UUID +from rest_framework import status from rest_framework.request import Request from rest_framework.response import Response from rest_framework.views import APIView @@ -8,10 +9,13 @@ import project.recommendation.serializers as serializers from project.recommendation.materials.suggested_content import get_suggested_contents -# endpoint /<uuid:material_id>/suggestions +# endpoint /items/<uuid:material_id>/recommendations class MaterialSuggestionsView(APIView): def get(self, request: Request, material_id: UUID): - result_serializer = serializers.SuggestedContentSerializer( - get_suggested_contents(material_id) - ) - return Response(result_serializer.data) + suggested_contents = get_suggested_contents(material_id) + if len(suggested_contents.results) == 0: + return Response( + {"messages": "No suggestions found"}, status=status.HTTP_404_NOT_FOUND + ) + result = serializers.SuggestedContentSerializer(suggested_contents) + return Response(result.data) -- GitLab From 20e5a880125e121c135e5a7ad07ef7eb79d915b2 Mon Sep 17 00:00:00 2001 From: "Xia, Ning" <ning.xia@tu-darmstadt.de> Date: Mon, 14 Apr 2025 22:53:14 +0200 Subject: [PATCH 16/16] added test for call the functions of endpotin /items/{}/recommendations --- tests/project/recommendation/test_query.py | 3 ++- tests/project/recommendation/test_serialiser.py | 13 +++++++++++++ 2 files changed, 15 insertions(+), 1 deletion(-) create mode 100644 tests/project/recommendation/test_serialiser.py diff --git a/tests/project/recommendation/test_query.py b/tests/project/recommendation/test_query.py index 66dcc92..0d9f5b6 100644 --- a/tests/project/recommendation/test_query.py +++ b/tests/project/recommendation/test_query.py @@ -81,4 +81,5 @@ def test_get_suggested_contents(): print("get suggested contents") results = get_suggested_contents(uuid) - print("\n".join(results.uris)) + for result in results: + print(result) diff --git a/tests/project/recommendation/test_serialiser.py b/tests/project/recommendation/test_serialiser.py new file mode 100644 index 0000000..966bb87 --- /dev/null +++ b/tests/project/recommendation/test_serialiser.py @@ -0,0 +1,13 @@ +from uuid import UUID + +from project.recommendation.materials.suggested_content import get_suggested_contents +from project.recommendation.serializers import SuggestedContentSerializer + + +def test_suggested_content_serializer(): + uuid = UUID("39ea23c6-a591-442f-afde-c3262e20f1e4") + + print("get suggested contents") + results = get_suggested_contents(uuid) + serializer = SuggestedContentSerializer(results) + print(serializer.data) -- GitLab