SciPost Code Repository

Skip to content
Snippets Groups Projects
Commit aeab25ee authored by Jean-Sébastien Caux's avatar Jean-Sébastien Caux
Browse files

Automate compensation calculations via management command

parent e756e746
No related branches found
No related tags found
No related merge requests found
......@@ -58,7 +58,6 @@ class SubsidyForm(forms.ModelForm):
class Meta:
model = Subsidy
fields = [
"algorithm",
"organization",
"subsidy_type",
"description",
......
__copyright__ = "Copyright © Stichting SciPost (SciPost Foundation)"
__license__ = "AGPL v3"
from django.core.management.base import BaseCommand
from finances.models import Subsidy
class Command(BaseCommand):
help = "Applies compensations from Subsidies to PubFracs"
def handle(self, *args, **kwargs):
# All Subsidy-givers first compensate themselves, starting from oldest Subsidy
for subsidy in Subsidy.objects.obtained().order_by("date_from"):
subsidy.compensate_own_pubfracs()
# then their children, again starting from oldest Subsidy
for subsidy in Subsidy.objects.obtained().order_by("date_from"):
subsidy.compensate_children_pubfracs()
......@@ -4,18 +4,18 @@ from django.db import migrations
def populate_pubfracs(apps, schema_editor):
OrgPubFraction = apps.get_model("journals.OrgPubFraction")
PubFrac = apps.get_model("finances.PubFrac")
# Copy all data from OrgPubFraction to the new PubFrac
for opf in OrgPubFraction.objects.all():
pubfrac = PubFrac(
organization=opf.organization,
publication=opf.publication,
fraction=opf.fraction
)
pubfrac.save()
# OrgPubFraction = apps.get_model("journals.OrgPubFraction")
# PubFrac = apps.get_model("finances.PubFrac")
# # Copy all data from OrgPubFraction to the new PubFrac
# for opf in OrgPubFraction.objects.all():
# pubfrac = PubFrac(
# organization=opf.organization,
# publication=opf.publication,
# fraction=opf.fraction
# )
# pubfrac.save()
pass # 2024-03-20 not needed anymore: PubFracs are algorithmically calculated
class Migration(migrations.Migration):
......
# Generated by Django 4.2.10 on 2024-03-20 15:28
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
("finances", "0043_alter_pubfrac_cf_value"),
]
operations = [
migrations.RemoveField(
model_name="subsidy",
name="algorithm",
),
migrations.RemoveField(
model_name="subsidy",
name="algorithm_data",
),
]
......@@ -39,64 +39,6 @@ class Subsidy(models.Model):
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
)
......@@ -185,28 +127,37 @@ class Subsidy(models.Model):
"""
return self.amount == self.payments.aggregate(Sum("amount"))["amount__sum"]
@property
def allocatable(self):
def compensate(self, pubfracs: PubFracQuerySet):
"""
Determine whether this Subsidy can be allocated.
Allocate subsidy to uncompensated pubfracs in queryset, up to depletion.
"""
implemented_algorithms = [self.ALGORITHM_ANY_AFF, self.ALGORITHM_ALL_AFF]
return (
self.status != SUBSIDY_WITHDRAWN
and self.algorithm != self.ALGORITHM_RESERVES
and self.algorithm in implemented_algorithms
)
for pubfrac in pubfracs.uncompensated():
if pubfrac.cf_value <= self.remainder:
pubfrac.compensated_by = self
pubfrac.save()
def allocate(self):
def compensate_own_pubfracs(self):
"""
Allocate the funds according to the algorithm specific by the instance.
Compensate PubFracs with direct affiliation to Subsidy-giver.
"""
from finances.allocate import allocate_to_any_aff, allocate_to_all_aff
max_year = self.date_until.year if self.date_until else self.date_from.year
pubfracs = self.organization.pubfracs.filter(
publication__publication_date__year__gte=self.date_from.year,
publication__publication_date__year__lte=max_year,
)
self.compensate(pubfracs)
if self.algorithm == self.ALGORITHM_ANY_AFF:
allocate_to_any_aff(self)
elif self.algorithm == self.ALGORITHM_ALL_AFF:
allocate_to_all_aff(self)
def compensate_children_pubfracs(self):
"""
Compensate PubFracs with direct affiliation to Subsidy-giver's children Organizations.
"""
max_year = self.date_until.year if self.date_until else self.date_from.year
for child in self.organization.children.all():
pubfracs = child.pubfracs.filter(
publication__publication_date__year__gte=self.date_from.year,
publication__publication_date__year__lte=max_year,
)
self.compensate(pubfracs)
@property
def total_compensations(self):
......
......@@ -4,7 +4,6 @@ __license__ = "AGPL v3"
import datetime
import hashlib
import pytz
import random
import string
......@@ -17,6 +16,15 @@ from django.urls import reverse
from django_countries.fields import CountryField
from scipost.constants import TITLE_CHOICES
from scipost.fields import ChoiceArrayField
from scipost.models import Contributor
from affiliates.models import AffiliatePublication
from colleges.models import Fellowship
from finances.models import PubFrac
from journals.models import Journal, Publication
from profiles.models import Profile
from .constants import (
ORGANIZATION_TYPES,
ORGTYPE_PRIVATE_BENEFACTOR,
......@@ -27,15 +35,6 @@ from .constants import (
)
from .managers import OrganizationQuerySet
from scipost.constants import TITLE_CHOICES
from scipost.fields import ChoiceArrayField
from scipost.models import Contributor
from affiliates.models import AffiliatePublication
from colleges.models import Fellowship
from finances.models import PubFrac
from journals.models import Journal, Publication
from profiles.models import Profile
class OrganizationLogo(models.Model):
"""
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment