diff --git a/scipost_django/finances/allocate.py b/scipost_django/finances/allocate.py
new file mode 100644
index 0000000000000000000000000000000000000000..124ad376684dec5fc4ac1b993ef0077156a7f3fd
--- /dev/null
+++ b/scipost_django/finances/allocate.py
@@ -0,0 +1,52 @@
+__copyright__ = "Copyright © Stichting SciPost (SciPost Foundation)"
+__license__ = "AGPL v3"
+
+
+from .models import Subsidy, PubFrac
+
+
+"""
+Allocate subsidy amount to compensations of PubFrac or coverage of expenditures.
+
+Algorithm choices:
+* [any_aff] Any PubFrac with affiliation to org
+* [any_ctry] Any PubFrac with an affiliation in given list of countries
+* [any_orgs] Any PubFrac with an affiliation in given list of orgs
+  (helps handling interrelated orgs)
+* [any_specs] Any PubFrac of publication in given list of specialties
+* [all_fund] All PubFracs of publication acknowledging org in Funders
+* [all ctry] All PubFracs of publications having at least one affiliation
+  in given list of countries
+* [all_spec] All PubFracs of publication in given list of specialties
+* [all_aff] All PubFracs of publication with at least one author with affiliation to org
+
+Our highest priority is for individual organizations to take responsibility
+for their publishing, so the preferred algorithm is aff_org.
+
+The algorithms are implemented in the following order,
+(decreasing level of specificity):
+* [any_aff]
+* [any_ctry]
+* [all_fund]
+* [all_ctry]
+* [all_spec]
+* [all_aff]
+"""
+
+
+def allocate_to_any_aff(subsidy: Subsidy):
+    """
+    Allocate the Subsidy to PubFracs with affiliation to Subsidy-giver.
+    """
+    max_year = subsidy.date_until.year if subsidy.date_until else subsidy.date_from.year
+    uncompensated_pubfracs = PubFrac.objects.filter(
+        organization=subsidy.organization,
+        publication__publication_date__year__gte=subsidy.date_from.year,
+        publication__publication_date__year__lte=max_year,
+        compensated_by__isnull=True,
+    )
+    print(f"{uncompensated_pubfracs.count() = }")
+    for pubfrac in uncompensated_pubfracs.all():
+        if pubfrac.cf_value <= subsidy.remainder:
+            pubfrac.compensated_by = subsidy
+            pubfrac.save()
diff --git a/scipost_django/finances/forms.py b/scipost_django/finances/forms.py
index 3cfa2f39514c762df446dae97c734e0d98984d25..0dbc90b9d112b9f40912475faf98bccce58b22c8 100644
--- a/scipost_django/finances/forms.py
+++ b/scipost_django/finances/forms.py
@@ -58,6 +58,7 @@ class SubsidyForm(forms.ModelForm):
     class Meta:
         model = Subsidy
         fields = [
+            "algorithm",
             "organization",
             "subsidy_type",
             "description",
diff --git a/scipost_django/finances/migrations/0042_subsidy_algorithm_subsidy_algorithm_data.py b/scipost_django/finances/migrations/0042_subsidy_algorithm_subsidy_algorithm_data.py
new file mode 100644
index 0000000000000000000000000000000000000000..8625cb11c484501c07ab1d80d87006728aedf9c0
--- /dev/null
+++ b/scipost_django/finances/migrations/0042_subsidy_algorithm_subsidy_algorithm_data.py
@@ -0,0 +1,62 @@
+# Generated by Django 4.2.10 on 2024-03-16 12:56
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ("finances", "0041_remove_publicationexpenditurecoverage_publication_and_more"),
+    ]
+
+    operations = [
+        migrations.AddField(
+            model_name="subsidy",
+            name="algorithm",
+            field=models.CharField(
+                choices=[
+                    ("any_aff", "Any PubFrac with affiliation to org"),
+                    (
+                        "any_ctry",
+                        "Any PubFrac with an affiliation in given list of countries",
+                    ),
+                    (
+                        "any_orgs",
+                        "Any PubFrac with an affiliation in given list of orgs",
+                    ),
+                    (
+                        "any_spec",
+                        "Any PubFrac of publication in given list of specialties",
+                    ),
+                    (
+                        "all_aff",
+                        "All PubFracs of publication with at least one author with affiliation to org",
+                    ),
+                    (
+                        "all_ctry",
+                        "All PubFracs of publications having at least one affiliation in given list of countries",
+                    ),
+                    (
+                        "all_orgs",
+                        "All PubFracs of publications having at least one affiliation in given list of orgs",
+                    ),
+                    (
+                        "all_spec",
+                        "All PubFracs of publication in given list of specialties",
+                    ),
+                    (
+                        "all_fund",
+                        "All PubFracs of publication acknowledging org in Funders",
+                    ),
+                    ("reserves", "Allocate to reserves fund"),
+                ],
+                default="reserves",
+                max_length=32,
+            ),
+        ),
+        migrations.AddField(
+            model_name="subsidy",
+            name="algorithm_data",
+            field=models.JSONField(default=dict),
+        ),
+    ]
diff --git a/scipost_django/finances/models/subsidy.py b/scipost_django/finances/models/subsidy.py
index e62a00cca4aabd819adf8e307204da6e3b2d03e5..c416a05d6f5a9bd76bf65771741d43bf9ce09d4d 100644
--- a/scipost_django/finances/models/subsidy.py
+++ b/scipost_django/finances/models/subsidy.py
@@ -28,11 +28,69 @@ class Subsidy(models.Model):
     * a donation
 
     The date_from field represents the date at which the Subsidy was formally agreed,
