Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Budget for Summ AI #3065

Draft
wants to merge 1 commit into
base: develop
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
39 changes: 37 additions & 2 deletions integreat_cms/cms/forms/regions/region_form.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@
from ....nominatim_api.nominatim_api_client import NominatimApiClient
from ...constants import duplicate_pbo_behaviors, status
from ...models import LanguageTreeNode, OfferTemplate, Page, PageTranslation, Region
from ...models.regions.region import format_mt_help_text
from ...models.regions.region import format_mt_help_text, format_summ_ai_help_text
from ...utils.slug_utils import generate_unique_slug_helper
from ...utils.translation_utils import gettext_many_lazy as __
from ..custom_model_form import CustomModelForm
Expand Down Expand Up @@ -131,6 +131,17 @@ class RegionForm(CustomModelForm):
required=False,
)

summ_ai_midyear_start_enabled = forms.BooleanField(
required=False,
label=_("Budget year start differs from the renewal date"),
help_text=__(
_("Enable to set starting date differing from the renewal date."),
format_summ_ai_help_text(
_("Budget will be set as a monthly fraction of {} credits")
),
),
)

mt_midyear_start_enabled = forms.BooleanField(
required=False,
label=_("Budget year start differs from the renewal date"),
Expand Down Expand Up @@ -194,6 +205,9 @@ class Meta:
"timezone",
"fallback_translations_enabled",
"summ_ai_enabled",
"summ_ai_renewal_month",
"summ_ai_midyear_start_enabled",
"summ_ai_midyear_start_month",
"hix_enabled",
"mt_renewal_month",
"mt_addon_booked",
Expand Down Expand Up @@ -231,6 +245,9 @@ def __init__(self, *args: Any, **kwargs: Any) -> None:
self.fields["summ_ai_enabled"].disabled = True
if not settings.TEXTLAB_API_ENABLED and not self.instance.hix_enabled:
self.fields["hix_enabled"].disabled = True
self.fields["summ_ai_midyear_start_enabled"].initial = (
self.instance.summ_ai_midyear_start_month is not None
)
self.fields["mt_midyear_start_enabled"].initial = (
self.instance.mt_midyear_start_month is not None
)
Expand Down Expand Up @@ -348,7 +365,25 @@ def clean(self) -> dict[str, Any]:
else:
cleaned_data["matomo_id"] = None

# If MT budget year differs from renewal date is set, make sure a budget year start date is set
# If Summ AI budget year differs from the set renewal date, make sure a budget year start date is set
if (
cleaned_data["summ_ai_midyear_start_enabled"]
and cleaned_data["summ_ai_midyear_start_month"] is None
):
self.add_error(
"summ_ai_midyear_start_month",
_(
"Please provide a valid budget year start date for simplified language translation."
),
)
elif (
not cleaned_data["summ_ai_midyear_start_enabled"]
or cleaned_data["summ_ai_midyear_start_month"]
== cleaned_data["summ_ai_renewal_month"]
):
cleaned_data["summ_ai_midyear_start_month"] = None

# If MT budget year differs from the set renewal date, make sure a budget year start date is set
if (
cleaned_data["mt_midyear_start_enabled"]
and cleaned_data["mt_midyear_start_month"] is None
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
# Generated by Django 4.2.13 on 2024-08-22 10:48

from django.db import migrations, models


class Migration(migrations.Migration):
"""
Add fields for tracking SUMM.AI budget
"""

dependencies = [
("cms", "0102_alter_contact_poi"),
]

operations = [
migrations.AddField(
model_name="region",
name="summ_ai_budget_used",
field=models.PositiveIntegerField(default=0, verbose_name="used budget"),
),
migrations.AddField(
model_name="region",
name="summ_ai_midyear_start_month",
field=models.PositiveIntegerField(
blank=True,
choices=[
(0, "January"),
(1, "February"),
(2, "March"),
(3, "April"),
(4, "May"),
(5, "June"),
(6, "July"),
(7, "August"),
(8, "September"),
(9, "October"),
(10, "November"),
(11, "December"),
],
default=None,
help_text="Month from which SUMM.AI was booked",
null=True,
verbose_name="Budget year start date for simplified language translation",
),
),
migrations.AddField(
model_name="region",
name="summ_ai_renewal_month",
field=models.PositiveIntegerField(
choices=[
(0, "January"),
(1, "February"),
(2, "March"),
(3, "April"),
(4, "May"),
(5, "June"),
(6, "July"),
(7, "August"),
(8, "September"),
(9, "October"),
(10, "November"),
(11, "December"),
],
default=0,
help_text="Budget usage will be reset on the 1st of the month",
verbose_name="Credits renewal date for simplified language translation",
),
),
]
62 changes: 62 additions & 0 deletions integreat_cms/cms/models/regions/region.py
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,19 @@
logger = logging.getLogger(__name__)


@keep_lazy_text
def format_summ_ai_help_text(help_text: Promise) -> str:
"""
Helper function to lazily format help text with number separators

:param help_text: MT field help text to format
:return: formatted help text
"""
return help_text.format(
floatformat(settings.SUMM_AI_CREDITS, "g"),
)


@keep_lazy_text
def format_mt_help_text(help_text: Promise) -> str:
"""
Expand Down Expand Up @@ -358,6 +371,27 @@ class Region(AbstractBaseModel):
),
)

summ_ai_midyear_start_month = models.PositiveIntegerField(
default=None,
blank=True,
null=True,
choices=months.CHOICES,
verbose_name=_("Budget year start date for simplified language translation"),
help_text=_("Month from which SUMM.AI was booked"),
)

summ_ai_renewal_month = models.PositiveIntegerField(
choices=months.CHOICES,
default=months.JANUARY,
verbose_name=_("Credits renewal date for simplified language translation"),
help_text=_("Budget usage will be reset on the 1st of the month"),
)

summ_ai_budget_used = models.PositiveIntegerField(
default=0,
verbose_name=_("used budget"),
)

mt_renewal_month = models.PositiveIntegerField(
choices=months.CHOICES,
default=months.JANUARY,
Expand Down Expand Up @@ -805,6 +839,25 @@ def mt_budget(self) -> int:
multiplier = (months_difference % 12) / 12
return int(multiplier * settings.MT_CREDITS_ADDON + settings.MT_CREDITS_FREE)

@property
def summ_ai_budget(self) -> int:
"""
Calculate the maximum translation credit budget (number of words) for simplified translations

:return: The region's total budget for simplified translations
"""
# All regions which did book the add-on, but not mid-year, get the add-on credits
if not self.summ_ai_midyear_start_month:
return settings.SUMM_AI_CREDITS
# All regions which booked the add-on in mid-year get a fraction of the add-on credits
# Calculate how many months lie between the renewal month and the start month of the add-on
months_difference = (
self.summ_ai_renewal_month - self.summ_ai_midyear_start_month
)
# Calculate the available fraction of the add-on
multiplier = (months_difference % 12) / 12
return int(multiplier * settings.SUMM_AI_CREDITS)

@property
def mt_budget_remaining(self) -> int:
"""
Expand All @@ -814,6 +867,15 @@ def mt_budget_remaining(self) -> int:
"""
return max(0, self.mt_budget - self.mt_budget_used)

@property
def summ_ai_budget_remaining(self) -> int:
"""
Calculate the remaining translation credit budget (number of words) for simplified translations

:return: The region's remaining budget for simplified translations
"""
return max(0, self.summ_ai_budget - self.summ_ai_budget_used)

@cached_property
def backend_edit_link(self) -> str:
"""
Expand Down
61 changes: 60 additions & 1 deletion integreat_cms/cms/templates/regions/region_form.html
Original file line number Diff line number Diff line change
Expand Up @@ -275,7 +275,9 @@ <h3 class="heading font-bold text-black">
</div>
</div>
<div class="pt-2">
<!-- Options related to SUMM.AI -->
<label>
{% trans "Simplified language machine translation" %}
</label>
{% render_field form.summ_ai_enabled class+="inline-block" %}
<label for="{{ form.summ_ai_enabled.id_for_label }}" class="!inline">
{{ form.summ_ai_enabled.label }}
Expand All @@ -291,6 +293,63 @@ <h3 class="heading font-bold text-black">
</p>
</div>
{% endif %}
{% if form.instance.id %}
<div class="max-w-xs">
<div class="flex flex-wrap justify-between">
<label class="secondary m-0">
{% trans "Current total budget" %}:
</label>
<p>
{{ form.instance.summ_ai_budget|intcomma }}
</p>
</div>
<div class="flex flex-wrap justify-between">
<label class="secondary m-0">
{% trans "Already consumed" %}:
</label>
<p>
{{ form.instance.summ_ai_budget_used|intcomma }}
</p>
</div>
<div class="flex flex-wrap justify-between">
<label class="secondary m-0">
{% trans "Remaining words" %}:
</label>
<p>
{{ form.instance.summ_ai_budget_remaining|intcomma }}
</p>
</div>
</div>
{% endif %}
<label for="{{ form.summ_ai_renewal_month.id_for_label }}" class="secondary">
{{ form.summ_ai_renewal_month.label }}
</label>
{% render_field form.summ_ai_renewal_month %}
<div class="help-text">
{{ form.summ_ai_renewal_month.help_text }}
</div>
<div id="summ-ai-toggle-div"
{% if form.summ_ai_enabled.field.disabled %}class="hidden"{% endif %}>
{% render_field form.summ_ai_midyear_start_enabled %}
<label for="{{ form.summ_ai_midyear_start_enabled.id_for_label }}"
class="secondary">
{{ form.summ_ai_midyear_start_enabled.label }}
</label>
<div class="help-text">
{{ form.summ_ai_midyear_start_enabled.help_text }}
</div>
<div id="summ-ai-renewal-toggle-div"
{% if not form.instance.summ_ai_midyear_start_enabled %}class="hidden"{% endif %}>
<label for="{{ form.summ_ai_midyear_start_month.id_for_label }}"
class="secondary">
{{ form.summ_ai_midyear_start_month.label }}
</label>
{% render_field form.summ_ai_midyear_start_month %}
<div class="help-text">
{{ form.summ_ai_midyear_start_month.help_text }}
</div>
</div>
</div>
</div>
<div>
<!-- Options related to HIX text analysis -->
Expand Down
10 changes: 10 additions & 0 deletions integreat_cms/core/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -979,6 +979,9 @@
#: This is ``True`` if SUMM_AI_API_KEY is set, ``False`` otherwise.
SUMM_AI_ENABLED: bool = bool(SUMM_AI_API_KEY)

#: An integer specifying the number of translation credits for simplified translations that can be bought as an add-on
SUMM_AI_CREDITS: Final[int] = int(os.environ.get("SUMM_AI_CREDITS", 10_000))

#: Whether requests to the SUMM.AI are done with the ``is_test`` flag
SUMM_AI_TEST_MODE: Final[bool] = strtobool(
os.environ.get("INTEGREAT_CMS_SUMM_AI_TEST_MODE", str(DEBUG))
Expand Down Expand Up @@ -1041,6 +1044,13 @@
).splitlines()
]

#: A floating point that specifies the percentage of SUMM_AI_CREDITS used as a soft margin
SUMM_AI_SOFT_MARGIN_FRACTION: Final[float] = float(
os.environ.get("INTEGREAT_CMS_SUMM_AI_SOFT_MARGIN", MT_SOFT_MARGIN_FRACTION)
)

#: The actual number of words which are used as soft margin
SUMM_AI_SOFT_MARGIN: Final[int] = int(SUMM_AI_SOFT_MARGIN_FRACTION * SUMM_AI_CREDITS)

################
# STATIC FILES #
Expand Down
9 changes: 3 additions & 6 deletions integreat_cms/core/utils/machine_translation_api_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,13 +17,11 @@

from ...cms.models import (
Event,
EventTranslation,
Page,
PageTranslation,
POI,
POITranslation,
Region,
)
from ...cms.models.abstract_content_translation import AbstractContentTranslation

from .word_count import word_count

Expand Down Expand Up @@ -79,16 +77,15 @@ def translate_object(self, obj: Event | Page | POI, language_slug: str) -> None:
def check_usage(
self,
region: Region,
source_translation: EventTranslation | (PageTranslation | POITranslation),
source_translation: str | AbstractContentTranslation,
) -> tuple[bool, int]:
"""
This function checks if the attempted translation would exceed the region's word limit

:param region: region for which to check usage
:param source_translation: single content object
:return: translation would exceed limit, region budget, attempted translation word count
:return: translation would exceed limit, word count of attempted translation
"""

words = word_count(source_translation)

# Check if translation would exceed MT usage limit
Expand Down
Loading
Loading