diff --git a/scipost_django/finances/management/commands/recompute_pubfrac_cf_value.py b/scipost_django/finances/management/commands/recompute_pubfrac_cf_value.py
new file mode 100644
index 0000000000000000000000000000000000000000..957ffd7de9158f56abb7385b834d8cd5ec932510
--- /dev/null
+++ b/scipost_django/finances/management/commands/recompute_pubfrac_cf_value.py
@@ -0,0 +1,21 @@
+__copyright__ = "Copyright © Stichting SciPost (SciPost Foundation)"
+__license__ = "AGPL v3"
+
+
+from django.core.management.base import BaseCommand
+
+from finances.models import PubFrac
+
+
+class Command(BaseCommand):
+    help = "For all PubFrac objects, recompute the cf_value field"
+
+    def handle(self, *args, **kwargs):
+        for pf in PubFrac.objects.all():
+            pf.cf_value = int(
+                pf.fraction
+                * pf.publication.get_journal().cost_per_publication(
+                    pf.publication.publication_date.year
+                )
+            )
+            pf.save()
diff --git a/scipost_django/finances/migrations/0033_populate_pubfracs.py b/scipost_django/finances/migrations/0033_populate_pubfracs.py
index 86aad1297c0f9b58a5658ce6e78dd6dbf34d99c2..d4a07844f03c1a91283da5339fa3ad8112aa0b64 100644
--- a/scipost_django/finances/migrations/0033_populate_pubfracs.py
+++ b/scipost_django/finances/migrations/0033_populate_pubfracs.py
@@ -12,7 +12,8 @@ def populate_pubfracs(apps, schema_editor):
         pubfrac = PubFrac(
             organization=opf.organization,
             publication=opf.publication,
-            fraction=opf.fraction)
+            fraction=opf.fraction
+        )
         pubfrac.save()
 
 
@@ -26,5 +27,5 @@ class Migration(migrations.Migration):
         migrations.RunPython(
             populate_pubfracs,
             reverse_code=migrations.RunPython.noop,
-            )
+        )
     ]
diff --git a/scipost_django/finances/migrations/0039_alter_pubfraccompensation_pubfrac.py b/scipost_django/finances/migrations/0039_alter_pubfraccompensation_pubfrac.py
new file mode 100644
index 0000000000000000000000000000000000000000..0fbd4b7faf590a64715a1cbf1d9eafbce4e7ea8d
--- /dev/null
+++ b/scipost_django/finances/migrations/0039_alter_pubfraccompensation_pubfrac.py
@@ -0,0 +1,23 @@
+# Generated by Django 4.2.10 on 2024-03-15 15:55
+
+from django.db import migrations, models
+import django.db.models.deletion
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ("finances", "0038_alter_pubfrac_options"),
+    ]
+
+    operations = [
+        migrations.AlterField(
+            model_name="pubfraccompensation",
+            name="pubfrac",
+            field=models.ForeignKey(
+                on_delete=django.db.models.deletion.CASCADE,
+                related_name="pubfrac_compensations",
+                to="finances.pubfrac",
+            ),
+        ),
+    ]
diff --git a/scipost_django/finances/models/pubfrac.py b/scipost_django/finances/models/pubfrac.py
index cd483acd7514f74b46235b07eb09833a1e2ace9f..b4c9015c048f8008af2038bfca8cbbf390f23708 100644
--- a/scipost_django/finances/models/pubfrac.py
+++ b/scipost_django/finances/models/pubfrac.py
@@ -42,16 +42,32 @@ class PubFrac(models.Model):
         verbose_name_plural = "PubFracs"
 
     def __str__(self):