-    or (e.g. for Sponsorship Agreements) the date at which the agreement enters into force.
-    The date_until field is optional, and represents (where applicable) the date
+    or (e.g. for Sponsorship Agreements) the date at which the agreement enters into
+    force. The date_until field is optional, and represents (where applicable) the date
     after which the object of the Subsidy is officially terminated.
     """
 
+    ALGORITHM_ANY_AFF = "any_aff"
+    ALGORITHM_ANY_CTRY = "any_ctry"
+    ALGORITHM_ANY_ORGS = "any_orgs"
+    ALGORITHM_ANY_SPEC = "any_spec"
+    ALGORITHM_ALL_AFF = "all_aff"
+    ALGORITHM_ALL_CTRY = "all_ctry"
+    ALGORITHM_ALL_ORGS = "all_orgs"
+    ALGORITHM_ALL_SPEC = "all_spec"
+    ALGORITHM_ALL_FUND = "all_fund"
+    ALGORITHM_RESERVES = "reserves"
+    ALGORITHM_CHOICES = (
+        (ALGORITHM_ANY_AFF, "Any PubFrac with affiliation to org"),
+        (
+            ALGORITHM_ANY_CTRY,
+            "Any PubFrac with an affiliation in given list of countries",
+        ),
+        (ALGORITHM_ANY_ORGS, "Any PubFrac with an affiliation in given list of orgs"),
+        (
+            ALGORITHM_ANY_SPEC,
+            "Any PubFrac of publication in given list of specialties",
+        ),
+        (
+            ALGORITHM_ALL_AFF,
+            (
+                "All PubFracs of publication with at least one author "
+                "with affiliation to org"
+            ),
+        ),
+        (
+            ALGORITHM_ALL_CTRY,
+            (
+                "All PubFracs of publications having at least one affiliation "
+                "in given list of countries"
+            ),
+        ),
+        (
+            ALGORITHM_ALL_ORGS,
+            (
+                "All PubFracs of publications having at least "
+                "one affiliation in given list of orgs"
+            ),
+        ),
+        (
+            ALGORITHM_ALL_SPEC,
+            "All PubFracs of publication in given list of specialties",
+        ),
+        (
+            ALGORITHM_ALL_FUND,
+            "All PubFracs of publication acknowledging org in Funders",
+        ),
+        (ALGORITHM_RESERVES, "Allocate to reserves fund"),
+    )
+    algorithm = models.CharField(
+        max_length=32,
+        choices=ALGORITHM_CHOICES,
+        default=ALGORITHM_RESERVES,
+    )
+    algorithm_data = models.JSONField(default=dict)
     organization = models.ForeignKey["Organization"](
         "organizations.Organization", on_delete=models.CASCADE
     )
@@ -121,6 +179,15 @@ class Subsidy(models.Model):
         """
         return self.amount == self.payments.aggregate(Sum("amount"))["amount__sum"]
 
