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

Feature/243 calculate sponsored percentage #255

Merged
Merged
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
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
# flake8: noqa: S311 -- Accept random int generation for database seeding

import random
from collections.abc import Iterable
from datetime import timedelta
from pathlib import Path

Expand Down Expand Up @@ -29,6 +30,7 @@
Role,
Site,
Siteadmin,
Sitetreespecies,
Sitetype,
SitetypeInternationalization,
TreespeciestypeInternationalization,
Expand Down Expand Up @@ -269,12 +271,32 @@ def create_sponsor_for_batch():
)


def create_species_for_site(site: Site, batches: Iterable[Batch]):
already_added_tree_type: dict[int, Sitetreespecies] = {}
for batch in batches:
for batch_specie in BatchSpecies.objects.filter(batch=batch):
quantity = batch_specie.quantity
# Add more to the site's quantity than the batches' quantity
# so they don't appear at 100%. Except Canopeum, let's use it as a 100% example
if site.name != "Canopeum":
quantity += random.randint(0, 50)
if batch_specie.tree_type.pk in already_added_tree_type:
site_tree_specie = already_added_tree_type[batch_specie.tree_type.pk]
site_tree_specie.quantity += quantity
site_tree_specie.save()
else:
site_tree_specie = Sitetreespecies.objects.create(
site=site, tree_type=batch_specie.tree_type, quantity=quantity
)
already_added_tree_type[batch_specie.tree_type.pk] = site_tree_specie


def create_batches_for_site(site):
num_batches = random.randint(3, 8)
for i in range(num_batches):
number_of_seed = random.randint(50, 200)
plant_count = random.randint(0, number_of_seed)
survived_count = random.randint(0, plant_count)
survived_count = random.randint(100, 200)
replace_count = random.randint(0, 50)

sponsor = create_sponsor_for_batch()

Expand All @@ -284,9 +306,8 @@ def create_batches_for_site(site):
size=random.randint(20, 150),
sponsor=sponsor,
soil_condition="Good",
plant_count=plant_count,
survived_count=survived_count,
replace_count=plant_count - survived_count,
replace_count=replace_count,
total_number_seed=number_of_seed,
total_propagation=random.randint(0, number_of_seed),
)
Expand All @@ -297,6 +318,7 @@ def create_batches_for_site(site):
batch=batch,
fertilizer_type=fertilizer_type,
)
yield batch


class Command(BaseCommand):
Expand Down Expand Up @@ -352,8 +374,7 @@ def handle(self, *args, **kwargs):
self.create_roles()
self.create_users()

self.create_canopeum_site()
self.create_other_sites()
self.create_sites()

self.create_siteadmins()
self.stdout.write(self.style.SUCCESS("Data Generated"))
Expand Down Expand Up @@ -479,8 +500,9 @@ def create_users(self):
role=Role.objects.get(name="User"),
)

def create_canopeum_site(self):
site = Site.objects.create(
def create_sites(self):
# Canopeum's site
site1 = Site.objects.create(
name="Canopeum",
is_public=True,
site_type=Sitetype.objects.get(
Expand Down Expand Up @@ -511,15 +533,16 @@ def create_canopeum_site(self):
link="https://www.canopeum-pos.com",
),
)
create_batches_for_site(site)
batches = create_batches_for_site(site1)
create_species_for_site(site1, batches)
post = Post.objects.create(
site=site,
site=site1,
body="The season is officially started; "
+ "new plants are starting to grow and our volunteers are very dedicated!",
share_count=5,
)
post.media.add(*Asset.objects.filter(asset__contains="canopeum_post_img"))
create_posts_for_site(site)
create_posts_for_site(site1)
Comment.objects.create(
body="Wow, I'm very excited to join the team!",
user=User.objects.get(email="[email protected]"),
Expand All @@ -530,8 +553,8 @@ def create_canopeum_site(self):
user=User.objects.get(email="[email protected]"),
post=post,
)
# end of Canopeum's site

def create_other_sites(self):
site_2 = Site.objects.create(
name="Maple Grove Retreat",
is_public=True,
Expand Down Expand Up @@ -564,7 +587,8 @@ def create_other_sites(self):
link="https://www.maplegroveretreat.com/events/maple-syrup-festival",
),
)
create_batches_for_site(site_2)
batches = create_batches_for_site(site_2)
create_species_for_site(site_2, batches)
create_posts_for_site(site_2)

site_3 = Site.objects.create(
Expand Down Expand Up @@ -600,7 +624,8 @@ def create_other_sites(self):
link="https://www.lakesideoasis.com/winter-getaway",
),
)
create_batches_for_site(site_3)
batches = create_batches_for_site(site_3)
create_species_for_site(site_3, batches)
create_posts_for_site(site_3)