-        return (f"{str(self.fraction)} (€{self.cf_value}) "
-                f"for {self.publication.doi_label} from {self.organization}")
+        return (
+            f"{str(self.fraction)} (€{self.cf_value}) "
+            f"for {self.publication.doi_label} from {self.organization}"
+        )
+
+    @property
+    def compensated(self):
+        """Compensated part of this PubFrac."""
+        return (
+            self.pubfrac_compensations.aggregate(models.Sum("amount"))["amount__sum"]
+            if self.pubfrac_compensations.exists()
+            else 0
+        )
+
+    @property
+    def arrears(self):
+        """Uncovered and uncompensated part of this PubFrac."""
+        return self.cf_value - self.compensated
 
 
 @receiver(pre_save, sender=PubFrac)
 def calculate_cf_value(sender, instance: PubFrac, **kwargs):
     """Calculate the cf_value field before saving."""
     instance.cf_value = int(
-        instance.fraction * instance.publication.get_journal(
-        ).cost_per_publication(
+        instance.fraction
+        * instance.publication.get_journal().cost_per_publication(
             instance.publication.publication_date.year
         )
     )
diff --git a/scipost_django/finances/models/pubfrac_compensation.py b/scipost_django/finances/models/pubfrac_compensation.py
index 41625f2d28d1683f08c63e3d4bac9dae2431562c..9539d973b17ea5463a6e54cb4e6c27044675c48e 100644
--- a/scipost_django/finances/models/pubfrac_compensation.py
+++ b/scipost_django/finances/models/pubfrac_compensation.py
@@ -18,7 +18,7 @@ class PubFracCompensation(models.Model):
 
     pubfrac = models.ForeignKey(
         "finances.PubFrac",
-        related_name="pubfracs",
+        related_name="pubfrac_compensations",
         on_delete=models.CASCADE,
     )
 
diff --git a/scipost_django/finances/models/subsidy.py b/scipost_django/finances/models/subsidy.py
index 27e281dc61bfc6b610590990ebdaee6d0a97b325..44a81a4be96b9c3a3b0c4c02034959c2601bad3c 100644
--- a/scipost_django/finances/models/subsidy.py
+++ b/scipost_django/finances/models/subsidy.py
@@ -57,10 +57,14 @@ class Subsidy(models.Model):
 
     def __str__(self):
         if self.amount_publicly_shown:
-            return (f"{self.date_from}{f' - {self.date_until}' if self.date_until else ''}: "
-                    f"€{self.amount} from {self.organization}")
-        return (f"{self.date_from}{f' - {self.date_until}' if self.date_until else ''}: "
-                f"from {self.organization}")
+            return (
+                f"{self.date_from}{f' - {self.date_until}' if self.date_until else ''}: "
+                f"€{self.amount} from {self.organization}"
+            )
+        return (
+            f"{self.date_from}{f' - {self.date_until}' if self.date_until else ''}: "
+            f"from {self.organization}"
+        )
 
     def get_absolute_url(self):
         return reverse("finances:subsidy_details", args=(self.id,))
@@ -116,3 +120,21 @@ class Subsidy(models.Model):
         Verify that there exist SubsidyPayment objects covering full amount.
         """
         return self.amount == self.payments.aggregate(Sum("amount"))["amount__sum"]
+
+    @property
+    def committed(self):
+        """
+        Sum of the amounts of all PubFracCompensations related to this Subsidy.
+        """
+        return (
+            self.pubfrac_compensations.aggregate(Sum("amount"))["amount__sum"]
+            if self.pubfrac_compensations.exists()
+            else 0
+        )
+
+    @property
+    def remainder(self):
+        """
+        Part of the Subsidy amount which hasn't been used in a PubFracCompensation.
+        """
+        return self.amount - self.committed
diff --git a/scipost_django/finances/utils.py b/scipost_django/finances/utils.py
index 8bd0f7a2602b7cb9267aebe7cc379c318e5116d3..676246545a369471a3887ce762cef973c8d46cf0 100644
--- a/scipost_django/finances/utils.py
+++ b/scipost_django/finances/utils.py
@@ -2,9 +2,58 @@ __copyright__ = "Copyright © Stichting SciPost (SciPost Foundation)"
 __license__ = "AGPL v3"
 
 
+from .models import Subsidy, PubFrac, PubFracCompensation
+
+
 def id_to_slug(id):
     return max(0, int(id) + 821)
 
 
 def slug_to_id(slug):
     return max(0, int(slug) - 821)
+
+
+def distribute_subsidy(subsidy: Subsidy, algorithm: str):
+    """
+    Allocate subsidy amount to compensations of PubFrac
+
+    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 = [
+        "PubFrac_ascribed_to_Org",
+        "full_PEX_if_author_affiliated_to_Org",
+        "PubFrac_author_affiliation_same_country_as_Org",
+        "full_PEX_author_affiliation_same_country_as_Org",
+        "full_PEX_if_pub_funding_ack_includes_Org",
+        "full_PEX_if_pub_matches_specialties",
+    ]
+
+    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:
+                pfc, created = PubFracCompensation.objects.get_or_create(
+                    subsidy=subsidy,
+                    pubfrac=pubfrac,
+                    amount=pubfrac.cf_value,
+                )
+                if created:
+                    distributed += pubfrac.cf_value
+            else:
+                break
diff --git a/scipost_django/journals/models/publication.py b/scipost_django/journals/models/publication.py
index c608d59515db6eb2ecdc2d1c959b89ecc278aeb7..888d0ca1f1143e6c4b6c117a5bd80211f1cb1fbd 100644
--- a/scipost_django/journals/models/publication.py
+++ b/scipost_django/journals/models/publication.py
@@ -25,6 +25,7 @@ from ..managers import PublicationQuerySet
 from ..validators import doi_publication_validator
 
 from common.utils import get_current_domain
+from finances.models import PubFracCompensation
 from scipost.constants import SCIPOST_APPROACHES
 from scipost.fields import ChoiceArrayField
 
@@ -419,11 +420,27 @@ class Publication(models.Model):
             "funding_statement" in self.metadata and self.metadata["funding_statement"]
         )
 
+    @property
+    def expenditure(self):
+        """The expenditure (as defined by the Journal) to produce this Publication."""
+        return self.get_journal().cost_per_publication(self.publication_date.year)
+
     @property
     def pubfracs_sum_to_1(self):
         """Checks that the support fractions sum up to one."""
         return self.pubfracs.aggregate(Sum("fraction"))["fraction__sum"] == 1
 
