From 3deda3f56b290f5da3f3db1ccb546e79c325f95b Mon Sep 17 00:00:00 2001 From: peeb <93371033+peschenbach@users.noreply.github.com> Date: Tue, 16 Apr 2024 20:01:14 +0200 Subject: [PATCH 01/10] Create LICENSE --- LICENSE | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) create mode 100644 LICENSE diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..9026a5c --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2024 peeb + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. From c41ca22adad912c8e739a75a59aece6026daf014 Mon Sep 17 00:00:00 2001 From: peeb <93371033+peschenbach@users.noreply.github.com> Date: Tue, 16 Apr 2024 20:02:32 +0200 Subject: [PATCH 02/10] Update README.md --- README.md | 8 -------- 1 file changed, 8 deletions(-) diff --git a/README.md b/README.md index 7f9aee0..d6f4fd8 100644 --- a/README.md +++ b/README.md @@ -42,12 +42,4 @@ Alternatively, if you have the docker desktop app, you can follow these steps: ### You're done! You can now access the platform at: localhost:3000 -### MIT Liscense -Copyright (c) <2024> - -Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. From ede4d8a20110e893f63b2bac7fd811cc5ab481ea Mon Sep 17 00:00:00 2001 From: Benny Clark Date: Tue, 31 Mar 2026 19:14:55 +0200 Subject: [PATCH 03/10] initial changes for march demo and ramen deployment --- README.md | 7 ++ compose.yaml | 8 +- create_issues.sh | 36 +++++++++ issues.csv | 17 +++++ .../api/migrations/0002_auto_20260325_2147.py | 48 ++++++++++++ platform/backend/mysite/settings.py | 5 +- platform/backend/mysite/urls.py | 22 +++--- .../user_api/migrations/0001_initial.py | 31 ++++++++ .../app/competitions/[challengeId]/page.tsx | 10 +-- .../frontend/app/competitions/create/page.tsx | 20 ++++- platform/frontend/app/competitions/page.tsx | 2 +- .../frontend/app/components/ClientWrapper.tsx | 10 ++- .../frontend/app/components/FileUpload 2.tsx | 5 +- platform/frontend/app/components/Header.tsx | 16 ++-- .../app/components/SingleCompetition.tsx | 9 ++- .../frontend/app/components/UserContext.tsx | 2 +- platform/frontend/app/components/utils.ts | 20 ++--- platform/frontend/app/datasets/page.tsx | 2 +- platform/frontend/app/login/page.tsx | 7 +- platform/frontend/app/page.tsx | 56 ++++++++------ platform/frontend/app/register/page.tsx | 5 +- platform/frontend/next.config.js | 15 +++- platform/frontend/tailwind.config.ts | 5 ++ platform/frontend/test-axios.js | 4 + worker/lrp.py | 73 ++++++++++++++++++ worker/shap.py | 75 +++++++++++++++++++ 26 files changed, 427 insertions(+), 83 deletions(-) create mode 100755 create_issues.sh create mode 100644 issues.csv create mode 100644 platform/backend/api/migrations/0002_auto_20260325_2147.py create mode 100644 platform/backend/user_api/migrations/0001_initial.py create mode 100644 platform/frontend/test-axios.js create mode 100644 worker/lrp.py create mode 100644 worker/shap.py diff --git a/README.md b/README.md index 55591bc..d14bf10 100644 --- a/README.md +++ b/README.md @@ -9,6 +9,13 @@ We recommend Docker Desktop for managing the images and containers. (Link: https # How to run the platform: +### running on ramen + +`sudo docker compose down -v` +`sudo docker compose build frontend` +`sudo docker compose up -d` +`sudo docker compose exec backend python manage.py migrate` + ### building images Inside the folder containing the repository, please run the following command: diff --git a/compose.yaml b/compose.yaml index 85c209e..584319b 100644 --- a/compose.yaml +++ b/compose.yaml @@ -8,6 +8,7 @@ services: user: root volumes: - /var/run/docker.sock:/var/run/docker.sock + - ./platform/backend:/app - ./media:/app/mysite/media depends_on: - db @@ -21,7 +22,7 @@ services: - ./platform/frontend:/app - /app/node_modules ports: - - "3000:3000" + - "8080:3000" working_dir: /app command: npm run dev environment: @@ -36,9 +37,8 @@ services: image: exact-worker # Wir definieren den Namen des Images explizit db: - image: postgres + image: postgres:16 restart: always - user: postgres secrets: - db-password volumes: @@ -47,7 +47,7 @@ services: - POSTGRES_DB=example - POSTGRES_PASSWORD_FILE=/run/secrets/db-password ports: - - "5432:5432" + - "5433:5432" healthcheck: test: ["CMD", "pg_isready"] interval: 10s diff --git a/create_issues.sh b/create_issues.sh new file mode 100755 index 0000000..2e1b8ec --- /dev/null +++ b/create_issues.sh @@ -0,0 +1,36 @@ +#!/bin/bash + +REPO="braindatalab/exact" +CSV_FILE="issues.csv" + +# Skip header +tail -n +1 "$CSV_FILE" | while IFS=',' read -r raw_title raw_body raw_labels; do + # Remove surrounding quotes and clean up spacing + title=$(echo "$raw_title" | sed 's/^"//;s/"$//' | xargs) + body=$(echo "$raw_body" | sed 's/^"//;s/"$//' | xargs) + labels=$(echo "$raw_labels" | sed 's/^"//;s/"$//' | xargs) + + # Write body to temp file + tmpfile=$(mktemp) + echo "$body" > "$tmpfile" + + echo "🛠 Creating issue: $title" + + # Build label flags + label_args=() + IFS=',' read -ra label_array <<< "$labels" + for label in "${label_array[@]}"; do + label_args+=(--label "$label") + done + + # Create the issue + gh issue create \ + --repo "$REPO" \ + --title "$title" \ + --body-file "$tmpfile" \ + "${label_args[@]}" + + rm "$tmpfile" + + echo "✅ Created: $title" +done diff --git a/issues.csv b/issues.csv new file mode 100644 index 0000000..4ffa46e --- /dev/null +++ b/issues.csv @@ -0,0 +1,17 @@ +"Define core user stories for the platform MVP","Create high-level user stories for registering, submitting a method, viewing leaderboard, and administering challenges.","planning,meta" +"Update user registration to require admin approval","Add a registration status field (e.g., pending, approved), create admin UI to approve users, and prevent unapproved users from accessing submission features.","frontend,backend,database" +"Design and store method name field for submissions","Add form field for method name in submission UI, store in DB, and expose via API.","frontend,backend,database" +"Display method names on leaderboard","Update leaderboard UI and API to show submitted method names alongside scores.","frontend,backend" +"Enable sorting leaderboard by method score","Allow users to click table headers to sort by score; add support for sorting server-side or client-side.","frontend,backend" +"Update leaderboard to show all metric results per submission","Show full list of metrics per method: EMD, IMA, etc. alongside method name and challenge.","frontend,backend" +"Enable leaderboard sorting by each metric, default to primary metric","Add column-based sorting to each metric; default sort by user-selected or predefined metric.","frontend,backend" +"Add metric selection (checkboxes) to challenge creation UI","Let admins select metrics (e.g. EMD, IMA) when creating a challenge; store this in DB.","frontend,backend" +"Create structure for XAI super-challenges","Group related scenarios (e.g. XAI-TRIS variants) under one meta-challenge with shared leaderboard and/or unified submission logic.","backend,database" +"Integrate all XAI-TRIS datasets as challenges","For each XAI-TRIS scenario, create challenge metadata, upload dataset, and add basic visualization/description.","backend,worker,database" +"Integrate MRI dataset into the platform","Add MRI semi-synthetic dataset as a challenge, with description and appropriate metrics.","backend,worker,database" +"Enable multi-worker processing system","Modify job dispatch system to handle concurrent/multiple workers processing submissions in parallel.","worker,backend" +"Implement full input/output validation and Docker sandboxing","Validate submission format, restrict Docker execution, check method outputs conform to required interface.","worker,security,backend" +"Add CI pipeline with GitHub Actions","Set up CI to lint, test backend/frontend, and verify Docker builds.","devops" +"Test deployment on PTB server","Ensure full system deploys on PTB infrastructure, including DB, backend, frontend, and workers.","devops,deployment" +"Platform stabilization and bug fixing sprint","Focus sprint on UI polish, error handling, and cross-component reliability.","frontend,backend,worker,qa" +"Populate known results from XAI-TRIS and MRI papers","Add historical experimental results to leaderboard for baseline comparisons.","backend,database" diff --git a/platform/backend/api/migrations/0002_auto_20260325_2147.py b/platform/backend/api/migrations/0002_auto_20260325_2147.py new file mode 100644 index 0000000..379fe7f --- /dev/null +++ b/platform/backend/api/migrations/0002_auto_20260325_2147.py @@ -0,0 +1,48 @@ +# Generated by Django 3.2.23 on 2026-03-25 21:47 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('api', '0001_initial'), + ] + + operations = [ + migrations.AddField( + model_name='score', + name='emd_score', + field=models.FloatField(blank=True, null=True), + ), + migrations.AddField( + model_name='score', + name='emd_std', + field=models.FloatField(blank=True, null=True), + ), + migrations.AddField( + model_name='score', + name='error_message', + field=models.TextField(blank=True, null=True), + ), + migrations.AddField( + model_name='score', + name='ima_score', + field=models.FloatField(blank=True, null=True), + ), + migrations.AddField( + model_name='score', + name='ima_std', + field=models.FloatField(blank=True, null=True), + ), + migrations.AddField( + model_name='score', + name='status', + field=models.CharField(choices=[('pending', 'Pending'), ('running', 'Running'), ('completed', 'Completed'), ('failed', 'Failed')], default='completed', max_length=20), + ), + migrations.AlterField( + model_name='score', + name='score', + field=models.FloatField(blank=True, null=True), + ), + ] diff --git a/platform/backend/mysite/settings.py b/platform/backend/mysite/settings.py index 6b92486..7e324a0 100644 --- a/platform/backend/mysite/settings.py +++ b/platform/backend/mysite/settings.py @@ -30,9 +30,8 @@ ALLOWED_HOSTS = ['*'] -CORS_ALLOWED_ORIGINS = [ - 'http://localhost:3000', - 'http://127.0.0.1:3000' +CORS_ALLOWED_ORIGIN_REGEXES = [ + r"^.*$" ] CORS_ALLOW_CREDENTIALS = True diff --git a/platform/backend/mysite/urls.py b/platform/backend/mysite/urls.py index 0650c84..699cc5f 100644 --- a/platform/backend/mysite/urls.py +++ b/platform/backend/mysite/urls.py @@ -22,19 +22,17 @@ urlpatterns = [ path('admin/', admin.site.urls), - path('api/xai//', xai_detail), # For submitting XAI-methods and getting the computed score - path('api/score//', score_detail), - path('api/dataset//', dataset_detail), - path('api/mlmodel//', mlmodel_detail), - path('api/xaimethod//', xaimethod_detail), - path('api/challenge/create/', create_challenge), - #path('api/challenge//delete/', delete_challenge), # New URL for deleting a challenge - path('challenge/form', challenge_form_view), # New URL pattern for the form, + path('api/xai/', xai_detail), # For submitting XAI-methods and getting the computed score + path('api/score/', score_detail), + path('api/dataset/', dataset_detail), + path('api/mlmodel/', mlmodel_detail), + path('api/xaimethod/', xaimethod_detail), + path('api/challenge/create', create_challenge), + path('challenge/form', challenge_form_view), # Form already didn't have it path('success/', success_view, name='success'), - path('api/challenge//', get_challenge), - path('api/challenges/', get_challenges), - # path('api/newscore/', add_score), - path('api/scores/', get_scores), + path('api/challenge/', get_challenge), + path('api/challenges', get_challenges), + path('api/scores', get_scores), path('', include('user_api.urls')), ] diff --git a/platform/backend/user_api/migrations/0001_initial.py b/platform/backend/user_api/migrations/0001_initial.py new file mode 100644 index 0000000..abeaf77 --- /dev/null +++ b/platform/backend/user_api/migrations/0001_initial.py @@ -0,0 +1,31 @@ +# Generated by Django 3.2.23 on 2026-03-25 21:47 + +from django.conf import settings +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + initial = True + + dependencies = [ + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ] + + operations = [ + migrations.CreateModel( + name='Company', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('name', models.CharField(max_length=200)), + ('description', models.TextField(blank=True, null=True)), + ('website', models.URLField(blank=True, null=True)), + ('created_at', models.DateTimeField(auto_now_add=True)), + ('user', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)), + ], + options={ + 'verbose_name_plural': 'Companies', + }, + ), + ] diff --git a/platform/frontend/app/competitions/[challengeId]/page.tsx b/platform/frontend/app/competitions/[challengeId]/page.tsx index b38b3a9..dfc94a9 100644 --- a/platform/frontend/app/competitions/[challengeId]/page.tsx +++ b/platform/frontend/app/competitions/[challengeId]/page.tsx @@ -89,7 +89,7 @@ const ChallengeDetail = ({ params }: { params: { challengeId: string } }) => { useEffect(() => { client - .get(`/api/challenge/${params.challengeId}`) + .get(`api/challenge/${params.challengeId}`) .then(({ data }) => { setChallenge(convertChallengeData(data)); }) @@ -134,7 +134,7 @@ const ChallengeDetail = ({ params }: { params: { challengeId: string } }) => { const finalMethodName = methodName?.trim() || defaultMethodName; formData.append("method_name", finalMethodName); client - .post(`/api/xai/${challenge.id}/`, formData) + .post(`api/xai/${challenge.id}`, formData) .then((res) => { const { message, score, detailed_scores }: { message: string; @@ -341,7 +341,7 @@ const ChallengeDetail = ({ params }: { params: { challengeId: string } }) => { fullWidth variant="light" component="a" - href={`http://localhost:8000/api/dataset/${challenge.id}`} + href={`${BASE_URL_API}/api/dataset/${challenge.id}`} > Download Dataset @@ -351,7 +351,7 @@ const ChallengeDetail = ({ params }: { params: { challengeId: string } }) => { fullWidth variant="light" component="a" - href={`http://localhost:8000/api/xaimethod/${challenge.id}`} + href={`${BASE_URL_API}/api/xaimethod/${challenge.id}`} > Download XAI Method Template @@ -361,7 +361,7 @@ const ChallengeDetail = ({ params }: { params: { challengeId: string } }) => { fullWidth variant="light" component="a" - href={`http://localhost:8000/api/mlmodel/${challenge.id}`} + href={`${BASE_URL_API}/api/mlmodel/${challenge.id}`} > Download ML Model diff --git a/platform/frontend/app/competitions/create/page.tsx b/platform/frontend/app/competitions/create/page.tsx index 1db4129..f9379e0 100644 --- a/platform/frontend/app/competitions/create/page.tsx +++ b/platform/frontend/app/competitions/create/page.tsx @@ -27,6 +27,7 @@ const CreateChallenge = () => { const [dataset, setDataset] = useState(null); const [mlmodel, setMlmodel] = useState(null); const [xaiMethod, setXaiMethod] = useState(null); + const [thumbnail, setThumbnail] = useState(null); const [isLoading, setIsLoading] = useState(false); const [error, setError] = useState(null); @@ -34,6 +35,7 @@ const CreateChallenge = () => { const datasetInputRef = useRef(null); const mlmodelInputRef = useRef(null); const xaiMethodInputRef = useRef(null); + const thumbnailInputRef = useRef(null); // Custom file change handler const handleFileChange = (event: ChangeEvent, setter: React.Dispatch>) => { @@ -77,13 +79,16 @@ const CreateChallenge = () => { formData.append("dataset", dataset); formData.append("mlmodel", mlmodel); formData.append("xai_method", xaiMethod); + if (thumbnail) { + formData.append("thumbnail", thumbnail); + } // Add creator if user is authenticated if (user && user.username) { formData.append("creator", user.username); } - const response = await client.post("/api/challenge/create/", formData, { + const response = await client.post("api/challenge/create", formData, { headers: { "Content-Type": "multipart/form-data", }, @@ -234,12 +239,21 @@ const CreateChallenge = () => { accept=".py,.ipynb,.txt,text/plain,text/python,application/x-python" /> + handleFileChange(e, setThumbnail)} + onClear={() => clearFileInput(thumbnailInputRef, setThumbnail)} + accept="image/png, image/jpeg, image/jpg" + /> + - + - +