site_4 = Site.objects.create(
Expand Down Expand Up @@ -636,7 +661,8 @@ def create_other_sites(self):
link="https://www.evergreentrail.com/guided-walks",
),
)
create_batches_for_site(site_4)
batches = create_batches_for_site(site_4)
create_species_for_site(site_4, batches)
create_posts_for_site(site_4)

def create_siteadmins(self):
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# Generated by Django 5.1 on 2024-09-26 20:15
# Generated by Django 5.1 on 2024-09-30 16:29

import canopeum_backend.models
import django.contrib.auth.models
Expand Down Expand Up @@ -232,7 +232,6 @@ class Migration(migrations.Migration):
('name', models.TextField(blank=True, null=True)),
('size', models.IntegerField(blank=True, null=True)),
('soil_condition', models.TextField(blank=True, null=True)),
('plant_count', models.IntegerField(blank=True, null=True)),
('survived_count', models.IntegerField(blank=True, null=True)),
('replace_count', models.IntegerField(blank=True, null=True)),
('total_number_seed', models.IntegerField(blank=True, null=True)),
Expand Down Expand Up @@ -316,9 +315,9 @@ class Migration(migrations.Migration):
name='Sitetreespecies',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('quantity', models.IntegerField(blank=True, null=True)),
('site', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='canopeum_backend.site')),
('tree_type', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.DO_NOTHING, to='canopeum_backend.treetype')),
('quantity', models.IntegerField()),
('site', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='canopeum_backend.site')),
('tree_type', models.ForeignKey(on_delete=django.db.models.deletion.DO_NOTHING, to='canopeum_backend.treetype')),
],
options={
'constraints': [models.UniqueConstraint(fields=('site', 'tree_type'), name='unique_tree_species_per_site')],
Expand Down
26 changes: 22 additions & 4 deletions canopeum_backend/canopeum_backend/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -156,6 +156,21 @@ class Site(models.Model):
announcement = models.ForeignKey(Announcement, models.SET_NULL, blank=True, null=True)
image = models.ForeignKey(Asset, models.SET_NULL, blank=True, null=True)

def get_plant_count(self) -> int:
site_species = Sitetreespecies.objects.filter(site=self)
return sum(specie.quantity for specie in site_species)

def get_sponsor_progress(self) -> float:
total_plant_count = self.get_plant_count()
if total_plant_count == 0:
return 0

batches = Batch.objects.filter(site=self)
sponsored_plant_count = sum(batch.plant_count() for batch in batches)

# Note: We don't cap the progress at 100% so it's obvious if there's a data issue
return sponsored_plant_count / total_plant_count * 100

@override
def delete(self, using=None, keep_parents=False):
# Coordinate
Expand Down Expand Up @@ -191,7 +206,6 @@ class Batch(models.Model):
sponsor = models.ForeignKey(BatchSponsor, models.CASCADE)
size = models.IntegerField(blank=True, null=True)
soil_condition = models.TextField(blank=True, null=True)
plant_count = models.IntegerField(blank=True, null=True)
survived_count = models.IntegerField(blank=True, null=True)
replace_count = models.IntegerField(blank=True, null=True)
total_number_seed = models.IntegerField(blank=True, null=True)
Expand All @@ -218,6 +232,10 @@ def add_supported_specie_by_id(self, pk: int):
tree_type = Treetype.objects.get(pk=pk)
return BatchSupportedSpecies.objects.create(tree_type=tree_type, batch=self)

def plant_count(self) -> int:
batch_species = BatchSpecies.objects.filter(batch=self)
return sum(specie.quantity for specie in batch_species)


class FertilizertypeInternationalization(models.Model):
en = models.TextField(db_column="EN", blank=True, null=True)
Expand Down Expand Up @@ -362,9 +380,9 @@ class SiteFollower(models.Model):


class Sitetreespecies(models.Model):
site = models.ForeignKey(Site, models.CASCADE, blank=True, null=True)
tree_type = models.ForeignKey(Treetype, models.DO_NOTHING, blank=True, null=True)
quantity = models.IntegerField(blank=True, null=True)
site = models.ForeignKey(Site, models.CASCADE)
tree_type = models.ForeignKey(Treetype, models.DO_NOTHING)
quantity = models.IntegerField()

class Meta:
constraints = (
Expand Down
28 changes: 14 additions & 14 deletions canopeum_backend/canopeum_backend/serializers.py
Original file line number Diff line number Diff line change
Expand Up @@ -543,9 +543,9 @@ class SiteSummarySerializer(serializers.ModelSerializer[Site]):
site_type = SiteTypeSerializer()
coordinate = CoordinatesSerializer()
plant_count = serializers.SerializerMethodField()
sponsor_progress = serializers.SerializerMethodField()
survived_count = serializers.SerializerMethodField()
propagation_count = serializers.SerializerMethodField()
progress = serializers.SerializerMethodField()
admins = SiteAdminSerializer(source="siteadmin_set", many=True)
batches = serializers.SerializerMethodField()

Expand All @@ -557,26 +557,26 @@ class Meta:
"coordinate",
"site_type",
"plant_count",
"sponsor_progress",
"survived_count",
"propagation_count",
"visitor_count",
"progress",
"admins",
"batches",
)

def get_plant_count(self, obj) -> int:
return random.randint(100, 200) # noqa: S311
def get_plant_count(self, obj: Site) -> int:
return obj.get_plant_count()

def get_sponsor_progress(self, obj: Site) -> float:
return obj.get_sponsor_progress()

def get_survived_count(self, obj) -> int:
return random.randint(50, 100) # noqa: S311

def get_propagation_count(self, obj) -> int:
return random.randint(5, 50) # noqa: S311

def get_progress(self, obj) -> float:
return random.randint(0, 10000) / 100 # noqa: S311

@extend_schema_field(BatchDetailSerializer(many=True))
def get_batches(self, obj):
batches = obj.batch_set.all().order_by("-updated_at")
Expand All @@ -587,9 +587,9 @@ class SiteSummaryDetailSerializer(serializers.ModelSerializer[Site]):
site_type = SiteTypeSerializer()
coordinate = CoordinatesSerializer()
plant_count = serializers.SerializerMethodField()
sponsor_progress = serializers.SerializerMethodField()
survived_count = serializers.SerializerMethodField()
propagation_count = serializers.SerializerMethodField()
progress = serializers.SerializerMethodField()
sponsors = serializers.SerializerMethodField()
admins = SiteAdminSerializer(source="siteadmin_set", many=True)
batches = serializers.SerializerMethodField()
Expand All @@ -603,28 +603,28 @@ class Meta:
"coordinate",
"site_type",
"plant_count",
"sponsor_progress",
"survived_count",
"propagation_count",
"visitor_count",
"sponsors",
"progress",
"admins",
"batches",
"weather",
)