+    @property
+    def compensated_expenditures(self):
+        """Uncompensated part of expenditures for this Publication."""
+        qs = PubFracCompensation.objects.filter(pubfrac__publication=self)
+        return qs.aggregate(Sum("amount"))["amount__sum"]
+
+    @property
+    def uncompensated_expenditures(self):
+        """Compensated part of expenditures for this Publication."""
+        return self.expenditure - self.compensated_expenditures
+
     @property
     def citation(self):
         if self.cf_citation:
diff --git a/scipost_django/journals/templates/journals/publication_detail.html b/scipost_django/journals/templates/journals/publication_detail.html
index 29e584b1c17884e04f17b02bec7c8547ed8896fe..4ccc19f61a18fb24e7393010665173c624790df9 100644
--- a/scipost_django/journals/templates/journals/publication_detail.html
+++ b/scipost_django/journals/templates/journals/publication_detail.html
@@ -170,6 +170,70 @@
     </div>
   {% endif %}
 
+  {% if 'edadmin' in user_roles %}
+    <h3 class="mt-4">
+      PubFracs, Compensations and Arrears
+    </h3>
+    <table class="table mt-2">
+      <thead class="table-light">
+	<tr>
+	  <th>Organization</th>
+	  <th>PubFrac</th>
+	  <th>Value</th>
+	  <th>Compensation</th>
+	  <th>Arrears</th>
+	</tr>
+      </thead>
+      <tbody>
+	{% for pubfrac in publication.pubfracs.all %}
+	  <tr>
+	    <td>{{ pubfrac.organization }}</td>
+	    <td>{{ pubfrac.fraction }}</td>
+	    <td>&euro;{{ pubfrac.cf_value }}</td>
+	    <td>
+	      <ul>
+		{% for pc in pubfrac.pubfrac_compensations.all %}
+		  <li>
+		    &euro;{{ pc.amount }}&emsp;
+		    {% if pc.subsidy.organization != pubfrac.organization %}
+		      from&nbsp;ally&nbsp;
+		      <a href="{% url 'organizations:organization_detail' pk=pc.subsidy.organization.id %}">{{ pc.subsidy.organization }}</a>
+		    {% endif %}
+		    (<a href="{% url 'finances:subsidy_details' pk=pc.subsidy.id %}" target="_blank">
+		      see Subsidy details
+		    </a>)
+		  </li>
+		{% empty %}
+		  <li>No compensation</li>
+		{% endfor %}
+	      </ul>
+	    </td>
+	    {% with pubfrac.arrears as arrears %}
+	      <td class="{% if arrears == 0 %}bg-success{% elif arrears < pubfrac.cf_value %}bg-warning{% else %}bg-danger{% endif %} bg-opacity-25">
+		&euro;{{ pubfrac.arrears }}
+	      </td>
+	    {% endwith %}
+	  </tr>
+	{% endfor %}
+	<tr>
+	  <th>Totals</th>
+	  <td>1</td>
+	  <td>{{ publication.expenditure }}</td>
+	  {% if publication.uncompensated_expenditures == 0 %}
+	    <td class="bg-success bg-opacity-25">{{ publication.compensated_expenditures }}</td>
+	    <td class="bg-success bg-opacity-25">{{ publication.uncompensated_expenditures }}</td>
+	  {% elif publication.uncompensated_expenditures < publication.expenditure %}
+	    <td class="bg-warning bg-opacity-25">{{ publication.compensated_expenditures }}</td>
+	    <td class="bg-warning bg-opacity-25">{{ publication.uncompensated_expenditures }}</td>
+	  {% else %}
+	    <td class="bg-danger bg-opacity-25">{{ publication.compensated_expenditures }}</td>
+	    <td class="bg-danger bg-opacity-25">{{ publication.uncompensated_expenditures }}</td>
+	  {% endif %}
+	</tr>
+      </tbody>
+    </table>
+  {% endif %}
+
   {% if publication.status == 'draft' and perms.scipost.can_draft_publication %}
     <hr class="divider">
     <div class="row">
diff --git a/scipost_django/templates/bi/arrow-left-right.html b/scipost_django/templates/bi/arrow-left-right.html
new file mode 100644
index 0000000000000000000000000000000000000000..8bbc4ac721d19dbfae35059bf9c652108b1a0913
--- /dev/null
+++ b/scipost_django/templates/bi/arrow-left-right.html
@@ -0,0 +1,3 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-arrow-left-right" viewBox="0 0 16 16">
+  <path fill-rule="evenodd" d="M1 11.5a.5.5 0 0 0 .5.5h11.793l-3.147 3.146a.5.5 0 0 0 .708.708l4-4a.5.5 0 0 0 0-.708l-4-4a.5.5 0 0 0-.708.708L13.293 11H1.5a.5.5 0 0 0-.5.5m14-7a.5.5 0 0 1-.5.5H2.707l3.147 3.146a.5.5 0 1 1-.708.708l-4-4a.5.5 0 0 1 0-.708l4-4a.5.5 0 1 1 .708.708L2.707 4H14.5a.5.5 0 0 1 .5.5"/>
+</svg>