+    def allocate(self):
+        """
+        Allocate the funds according to the algorithm specific by the instance.
+        """
+        from finances.allocate import allocate_to_any_aff
+
+        if self.algorithm == self.ALGORITHM_ANY_AFF:
+            allocate_to_any_aff(self)
+
     @property
     def total_compensations(self):
         """
diff --git a/scipost_django/finances/templates/finances/_subsidy_details.html b/scipost_django/finances/templates/finances/_subsidy_details.html
index 92d363fc9c1901329c603819eb8c3db623d20b26..04f7c3965639775c48cdc6f2868620408b206f0d 100644
--- a/scipost_django/finances/templates/finances/_subsidy_details.html
+++ b/scipost_django/finances/templates/finances/_subsidy_details.html
@@ -35,6 +35,10 @@
 	</tr>
       {% endif %}
       {% if perms.scipost.can_manage_subsidies %}
+	<tr>
+	  <td>Allocation algorithm</td>
+	  <td>{{ subsidy.get_algorithm_display }}</td>
+	</tr>
 	<tr>
 	  <td>Renewable?</td><td>{% if subsidy.renewable == True %}Yes, renewal action date: <span class="bg-{{ subsidy.renewal_action_date_color_class }}">{{ subsidy.renewal_action_date }}</span>{% elif subsidy.renewable == None %}Undetermined [please update]{% else %}No{% endif %}</td>
 	</tr>
@@ -111,6 +115,11 @@
   {% if subsidy.amount_publicly_shown %}
 
     <h3 class="highlight">Expenditures compensated by this Subsidy</h3>
+
+    {% if 'edadmin' in user_roles %}
+      <a class="btn btn-primary" href="{% url 'finances:allocate_subsidy' subsidy_id=subsidy.id %}">Allocate this Subsidy</a>
+    {% endif %}
+
     <div class="row">
       <div class="col-lg-6">
 	<table class="table mt-2 caption-top">
@@ -150,7 +159,7 @@
 	      <td>&euro;{{ subsidy.amount }}</td>
 	    </tr>
 	    <tr>
-	      <th>Compensations</th>
+	      <th>PubFrac Compensations</th>
 	      <td>&euro;{{ subsidy.total_compensations }}</td>
 	    </tr>
 	    <tr class="bg-secondary bg-opacity-10">
diff --git a/scipost_django/finances/urls.py b/scipost_django/finances/urls.py
index 45964d99921d483efddac135f79108ee38fbcfd0..ae25f1d0e26d23b433f35b1a876dfabeffb5b052 100644
--- a/scipost_django/finances/urls.py
+++ b/scipost_django/finances/urls.py
@@ -41,6 +41,11 @@ urlpatterns = [
                     "<int:subsidy_id>/",
                     include(
                         [
+                            path(
+                                "allocate",
+                                views.allocate_subsidy,
+                                name="allocate_subsidy",
+                            ),
                             path(
                                 "_hx_subsidy_finadmin_details",
                                 views._hx_subsidy_finadmin_details,
diff --git a/scipost_django/finances/utils.py b/scipost_django/finances/utils.py
index ca818cf3c2ad34bf802dffcefb633637f89a38e7..0a5c77d71990a702d70408c406e5d18866af55e0 100644
--- a/scipost_django/finances/utils.py
+++ b/scipost_django/finances/utils.py
@@ -11,46 +11,3 @@ def id_to_slug(id):
 
 def slug_to_id(slug):
     return max(0, int(slug) - 821)
-
-
-def allocate_subsidy(subsidy: Subsidy, algorithm: str):
-    """
-    Allocate subsidy amount to compensations of PubFrac or coverage of expenditures.
-
-    Algorithm choices:
-    * any PubFrac ascribed to org from affiliations
-    * full PEX of publication having at least one author affiliated to org
-    * any PubFrac involving an affiliation with same country as org
-    * full PEX of publication having at least one author affiliation with same country as org
-    * full PEX of publication acknowledging org in Funders
-    * full PEX of publication in specialties specified by Subsidy
-    """
-
-    algorithms = [
-        "compensate_PubFrac_related_to_Org",
-        "cover_full_PEX_if_PubFrac_related_to_Org",
-        "compensate_PubFrac_if_author_affiliation_same_country_as_Org",
-        "cover_full_PEX_if_author_affiliation_same_country_as_Org",
-        "cover_full_PEX_if_pub_funding_ack_includes_Org",
-        "cover_full_PEX_if_pub_matches_specialties",
-        "allocate_to_reserve_fund",
-    ]
-
-    if algorithm is "PubFrac_ascribed_to_Org":
-        max_year = (
-            subsidy.date_until.year if subsidy.date_until else subsidy.date_from.year
-        )
-        pubfracs = PubFrac.objects.filter(
-            organization=subsidy.organization,
-            publication__publication_date__year__gte=subsidy.date_from.year,
-            publication__publication_date__year__lte=max_year,
-        )
-        distributed = 0
-        for pubfrac in pubfracs.all():
-            print(f"{distributed = };\tadding {pubfrac = }")
-            if pubfrac.cf_value <= subsidy.remainder:
-                pubfrac.compensated_by = subsidy
-                pubfrac.save()
-                distributed += pubfrac.cf_value
-            else:
-                break
diff --git a/scipost_django/finances/views.py b/scipost_django/finances/views.py
index 0e21f066ddd87075702160b269c13882fdbcfe30..ceb0db23e18d5273c4427ed4d356e58cb82318f4 100644
--- a/scipost_django/finances/views.py
+++ b/scipost_django/finances/views.py
@@ -24,7 +24,7 @@ from django.contrib.auth.decorators import login_required, permission_required
 from django.contrib.auth.mixins import LoginRequiredMixin
 from django.core.exceptions import PermissionDenied
 from django.core.paginator import Paginator
-from django.urls import reverse_lazy
+from django.urls import reverse, reverse_lazy
 from django.utils import timezone
 from django.http import Http404, HttpResponse
 from django.shortcuts import get_object_or_404, render, redirect
@@ -420,6 +420,13 @@ def _hx_subsidy_list(request):
     return render(request, "finances/_hx_subsidy_list.html", context)
 
 
+@permission_required("scipost.can_manage_subsidies", raise_exception=True)
+def allocate_subsidy(request, subsidy_id:int):
+    subsidy = get_object_or_404(Subsidy, pk=subsidy_id)
+    subsidy.allocate()
+    return redirect(reverse("finances:subsidy_details", kwargs={"pk": subsidy.id}))
+
+
 @permission_required("scipost.can_manage_subsidies", raise_exception=True)
 def _hx_subsidy_finadmin_details(request, subsidy_id: int):
     subsidy = get_object_or_404(Subsidy, pk=subsidy_id)