def get_plant_count(self, obj) -> int:
return random.randint(100, 200) # noqa: S311
def get_plant_count(self, obj: Site) -> int:
return obj.get_plant_count()

def get_sponsor_progress(self, obj: Site) -> float:
return obj.get_sponsor_progress()

def get_survived_count(self, obj) -> int:
return random.randint(50, 100) # noqa: S311

def get_propagation_count(self, obj) -> int:
return random.randint(5, 50) # noqa: S311

def get_progress(self, obj) -> float:
return random.randint(0, 10000) / 100 # noqa: S311

@extend_schema_field(BatchSponsorSerializer(many=True))
def get_sponsors(self, obj):
batches = Batch.objects.filter(site=obj)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,7 @@ const AnalyticsSiteHeader = ({ siteSummary }: Props) => {
</div>

<div className='mt-1'>
<SiteSponsorProgress progress={siteSummary.progress} />
<SiteSponsorProgress progress={siteSummary.sponsorProgress} />
</div>
</div>
</div>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,8 @@
if (siteSummaries.length === 0) return options

// total-functions/no-partial-division -- length checked above
options.average = siteSummaries.reduce(

Check warning on line 24 in canopeum_frontend/src/components/analytics/SiteSuccessRatesChart.tsx

View workflow job for this annotation

GitHub Actions / Lint

Division is partial. You should wrap it in a wrapper that returns undefined when the denominator is zero
(accumulator, current) => accumulator + current.progress,
(accumulator, current) => accumulator + current.sponsorProgress,
0,
) / siteSummaries.length

Expand All @@ -34,10 +34,10 @@
// However, bars will appear really thin, so we use stacked bars to stack
// 0-height bars on top of each other.
const strackedSerie = Array.from<number>({ length: siteSummaries.length }).fill(0)
strackedSerie[siteIndex] = site.progress
strackedSerie[siteIndex] = site.sponsorProgress

options.colors.push(
site.progress > options.average
site.sponsorProgress > options.average
? 'var(--bs-primary)'
: 'var(--bs-secondary)',
)
Expand All @@ -60,7 +60,7 @@
const renderChartTooltip = (props: ChartsAxisContentProps) => {
const selectedSerie = props.series.find(serie => serie.id === props.axisValue)
// total-functions/no-unsafe-type-assertion -- value type is known from the context
const data = selectedSerie?.data.find(value => !!value) as number | undefined

Check warning on line 63 in canopeum_frontend/src/components/analytics/SiteSuccessRatesChart.tsx

View workflow job for this annotation

GitHub Actions / Lint

This type assertion is not type-safe

return (
<div className='p-2'>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -115,7 +115,7 @@ const SiteSummaryCard = ({ site, admins, onSiteChange, onSiteEdit }: Props) => {
</div>

<div className='mt-4'>
<SiteSponsorProgress progress={site.progress} />
<SiteSponsorProgress progress={site.sponsorProgress} />
</div>
</div>
</div>
Expand Down
Loading
Loading