diff --git a/.gitignore b/.gitignore
index c6c093e6f..f0ee47d9b 100644
--- a/.gitignore
+++ b/.gitignore
@@ -11,6 +11,7 @@ dist/
env/
doc/html
data/
+galaxy/test_galaxy_state.json
/static/
sith/settings_custom.py
sith/search_indexes/
diff --git a/com/tests.py b/com/tests.py
index 2a5206b24..6dde46db9 100644
--- a/com/tests.py
+++ b/com/tests.py
@@ -159,6 +159,33 @@ def test_news_owner(self):
self.assertFalse(self.new.is_owned_by(self.anonymous))
self.assertFalse(self.new.is_owned_by(self.sli))
+ def test_news_viewer(self):
+ """
+ Test that moderated news can be viewed by anyone
+ and not moderated news only by com admins
+ """
+ # by default a news isn't moderated
+ self.assertTrue(self.new.can_be_viewed_by(self.com_admin))
+ self.assertFalse(self.new.can_be_viewed_by(self.sli))
+ self.assertFalse(self.new.can_be_viewed_by(self.anonymous))
+ self.assertFalse(self.new.can_be_viewed_by(self.author))
+
+ self.new.is_moderated = True
+ self.new.save()
+ self.assertTrue(self.new.can_be_viewed_by(self.com_admin))
+ self.assertTrue(self.new.can_be_viewed_by(self.sli))
+ self.assertTrue(self.new.can_be_viewed_by(self.anonymous))
+ self.assertTrue(self.new.can_be_viewed_by(self.author))
+
+ def test_news_editor(self):
+ """
+ Test that only com admins can edit news
+ """
+ self.assertTrue(self.new.can_be_edited_by(self.com_admin))
+ self.assertFalse(self.new.can_be_edited_by(self.sli))
+ self.assertFalse(self.new.can_be_edited_by(self.anonymous))
+ self.assertFalse(self.new.can_be_edited_by(self.author))
+
class WeekmailArticleTest(TestCase):
@classmethod
diff --git a/core/management/commands/populate.py b/core/management/commands/populate.py
index d89ff1e25..c78de4016 100644
--- a/core/management/commands/populate.py
+++ b/core/management/commands/populate.py
@@ -208,6 +208,8 @@ def handle(self, *args, **options):
# Here we add a lot of test datas, that are not necessary for the Sith, but that provide a basic development environment
if not options["prod"]:
+ self.now = timezone.now().replace(hour=12)
+
# Adding user Skia
skia = User(
username="skia",
@@ -914,6 +916,7 @@ def handle(self, *args, **options):
Membership(
user=comunity,
club=bar_club,
+ start_date=self.now,
role=settings.SITH_CLUB_ROLES_ID["Board member"],
).save()
# Adding user tutu
@@ -1072,7 +1075,7 @@ def handle(self, *args, **options):
ForumTopic(forum=hall)
# News
- friday = timezone.now()
+ friday = self.now
while friday.weekday() != 4:
friday += timedelta(hours=6)
friday.replace(hour=20, minute=0, second=0)
@@ -1090,8 +1093,8 @@ def handle(self, *args, **options):
n.save()
NewsDate(
news=n,
- start_date=timezone.now() + timedelta(hours=70),
- end_date=timezone.now() + timedelta(hours=72),
+ start_date=self.now + timedelta(hours=70),
+ end_date=self.now + timedelta(hours=72),
).save()
n = News(
title="Repas barman",
@@ -1107,8 +1110,8 @@ def handle(self, *args, **options):
n.save()
NewsDate(
news=n,
- start_date=timezone.now() + timedelta(hours=72),
- end_date=timezone.now() + timedelta(hours=84),
+ start_date=self.now + timedelta(hours=72),
+ end_date=self.now + timedelta(hours=84),
).save()
n = News(
title="Repas fromager",
@@ -1123,8 +1126,8 @@ def handle(self, *args, **options):
n.save()
NewsDate(
news=n,
- start_date=timezone.now() + timedelta(hours=96),
- end_date=timezone.now() + timedelta(hours=100),
+ start_date=self.now + timedelta(hours=96),
+ end_date=self.now + timedelta(hours=100),
).save()
n = News(
title="SdF",
@@ -1140,7 +1143,7 @@ def handle(self, *args, **options):
NewsDate(
news=n,
start_date=friday + timedelta(hours=24 * 7 + 1),
- end_date=timezone.now() + timedelta(hours=24 * 7 + 9),
+ end_date=self.now + timedelta(hours=24 * 7 + 9),
).save()
# Weekly
n = News(
@@ -1271,28 +1274,28 @@ def handle(self, *args, **options):
club=troll,
role=9,
description="Padawan Troll",
- start_date=timezone.now() - timedelta(days=17),
+ start_date=self.now - timedelta(days=17),
).save()
Membership(
user=krophil,
club=troll,
role=10,
description="Maitre Troll",
- start_date=timezone.now() - timedelta(days=200),
+ start_date=self.now - timedelta(days=200),
).save()
Membership(
user=skia,
club=troll,
role=2,
description="Grand Ancien Troll",
- start_date=timezone.now() - timedelta(days=400),
- end_date=timezone.now() - timedelta(days=86),
+ start_date=self.now - timedelta(days=400),
+ end_date=self.now - timedelta(days=86),
).save()
Membership(
user=richard,
club=troll,
role=2,
description="",
- start_date=timezone.now() - timedelta(days=200),
- end_date=timezone.now() - timedelta(days=100),
+ start_date=self.now - timedelta(days=200),
+ end_date=self.now - timedelta(days=100),
).save()
diff --git a/core/models.py b/core/models.py
index fb28faa69..c8a38426f 100644
--- a/core/models.py
+++ b/core/models.py
@@ -810,6 +810,10 @@ def is_owner(self, obj):
def can_edit(self, obj):
return False
+ @property
+ def is_com_admin(self):
+ return False
+
def can_view(self, obj):
if (
hasattr(obj, "view_groups")
diff --git a/core/static/core/style.scss b/core/static/core/style.scss
index 7abfa8ca2..9ba72e7d0 100644
--- a/core/static/core/style.scss
+++ b/core/static/core/style.scss
@@ -1031,7 +1031,7 @@ thead {
}
tbody > tr {
- &:nth-child(even) {
+ &:nth-child(even):not(.highlight) {
background: $primary-neutral-light-color;
}
&.clickable:hover {
diff --git a/core/tests.py b/core/tests.py
index a2e9c5263..61d497a56 100644
--- a/core/tests.py
+++ b/core/tests.py
@@ -15,17 +15,18 @@
#
import os
-from datetime import timedelta
+from datetime import date, timedelta
+import freezegun
from django.core.cache import cache
from django.test import Client, TestCase
from django.urls import reverse
-from django.core.management import call_command
from django.utils.timezone import now
from club.models import Membership
-from core.models import User, Group, Page, AnonymousUser
from core.markdown import markdown
+from core.models import AnonymousUser, Group, Page, User
+from core.utils import get_semester_code, get_start_of_semester
from sith import settings
"""
@@ -617,3 +618,75 @@ def test_not_existing_group(self):
returns False
"""
self.assertFalse(self.skia.is_in_group(name="This doesn't exist"))
+
+
+class DateUtilsTest(TestCase):
+ @classmethod
+ def setUpTestData(cls):
+ cls.autumn_month = settings.SITH_SEMESTER_START_AUTUMN[0]
+ cls.autumn_day = settings.SITH_SEMESTER_START_AUTUMN[1]
+ cls.spring_month = settings.SITH_SEMESTER_START_SPRING[0]
+ cls.spring_day = settings.SITH_SEMESTER_START_SPRING[1]
+
+ cls.autumn_semester_january = date(2025, 1, 4)
+ cls.autumn_semester_september = date(2024, 9, 4)
+ cls.autumn_first_day = date(2024, cls.autumn_month, cls.autumn_day)
+
+ cls.spring_semester_march = date(2023, 3, 4)
+ cls.spring_first_day = date(2023, cls.spring_month, cls.spring_day)
+
+ def test_get_semester(self):
+ """
+ Test that the get_semester function returns the correct semester string
+ """
+ self.assertEqual(get_semester_code(self.autumn_semester_january), "A24")
+ self.assertEqual(get_semester_code(self.autumn_semester_september), "A24")
+ self.assertEqual(get_semester_code(self.autumn_first_day), "A24")
+
+ self.assertEqual(get_semester_code(self.spring_semester_march), "P23")
+ self.assertEqual(get_semester_code(self.spring_first_day), "P23")
+
+ def test_get_start_of_semester_fixed_date(self):
+ """
+ Test that the get_start_of_semester correctly the starting date of the semester.
+ """
+ automn_2024 = date(2024, self.autumn_month, self.autumn_day)
+ self.assertEqual(
+ get_start_of_semester(self.autumn_semester_january), automn_2024
+ )
+ self.assertEqual(
+ get_start_of_semester(self.autumn_semester_september), automn_2024
+ )
+ self.assertEqual(get_start_of_semester(self.autumn_first_day), automn_2024)
+
+ spring_2023 = date(2023, self.spring_month, self.spring_day)
+ self.assertEqual(get_start_of_semester(self.spring_semester_march), spring_2023)
+ self.assertEqual(get_start_of_semester(self.spring_first_day), spring_2023)
+
+ def test_get_start_of_semester_today(self):
+ """
+ Test that the get_start_of_semester returns the start of the current semester
+ when no date is given
+ """
+ with freezegun.freeze_time(self.autumn_semester_september):
+ self.assertEqual(get_start_of_semester(), self.autumn_first_day)
+
+ with freezegun.freeze_time(self.spring_semester_march):
+ self.assertEqual(get_start_of_semester(), self.spring_first_day)
+
+ def test_get_start_of_semester_changing_date(self):
+ """
+ Test that the get_start_of_semester correctly gives the starting date of the semester,
+ even when the semester changes while the server isn't restarted.
+ """
+ spring_2023 = date(2023, self.spring_month, self.spring_day)
+ autumn_2023 = date(2023, self.autumn_month, self.autumn_day)
+ mid_spring = spring_2023 + timedelta(days=45)
+ mid_autumn = autumn_2023 + timedelta(days=45)
+
+ with freezegun.freeze_time(mid_spring) as frozen_time:
+ self.assertEqual(get_start_of_semester(), spring_2023)
+
+ # forward time to the middle of the next semester
+ frozen_time.move_to(mid_autumn)
+ self.assertEqual(get_start_of_semester(), autumn_2023)
diff --git a/core/utils.py b/core/utils.py
index a053e2d5f..d30e3ebf6 100644
--- a/core/utils.py
+++ b/core/utils.py
@@ -15,20 +15,19 @@
#
import os
-import subprocess
import re
+import subprocess
+from datetime import date
# Image utils
-
from io import BytesIO
-from datetime import date
-
-from PIL import ExifTags
+from typing import Optional
import PIL
-
from django.conf import settings
from django.core.files.base import ContentFile
+from PIL import ExifTags
+from django.utils import timezone
def get_git_revision_short_hash() -> str:
@@ -44,34 +43,54 @@ def get_git_revision_short_hash() -> str:
return ""
-def get_start_of_semester(d=date.today()):
+def get_start_of_semester(today: Optional[date] = None) -> date:
"""
- This function computes the start date of the semester with respect to the given date (default is today),
- and the start date given in settings.SITH_START_DATE.
- It takes the nearest past start date.
- Exemples: with SITH_START_DATE = (8, 15)
- Today -> Start date
- 2015-03-17 -> 2015-02-15
- 2015-01-11 -> 2014-08-15
+ Return the date of the start of the semester of the given date.
+ If no date is given, return the start date of the current semester.
+
+ The current semester is computed as follows:
+
+ - If the date is between 15/08 and 31/12 => Autumn semester.
+ - If the date is between 01/01 and 15/02 => Autumn semester of the previous year.
+ - If the date is between 15/02 and 15/08 => Spring semester
+
+ :param today: the date to use to compute the semester. If None, use today's date.
+ :return: the date of the start of the semester
"""
- today = d
- year = today.year
- start = date(year, settings.SITH_START_DATE[0], settings.SITH_START_DATE[1])
- start2 = start.replace(month=(start.month + 6) % 12)
- spring, autumn = min(start, start2), max(start, start2)
- if today > autumn: # autumn semester
+ if today is None:
+ today = timezone.now().date()
+
+ autumn = date(today.year, *settings.SITH_SEMESTER_START_AUTUMN)
+ spring = date(today.year, *settings.SITH_SEMESTER_START_SPRING)
+
+ if today >= autumn: # between 15/08 (included) and 31/12 -> autumn semester
return autumn
- if today > spring: # spring semester
+ if today >= spring: # between 15/02 (included) and 15/08 -> spring semester
return spring
- return autumn.replace(year=year - 1) # autumn semester of last year
+ # between 01/01 and 15/02 -> autumn semester of the previous year
+ return autumn.replace(year=autumn.year - 1)
+
+
+def get_semester_code(d: Optional[date] = None) -> str:
+ """
+ Return the semester code of the given date.
+ If no date is given, return the semester code of the current semester.
+
+ The semester code is an upper letter (A for autumn, P for spring),
+ followed by the last two digits of the year.
+ For example, the autumn semester of 2018 is "A18".
+ :param d: the date to use to compute the semester. If None, use today's date.
+ :return: the semester code corresponding to the given date
+ """
+ if d is None:
+ d = timezone.now().date()
-def get_semester(d=date.today()):
start = get_start_of_semester(d)
- if start.month <= 6:
- return "P" + str(start.year)[-2:]
- else:
+
+ if (start.month, start.day) == settings.SITH_SEMESTER_START_AUTUMN:
return "A" + str(start.year)[-2:]
+ return "P" + str(start.year)[-2:]
def file_exist(path):
diff --git a/counter/models.py b/counter/models.py
index c6389349b..476aaf13d 100644
--- a/counter/models.py
+++ b/counter/models.py
@@ -15,7 +15,7 @@
#
from __future__ import annotations
-from typing import Tuple
+from typing import Tuple, Optional
from django.db import models
from django.db.models import F, Value, Sum, QuerySet, OuterRef, Exists
@@ -536,7 +536,7 @@ def get_top_barmen(self) -> QuerySet:
.order_by("-perm_sum")
)
- def get_top_customers(self, since=get_start_of_semester()) -> QuerySet:
+ def get_top_customers(self, since: Optional[date] = None) -> QuerySet:
"""
Return a QuerySet querying the money spent by customers of this counter
since the specified date, ordered by descending amount of money spent.
@@ -546,6 +546,8 @@ def get_top_customers(self, since=get_start_of_semester()) -> QuerySet:
- the nickname of the customer
- the amount of money spent by the customer
"""
+ if since is None:
+ since = get_start_of_semester()
return (
self.sellings.filter(date__gte=since)
.annotate(
@@ -557,7 +559,8 @@ def get_top_customers(self, since=get_start_of_semester()) -> QuerySet:
)
.annotate(nickname=F("customer__user__nick_name"))
.annotate(promo=F("customer__user__promo"))
- .values("customer__user", "name", "nickname")
+ .annotate(user=F("customer__user"))
+ .values("user", "promo", "name", "nickname")
.annotate(
selling_sum=Sum(
F("unit_price") * F("quantity"), output_field=CurrencyField()
@@ -567,15 +570,17 @@ def get_top_customers(self, since=get_start_of_semester()) -> QuerySet:
.order_by("-selling_sum")
)
- def get_total_sales(self, since=get_start_of_semester()) -> CurrencyField:
+ def get_total_sales(self, since=None) -> CurrencyField:
"""
Compute and return the total turnover of this counter
since the date specified in parameter (by default, since the start of the current
semester)
:param since: timestamp from which to perform the calculation
- :type since: datetime | date
+ :type since: datetime | date | None
:return: Total revenue earned at this counter
"""
+ if since is None:
+ since = get_start_of_semester()
if isinstance(since, date):
since = datetime.combine(since, datetime.min.time())
total = self.sellings.filter(date__gte=since).aggregate(
diff --git a/counter/templates/counter/stats.jinja b/counter/templates/counter/stats.jinja
index 03b7f4e09..d6cc14f08 100644
--- a/counter/templates/counter/stats.jinja
+++ b/counter/templates/counter/stats.jinja
@@ -11,7 +11,9 @@
{% block content %}
{% trans counter_name=counter %}{{ counter_name }} stats{% endtrans %}
- {% trans counter_name=counter.name %}Top 100 {{ counter_name }}{% endtrans %}
+
+ {% trans counter_name=counter.name %}Top 100 {{ counter_name }}{% endtrans %} ({{ current_semester }})
+
- {% trans counter_name=counter.name %}Top 100 barman {{ counter_name }}{% endtrans %}
+
+ {% trans counter_name=counter.name %}Top 100 barman {{ counter_name }}{% endtrans %} ({{ current_semester }})
+
diff --git a/counter/tests.py b/counter/tests.py
index ed83e9357..6079099ad 100644
--- a/counter/tests.py
+++ b/counter/tests.py
@@ -13,6 +13,7 @@
# OR WITHIN THE LOCAL FILE "LICENSE"
#
#
+from datetime import date, timedelta
import json
import re
import string
@@ -322,42 +323,49 @@ def test_top_customer(self):
Test the result of Counter.get_top_customers() is correct
"""
top = iter(self.counter.get_top_customers())
- self.assertEqual(
- next(top),
+ expected_results = [
{
- "customer__user": self.sli.id,
+ "user": self.sli.id,
"name": f"{self.sli.first_name} {self.sli.last_name}",
+ "promo": self.sli.promo,
"nickname": self.sli.nick_name,
"selling_sum": 2000,
},
- )
- self.assertEqual(
- next(top),
{
- "customer__user": self.skia.id,
+ "user": self.skia.id,
"name": f"{self.skia.first_name} {self.skia.last_name}",
+ "promo": self.skia.promo,
"nickname": self.skia.nick_name,
"selling_sum": 1000,
},
- )
- self.assertEqual(
- next(top),
{
- "customer__user": self.krophil.id,
+ "user": self.krophil.id,
"name": f"{self.krophil.first_name} {self.krophil.last_name}",
+ "promo": self.krophil.promo,
"nickname": self.krophil.nick_name,
"selling_sum": 100,
},
- )
- self.assertEqual(
- next(top),
{
- "customer__user": self.root.id,
+ "user": self.root.id,
"name": f"{self.root.first_name} {self.root.last_name}",
+ "promo": self.root.promo,
"nickname": self.root.nick_name,
"selling_sum": 2,
},
- )
+ ]
+
+ for result in expected_results:
+ self.assertEqual(
+ next(top),
+ {
+ "user": result["user"],
+ "name": result["name"],
+ "promo": result["promo"],
+ "nickname": result["nickname"],
+ "selling_sum": result["selling_sum"],
+ },
+ )
+
self.assertIsNone(next(top, None))
diff --git a/counter/views.py b/counter/views.py
index 4d5af2920..6bbc819de 100644
--- a/counter/views.py
+++ b/counter/views.py
@@ -48,7 +48,7 @@
from datetime import timedelta, datetime
from http import HTTPStatus
-from core.utils import get_start_of_semester
+from core.utils import get_start_of_semester, get_semester_code
from core.views import CanViewMixin, TabedViewMixin, CanEditMixin
from core.views.forms import LoginForm
from core.models import User
@@ -1354,13 +1354,14 @@ class CounterStatView(DetailView, CounterAdminMixin):
def get_context_data(self, **kwargs):
"""Add stats to the context"""
- counter = self.object
+ counter: Counter = self.object
semester_start = get_start_of_semester()
office_hours = counter.get_top_barmen()
kwargs = super(CounterStatView, self).get_context_data(**kwargs)
kwargs.update(
{
"counter": counter,
+ "current_semester": get_semester_code(),
"total_sellings": counter.get_total_sales(since=semester_start),
"top_customers": counter.get_top_customers(since=semester_start)[:100],
"top_barman": office_hours[:100],
diff --git a/galaxy/management/commands/generate_galaxy_test_data.py b/galaxy/management/commands/generate_galaxy_test_data.py
new file mode 100644
index 000000000..f64424874
--- /dev/null
+++ b/galaxy/management/commands/generate_galaxy_test_data.py
@@ -0,0 +1,411 @@
+# -*- coding:utf-8 -*
+#
+# Copyright 2023
+# - Skia
+#
+# Ce fichier fait partie du site de l'Association des Étudiants de l'UTBM,
+# http://ae.utbm.fr.
+#
+# This program is free software; you can redistribute it and/or modify it under
+# the terms of the GNU General Public License a published by the Free Software
+# Foundation; either version 3 of the License, or (at your option) any later
+# version.
+#
+# This program is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
+# details.
+#
+# You should have received a copy of the GNU General Public License along with
+# this program; if not, write to the Free Sofware Foundation, Inc., 59 Temple
+# Place - Suite 330, Boston, MA 02111-1307, USA.
+#
+#
+import warnings
+from typing import Final, Optional
+
+from django.conf import settings
+from django.core.files.base import ContentFile
+from django.core.management.base import BaseCommand
+from django.utils import timezone
+
+from datetime import timedelta
+
+import logging
+
+from club.models import Club, Membership
+from core.models import User, Group, Page, SithFile
+from subscription.models import Subscription
+from sas.models import Album, Picture, PeoplePictureRelation
+
+
+RED_PIXEL_PNG: Final[bytes] = (
+ b"\x89\x50\x4e\x47\x0d\x0a\x1a\x0a\x00\x00\x00\x0d\x49\x48\x44\x52"
+ b"\x00\x00\x00\x01\x00\x00\x00\x01\x08\x02\x00\x00\x00\x90\x77\x53"
+ b"\xde\x00\x00\x00\x0c\x49\x44\x41\x54\x08\xd7\x63\xf8\xcf\xc0\x00"
+ b"\x00\x03\x01\x01\x00\x18\xdd\x8d\xb0\x00\x00\x00\x00\x49\x45\x4e"
+ b"\x44\xae\x42\x60\x82"
+)
+
+USER_PACK_SIZE: Final[int] = 1000
+
+
+class Command(BaseCommand):
+ help = "Procedurally generate representative data for developing the Galaxy"
+
+ def __init__(self, *args, **kwargs):
+ super().__init__(*args, **kwargs)
+ self.now = timezone.now().replace(hour=12)
+
+ self.users: Optional[list[User]] = None
+ self.clubs: Optional[list[Club]] = None
+ self.picts: Optional[list[Picture]] = None
+ self.pictures_tags: Optional[list[PeoplePictureRelation]] = None
+
+ def add_arguments(self, parser):
+ parser.add_argument(
+ "--user-pack-count",
+ help=f"Number of packs of {USER_PACK_SIZE} users to create",
+ type=int,
+ default=1,
+ )
+ parser.add_argument(
+ "--club-count", help="Number of clubs to create", type=int, default=50
+ )
+
+ def handle(self, *args, **options):
+ self.logger = logging.getLogger("main")
+ if options["verbosity"] < 0 or 2 < options["verbosity"]:
+ warnings.warn("verbosity level should be between 0 and 2 included")
+
+ if options["verbosity"] == 2:
+ self.logger.setLevel(logging.DEBUG)
+ elif options["verbosity"] == 1:
+ self.logger.setLevel(logging.INFO)
+ else:
+ self.logger.setLevel(logging.ERROR)
+
+ self.logger.info("The Galaxy is being populated by the Sith.")
+
+ self.logger.info("Cleaning old Galaxy population")
+ Club.objects.filter(unix_name__startswith="galaxy-").delete()
+ Group.objects.filter(name__startswith="galaxy-").delete()
+ Page.objects.filter(name__startswith="galaxy-").delete()
+ User.objects.filter(username__startswith="galaxy-").delete()
+ Picture.objects.filter(name__startswith="galaxy-").delete()
+ Album.objects.filter(name__startswith="galaxy-").delete()
+ self.logger.info("Done. Populating a new Galaxy")
+
+ self.NB_USERS = options["user_pack_count"] * USER_PACK_SIZE
+ self.NB_CLUBS = options["club_count"]
+
+ root = User.objects.filter(username="root").first()
+ sas = SithFile.objects.get(id=settings.SITH_SAS_ROOT_DIR_ID)
+ self.galaxy_album = Album.objects.create(
+ name="galaxy-register-file",
+ owner=root,
+ is_moderated=True,
+ is_in_sas=True,
+ parent=sas,
+ )
+
+ self.make_clubs()
+ self.make_users()
+ self.make_families()
+ self.make_club_memberships()
+ self.make_pictures()
+ self.make_pictures_memberships()
+ half_pack = USER_PACK_SIZE // 2
+ for u in range(half_pack, self.NB_USERS, half_pack):
+ self.make_important_citizen(u)
+
+ def make_clubs(self):
+ """
+ Create all the clubs (:class:`club.models.Club`)
+ and store them in `self.clubs` for fast access later.
+ Don't create the meta groups (:class:`core.models.MetaGroup`)
+ nor the pages of the clubs (:class:`core.models.Page`)
+ """
+ self.clubs = []
+ for i in range(self.NB_CLUBS):
+ self.clubs.append(Club(unix_name=f"galaxy-club-{i}", name=f"club-{i}"))
+ # We don't need to create corresponding groups here, as the Galaxy doesn't care about them
+ Club.objects.bulk_create(self.clubs)
+ self.clubs = Club.objects.filter(unix_name__startswith="galaxy-").all()
+
+ def make_users(self):
+ """
+ Create all the users and store them in `self.users` for fast access later.
+
+ Also create a subscription for all the generated users.
+ """
+ self.users = []
+ for i in range(self.NB_USERS):
+ u = User(
+ username=f"galaxy-user-{i}",
+ email=f"{i}@galaxy.test",
+ first_name="Citizen",
+ last_name=f"n°{i}",
+ )
+ self.logger.info(f"Creating {u}")
+ self.users.append(u)
+ User.objects.bulk_create(self.users)
+ self.users = User.objects.filter(username__startswith="galaxy-").all()
+
+ # now that users are created, create their subscription
+ subs = []
+ for i in range(self.NB_USERS):
+ u = self.users[i]
+ self.logger.info(f"Registering {u}")
+ subs.append(
+ Subscription(
+ member=u,
+ subscription_start=Subscription.compute_start(
+ self.now - timedelta(days=self.NB_USERS - i)
+ ),
+ subscription_end=Subscription.compute_end(duration=2),
+ )
+ )
+ Subscription.objects.bulk_create(subs)
+
+ def make_families(self):
+ """
+ Generate the godfather/godchild relations for the users contained in :attr:`self.users`.
+
+ The :meth:`make_users` method must have been called beforehand.
+
+ This will iterate on all citizen after the 200th.
+ Then it will take 14 other citizen among the previous 200
+ (godfathers are usually older), and apply another
+ heuristic to determine whether they should have a family link
+ """
+ if self.users is None:
+ raise RuntimeError(
+ "The `make_users()` method must be called before `make_families()`"
+ )
+ for i in range(200, self.NB_USERS):
+ godfathers = []
+ for j in range(i - 200, i, 14): # this will loop 14 times (14² = 196)
+ if (i / 10) % 10 == (i + j) % 10:
+ u1 = self.users[i]
+ u2 = self.users[j]
+ self.logger.info(f"Making {u2} the godfather of {u1}")
+ godfathers.append(u2)
+ u1.godfathers.set(godfathers)
+
+ def make_club_memberships(self):
+ """
+ Assign users to clubs and give them a role in a pseudo-random way.
+
+ The :meth:`make_users` and :meth:`make_clubs` methods
+ must have been called beforehand.
+
+ Work by making multiples passes on all users to affect
+ them some pseudo-random roles in some clubs.
+ The multiple passes are useful to get some variations over who goes where.
+ Each pass for each user has a chance to affect her to two different clubs,
+ increasing a bit more the created chaos, while remaining purely deterministic.
+ """
+ if self.users is None:
+ raise RuntimeError(
+ "The `make_users()` method must be called before `make_club_memberships()`"
+ )
+ if self.clubs is None:
+ raise RuntimeError(
+ "The `make_clubs()` method must be called before `make_club_memberships()`"
+ )
+ memberships = []
+ for i in range(1, 11): # users can be in up to 20 clubs
+ self.logger.info(f"Club membership, pass {i}")
+ for uid in range(
+ i, self.NB_USERS, i
+ ): # Pass #1 will make sure every user is at least in one club
+ user = self.users[uid]
+ club = self.clubs[(uid + i**2) % self.NB_CLUBS]
+
+ start = self.now - timedelta(
+ days=(((self.NB_USERS - uid) * i) // 110)
+ ) # older users were in clubs before newer users
+ end = start + timedelta(days=180) # about one semester
+ self.logger.debug(
+ f"Making {user} a member of club {club} from {start} to {end}"
+ )
+ memberships.append(
+ Membership(
+ user=user,
+ club=club,
+ role=(uid + i) % 10 + 1, # spread the different roles
+ start_date=start,
+ end_date=end,
+ )
+ )
+
+ for uid in range(
+ 10 + i * 2, self.NB_USERS, 10 + i * 2
+ ): # Make a second affectation that will skip most users, to make a few citizen more important
+ user = self.users[uid]
+ club = self.clubs[(uid + i**3) % self.NB_CLUBS]
+
+ start = self.now - timedelta(
+ days=(((self.NB_USERS - uid) * i) // 100)
+ ) # older users were in clubs before newer users
+ end = start + timedelta(days=180) # about one semester
+ self.logger.debug(
+ f"Making {user} a member of club {club} from {start} to {end}"
+ )
+ memberships.append(
+ Membership(
+ user=user,
+ club=club,
+ role=((uid // 10) + i) % 10 + 1, # spread the different roles
+ start_date=start,
+ end_date=end,
+ )
+ )
+ Membership.objects.bulk_create(memberships)
+
+ def make_pictures(self):
+ """
+ Create pictures for users to be tagged on later.
+
+ The :meth:`make_users` method must have been called beforehand.
+ """
+ if self.users is None:
+ raise RuntimeError(
+ "The `make_users()` method must be called before `make_families()`"
+ )
+ self.picts = []
+ # Create twice as many pictures as users
+ for i in range(self.NB_USERS * 2):
+ u = self.users[i % self.NB_USERS]
+ self.logger.info(f"Making Picture {i // self.NB_USERS} for {u}")
+ self.picts.append(
+ Picture(
+ owner=u,
+ name=f"galaxy-picture {u} {i // self.NB_USERS}",
+ is_moderated=True,
+ is_folder=False,
+ parent=self.galaxy_album,
+ is_in_sas=True,
+ file=ContentFile(RED_PIXEL_PNG),
+ compressed=ContentFile(RED_PIXEL_PNG),
+ thumbnail=ContentFile(RED_PIXEL_PNG),
+ mime_type="image/png",
+ size=len(RED_PIXEL_PNG),
+ )
+ )
+ self.picts[i].file.name = self.picts[i].name
+ self.picts[i].compressed.name = self.picts[i].name
+ self.picts[i].thumbnail.name = self.picts[i].name
+ Picture.objects.bulk_create(self.picts)
+ self.picts = Picture.objects.filter(name__startswith="galaxy-").all()
+
+ def make_pictures_memberships(self):
+ """
+ Assign users to pictures and make enough of them for our
+ created users to be eligible for promotion as citizen.
+
+ See :meth:`galaxy.models.Galaxy.rule` for details on promotion to citizen.
+ """
+ self.pictures_tags = []
+
+ # We don't want to handle limits, users in the middle will be far enough
+ def _tag_neighbors(uid, neighbor_dist, pict_offset, pict_dist):
+ u2 = self.users[uid - neighbor_dist]
+ u3 = self.users[uid + neighbor_dist]
+ self.pictures_tags += [
+ PeoplePictureRelation(user=u2, picture=self.picts[uid + pict_offset]),
+ PeoplePictureRelation(user=u3, picture=self.picts[uid + pict_offset]),
+ PeoplePictureRelation(user=u2, picture=self.picts[uid - pict_dist]),
+ PeoplePictureRelation(user=u3, picture=self.picts[uid - pict_dist]),
+ PeoplePictureRelation(user=u2, picture=self.picts[uid + pict_dist]),
+ PeoplePictureRelation(user=u3, picture=self.picts[uid + pict_dist]),
+ ]
+
+ for uid in range(200, self.NB_USERS - 200):
+ u1 = self.users[uid]
+ self.logger.info(f"Pictures of {u1}")
+ self.pictures_tags += [
+ PeoplePictureRelation(user=u1, picture=self.picts[uid]),
+ PeoplePictureRelation(user=u1, picture=self.picts[uid - 14]),
+ PeoplePictureRelation(user=u1, picture=self.picts[uid + 14]),
+ PeoplePictureRelation(user=u1, picture=self.picts[uid - 20]),
+ PeoplePictureRelation(user=u1, picture=self.picts[uid + 20]),
+ PeoplePictureRelation(user=u1, picture=self.picts[uid - 21]),
+ PeoplePictureRelation(user=u1, picture=self.picts[uid + 21]),
+ PeoplePictureRelation(user=u1, picture=self.picts[uid - 22]),
+ PeoplePictureRelation(user=u1, picture=self.picts[uid + 22]),
+ PeoplePictureRelation(user=u1, picture=self.picts[uid - 30]),
+ PeoplePictureRelation(user=u1, picture=self.picts[uid + 30]),
+ PeoplePictureRelation(user=u1, picture=self.picts[uid - 31]),
+ PeoplePictureRelation(user=u1, picture=self.picts[uid + 31]),
+ PeoplePictureRelation(user=u1, picture=self.picts[uid - 32]),
+ PeoplePictureRelation(user=u1, picture=self.picts[uid + 32]),
+ ]
+
+ if uid % 3 == 0:
+ _tag_neighbors(uid, 1, 0, 40)
+ if uid % 5 == 0:
+ _tag_neighbors(uid, 2, 0, 50)
+ if uid % 10 == 0:
+ _tag_neighbors(uid, 3, 0, 60)
+ if uid % 20 == 0:
+ _tag_neighbors(uid, 5, 0, 70)
+ if uid % 25 == 0:
+ _tag_neighbors(uid, 10, 0, 80)
+
+ if uid % 2 == 1:
+ _tag_neighbors(uid, 1, self.NB_USERS, 90)
+ if uid % 15 == 0:
+ _tag_neighbors(uid, 5, self.NB_USERS, 100)
+ if uid % 30 == 0:
+ _tag_neighbors(uid, 4, self.NB_USERS, 110)
+ PeoplePictureRelation.objects.bulk_create(self.pictures_tags)
+
+ def make_important_citizen(self, uid: int):
+ """
+ Make the user whose uid is given in parameter a more important citizen,
+ thus triggering many more connections to others (lanes)
+ and dragging him towards the center of the Galaxy.
+
+ This promotion is obtained by adding more family links
+ and by tagging the user in more pictures.
+
+ The users chosen to be added to this user's family shall
+ also be tagged in more pictures, thus making them also
+ more important.
+
+ :param uid: the id of the user to make more important
+ """
+ u1 = self.users[uid]
+ u2 = self.users[uid - 100]
+ u3 = self.users[uid + 100]
+ u1.godfathers.add(u2)
+ u1.godchildren.add(u3)
+ self.logger.info(f"{u1} will be important and close to {u2} and {u3}")
+ pictures_tags = []
+ for p in range( # Mix them with other citizen for more chaos
+ uid - 400, uid - 200
+ ):
+ # users may already be on the pictures
+ if not self.picts[p].people.filter(user=u1).exists():
+ pictures_tags.append(
+ PeoplePictureRelation(user=u1, picture=self.picts[p])
+ )
+ if not self.picts[p].people.filter(user=u2).exists():
+ pictures_tags.append(
+ PeoplePictureRelation(user=u2, picture=self.picts[p])
+ )
+ if not self.picts[p + self.NB_USERS].people.filter(user=u1).exists():
+ pictures_tags.append(
+ PeoplePictureRelation(
+ user=u1, picture=self.picts[p + self.NB_USERS]
+ )
+ )
+ if not self.picts[p + self.NB_USERS].people.filter(user=u2).exists():
+ pictures_tags.append(
+ PeoplePictureRelation(
+ user=u2, picture=self.picts[p + self.NB_USERS]
+ )
+ )
+ PeoplePictureRelation.objects.bulk_create(pictures_tags)
diff --git a/galaxy/management/commands/rule_galaxy.py b/galaxy/management/commands/rule_galaxy.py
index 1db3c9751..55cb9ae98 100644
--- a/galaxy/management/commands/rule_galaxy.py
+++ b/galaxy/management/commands/rule_galaxy.py
@@ -21,6 +21,7 @@
# Place - Suite 330, Boston, MA 02111-1307, USA.
#
#
+import warnings
from django.core.management.base import BaseCommand
from django.db import connection
@@ -41,19 +42,21 @@ class Command(BaseCommand):
def handle(self, *args, **options):
logger = logging.getLogger("main")
- if options["verbosity"] > 1:
+ if options["verbosity"] < 0 or 2 < options["verbosity"]:
+ warnings.warn("verbosity level should be between 0 and 2 included")
+
+ if options["verbosity"] == 2:
logger.setLevel(logging.DEBUG)
- elif options["verbosity"] > 0:
+ elif options["verbosity"] == 1:
logger.setLevel(logging.INFO)
else:
- logger.setLevel(logging.NOTSET)
+ logger.setLevel(logging.ERROR)
logger.info("The Galaxy is being ruled by the Sith.")
- Galaxy.rule()
- logger.info(
- "Caching current Galaxy state for a quicker display of the Empire's power."
- )
- Galaxy.make_state()
+ galaxy = Galaxy.objects.create()
+ galaxy.rule()
+ logger.info("Sending old galaxies' remains to garbage.")
+ Galaxy.objects.filter(state__isnull=True).delete()
logger.info("Ruled the galaxy in {} queries.".format(len(connection.queries)))
if options["verbosity"] > 2:
diff --git a/galaxy/migrations/0002_auto_20230412_1130.py b/galaxy/migrations/0002_auto_20230412_1130.py
new file mode 100644
index 000000000..be01af99e
--- /dev/null
+++ b/galaxy/migrations/0002_auto_20230412_1130.py
@@ -0,0 +1,45 @@
+# Generated by Django 3.2.16 on 2023-04-12 09:30
+
+from django.conf import settings
+from django.db import migrations, models
+import django.db.models.deletion
+
+
+class Migration(migrations.Migration):
+ dependencies = [
+ migrations.swappable_dependency(settings.AUTH_USER_MODEL),
+ ("galaxy", "0001_initial"),
+ ]
+
+ operations = [
+ migrations.AlterModelOptions(
+ name="galaxy",
+ options={"ordering": ["pk"]},
+ ),
+ migrations.AddField(
+ model_name="galaxystar",
+ name="galaxy",
+ field=models.ForeignKey(
+ null=True,
+ on_delete=django.db.models.deletion.CASCADE,
+ related_name="stars",
+ to="galaxy.galaxy",
+ verbose_name="the galaxy this star belongs to",
+ ),
+ ),
+ migrations.AlterField(
+ model_name="galaxy",
+ name="state",
+ field=models.JSONField(null=True, verbose_name="The galaxy current state"),
+ ),
+ migrations.AlterField(
+ model_name="galaxystar",
+ name="owner",
+ field=models.ForeignKey(
+ on_delete=django.db.models.deletion.CASCADE,
+ related_name="stars",
+ to=settings.AUTH_USER_MODEL,
+ verbose_name="star owner",
+ ),
+ ),
+ ]
diff --git a/galaxy/models.py b/galaxy/models.py
index cc4a3e727..744cca794 100644
--- a/galaxy/models.py
+++ b/galaxy/models.py
@@ -22,16 +22,19 @@
#
#
+from __future__ import annotations
+
import math
import logging
+import time
+
+from typing import List, TypedDict, NamedTuple, Union, Optional
-from typing import Tuple
from django.db import models
from django.db.models import Q, Case, F, Value, When, Count
from django.db.models.functions import Concat
from django.utils import timezone
from django.utils.translation import gettext_lazy as _
-from typing import List, TypedDict
from core.models import User
from club.models import Club
@@ -40,30 +43,58 @@
class GalaxyStar(models.Model):
"""
- This class defines a star (vertex -> user) in the galaxy graph, storing a reference to its owner citizen, and being
- referenced by GalaxyLane.
+ Define a star (vertex -> user) in the galaxy graph,
+ storing a reference to its owner citizen.
+
+ Stars are linked to each others through the :class:`GalaxyLane` model.
- It also stores the individual mass of this star, used to push it towards the center of the galaxy.
+ Each GalaxyStar has a mass which push it towards the center of the galaxy.
+ This mass is proportional to the number of pictures the owner of the star
+ is tagged on.
"""
- owner = models.OneToOneField(
+ owner = models.ForeignKey(
User,
verbose_name=_("star owner"),
- related_name="galaxy_user",
+ related_name="stars",
on_delete=models.CASCADE,
)
mass = models.PositiveIntegerField(
_("star mass"),
default=0,
)
+ galaxy = models.ForeignKey(
+ "Galaxy",
+ verbose_name=_("the galaxy this star belongs to"),
+ related_name="stars",
+ on_delete=models.CASCADE,
+ null=True,
+ )
def __str__(self):
return str(self.owner)
+@property
+def current_star(self) -> Optional[GalaxyStar]:
+ """
+ The star of this user in the :class:`Galaxy`.
+ Only take into account the most recent active galaxy.
+
+ :return: The star of this user if there is an active Galaxy
+ and this user is a citizen of it, else ``None``
+ """
+ return self.stars.filter(galaxy=Galaxy.get_current_galaxy()).last()
+
+
+# Adding a shortcut to User class for getting its star belonging to the latest ruled Galaxy
+setattr(User, "current_star", current_star)
+
+
class GalaxyLane(models.Model):
"""
- This class defines a lane (edge -> link between galaxy citizen) in the galaxy map, storing a reference to both its
+ Define a lane (edge -> link between galaxy citizen)
+ in the galaxy map, storing a reference to both its
ends and the distance it covers.
Score details between citizen owning the stars is also stored here.
"""
@@ -110,7 +141,35 @@ class GalaxyDict(TypedDict):
links: List
+class RelationScore(NamedTuple):
+ family: int
+ pictures: int
+ clubs: int
+
+
class Galaxy(models.Model):
+ """
+ The Galaxy, a graph linking the active users between each others.
+ The distance between two users is given by a relation score which takes
+ into account a few parameter like the number of pictures they are both tagged on,
+ the time during which they were in the same clubs and whether they are
+ in the same family.
+
+ The citizens of the Galaxy are represented by :class:`GalaxyStar`
+ and their relations by :class:`GalaxyLane`.
+
+ Several galaxies can coexist. In this case, only the most recent active one
+ shall usually be taken into account.
+ This is useful to keep the current galaxy while generating a new one
+ and swapping them only at the very end.
+
+ Please take into account that generating the galaxy is a very expensive
+ operation. For this reason, try not to call the :meth:`rule` method more
+ than once a day in production.
+
+ To quickly access to the state of a galaxy, use the :attr:`state` attribute.
+ """
+
logger = logging.getLogger("main")
GALAXY_SCALE_FACTOR = 2_000
@@ -118,77 +177,44 @@ class Galaxy(models.Model):
PICTURE_POINTS = 2 # Equivalent to two days as random members of a club.
CLUBS_POINTS = 1 # One day together as random members in a club is one point.
- state = models.JSONField("current state")
+ state = models.JSONField(_("The galaxy current state"), null=True)
- @staticmethod
- def make_state() -> None:
- """
- Compute JSON structure to send to 3d-force-graph: https://github.com/vasturiano/3d-force-graph/
- """
- without_nickname = Concat(
- F("owner__first_name"), Value(" "), F("owner__last_name")
- )
- with_nickname = Concat(
- F("owner__first_name"),
- Value(" "),
- F("owner__last_name"),
- Value(" ("),
- F("owner__nick_name"),
- Value(")"),
- )
- stars = GalaxyStar.objects.annotate(
- owner_name=Case(
- When(owner__nick_name=None, then=without_nickname),
- default=with_nickname,
- )
- )
- lanes = GalaxyLane.objects.annotate(
- star1_owner=F("star1__owner__id"),
- star2_owner=F("star2__owner__id"),
- )
- json = GalaxyDict(
- nodes=[
- StarDict(id=star.owner_id, name=star.owner_name, mass=star.mass)
- for star in stars
- ],
- links=[],
- )
- # Make bidirectional links
- # TODO: see if this impacts performance with a big graph
- for path in lanes:
- json["links"].append(
- {
- "source": path.star1_owner,
- "target": path.star2_owner,
- "value": path.distance,
- }
- )
- json["links"].append(
- {
- "source": path.star2_owner,
- "target": path.star1_owner,
- "value": path.distance,
- }
- )
- Galaxy.objects.all().delete()
- Galaxy(state=json).save()
+ class Meta:
+ ordering = ["pk"]
+
+ def __str__(self):
+ stars_count = self.stars.count()
+ s = f"GLX-ID{self.pk}-SC{stars_count}-"
+ if self.state is None:
+ s += "CHS" # CHAOS
+ else:
+ s += "RLD" # RULED
+ return s
+
+ @classmethod
+ def get_current_galaxy(
+ cls,
+ ) -> Galaxy: # __future__.annotations is required for this
+ return Galaxy.objects.filter(state__isnull=False).last()
###################
# User self score #
###################
@classmethod
- def compute_user_score(cls, user) -> int:
+ def compute_user_score(cls, user: User) -> int:
"""
- This compute an individual score for each citizen. It will later be used by the graph algorithm to push
+ Compute an individual score for each citizen.
+ It will later be used by the graph algorithm to push
higher scores towards the center of the galaxy.
Idea: This could be added to the computation:
- - Forum posts
- - Picture count
- - Counter consumption
- - Barman time
- - ...
+
+ - Forum posts
+ - Picture count
+ - Counter consumption
+ - Barman time
+ - ...
"""
user_score = 1
user_score += cls.query_user_score(user)
@@ -203,7 +229,11 @@ def compute_user_score(cls, user) -> int:
return user_score
@classmethod
- def query_user_score(cls, user) -> int:
+ def query_user_score(cls, user: User) -> int:
+ """
+ Perform the db query to get the individual score
+ of the given user in the galaxy.
+ """
score_query = (
User.objects.filter(id=user.id)
.annotate(
@@ -230,26 +260,48 @@ def query_user_score(cls, user) -> int:
####################
@classmethod
- def compute_users_score(cls, user1, user2) -> Tuple[int, int, int, int]:
+ def compute_users_score(cls, user1: User, user2: User) -> RelationScore:
+ """
+ Compute the relationship scores of the two given users
+ in the following fields :
+
+ - family: if they have some godfather/godchild relation
+ - pictures: in how many pictures are both tagged
+ - clubs: during how many days they were members of the same clubs
+ """
family = cls.compute_users_family_score(user1, user2)
pictures = cls.compute_users_pictures_score(user1, user2)
clubs = cls.compute_users_clubs_score(user1, user2)
- score = family + pictures + clubs
- return score, family, pictures, clubs
+ return RelationScore(family=family, pictures=pictures, clubs=clubs)
@classmethod
- def compute_users_family_score(cls, user1, user2) -> int:
+ def compute_users_family_score(cls, user1: User, user2: User) -> int:
+ """
+ Compute the family score of the relation between the given users.
+ This takes into account mutual godfathers.
+
+ :return: 366 if user1 is the godfather of user2 (or vice versa) else 0
+ """
link_count = User.objects.filter(
Q(id=user1.id, godfathers=user2) | Q(id=user2.id, godfathers=user1)
).count()
- if link_count:
+ if link_count > 0:
cls.logger.debug(
f"\t\t- '{user1}' and '{user2}' have {link_count} direct family link"
)
return link_count * cls.FAMILY_LINK_POINTS
@classmethod
- def compute_users_pictures_score(cls, user1, user2) -> int:
+ def compute_users_pictures_score(cls, user1: User, user2: User) -> int:
+ """
+ Compute the pictures score of the relation between the given users.
+
+ The pictures score is obtained by counting the number
+ of :class:`Picture` in which they have been both identified.
+ This score is then multiplied by 2.
+
+ :return: The number of pictures both users have in common, times 2
+ """
picture_count = (
Picture.objects.filter(people__user__in=(user1,))
.filter(people__user__in=(user2,))
@@ -262,7 +314,21 @@ def compute_users_pictures_score(cls, user1, user2) -> int:
return picture_count * cls.PICTURE_POINTS
@classmethod
- def compute_users_clubs_score(cls, user1, user2) -> int:
+ def compute_users_clubs_score(cls, user1: User, user2: User) -> int:
+ """
+ Compute the clubs score of the relation between the given users.
+
+ The club score is obtained by counting the number of days
+ during which the memberships (see :class:`club.models.Membership`)
+ of both users overlapped.
+
+ For example, if user1 was a member of Unitec from 01/01/2020 to 31/12/2021
+ (two years) and user2 was a member of the same club from 01/01/2021 to
+ 31/12/2022 (also two years, but with an offset of one year), then their
+ club score is 365.
+
+ :return: the number of days during which both users were in the same club
+ """
common_clubs = Club.objects.filter(members__in=user1.memberships.all()).filter(
members__in=user2.memberships.all()
)
@@ -272,6 +338,7 @@ def compute_users_clubs_score(cls, user1, user2) -> int:
score = 0
for user1_membership in user1_memberships:
if user1_membership.end_date is None:
+ # user1_membership.save() is not called in this function, hence this is safe
user1_membership.end_date = timezone.now().date()
query = Q( # start2 <= start1 <= end2
start_date__lte=user1_membership.start_date,
@@ -312,54 +379,17 @@ def compute_users_clubs_score(cls, user1, user2) -> int:
###################
@classmethod
- def rule(cls) -> None:
- GalaxyStar.objects.all().delete()
- # The following is a no-op thanks to cascading, but in case that changes in the future, better keep it anyway.
- GalaxyLane.objects.all().delete()
- rulable_users = (
- User.objects.filter(subscriptions__isnull=False)
- .filter(
- Q(godchildren__isnull=False)
- | Q(godfathers__isnull=False)
- | Q(pictures__isnull=False)
- | Q(memberships__isnull=False)
- )
- .distinct()
- )
- # force fetch of the whole query to make sure there won't
- # be any more db hits
- # this is memory expensive but prevents a lot of db hits, therefore
- # is far more time efficient
- rulable_users = list(rulable_users)
- while len(rulable_users) > 0:
- user1 = rulable_users.pop()
- for user2 in rulable_users:
- cls.logger.debug("")
- cls.logger.debug(f"\t> Ruling '{user1}' against '{user2}'")
- star1, _ = GalaxyStar.objects.get_or_create(owner=user1)
- star2, _ = GalaxyStar.objects.get_or_create(owner=user2)
- if star1.mass == 0:
- star1.mass = cls.compute_user_score(user1)
- star1.save()
- if star2.mass == 0:
- star2.mass = cls.compute_user_score(user2)
- star2.save()
- users_score, family, pictures, clubs = cls.compute_users_score(
- user1, user2
- )
- if users_score > 0:
- GalaxyLane(
- star1=star1,
- star2=star2,
- distance=cls.scale_distance(users_score),
- family=family,
- pictures=pictures,
- clubs=clubs,
- ).save()
+ def scale_distance(cls, value: Union[int, float]) -> int:
+ """
+ Given a numeric value, return a scaled value which can
+ be used in the Galaxy's graphical interface to set the distance
+ between two stars
- @classmethod
- def scale_distance(cls, value) -> int:
+ :return: the scaled value usable in the Galaxy's 3d graph
+ """
# TODO: this will need adjustements with the real, typical data on Taiste
+ if value == 0:
+ return 4000 # Following calculus would give us +∞, we cap it to 4000
cls.logger.debug(f"\t\t> Score: {value}")
# Invert score to draw close users together
@@ -376,3 +406,224 @@ def scale_distance(cls, value) -> int:
)
cls.logger.debug(f"\t\t> Scaled distance: {value}")
return int(value)
+
+ def rule(self, picture_count_threshold=10) -> None:
+ """
+ Main function of the Galaxy.
+ Iterate over all the rulable users to promote them to citizens.
+ A citizen is a user who has a corresponding star in the Galaxy.
+ Also build up the lanes, which are the links between the different citizen.
+
+ Users who can be ruled are defined with the `picture_count_threshold`:
+ all users who are identified in a strictly lower number of pictures
+ won't be promoted to citizens.
+ This does very effectively limit the quantity of computing to do
+ and only includes users who have had a minimum of activity.
+
+ This method still remains very expensive, so think thoroughly before
+ you call it, especially in production.
+
+ :param picture_count_threshold: the minimum number of picture to have to be
+ included in the galaxy
+ """
+ total_time = time.time()
+ self.logger.info("Listing rulable citizen.")
+ rulable_users = (
+ User.objects.filter(subscriptions__isnull=False)
+ .annotate(pictures_count=Count("pictures"))
+ .filter(pictures_count__gt=picture_count_threshold)
+ .distinct()
+ )
+
+ # force fetch of the whole query to make sure there won't
+ # be any more db hits
+ # this is memory expensive but prevents a lot of db hits, therefore
+ # is far more time efficient
+
+ rulable_users = list(rulable_users)
+ rulable_users_count = len(rulable_users)
+ user1_count = 0
+ self.logger.info(
+ f"{rulable_users_count} citizen have been listed. Starting to rule."
+ )
+
+ stars = []
+ self.logger.info("Creating stars for all citizen")
+ for user in rulable_users:
+ star = GalaxyStar(
+ owner=user, galaxy=self, mass=self.compute_user_score(user)
+ )
+ stars.append(star)
+ GalaxyStar.objects.bulk_create(stars)
+
+ stars = {}
+ for star in GalaxyStar.objects.filter(galaxy=self):
+ stars[star.owner.id] = star
+
+ self.logger.info("Creating lanes between stars")
+ # Display current speed every $speed_count_frequency users
+ speed_count_frequency = max(rulable_users_count // 10, 1) # ten time at most
+ global_avg_speed_accumulator = 0
+ global_avg_speed_count = 0
+ t_global_start = time.time()
+ while len(rulable_users) > 0:
+ user1 = rulable_users.pop()
+ user1_count += 1
+ rulable_users_count2 = len(rulable_users)
+
+ star1 = stars[user1.id]
+
+ user_avg_speed = 0
+ user_avg_speed_count = 0
+
+ tstart = time.time()
+ lanes = []
+ for user2_count, user2 in enumerate(rulable_users, start=1):
+ self.logger.debug("")
+ self.logger.debug(
+ f"\t> Examining '{user1}' ({user1_count}/{rulable_users_count}) with '{user2}' ({user2_count}/{rulable_users_count2})"
+ )
+
+ star2 = stars[user2.id]
+
+ score = Galaxy.compute_users_score(user1, user2)
+ distance = self.scale_distance(sum(score))
+ if distance < 30: # TODO: this needs tuning with real-world data
+ lanes.append(
+ GalaxyLane(
+ star1=star1,
+ star2=star2,
+ distance=distance,
+ family=score.family,
+ pictures=score.pictures,
+ clubs=score.clubs,
+ )
+ )
+
+ if user2_count % speed_count_frequency == 0:
+ tend = time.time()
+ delta = tend - tstart
+ speed = float(speed_count_frequency) / delta
+ user_avg_speed += speed
+ user_avg_speed_count += 1
+ self.logger.debug(
+ f"\tSpeed: {speed:.2f} users per second (time for last {speed_count_frequency} citizens: {delta:.2f} second)"
+ )
+ tstart = time.time()
+
+ GalaxyLane.objects.bulk_create(lanes)
+
+ self.logger.info("")
+
+ t_global_end = time.time()
+ global_delta = t_global_end - t_global_start
+ speed = 1.0 / global_delta
+ global_avg_speed_accumulator += speed
+ global_avg_speed_count += 1
+ global_avg_speed = global_avg_speed_accumulator / global_avg_speed_count
+
+ self.logger.info(f" Ruling of {self} ".center(60, "#"))
+ self.logger.info(
+ f"Progression: {user1_count}/{rulable_users_count} citizen -- {rulable_users_count - user1_count} remaining"
+ )
+ self.logger.info(f"Speed: {60.0*global_avg_speed:.2f} citizen per minute")
+
+ # We can divide the computed ETA by 2 because each loop, there is one citizen less to check, and maths tell
+ # us that this averages to a division by two
+ eta = rulable_users_count2 / global_avg_speed / 2
+ eta_hours = int(eta // 3600)
+ eta_minutes = int(eta // 60 % 60)
+ self.logger.info(
+ f"ETA: {eta_hours} hours {eta_minutes} minutes ({eta / 3600 / 24:.2f} days)"
+ )
+ self.logger.info("#" * 60)
+ t_global_start = time.time()
+
+ # Here, we get the IDs of the old galaxies that we'll need to delete. In normal operation, only one galaxy
+ # should be returned, and we can't delete it yet, as it's the one still displayed by the Sith.
+ old_galaxies_pks = list(
+ Galaxy.objects.filter(state__isnull=False).values_list("pk", flat=True)
+ )
+ self.logger.info(
+ f"These old galaxies will be deleted once the new one is ready: {old_galaxies_pks}"
+ )
+
+ # Making the state sets this new galaxy as being ready. From now on, the Sith will show us to the world.
+ self.make_state()
+
+ # Avoid accident if there is nothing to delete
+ if len(old_galaxies_pks) > 0:
+ # Former galaxies can now be deleted.
+ Galaxy.objects.filter(pk__in=old_galaxies_pks).delete()
+
+ total_time = time.time() - total_time
+ total_time_hours = int(total_time // 3600)
+ total_time_minutes = int(total_time // 60 % 60)
+ total_time_seconds = int(total_time % 60)
+ self.logger.info(
+ f"{self} ruled in {total_time:.2f} seconds ({total_time_hours} hours, {total_time_minutes} minutes, {total_time_seconds} seconds)"
+ )
+
+ def make_state(self) -> None:
+ """
+ Compute JSON structure to send to 3d-force-graph: https://github.com/vasturiano/3d-force-graph/
+ """
+ self.logger.info(
+ "Caching current Galaxy state for a quicker display of the Empire's power."
+ )
+
+ without_nickname = Concat(
+ F("owner__first_name"), Value(" "), F("owner__last_name")
+ )
+ with_nickname = Concat(
+ F("owner__first_name"),
+ Value(" "),
+ F("owner__last_name"),
+ Value(" ("),
+ F("owner__nick_name"),
+ Value(")"),
+ )
+ stars = (
+ GalaxyStar.objects.filter(galaxy=self)
+ .order_by(
+ "owner"
+ ) # This helps determinism for the tests and doesn't cost much
+ .annotate(
+ owner_name=Case(
+ When(owner__nick_name=None, then=without_nickname),
+ default=with_nickname,
+ )
+ )
+ )
+ lanes = (
+ GalaxyLane.objects.filter(star1__galaxy=self)
+ .order_by(
+ "star1"
+ ) # This helps determinism for the tests and doesn't cost much
+ .annotate(
+ star1_owner=F("star1__owner__id"),
+ star2_owner=F("star2__owner__id"),
+ )
+ )
+ json = GalaxyDict(
+ nodes=[
+ StarDict(
+ id=star.owner_id,
+ name=star.owner_name,
+ mass=star.mass,
+ )
+ for star in stars
+ ],
+ links=[],
+ )
+ for path in lanes:
+ json["links"].append(
+ {
+ "source": path.star1_owner,
+ "target": path.star2_owner,
+ "value": path.distance,
+ }
+ )
+ self.state = json
+ self.save()
+ self.logger.info(f"{self} is now ready!")
diff --git a/galaxy/ref_galaxy_state.json b/galaxy/ref_galaxy_state.json
new file mode 100644
index 000000000..7c19b796d
--- /dev/null
+++ b/galaxy/ref_galaxy_state.json
@@ -0,0 +1 @@
+{"nodes": [{"id": 215, "name": "Citizen n\u00b0202", "mass": 5}, {"id": 219, "name": "Citizen n\u00b0206", "mass": 5}, {"id": 221, "name": "Citizen n\u00b0208", "mass": 5}, {"id": 225, "name": "Citizen n\u00b0212", "mass": 5}, {"id": 227, "name": "Citizen n\u00b0214", "mass": 5}, {"id": 228, "name": "Citizen n\u00b0215", "mass": 5}, {"id": 231, "name": "Citizen n\u00b0218", "mass": 5}, {"id": 233, "name": "Citizen n\u00b0220", "mass": 5}, {"id": 243, "name": "Citizen n\u00b0230", "mass": 5}, {"id": 245, "name": "Citizen n\u00b0232", "mass": 5}, {"id": 248, "name": "Citizen n\u00b0235", "mass": 5}, {"id": 249, "name": "Citizen n\u00b0236", "mass": 5}, {"id": 251, "name": "Citizen n\u00b0238", "mass": 5}, {"id": 255, "name": "Citizen n\u00b0242", "mass": 5}, {"id": 257, "name": "Citizen n\u00b0244", "mass": 5}, {"id": 261, "name": "Citizen n\u00b0248", "mass": 5}, {"id": 263, "name": "Citizen n\u00b0250", "mass": 5}, {"id": 273, "name": "Citizen n\u00b0260", "mass": 6}, {"id": 275, "name": "Citizen n\u00b0262", "mass": 5}, {"id": 278, "name": "Citizen n\u00b0265", "mass": 5}, {"id": 279, "name": "Citizen n\u00b0266", "mass": 5}, {"id": 281, "name": "Citizen n\u00b0268", "mass": 5}, {"id": 285, "name": "Citizen n\u00b0272", "mass": 5}, {"id": 287, "name": "Citizen n\u00b0274", "mass": 5}, {"id": 291, "name": "Citizen n\u00b0278", "mass": 5}, {"id": 293, "name": "Citizen n\u00b0280", "mass": 6}, {"id": 303, "name": "Citizen n\u00b0290", "mass": 6}, {"id": 305, "name": "Citizen n\u00b0292", "mass": 5}, {"id": 309, "name": "Citizen n\u00b0296", "mass": 5}, {"id": 311, "name": "Citizen n\u00b0298", "mass": 5}, {"id": 315, "name": "Citizen n\u00b0302", "mass": 5}, {"id": 317, "name": "Citizen n\u00b0304", "mass": 5}, {"id": 321, "name": "Citizen n\u00b0308", "mass": 5}, {"id": 323, "name": "Citizen n\u00b0310", "mass": 6}, {"id": 333, "name": "Citizen n\u00b0320", "mass": 5}, {"id": 335, "name": "Citizen n\u00b0322", "mass": 5}, {"id": 339, "name": "Citizen n\u00b0326", "mass": 5}, {"id": 341, "name": "Citizen n\u00b0328", "mass": 5}, {"id": 345, "name": "Citizen n\u00b0332", "mass": 5}, {"id": 347, "name": "Citizen n\u00b0334", "mass": 5}, {"id": 348, "name": "Citizen n\u00b0335", "mass": 5}, {"id": 351, "name": "Citizen n\u00b0338", "mass": 5}, {"id": 353, "name": "Citizen n\u00b0340", "mass": 6}, {"id": 363, "name": "Citizen n\u00b0350", "mass": 5}, {"id": 365, "name": "Citizen n\u00b0352", "mass": 5}, {"id": 369, "name": "Citizen n\u00b0356", "mass": 5}, {"id": 371, "name": "Citizen n\u00b0358", "mass": 5}, {"id": 375, "name": "Citizen n\u00b0362", "mass": 5}, {"id": 377, "name": "Citizen n\u00b0364", "mass": 5}, {"id": 378, "name": "Citizen n\u00b0365", "mass": 5}, {"id": 381, "name": "Citizen n\u00b0368", "mass": 5}, {"id": 383, "name": "Citizen n\u00b0370", "mass": 5}, {"id": 393, "name": "Citizen n\u00b0380", "mass": 5}, {"id": 395, "name": "Citizen n\u00b0382", "mass": 5}, {"id": 398, "name": "Citizen n\u00b0385", "mass": 5}, {"id": 399, "name": "Citizen n\u00b0386", "mass": 5}, {"id": 401, "name": "Citizen n\u00b0388", "mass": 5}, {"id": 405, "name": "Citizen n\u00b0392", "mass": 5}, {"id": 407, "name": "Citizen n\u00b0394", "mass": 5}, {"id": 411, "name": "Citizen n\u00b0398", "mass": 5}, {"id": 413, "name": "Citizen n\u00b0400", "mass": 10}, {"id": 423, "name": "Citizen n\u00b0410", "mass": 6}, {"id": 425, "name": "Citizen n\u00b0412", "mass": 5}, {"id": 428, "name": "Citizen n\u00b0415", "mass": 5}, {"id": 429, "name": "Citizen n\u00b0416", "mass": 5}, {"id": 431, "name": "Citizen n\u00b0418", "mass": 5}, {"id": 435, "name": "Citizen n\u00b0422", "mass": 5}, {"id": 437, "name": "Citizen n\u00b0424", "mass": 5}, {"id": 441, "name": "Citizen n\u00b0428", "mass": 5}, {"id": 443, "name": "Citizen n\u00b0430", "mass": 5}, {"id": 453, "name": "Citizen n\u00b0440", "mass": 6}, {"id": 455, "name": "Citizen n\u00b0442", "mass": 5}, {"id": 459, "name": "Citizen n\u00b0446", "mass": 5}, {"id": 461, "name": "Citizen n\u00b0448", "mass": 5}, {"id": 465, "name": "Citizen n\u00b0452", "mass": 5}, {"id": 467, "name": "Citizen n\u00b0454", "mass": 5}, {"id": 471, "name": "Citizen n\u00b0458", "mass": 5}, {"id": 473, "name": "Citizen n\u00b0460", "mass": 6}, {"id": 483, "name": "Citizen n\u00b0470", "mass": 5}, {"id": 485, "name": "Citizen n\u00b0472", "mass": 5}, {"id": 489, "name": "Citizen n\u00b0476", "mass": 5}, {"id": 491, "name": "Citizen n\u00b0478", "mass": 5}, {"id": 495, "name": "Citizen n\u00b0482", "mass": 5}, {"id": 497, "name": "Citizen n\u00b0484", "mass": 5}, {"id": 498, "name": "Citizen n\u00b0485", "mass": 5}, {"id": 501, "name": "Citizen n\u00b0488", "mass": 5}, {"id": 503, "name": "Citizen n\u00b0490", "mass": 6}, {"id": 513, "name": "Citizen n\u00b0500", "mass": 10}, {"id": 515, "name": "Citizen n\u00b0502", "mass": 5}, {"id": 519, "name": "Citizen n\u00b0506", "mass": 5}, {"id": 521, "name": "Citizen n\u00b0508", "mass": 5}, {"id": 525, "name": "Citizen n\u00b0512", "mass": 5}, {"id": 527, "name": "Citizen n\u00b0514", "mass": 5}, {"id": 528, "name": "Citizen n\u00b0515", "mass": 5}, {"id": 531, "name": "Citizen n\u00b0518", "mass": 5}, {"id": 533, "name": "Citizen n\u00b0520", "mass": 5}, {"id": 543, "name": "Citizen n\u00b0530", "mass": 5}, {"id": 545, "name": "Citizen n\u00b0532", "mass": 5}, {"id": 548, "name": "Citizen n\u00b0535", "mass": 5}, {"id": 549, "name": "Citizen n\u00b0536", "mass": 5}, {"id": 551, "name": "Citizen n\u00b0538", "mass": 5}, {"id": 555, "name": "Citizen n\u00b0542", "mass": 5}, {"id": 557, "name": "Citizen n\u00b0544", "mass": 5}, {"id": 561, "name": "Citizen n\u00b0548", "mass": 5}, {"id": 563, "name": "Citizen n\u00b0550", "mass": 5}, {"id": 573, "name": "Citizen n\u00b0560", "mass": 6}, {"id": 575, "name": "Citizen n\u00b0562", "mass": 5}, {"id": 578, "name": "Citizen n\u00b0565", "mass": 5}, {"id": 579, "name": "Citizen n\u00b0566", "mass": 5}, {"id": 581, "name": "Citizen n\u00b0568", "mass": 5}, {"id": 585, "name": "Citizen n\u00b0572", "mass": 5}, {"id": 587, "name": "Citizen n\u00b0574", "mass": 5}, {"id": 591, "name": "Citizen n\u00b0578", "mass": 5}, {"id": 593, "name": "Citizen n\u00b0580", "mass": 5}, {"id": 603, "name": "Citizen n\u00b0590", "mass": 6}, {"id": 605, "name": "Citizen n\u00b0592", "mass": 5}, {"id": 609, "name": "Citizen n\u00b0596", "mass": 5}, {"id": 611, "name": "Citizen n\u00b0598", "mass": 5}, {"id": 615, "name": "Citizen n\u00b0602", "mass": 5}, {"id": 617, "name": "Citizen n\u00b0604", "mass": 5}, {"id": 621, "name": "Citizen n\u00b0608", "mass": 5}, {"id": 623, "name": "Citizen n\u00b0610", "mass": 6}, {"id": 633, "name": "Citizen n\u00b0620", "mass": 5}, {"id": 635, "name": "Citizen n\u00b0622", "mass": 5}, {"id": 639, "name": "Citizen n\u00b0626", "mass": 5}, {"id": 641, "name": "Citizen n\u00b0628", "mass": 5}, {"id": 645, "name": "Citizen n\u00b0632", "mass": 5}, {"id": 647, "name": "Citizen n\u00b0634", "mass": 5}, {"id": 648, "name": "Citizen n\u00b0635", "mass": 5}, {"id": 651, "name": "Citizen n\u00b0638", "mass": 5}, {"id": 653, "name": "Citizen n\u00b0640", "mass": 6}, {"id": 663, "name": "Citizen n\u00b0650", "mass": 5}, {"id": 665, "name": "Citizen n\u00b0652", "mass": 5}, {"id": 669, "name": "Citizen n\u00b0656", "mass": 5}, {"id": 671, "name": "Citizen n\u00b0658", "mass": 5}, {"id": 675, "name": "Citizen n\u00b0662", "mass": 5}, {"id": 677, "name": "Citizen n\u00b0664", "mass": 5}, {"id": 678, "name": "Citizen n\u00b0665", "mass": 5}, {"id": 681, "name": "Citizen n\u00b0668", "mass": 5}, {"id": 683, "name": "Citizen n\u00b0670", "mass": 5}, {"id": 693, "name": "Citizen n\u00b0680", "mass": 5}, {"id": 695, "name": "Citizen n\u00b0682", "mass": 5}, {"id": 698, "name": "Citizen n\u00b0685", "mass": 5}, {"id": 699, "name": "Citizen n\u00b0686", "mass": 5}, {"id": 701, "name": "Citizen n\u00b0688", "mass": 5}, {"id": 705, "name": "Citizen n\u00b0692", "mass": 5}, {"id": 707, "name": "Citizen n\u00b0694", "mass": 5}, {"id": 711, "name": "Citizen n\u00b0698", "mass": 5}, {"id": 713, "name": "Citizen n\u00b0700", "mass": 6}, {"id": 723, "name": "Citizen n\u00b0710", "mass": 6}, {"id": 725, "name": "Citizen n\u00b0712", "mass": 5}, {"id": 728, "name": "Citizen n\u00b0715", "mass": 5}, {"id": 729, "name": "Citizen n\u00b0716", "mass": 5}, {"id": 731, "name": "Citizen n\u00b0718", "mass": 5}, {"id": 735, "name": "Citizen n\u00b0722", "mass": 5}, {"id": 737, "name": "Citizen n\u00b0724", "mass": 5}, {"id": 741, "name": "Citizen n\u00b0728", "mass": 5}, {"id": 743, "name": "Citizen n\u00b0730", "mass": 5}, {"id": 753, "name": "Citizen n\u00b0740", "mass": 6}, {"id": 755, "name": "Citizen n\u00b0742", "mass": 5}, {"id": 759, "name": "Citizen n\u00b0746", "mass": 5}, {"id": 761, "name": "Citizen n\u00b0748", "mass": 5}, {"id": 765, "name": "Citizen n\u00b0752", "mass": 5}, {"id": 767, "name": "Citizen n\u00b0754", "mass": 5}, {"id": 771, "name": "Citizen n\u00b0758", "mass": 5}, {"id": 773, "name": "Citizen n\u00b0760", "mass": 6}, {"id": 783, "name": "Citizen n\u00b0770", "mass": 5}, {"id": 785, "name": "Citizen n\u00b0772", "mass": 5}, {"id": 789, "name": "Citizen n\u00b0776", "mass": 5}, {"id": 791, "name": "Citizen n\u00b0778", "mass": 5}, {"id": 795, "name": "Citizen n\u00b0782", "mass": 5}, {"id": 797, "name": "Citizen n\u00b0784", "mass": 5}, {"id": 798, "name": "Citizen n\u00b0785", "mass": 5}, {"id": 801, "name": "Citizen n\u00b0788", "mass": 5}, {"id": 803, "name": "Citizen n\u00b0790", "mass": 5}], "links": [{"source": 228, "target": 225, "value": 8}, {"source": 231, "target": 221, "value": 9}, {"source": 233, "target": 221, "value": 4}, {"source": 245, "target": 233, "value": 4}, {"source": 248, "target": 219, "value": 9}, {"source": 248, "target": 233, "value": 4}, {"source": 248, "target": 245, "value": 8}, {"source": 249, "target": 228, "value": 8}, {"source": 251, "target": 245, "value": 10}, {"source": 251, "target": 249, "value": 9}, {"source": 255, "target": 245, "value": 9}, {"source": 255, "target": 251, "value": 7}, {"source": 257, "target": 219, "value": 8}, {"source": 257, "target": 233, "value": 4}, {"source": 257, "target": 245, "value": 8}, {"source": 257, "target": 248, "value": 7}, {"source": 261, "target": 221, "value": 10}, {"source": 261, "target": 249, "value": 8}, {"source": 273, "target": 219, "value": 10}, {"source": 273, "target": 221, "value": 2}, {"source": 273, "target": 231, "value": 9}, {"source": 273, "target": 248, "value": 9}, {"source": 273, "target": 257, "value": 9}, {"source": 273, "target": 261, "value": 8}, {"source": 275, "target": 225, "value": 3}, {"source": 275, "target": 228, "value": 8}, {"source": 278, "target": 225, "value": 7}, {"source": 278, "target": 228, "value": 3}, {"source": 278, "target": 249, "value": 9}, {"source": 278, "target": 275, "value": 8}, {"source": 279, "target": 221, "value": 4}, {"source": 279, "target": 227, "value": 9}, {"source": 279, "target": 233, "value": 4}, {"source": 281, "target": 221, "value": 10}, {"source": 281, "target": 231, "value": 3}, {"source": 281, "target": 243, "value": 8}, {"source": 281, "target": 273, "value": 10}, {"source": 285, "target": 233, "value": 4}, {"source": 285, "target": 245, "value": 10}, {"source": 285, "target": 248, "value": 10}, {"source": 285, "target": 261, "value": 8}, {"source": 285, "target": 273, "value": 8}, {"source": 287, "target": 225, "value": 8}, {"source": 287, "target": 263, "value": 9}, {"source": 293, "target": 221, "value": 11}, {"source": 293, "target": 243, "value": 1}, {"source": 293, "target": 245, "value": 9}, {"source": 293, "target": 251, "value": 8}, {"source": 293, "target": 255, "value": 8}, {"source": 293, "target": 281, "value": 8}, {"source": 293, "target": 285, "value": 8}, {"source": 293, "target": 291, "value": 8}, {"source": 303, "target": 227, "value": 9}, {"source": 303, "target": 228, "value": 8}, {"source": 303, "target": 249, "value": 10}, {"source": 303, "target": 278, "value": 9}, {"source": 303, "target": 279, "value": 8}, {"source": 303, "target": 293, "value": 9}, {"source": 305, "target": 245, "value": 9}, {"source": 305, "target": 251, "value": 7}, {"source": 305, "target": 255, "value": 2}, {"source": 305, "target": 293, "value": 8}, {"source": 309, "target": 219, "value": 9}, {"source": 309, "target": 221, "value": 8}, {"source": 309, "target": 248, "value": 8}, {"source": 309, "target": 257, "value": 8}, {"source": 309, "target": 261, "value": 9}, {"source": 309, "target": 263, "value": 11}, {"source": 309, "target": 273, "value": 8}, {"source": 311, "target": 249, "value": 8}, {"source": 311, "target": 261, "value": 3}, {"source": 311, "target": 285, "value": 8}, {"source": 315, "target": 215, "value": 4}, {"source": 317, "target": 221, "value": 10}, {"source": 317, "target": 227, "value": 9}, {"source": 317, "target": 233, "value": 10}, {"source": 317, "target": 243, "value": 4}, {"source": 317, "target": 255, "value": 8}, {"source": 317, "target": 279, "value": 8}, {"source": 317, "target": 293, "value": 3}, {"source": 317, "target": 305, "value": 8}, {"source": 321, "target": 219, "value": 10}, {"source": 321, "target": 221, "value": 2}, {"source": 321, "target": 225, "value": 7}, {"source": 321, "target": 228, "value": 8}, {"source": 321, "target": 233, "value": 4}, {"source": 321, "target": 249, "value": 11}, {"source": 321, "target": 251, "value": 8}, {"source": 321, "target": 261, "value": 9}, {"source": 321, "target": 275, "value": 7}, {"source": 321, "target": 278, "value": 7}, {"source": 321, "target": 279, "value": 4}, {"source": 321, "target": 293, "value": 11}, {"source": 321, "target": 309, "value": 8}, {"source": 323, "target": 219, "value": 10}, {"source": 323, "target": 221, "value": 8}, {"source": 323, "target": 248, "value": 9}, {"source": 323, "target": 257, "value": 9}, {"source": 323, "target": 261, "value": 8}, {"source": 323, "target": 273, "value": 1}, {"source": 323, "target": 309, "value": 8}, {"source": 333, "target": 221, "value": 4}, {"source": 333, "target": 233, "value": 0}, {"source": 333, "target": 243, "value": 9}, {"source": 333, "target": 245, "value": 8}, {"source": 333, "target": 248, "value": 8}, {"source": 333, "target": 257, "value": 4}, {"source": 333, "target": 279, "value": 4}, {"source": 333, "target": 281, "value": 8}, {"source": 333, "target": 285, "value": 9}, {"source": 333, "target": 293, "value": 9}, {"source": 333, "target": 309, "value": 8}, {"source": 333, "target": 317, "value": 9}, {"source": 333, "target": 321, "value": 4}, {"source": 335, "target": 233, "value": 10}, {"source": 335, "target": 243, "value": 11}, {"source": 335, "target": 273, "value": 8}, {"source": 335, "target": 285, "value": 3}, {"source": 335, "target": 293, "value": 10}, {"source": 335, "target": 333, "value": 8}, {"source": 339, "target": 243, "value": 11}, {"source": 339, "target": 293, "value": 10}, {"source": 339, "target": 335, "value": 8}, {"source": 341, "target": 251, "value": 9}, {"source": 341, "target": 291, "value": 3}, {"source": 341, "target": 293, "value": 4}, {"source": 341, "target": 303, "value": 8}, {"source": 345, "target": 233, "value": 4}, {"source": 345, "target": 245, "value": 2}, {"source": 345, "target": 248, "value": 8}, {"source": 345, "target": 257, "value": 8}, {"source": 345, "target": 285, "value": 9}, {"source": 345, "target": 333, "value": 8}, {"source": 347, "target": 221, "value": 8}, {"source": 347, "target": 273, "value": 4}, {"source": 347, "target": 285, "value": 8}, {"source": 347, "target": 293, "value": 7}, {"source": 347, "target": 323, "value": 9}, {"source": 348, "target": 219, "value": 8}, {"source": 348, "target": 233, "value": 4}, {"source": 348, "target": 245, "value": 8}, {"source": 348, "target": 248, "value": 4}, {"source": 348, "target": 257, "value": 7}, {"source": 348, "target": 273, "value": 9}, {"source": 348, "target": 285, "value": 10}, {"source": 348, "target": 309, "value": 8}, {"source": 348, "target": 323, "value": 9}, {"source": 348, "target": 333, "value": 8}, {"source": 348, "target": 345, "value": 8}, {"source": 351, "target": 251, "value": 3}, {"source": 351, "target": 263, "value": 8}, {"source": 351, "target": 309, "value": 9}, {"source": 351, "target": 341, "value": 9}, {"source": 353, "target": 215, "value": 8}, {"source": 353, "target": 227, "value": 4}, {"source": 353, "target": 228, "value": 9}, {"source": 353, "target": 249, "value": 10}, {"source": 353, "target": 278, "value": 9}, {"source": 353, "target": 279, "value": 4}, {"source": 353, "target": 293, "value": 9}, {"source": 353, "target": 303, "value": 1}, {"source": 353, "target": 315, "value": 8}, {"source": 353, "target": 341, "value": 8}, {"source": 363, "target": 255, "value": 9}, {"source": 363, "target": 261, "value": 9}, {"source": 363, "target": 263, "value": 2}, {"source": 363, "target": 285, "value": 8}, {"source": 363, "target": 287, "value": 9}, {"source": 363, "target": 305, "value": 8}, {"source": 363, "target": 309, "value": 10}, {"source": 363, "target": 311, "value": 9}, {"source": 363, "target": 317, "value": 7}, {"source": 363, "target": 351, "value": 8}, {"source": 365, "target": 215, "value": 4}, {"source": 365, "target": 225, "value": 9}, {"source": 365, "target": 227, "value": 4}, {"source": 365, "target": 228, "value": 10}, {"source": 365, "target": 275, "value": 9}, {"source": 365, "target": 278, "value": 10}, {"source": 365, "target": 291, "value": 8}, {"source": 365, "target": 293, "value": 9}, {"source": 365, "target": 315, "value": 3}, {"source": 365, "target": 317, "value": 4}, {"source": 365, "target": 321, "value": 9}, {"source": 365, "target": 341, "value": 8}, {"source": 365, "target": 353, "value": 8}, {"source": 369, "target": 219, "value": 4}, {"source": 369, "target": 221, "value": 9}, {"source": 369, "target": 231, "value": 8}, {"source": 369, "target": 248, "value": 8}, {"source": 369, "target": 257, "value": 8}, {"source": 369, "target": 273, "value": 5}, {"source": 369, "target": 281, "value": 8}, {"source": 369, "target": 309, "value": 9}, {"source": 369, "target": 321, "value": 10}, {"source": 369, "target": 323, "value": 10}, {"source": 369, "target": 348, "value": 8}, {"source": 371, "target": 221, "value": 4}, {"source": 371, "target": 261, "value": 10}, {"source": 371, "target": 293, "value": 11}, {"source": 371, "target": 309, "value": 8}, {"source": 371, "target": 321, "value": 3}, {"source": 375, "target": 225, "value": 4}, {"source": 375, "target": 228, "value": 8}, {"source": 375, "target": 275, "value": 4}, {"source": 375, "target": 278, "value": 8}, {"source": 375, "target": 321, "value": 7}, {"source": 375, "target": 365, "value": 9}, {"source": 377, "target": 221, "value": 10}, {"source": 377, "target": 225, "value": 9}, {"source": 377, "target": 227, "value": 4}, {"source": 377, "target": 231, "value": 8}, {"source": 377, "target": 243, "value": 10}, {"source": 377, "target": 255, "value": 10}, {"source": 377, "target": 273, "value": 4}, {"source": 377, "target": 275, "value": 9}, {"source": 377, "target": 279, "value": 10}, {"source": 377, "target": 281, "value": 8}, {"source": 377, "target": 285, "value": 9}, {"source": 377, "target": 293, "value": 10}, {"source": 377, "target": 303, "value": 9}, {"source": 377, "target": 305, "value": 10}, {"source": 377, "target": 317, "value": 9}, {"source": 377, "target": 335, "value": 4}, {"source": 377, "target": 339, "value": 8}, {"source": 377, "target": 353, "value": 4}, {"source": 377, "target": 365, "value": 4}, {"source": 377, "target": 369, "value": 7}, {"source": 377, "target": 375, "value": 8}, {"source": 378, "target": 225, "value": 8}, {"source": 378, "target": 228, "value": 4}, {"source": 378, "target": 249, "value": 8}, {"source": 378, "target": 275, "value": 8}, {"source": 378, "target": 278, "value": 4}, {"source": 378, "target": 303, "value": 9}, {"source": 378, "target": 321, "value": 8}, {"source": 378, "target": 353, "value": 9}, {"source": 378, "target": 365, "value": 10}, {"source": 378, "target": 375, "value": 8}, {"source": 381, "target": 221, "value": 10}, {"source": 381, "target": 231, "value": 4}, {"source": 381, "target": 233, "value": 4}, {"source": 381, "target": 243, "value": 8}, {"source": 381, "target": 257, "value": 8}, {"source": 381, "target": 273, "value": 10}, {"source": 381, "target": 281, "value": 2}, {"source": 381, "target": 291, "value": 9}, {"source": 381, "target": 293, "value": 8}, {"source": 381, "target": 333, "value": 2}, {"source": 381, "target": 341, "value": 9}, {"source": 381, "target": 369, "value": 8}, {"source": 381, "target": 377, "value": 8}, {"source": 383, "target": 221, "value": 4}, {"source": 383, "target": 233, "value": 1}, {"source": 383, "target": 257, "value": 8}, {"source": 383, "target": 279, "value": 4}, {"source": 383, "target": 317, "value": 9}, {"source": 383, "target": 321, "value": 4}, {"source": 383, "target": 333, "value": 1}, {"source": 383, "target": 335, "value": 9}, {"source": 383, "target": 381, "value": 7}, {"source": 393, "target": 243, "value": 1}, {"source": 393, "target": 245, "value": 9}, {"source": 393, "target": 251, "value": 8}, {"source": 393, "target": 255, "value": 8}, {"source": 393, "target": 281, "value": 8}, {"source": 393, "target": 293, "value": 1}, {"source": 393, "target": 305, "value": 8}, {"source": 393, "target": 317, "value": 3}, {"source": 393, "target": 333, "value": 9}, {"source": 393, "target": 335, "value": 10}, {"source": 393, "target": 339, "value": 10}, {"source": 393, "target": 377, "value": 9}, {"source": 393, "target": 381, "value": 8}, {"source": 395, "target": 233, "value": 4}, {"source": 395, "target": 245, "value": 4}, {"source": 395, "target": 248, "value": 8}, {"source": 395, "target": 285, "value": 10}, {"source": 395, "target": 333, "value": 8}, {"source": 395, "target": 345, "value": 3}, {"source": 395, "target": 348, "value": 8}, {"source": 398, "target": 219, "value": 8}, {"source": 398, "target": 233, "value": 4}, {"source": 398, "target": 243, "value": 9}, {"source": 398, "target": 245, "value": 8}, {"source": 398, "target": 248, "value": 4}, {"source": 398, "target": 257, "value": 8}, {"source": 398, "target": 273, "value": 10}, {"source": 398, "target": 281, "value": 8}, {"source": 398, "target": 285, "value": 10}, {"source": 398, "target": 293, "value": 9}, {"source": 398, "target": 309, "value": 9}, {"source": 398, "target": 323, "value": 9}, {"source": 398, "target": 333, "value": 4}, {"source": 398, "target": 345, "value": 7}, {"source": 398, "target": 348, "value": 3}, {"source": 398, "target": 369, "value": 8}, {"source": 398, "target": 381, "value": 8}, {"source": 398, "target": 393, "value": 9}, {"source": 398, "target": 395, "value": 8}, {"source": 399, "target": 228, "value": 9}, {"source": 399, "target": 249, "value": 4}, {"source": 399, "target": 251, "value": 10}, {"source": 399, "target": 278, "value": 8}, {"source": 399, "target": 303, "value": 11}, {"source": 399, "target": 321, "value": 11}, {"source": 399, "target": 353, "value": 10}, {"source": 399, "target": 378, "value": 8}, {"source": 401, "target": 251, "value": 4}, {"source": 401, "target": 263, "value": 8}, {"source": 401, "target": 341, "value": 9}, {"source": 401, "target": 351, "value": 3}, {"source": 401, "target": 363, "value": 8}, {"source": 405, "target": 215, "value": 9}, {"source": 405, "target": 233, "value": 10}, {"source": 405, "target": 245, "value": 10}, {"source": 405, "target": 251, "value": 8}, {"source": 405, "target": 255, "value": 2}, {"source": 405, "target": 263, "value": 11}, {"source": 405, "target": 293, "value": 8}, {"source": 405, "target": 303, "value": 9}, {"source": 405, "target": 305, "value": 2}, {"source": 405, "target": 309, "value": 8}, {"source": 405, "target": 315, "value": 9}, {"source": 405, "target": 317, "value": 8}, {"source": 405, "target": 333, "value": 10}, {"source": 405, "target": 335, "value": 8}, {"source": 405, "target": 351, "value": 9}, {"source": 405, "target": 353, "value": 4}, {"source": 405, "target": 363, "value": 4}, {"source": 405, "target": 365, "value": 9}, {"source": 405, "target": 377, "value": 11}, {"source": 405, "target": 383, "value": 10}, {"source": 405, "target": 393, "value": 8}, {"source": 407, "target": 233, "value": 4}, {"source": 407, "target": 245, "value": 8}, {"source": 407, "target": 257, "value": 4}, {"source": 407, "target": 333, "value": 4}, {"source": 407, "target": 345, "value": 7}, {"source": 407, "target": 381, "value": 8}, {"source": 407, "target": 383, "value": 9}, {"source": 411, "target": 249, "value": 8}, {"source": 411, "target": 261, "value": 4}, {"source": 411, "target": 285, "value": 8}, {"source": 411, "target": 311, "value": 4}, {"source": 411, "target": 363, "value": 9}, {"source": 413, "target": 215, "value": 26}, {"source": 413, "target": 219, "value": 27}, {"source": 413, "target": 221, "value": 26}, {"source": 413, "target": 225, "value": 6}, {"source": 413, "target": 227, "value": 28}, {"source": 413, "target": 228, "value": 6}, {"source": 413, "target": 231, "value": 27}, {"source": 413, "target": 233, "value": 27}, {"source": 413, "target": 243, "value": 29}, {"source": 413, "target": 245, "value": 28}, {"source": 413, "target": 248, "value": 28}, {"source": 413, "target": 249, "value": 29}, {"source": 413, "target": 251, "value": 28}, {"source": 413, "target": 255, "value": 28}, {"source": 413, "target": 257, "value": 29}, {"source": 413, "target": 261, "value": 6}, {"source": 413, "target": 263, "value": 1}, {"source": 413, "target": 273, "value": 7}, {"source": 413, "target": 275, "value": 6}, {"source": 413, "target": 278, "value": 6}, {"source": 413, "target": 279, "value": 29}, {"source": 413, "target": 287, "value": 4}, {"source": 413, "target": 309, "value": 8}, {"source": 413, "target": 321, "value": 7}, {"source": 413, "target": 323, "value": 8}, {"source": 413, "target": 339, "value": 7}, {"source": 413, "target": 351, "value": 7}, {"source": 413, "target": 363, "value": 1}, {"source": 413, "target": 365, "value": 8}, {"source": 413, "target": 375, "value": 8}, {"source": 413, "target": 378, "value": 8}, {"source": 413, "target": 401, "value": 8}, {"source": 413, "target": 405, "value": 9}, {"source": 423, "target": 219, "value": 10}, {"source": 423, "target": 221, "value": 8}, {"source": 423, "target": 248, "value": 8}, {"source": 423, "target": 257, "value": 9}, {"source": 423, "target": 261, "value": 8}, {"source": 423, "target": 273, "value": 1}, {"source": 423, "target": 309, "value": 8}, {"source": 423, "target": 323, "value": 2}, {"source": 423, "target": 347, "value": 8}, {"source": 423, "target": 348, "value": 8}, {"source": 423, "target": 369, "value": 10}, {"source": 423, "target": 398, "value": 9}, {"source": 423, "target": 413, "value": 8}, {"source": 425, "target": 225, "value": 2}, {"source": 425, "target": 228, "value": 8}, {"source": 425, "target": 275, "value": 4}, {"source": 425, "target": 278, "value": 8}, {"source": 425, "target": 287, "value": 8}, {"source": 425, "target": 321, "value": 8}, {"source": 425, "target": 365, "value": 9}, {"source": 425, "target": 375, "value": 3}, {"source": 425, "target": 377, "value": 9}, {"source": 425, "target": 378, "value": 8}, {"source": 425, "target": 413, "value": 8}, {"source": 428, "target": 225, "value": 8}, {"source": 428, "target": 228, "value": 4}, {"source": 428, "target": 249, "value": 8}, {"source": 428, "target": 275, "value": 8}, {"source": 428, "target": 278, "value": 4}, {"source": 428, "target": 303, "value": 9}, {"source": 428, "target": 321, "value": 8}, {"source": 428, "target": 353, "value": 9}, {"source": 428, "target": 365, "value": 9}, {"source": 428, "target": 375, "value": 7}, {"source": 428, "target": 378, "value": 3}, {"source": 428, "target": 399, "value": 8}, {"source": 428, "target": 413, "value": 8}, {"source": 428, "target": 425, "value": 8}, {"source": 429, "target": 221, "value": 11}, {"source": 429, "target": 225, "value": 8}, {"source": 429, "target": 233, "value": 11}, {"source": 429, "target": 243, "value": 9}, {"source": 429, "target": 255, "value": 8}, {"source": 429, "target": 279, "value": 4}, {"source": 429, "target": 287, "value": 9}, {"source": 429, "target": 291, "value": 8}, {"source": 429, "target": 293, "value": 8}, {"source": 429, "target": 305, "value": 8}, {"source": 429, "target": 317, "value": 8}, {"source": 429, "target": 333, "value": 11}, {"source": 429, "target": 335, "value": 9}, {"source": 429, "target": 339, "value": 9}, {"source": 429, "target": 341, "value": 8}, {"source": 429, "target": 377, "value": 4}, {"source": 429, "target": 381, "value": 9}, {"source": 429, "target": 383, "value": 10}, {"source": 429, "target": 393, "value": 8}, {"source": 429, "target": 405, "value": 8}, {"source": 429, "target": 425, "value": 9}, {"source": 431, "target": 221, "value": 10}, {"source": 431, "target": 231, "value": 4}, {"source": 431, "target": 243, "value": 9}, {"source": 431, "target": 273, "value": 10}, {"source": 431, "target": 281, "value": 2}, {"source": 431, "target": 293, "value": 9}, {"source": 431, "target": 333, "value": 8}, {"source": 431, "target": 369, "value": 7}, {"source": 431, "target": 377, "value": 7}, {"source": 431, "target": 381, "value": 2}, {"source": 431, "target": 393, "value": 9}, {"source": 431, "target": 398, "value": 8}, {"source": 435, "target": 273, "value": 8}, {"source": 435, "target": 285, "value": 4}, {"source": 435, "target": 335, "value": 4}, {"source": 435, "target": 377, "value": 9}, {"source": 437, "target": 225, "value": 8}, {"source": 437, "target": 228, "value": 8}, {"source": 437, "target": 249, "value": 8}, {"source": 437, "target": 263, "value": 9}, {"source": 437, "target": 278, "value": 8}, {"source": 437, "target": 285, "value": 8}, {"source": 437, "target": 287, "value": 4}, {"source": 437, "target": 293, "value": 9}, {"source": 437, "target": 303, "value": 10}, {"source": 437, "target": 347, "value": 9}, {"source": 437, "target": 353, "value": 10}, {"source": 437, "target": 363, "value": 9}, {"source": 437, "target": 378, "value": 8}, {"source": 437, "target": 399, "value": 8}, {"source": 437, "target": 413, "value": 4}, {"source": 437, "target": 425, "value": 8}, {"source": 437, "target": 428, "value": 7}, {"source": 437, "target": 429, "value": 9}, {"source": 441, "target": 291, "value": 4}, {"source": 441, "target": 293, "value": 5}, {"source": 441, "target": 303, "value": 8}, {"source": 441, "target": 341, "value": 2}, {"source": 441, "target": 353, "value": 8}, {"source": 441, "target": 365, "value": 8}, {"source": 441, "target": 381, "value": 9}, {"source": 441, "target": 429, "value": 8}, {"source": 443, "target": 243, "value": 2}, {"source": 443, "target": 281, "value": 8}, {"source": 443, "target": 293, "value": 1}, {"source": 443, "target": 317, "value": 4}, {"source": 443, "target": 333, "value": 9}, {"source": 443, "target": 335, "value": 10}, {"source": 443, "target": 339, "value": 10}, {"source": 443, "target": 377, "value": 9}, {"source": 443, "target": 381, "value": 8}, {"source": 443, "target": 393, "value": 1}, {"source": 443, "target": 398, "value": 9}, {"source": 443, "target": 429, "value": 8}, {"source": 443, "target": 431, "value": 9}, {"source": 453, "target": 215, "value": 4}, {"source": 453, "target": 227, "value": 4}, {"source": 453, "target": 228, "value": 8}, {"source": 453, "target": 249, "value": 10}, {"source": 453, "target": 263, "value": 9}, {"source": 453, "target": 278, "value": 8}, {"source": 453, "target": 279, "value": 4}, {"source": 453, "target": 293, "value": 10}, {"source": 453, "target": 303, "value": 1}, {"source": 453, "target": 315, "value": 4}, {"source": 453, "target": 341, "value": 8}, {"source": 453, "target": 353, "value": 0}, {"source": 453, "target": 363, "value": 9}, {"source": 453, "target": 365, "value": 4}, {"source": 453, "target": 377, "value": 4}, {"source": 453, "target": 378, "value": 8}, {"source": 453, "target": 399, "value": 9}, {"source": 453, "target": 401, "value": 8}, {"source": 453, "target": 405, "value": 3}, {"source": 453, "target": 413, "value": 9}, {"source": 453, "target": 428, "value": 8}, {"source": 453, "target": 437, "value": 9}, {"source": 453, "target": 441, "value": 8}, {"source": 455, "target": 245, "value": 10}, {"source": 455, "target": 251, "value": 8}, {"source": 455, "target": 255, "value": 4}, {"source": 455, "target": 263, "value": 9}, {"source": 455, "target": 293, "value": 8}, {"source": 455, "target": 305, "value": 4}, {"source": 455, "target": 363, "value": 9}, {"source": 455, "target": 377, "value": 11}, {"source": 455, "target": 393, "value": 8}, {"source": 455, "target": 401, "value": 8}, {"source": 455, "target": 405, "value": 3}, {"source": 455, "target": 413, "value": 9}, {"source": 455, "target": 429, "value": 8}, {"source": 455, "target": 453, "value": 7}, {"source": 459, "target": 263, "value": 11}, {"source": 459, "target": 309, "value": 4}, {"source": 459, "target": 333, "value": 8}, {"source": 459, "target": 351, "value": 10}, {"source": 459, "target": 363, "value": 10}, {"source": 459, "target": 405, "value": 7}, {"source": 459, "target": 413, "value": 10}, {"source": 461, "target": 215, "value": 8}, {"source": 461, "target": 221, "value": 9}, {"source": 461, "target": 249, "value": 8}, {"source": 461, "target": 261, "value": 2}, {"source": 461, "target": 263, "value": 8}, {"source": 461, "target": 273, "value": 8}, {"source": 461, "target": 285, "value": 8}, {"source": 461, "target": 287, "value": 8}, {"source": 461, "target": 309, "value": 4}, {"source": 461, "target": 311, "value": 4}, {"source": 461, "target": 315, "value": 8}, {"source": 461, "target": 321, "value": 9}, {"source": 461, "target": 323, "value": 8}, {"source": 461, "target": 333, "value": 8}, {"source": 461, "target": 339, "value": 10}, {"source": 461, "target": 353, "value": 8}, {"source": 461, "target": 363, "value": 4}, {"source": 461, "target": 365, "value": 7}, {"source": 461, "target": 371, "value": 9}, {"source": 461, "target": 405, "value": 9}, {"source": 461, "target": 411, "value": 3}, {"source": 461, "target": 413, "value": 2}, {"source": 461, "target": 423, "value": 8}, {"source": 461, "target": 437, "value": 8}, {"source": 461, "target": 453, "value": 4}, {"source": 461, "target": 459, "value": 8}, {"source": 465, "target": 215, "value": 4}, {"source": 465, "target": 227, "value": 8}, {"source": 465, "target": 315, "value": 4}, {"source": 465, "target": 317, "value": 9}, {"source": 465, "target": 353, "value": 8}, {"source": 465, "target": 365, "value": 2}, {"source": 465, "target": 377, "value": 8}, {"source": 465, "target": 405, "value": 9}, {"source": 465, "target": 453, "value": 4}, {"source": 465, "target": 461, "value": 7}, {"source": 467, "target": 243, "value": 9}, {"source": 467, "target": 255, "value": 10}, {"source": 467, "target": 293, "value": 4}, {"source": 467, "target": 305, "value": 8}, {"source": 467, "target": 317, "value": 4}, {"source": 467, "target": 363, "value": 8}, {"source": 467, "target": 393, "value": 4}, {"source": 467, "target": 405, "value": 7}, {"source": 467, "target": 443, "value": 9}, {"source": 471, "target": 221, "value": 4}, {"source": 471, "target": 261, "value": 10}, {"source": 471, "target": 293, "value": 11}, {"source": 471, "target": 309, "value": 8}, {"source": 471, "target": 321, "value": 4}, {"source": 471, "target": 371, "value": 4}, {"source": 471, "target": 461, "value": 8}, {"source": 473, "target": 219, "value": 9}, {"source": 473, "target": 221, "value": 4}, {"source": 473, "target": 248, "value": 8}, {"source": 473, "target": 257, "value": 9}, {"source": 473, "target": 261, "value": 8}, {"source": 473, "target": 273, "value": 1}, {"source": 473, "target": 285, "value": 8}, {"source": 473, "target": 309, "value": 8}, {"source": 473, "target": 323, "value": 1}, {"source": 473, "target": 335, "value": 8}, {"source": 473, "target": 347, "value": 4}, {"source": 473, "target": 348, "value": 8}, {"source": 473, "target": 369, "value": 10}, {"source": 473, "target": 377, "value": 9}, {"source": 473, "target": 398, "value": 8}, {"source": 473, "target": 413, "value": 9}, {"source": 473, "target": 423, "value": 1}, {"source": 473, "target": 435, "value": 8}, {"source": 473, "target": 461, "value": 8}, {"source": 483, "target": 221, "value": 4}, {"source": 483, "target": 233, "value": 1}, {"source": 483, "target": 257, "value": 8}, {"source": 483, "target": 279, "value": 4}, {"source": 483, "target": 317, "value": 9}, {"source": 483, "target": 321, "value": 4}, {"source": 483, "target": 333, "value": 1}, {"source": 483, "target": 335, "value": 10}, {"source": 483, "target": 381, "value": 8}, {"source": 483, "target": 383, "value": 2}, {"source": 483, "target": 405, "value": 10}, {"source": 483, "target": 407, "value": 8}, {"source": 483, "target": 429, "value": 9}, {"source": 485, "target": 233, "value": 4}, {"source": 485, "target": 245, "value": 9}, {"source": 485, "target": 248, "value": 9}, {"source": 485, "target": 273, "value": 8}, {"source": 485, "target": 285, "value": 2}, {"source": 485, "target": 293, "value": 8}, {"source": 485, "target": 333, "value": 8}, {"source": 485, "target": 335, "value": 4}, {"source": 485, "target": 345, "value": 9}, {"source": 485, "target": 347, "value": 8}, {"source": 485, "target": 348, "value": 9}, {"source": 485, "target": 377, "value": 10}, {"source": 485, "target": 395, "value": 9}, {"source": 485, "target": 398, "value": 9}, {"source": 485, "target": 435, "value": 3}, {"source": 485, "target": 437, "value": 9}, {"source": 485, "target": 473, "value": 8}, {"source": 489, "target": 243, "value": 3}, {"source": 489, "target": 251, "value": 8}, {"source": 489, "target": 263, "value": 8}, {"source": 489, "target": 281, "value": 8}, {"source": 489, "target": 287, "value": 9}, {"source": 489, "target": 293, "value": 2}, {"source": 489, "target": 317, "value": 10}, {"source": 489, "target": 333, "value": 10}, {"source": 489, "target": 335, "value": 8}, {"source": 489, "target": 339, "value": 4}, {"source": 489, "target": 341, "value": 9}, {"source": 489, "target": 351, "value": 8}, {"source": 489, "target": 363, "value": 8}, {"source": 489, "target": 377, "value": 8}, {"source": 489, "target": 381, "value": 8}, {"source": 489, "target": 393, "value": 2}, {"source": 489, "target": 398, "value": 9}, {"source": 489, "target": 401, "value": 8}, {"source": 489, "target": 413, "value": 2}, {"source": 489, "target": 429, "value": 9}, {"source": 489, "target": 431, "value": 8}, {"source": 489, "target": 437, "value": 8}, {"source": 489, "target": 443, "value": 3}, {"source": 489, "target": 461, "value": 4}, {"source": 489, "target": 467, "value": 10}, {"source": 491, "target": 291, "value": 4}, {"source": 491, "target": 293, "value": 10}, {"source": 491, "target": 341, "value": 4}, {"source": 491, "target": 365, "value": 8}, {"source": 491, "target": 381, "value": 9}, {"source": 491, "target": 429, "value": 7}, {"source": 491, "target": 441, "value": 3}, {"source": 495, "target": 233, "value": 4}, {"source": 495, "target": 245, "value": 4}, {"source": 495, "target": 248, "value": 8}, {"source": 495, "target": 285, "value": 10}, {"source": 495, "target": 333, "value": 8}, {"source": 495, "target": 345, "value": 4}, {"source": 495, "target": 348, "value": 8}, {"source": 495, "target": 395, "value": 4}, {"source": 495, "target": 398, "value": 8}, {"source": 495, "target": 485, "value": 8}, {"source": 497, "target": 221, "value": 8}, {"source": 497, "target": 263, "value": 5}, {"source": 497, "target": 273, "value": 4}, {"source": 497, "target": 285, "value": 8}, {"source": 497, "target": 293, "value": 8}, {"source": 497, "target": 309, "value": 4}, {"source": 497, "target": 323, "value": 9}, {"source": 497, "target": 347, "value": 4}, {"source": 497, "target": 351, "value": 4}, {"source": 497, "target": 363, "value": 4}, {"source": 497, "target": 405, "value": 4}, {"source": 497, "target": 413, "value": 4}, {"source": 497, "target": 423, "value": 9}, {"source": 497, "target": 437, "value": 9}, {"source": 497, "target": 459, "value": 4}, {"source": 497, "target": 473, "value": 4}, {"source": 497, "target": 485, "value": 8}, {"source": 498, "target": 219, "value": 8}, {"source": 498, "target": 233, "value": 4}, {"source": 498, "target": 245, "value": 8}, {"source": 498, "target": 248, "value": 4}, {"source": 498, "target": 257, "value": 8}, {"source": 498, "target": 273, "value": 10}, {"source": 498, "target": 285, "value": 10}, {"source": 498, "target": 309, "value": 9}, {"source": 498, "target": 323, "value": 10}, {"source": 498, "target": 333, "value": 9}, {"source": 498, "target": 345, "value": 8}, {"source": 498, "target": 348, "value": 4}, {"source": 498, "target": 369, "value": 8}, {"source": 498, "target": 395, "value": 8}, {"source": 498, "target": 398, "value": 4}, {"source": 498, "target": 423, "value": 9}, {"source": 498, "target": 473, "value": 9}, {"source": 498, "target": 485, "value": 9}, {"source": 498, "target": 495, "value": 8}, {"source": 501, "target": 249, "value": 8}, {"source": 501, "target": 251, "value": 4}, {"source": 501, "target": 261, "value": 9}, {"source": 501, "target": 263, "value": 8}, {"source": 501, "target": 311, "value": 9}, {"source": 501, "target": 341, "value": 10}, {"source": 501, "target": 351, "value": 4}, {"source": 501, "target": 363, "value": 8}, {"source": 501, "target": 401, "value": 2}, {"source": 501, "target": 411, "value": 9}, {"source": 501, "target": 413, "value": 8}, {"source": 501, "target": 453, "value": 8}, {"source": 501, "target": 455, "value": 8}, {"source": 501, "target": 461, "value": 9}, {"source": 501, "target": 489, "value": 8}, {"source": 503, "target": 227, "value": 8}, {"source": 503, "target": 228, "value": 8}, {"source": 503, "target": 245, "value": 8}, {"source": 503, "target": 249, "value": 9}, {"source": 503, "target": 251, "value": 9}, {"source": 503, "target": 257, "value": 8}, {"source": 503, "target": 278, "value": 8}, {"source": 503, "target": 279, "value": 9}, {"source": 503, "target": 293, "value": 10}, {"source": 503, "target": 303, "value": 2}, {"source": 503, "target": 341, "value": 8}, {"source": 503, "target": 345, "value": 8}, {"source": 503, "target": 351, "value": 9}, {"source": 503, "target": 353, "value": 1}, {"source": 503, "target": 377, "value": 8}, {"source": 503, "target": 378, "value": 8}, {"source": 503, "target": 399, "value": 9}, {"source": 503, "target": 401, "value": 9}, {"source": 503, "target": 405, "value": 9}, {"source": 503, "target": 407, "value": 7}, {"source": 503, "target": 428, "value": 8}, {"source": 503, "target": 437, "value": 9}, {"source": 503, "target": 441, "value": 8}, {"source": 503, "target": 453, "value": 1}, {"source": 503, "target": 501, "value": 8}, {"source": 513, "target": 215, "value": 26}, {"source": 513, "target": 219, "value": 27}, {"source": 513, "target": 221, "value": 26}, {"source": 513, "target": 225, "value": 6}, {"source": 513, "target": 227, "value": 29}, {"source": 513, "target": 228, "value": 6}, {"source": 513, "target": 231, "value": 28}, {"source": 513, "target": 233, "value": 29}, {"source": 513, "target": 243, "value": 29}, {"source": 513, "target": 245, "value": 28}, {"source": 513, "target": 248, "value": 29}, {"source": 513, "target": 249, "value": 29}, {"source": 513, "target": 251, "value": 28}, {"source": 513, "target": 255, "value": 28}, {"source": 513, "target": 257, "value": 29}, {"source": 513, "target": 261, "value": 29}, {"source": 513, "target": 263, "value": 1}, {"source": 513, "target": 273, "value": 28}, {"source": 513, "target": 275, "value": 6}, {"source": 513, "target": 278, "value": 6}, {"source": 513, "target": 287, "value": 3}, {"source": 513, "target": 309, "value": 8}, {"source": 513, "target": 321, "value": 7}, {"source": 513, "target": 351, "value": 7}, {"source": 513, "target": 363, "value": 1}, {"source": 513, "target": 365, "value": 9}, {"source": 513, "target": 375, "value": 8}, {"source": 513, "target": 378, "value": 8}, {"source": 513, "target": 401, "value": 8}, {"source": 513, "target": 405, "value": 9}, {"source": 513, "target": 413, "value": 0}, {"source": 513, "target": 425, "value": 8}, {"source": 513, "target": 428, "value": 8}, {"source": 513, "target": 437, "value": 4}, {"source": 513, "target": 453, "value": 9}, {"source": 513, "target": 455, "value": 9}, {"source": 513, "target": 459, "value": 9}, {"source": 513, "target": 461, "value": 4}, {"source": 513, "target": 489, "value": 4}, {"source": 513, "target": 497, "value": 4}, {"source": 513, "target": 501, "value": 8}, {"source": 515, "target": 215, "value": 4}, {"source": 515, "target": 315, "value": 4}, {"source": 515, "target": 353, "value": 8}, {"source": 515, "target": 365, "value": 4}, {"source": 515, "target": 405, "value": 9}, {"source": 515, "target": 453, "value": 4}, {"source": 515, "target": 461, "value": 7}, {"source": 515, "target": 465, "value": 3}, {"source": 519, "target": 219, "value": 4}, {"source": 519, "target": 221, "value": 9}, {"source": 519, "target": 231, "value": 8}, {"source": 519, "target": 248, "value": 9}, {"source": 519, "target": 257, "value": 8}, {"source": 519, "target": 273, "value": 5}, {"source": 519, "target": 281, "value": 8}, {"source": 519, "target": 309, "value": 10}, {"source": 519, "target": 321, "value": 10}, {"source": 519, "target": 323, "value": 11}, {"source": 519, "target": 348, "value": 9}, {"source": 519, "target": 369, "value": 2}, {"source": 519, "target": 377, "value": 8}, {"source": 519, "target": 381, "value": 8}, {"source": 519, "target": 398, "value": 8}, {"source": 519, "target": 423, "value": 10}, {"source": 519, "target": 431, "value": 8}, {"source": 519, "target": 473, "value": 10}, {"source": 519, "target": 498, "value": 8}, {"source": 521, "target": 221, "value": 2}, {"source": 521, "target": 233, "value": 8}, {"source": 521, "target": 261, "value": 10}, {"source": 521, "target": 279, "value": 8}, {"source": 521, "target": 293, "value": 11}, {"source": 521, "target": 309, "value": 8}, {"source": 521, "target": 321, "value": 2}, {"source": 521, "target": 333, "value": 8}, {"source": 521, "target": 371, "value": 4}, {"source": 521, "target": 383, "value": 8}, {"source": 521, "target": 461, "value": 9}, {"source": 521, "target": 471, "value": 3}, {"source": 521, "target": 483, "value": 8}, {"source": 525, "target": 225, "value": 2}, {"source": 525, "target": 228, "value": 8}, {"source": 525, "target": 251, "value": 8}, {"source": 525, "target": 273, "value": 8}, {"source": 525, "target": 275, "value": 4}, {"source": 525, "target": 278, "value": 8}, {"source": 525, "target": 285, "value": 9}, {"source": 525, "target": 287, "value": 8}, {"source": 525, "target": 321, "value": 8}, {"source": 525, "target": 335, "value": 9}, {"source": 525, "target": 351, "value": 8}, {"source": 525, "target": 365, "value": 10}, {"source": 525, "target": 375, "value": 4}, {"source": 525, "target": 377, "value": 4}, {"source": 525, "target": 378, "value": 8}, {"source": 525, "target": 401, "value": 8}, {"source": 525, "target": 413, "value": 8}, {"source": 525, "target": 425, "value": 2}, {"source": 525, "target": 428, "value": 8}, {"source": 525, "target": 429, "value": 9}, {"source": 525, "target": 435, "value": 9}, {"source": 525, "target": 437, "value": 8}, {"source": 525, "target": 473, "value": 8}, {"source": 525, "target": 485, "value": 9}, {"source": 525, "target": 501, "value": 8}, {"source": 525, "target": 503, "value": 8}, {"source": 525, "target": 513, "value": 8}, {"source": 527, "target": 227, "value": 4}, {"source": 527, "target": 279, "value": 10}, {"source": 527, "target": 303, "value": 9}, {"source": 527, "target": 317, "value": 10}, {"source": 527, "target": 353, "value": 4}, {"source": 527, "target": 365, "value": 4}, {"source": 527, "target": 377, "value": 4}, {"source": 527, "target": 453, "value": 4}, {"source": 527, "target": 465, "value": 7}, {"source": 527, "target": 503, "value": 8}, {"source": 528, "target": 225, "value": 8}, {"source": 528, "target": 228, "value": 4}, {"source": 528, "target": 249, "value": 8}, {"source": 528, "target": 275, "value": 8}, {"source": 528, "target": 278, "value": 4}, {"source": 528, "target": 303, "value": 10}, {"source": 528, "target": 321, "value": 8}, {"source": 528, "target": 353, "value": 10}, {"source": 528, "target": 365, "value": 10}, {"source": 528, "target": 375, "value": 8}, {"source": 528, "target": 378, "value": 4}, {"source": 528, "target": 399, "value": 8}, {"source": 528, "target": 413, "value": 8}, {"source": 528, "target": 425, "value": 8}, {"source": 528, "target": 428, "value": 4}, {"source": 528, "target": 437, "value": 7}, {"source": 528, "target": 453, "value": 9}, {"source": 528, "target": 503, "value": 8}, {"source": 528, "target": 513, "value": 8}, {"source": 528, "target": 525, "value": 8}, {"source": 531, "target": 221, "value": 10}, {"source": 531, "target": 231, "value": 4}, {"source": 531, "target": 273, "value": 4}, {"source": 531, "target": 279, "value": 9}, {"source": 531, "target": 281, "value": 4}, {"source": 531, "target": 285, "value": 8}, {"source": 531, "target": 335, "value": 8}, {"source": 531, "target": 369, "value": 8}, {"source": 531, "target": 377, "value": 4}, {"source": 531, "target": 381, "value": 4}, {"source": 531, "target": 429, "value": 9}, {"source": 531, "target": 431, "value": 4}, {"source": 531, "target": 435, "value": 7}, {"source": 531, "target": 473, "value": 8}, {"source": 531, "target": 485, "value": 7}, {"source": 531, "target": 519, "value": 8}, {"source": 531, "target": 525, "value": 9}, {"source": 533, "target": 221, "value": 4}, {"source": 533, "target": 233, "value": 0}, {"source": 533, "target": 243, "value": 9}, {"source": 533, "target": 245, "value": 8}, {"source": 533, "target": 248, "value": 8}, {"source": 533, "target": 257, "value": 4}, {"source": 533, "target": 279, "value": 4}, {"source": 533, "target": 281, "value": 8}, {"source": 533, "target": 285, "value": 9}, {"source": 533, "target": 291, "value": 9}, {"source": 533, "target": 293, "value": 9}, {"source": 533, "target": 317, "value": 8}, {"source": 533, "target": 321, "value": 4}, {"source": 533, "target": 333, "value": 0}, {"source": 533, "target": 335, "value": 10}, {"source": 533, "target": 341, "value": 9}, {"source": 533, "target": 345, "value": 8}, {"source": 533, "target": 348, "value": 8}, {"source": 533, "target": 381, "value": 2}, {"source": 533, "target": 383, "value": 1}, {"source": 533, "target": 393, "value": 9}, {"source": 533, "target": 395, "value": 8}, {"source": 533, "target": 398, "value": 4}, {"source": 533, "target": 405, "value": 11}, {"source": 533, "target": 407, "value": 4}, {"source": 533, "target": 429, "value": 4}, {"source": 533, "target": 431, "value": 8}, {"source": 533, "target": 441, "value": 9}, {"source": 533, "target": 443, "value": 9}, {"source": 533, "target": 483, "value": 1}, {"source": 533, "target": 485, "value": 8}, {"source": 533, "target": 489, "value": 8}, {"source": 533, "target": 491, "value": 9}, {"source": 533, "target": 495, "value": 8}, {"source": 533, "target": 498, "value": 8}, {"source": 533, "target": 521, "value": 8}, {"source": 543, "target": 243, "value": 2}, {"source": 543, "target": 281, "value": 8}, {"source": 543, "target": 293, "value": 1}, {"source": 543, "target": 317, "value": 4}, {"source": 543, "target": 333, "value": 10}, {"source": 543, "target": 335, "value": 9}, {"source": 543, "target": 339, "value": 9}, {"source": 543, "target": 377, "value": 8}, {"source": 543, "target": 381, "value": 8}, {"source": 543, "target": 393, "value": 1}, {"source": 543, "target": 398, "value": 9}, {"source": 543, "target": 429, "value": 7}, {"source": 543, "target": 431, "value": 9}, {"source": 543, "target": 443, "value": 2}, {"source": 543, "target": 467, "value": 8}, {"source": 543, "target": 489, "value": 2}, {"source": 543, "target": 533, "value": 8}, {"source": 545, "target": 228, "value": 9}, {"source": 545, "target": 233, "value": 4}, {"source": 545, "target": 243, "value": 9}, {"source": 545, "target": 245, "value": 2}, {"source": 545, "target": 248, "value": 8}, {"source": 545, "target": 249, "value": 8}, {"source": 545, "target": 257, "value": 8}, {"source": 545, "target": 273, "value": 10}, {"source": 545, "target": 278, "value": 9}, {"source": 545, "target": 285, "value": 10}, {"source": 545, "target": 293, "value": 9}, {"source": 545, "target": 303, "value": 11}, {"source": 545, "target": 317, "value": 8}, {"source": 545, "target": 323, "value": 10}, {"source": 545, "target": 333, "value": 8}, {"source": 545, "target": 345, "value": 2}, {"source": 545, "target": 348, "value": 8}, {"source": 545, "target": 353, "value": 11}, {"source": 545, "target": 378, "value": 8}, {"source": 545, "target": 393, "value": 9}, {"source": 545, "target": 395, "value": 4}, {"source": 545, "target": 398, "value": 8}, {"source": 545, "target": 399, "value": 8}, {"source": 545, "target": 407, "value": 8}, {"source": 545, "target": 423, "value": 9}, {"source": 545, "target": 428, "value": 8}, {"source": 545, "target": 437, "value": 8}, {"source": 545, "target": 443, "value": 9}, {"source": 545, "target": 453, "value": 9}, {"source": 545, "target": 473, "value": 9}, {"source": 545, "target": 485, "value": 9}, {"source": 545, "target": 495, "value": 3}, {"source": 545, "target": 498, "value": 8}, {"source": 545, "target": 503, "value": 4}, {"source": 545, "target": 528, "value": 8}, {"source": 545, "target": 533, "value": 8}, {"source": 545, "target": 543, "value": 8}, {"source": 548, "target": 219, "value": 8}, {"source": 548, "target": 233, "value": 4}, {"source": 548, "target": 245, "value": 8}, {"source": 548, "target": 248, "value": 4}, {"source": 548, "target": 257, "value": 8}, {"source": 548, "target": 273, "value": 10}, {"source": 548, "target": 285, "value": 10}, {"source": 548, "target": 309, "value": 9}, {"source": 548, "target": 323, "value": 10}, {"source": 548, "target": 333, "value": 9}, {"source": 548, "target": 345, "value": 8}, {"source": 548, "target": 348, "value": 4}, {"source": 548, "target": 369, "value": 8}, {"source": 548, "target": 395, "value": 8}, {"source": 548, "target": 398, "value": 4}, {"source": 548, "target": 423, "value": 9}, {"source": 548, "target": 473, "value": 9}, {"source": 548, "target": 485, "value": 9}, {"source": 548, "target": 495, "value": 7}, {"source": 548, "target": 498, "value": 3}, {"source": 548, "target": 519, "value": 8}, {"source": 548, "target": 533, "value": 8}, {"source": 548, "target": 545, "value": 8}, {"source": 549, "target": 228, "value": 9}, {"source": 549, "target": 249, "value": 2}, {"source": 549, "target": 251, "value": 10}, {"source": 549, "target": 261, "value": 8}, {"source": 549, "target": 263, "value": 9}, {"source": 549, "target": 278, "value": 9}, {"source": 549, "target": 303, "value": 11}, {"source": 549, "target": 309, "value": 9}, {"source": 549, "target": 311, "value": 8}, {"source": 549, "target": 321, "value": 11}, {"source": 549, "target": 351, "value": 8}, {"source": 549, "target": 353, "value": 10}, {"source": 549, "target": 363, "value": 9}, {"source": 549, "target": 378, "value": 8}, {"source": 549, "target": 399, "value": 4}, {"source": 549, "target": 405, "value": 9}, {"source": 549, "target": 411, "value": 8}, {"source": 549, "target": 413, "value": 8}, {"source": 549, "target": 428, "value": 8}, {"source": 549, "target": 437, "value": 8}, {"source": 549, "target": 453, "value": 10}, {"source": 549, "target": 459, "value": 8}, {"source": 549, "target": 461, "value": 8}, {"source": 549, "target": 497, "value": 4}, {"source": 549, "target": 501, "value": 8}, {"source": 549, "target": 503, "value": 9}, {"source": 549, "target": 513, "value": 8}, {"source": 549, "target": 528, "value": 8}, {"source": 549, "target": 545, "value": 8}, {"source": 551, "target": 251, "value": 4}, {"source": 551, "target": 341, "value": 10}, {"source": 551, "target": 351, "value": 4}, {"source": 551, "target": 401, "value": 4}, {"source": 551, "target": 489, "value": 7}, {"source": 551, "target": 501, "value": 3}, {"source": 551, "target": 503, "value": 9}, {"source": 551, "target": 525, "value": 8}, {"source": 555, "target": 245, "value": 10}, {"source": 555, "target": 251, "value": 8}, {"source": 555, "target": 255, "value": 4}, {"source": 555, "target": 293, "value": 8}, {"source": 555, "target": 305, "value": 4}, {"source": 555, "target": 377, "value": 11}, {"source": 555, "target": 393, "value": 8}, {"source": 555, "target": 405, "value": 4}, {"source": 555, "target": 429, "value": 8}, {"source": 555, "target": 455, "value": 4}, {"source": 557, "target": 219, "value": 8}, {"source": 557, "target": 233, "value": 3}, {"source": 557, "target": 245, "value": 8}, {"source": 557, "target": 248, "value": 8}, {"source": 557, "target": 255, "value": 8}, {"source": 557, "target": 257, "value": 2}, {"source": 557, "target": 273, "value": 11}, {"source": 557, "target": 305, "value": 8}, {"source": 557, "target": 309, "value": 9}, {"source": 557, "target": 317, "value": 9}, {"source": 557, "target": 323, "value": 10}, {"source": 557, "target": 333, "value": 3}, {"source": 557, "target": 335, "value": 9}, {"source": 557, "target": 345, "value": 8}, {"source": 557, "target": 348, "value": 8}, {"source": 557, "target": 363, "value": 9}, {"source": 557, "target": 369, "value": 8}, {"source": 557, "target": 381, "value": 8}, {"source": 557, "target": 383, "value": 4}, {"source": 557, "target": 398, "value": 8}, {"source": 557, "target": 405, "value": 4}, {"source": 557, "target": 407, "value": 4}, {"source": 557, "target": 423, "value": 9}, {"source": 557, "target": 467, "value": 9}, {"source": 557, "target": 473, "value": 9}, {"source": 557, "target": 483, "value": 4}, {"source": 557, "target": 498, "value": 8}, {"source": 557, "target": 503, "value": 7}, {"source": 557, "target": 519, "value": 8}, {"source": 557, "target": 533, "value": 2}, {"source": 557, "target": 545, "value": 8}, {"source": 557, "target": 548, "value": 7}, {"source": 561, "target": 249, "value": 8}, {"source": 561, "target": 261, "value": 2}, {"source": 561, "target": 273, "value": 8}, {"source": 561, "target": 285, "value": 8}, {"source": 561, "target": 311, "value": 4}, {"source": 561, "target": 323, "value": 8}, {"source": 561, "target": 363, "value": 10}, {"source": 561, "target": 411, "value": 4}, {"source": 561, "target": 413, "value": 9}, {"source": 561, "target": 423, "value": 8}, {"source": 561, "target": 461, "value": 2}, {"source": 561, "target": 473, "value": 8}, {"source": 561, "target": 501, "value": 9}, {"source": 561, "target": 549, "value": 8}, {"source": 563, "target": 225, "value": 8}, {"source": 563, "target": 228, "value": 9}, {"source": 563, "target": 263, "value": 2}, {"source": 563, "target": 275, "value": 8}, {"source": 563, "target": 278, "value": 9}, {"source": 563, "target": 287, "value": 8}, {"source": 563, "target": 309, "value": 9}, {"source": 563, "target": 321, "value": 8}, {"source": 563, "target": 351, "value": 8}, {"source": 563, "target": 363, "value": 2}, {"source": 563, "target": 365, "value": 9}, {"source": 563, "target": 375, "value": 8}, {"source": 563, "target": 378, "value": 9}, {"source": 563, "target": 401, "value": 8}, {"source": 563, "target": 405, "value": 9}, {"source": 563, "target": 413, "value": 1}, {"source": 563, "target": 425, "value": 8}, {"source": 563, "target": 428, "value": 9}, {"source": 563, "target": 437, "value": 8}, {"source": 563, "target": 453, "value": 9}, {"source": 563, "target": 455, "value": 10}, {"source": 563, "target": 459, "value": 9}, {"source": 563, "target": 461, "value": 8}, {"source": 563, "target": 489, "value": 8}, {"source": 563, "target": 497, "value": 4}, {"source": 563, "target": 501, "value": 8}, {"source": 563, "target": 513, "value": 1}, {"source": 563, "target": 525, "value": 8}, {"source": 563, "target": 528, "value": 8}, {"source": 563, "target": 549, "value": 8}, {"source": 573, "target": 219, "value": 9}, {"source": 573, "target": 221, "value": 2}, {"source": 573, "target": 227, "value": 8}, {"source": 573, "target": 233, "value": 8}, {"source": 573, "target": 248, "value": 8}, {"source": 573, "target": 249, "value": 8}, {"source": 573, "target": 251, "value": 4}, {"source": 573, "target": 257, "value": 8}, {"source": 573, "target": 261, "value": 8}, {"source": 573, "target": 273, "value": 1}, {"source": 573, "target": 279, "value": 8}, {"source": 573, "target": 285, "value": 8}, {"source": 573, "target": 293, "value": 10}, {"source": 573, "target": 309, "value": 8}, {"source": 573, "target": 317, "value": 10}, {"source": 573, "target": 321, "value": 2}, {"source": 573, "target": 323, "value": 1}, {"source": 573, "target": 333, "value": 9}, {"source": 573, "target": 335, "value": 8}, {"source": 573, "target": 347, "value": 4}, {"source": 573, "target": 348, "value": 8}, {"source": 573, "target": 351, "value": 9}, {"source": 573, "target": 365, "value": 4}, {"source": 573, "target": 369, "value": 9}, {"source": 573, "target": 371, "value": 9}, {"source": 573, "target": 377, "value": 4}, {"source": 573, "target": 383, "value": 9}, {"source": 573, "target": 398, "value": 8}, {"source": 573, "target": 399, "value": 8}, {"source": 573, "target": 401, "value": 9}, {"source": 573, "target": 413, "value": 9}, {"source": 573, "target": 423, "value": 1}, {"source": 573, "target": 435, "value": 8}, {"source": 573, "target": 461, "value": 8}, {"source": 573, "target": 465, "value": 8}, {"source": 573, "target": 471, "value": 9}, {"source": 573, "target": 473, "value": 1}, {"source": 573, "target": 483, "value": 8}, {"source": 573, "target": 485, "value": 8}, {"source": 573, "target": 497, "value": 4}, {"source": 573, "target": 498, "value": 8}, {"source": 573, "target": 501, "value": 9}, {"source": 573, "target": 503, "value": 7}, {"source": 573, "target": 519, "value": 9}, {"source": 573, "target": 521, "value": 4}, {"source": 573, "target": 525, "value": 4}, {"source": 573, "target": 527, "value": 7}, {"source": 573, "target": 531, "value": 7}, {"source": 573, "target": 533, "value": 9}, {"source": 573, "target": 545, "value": 9}, {"source": 573, "target": 548, "value": 8}, {"source": 573, "target": 549, "value": 8}, {"source": 573, "target": 551, "value": 9}, {"source": 573, "target": 557, "value": 8}, {"source": 573, "target": 561, "value": 8}, {"source": 575, "target": 225, "value": 4}, {"source": 575, "target": 228, "value": 8}, {"source": 575, "target": 275, "value": 4}, {"source": 575, "target": 278, "value": 8}, {"source": 575, "target": 321, "value": 8}, {"source": 575, "target": 365, "value": 10}, {"source": 575, "target": 375, "value": 4}, {"source": 575, "target": 377, "value": 10}, {"source": 575, "target": 378, "value": 8}, {"source": 575, "target": 413, "value": 8}, {"source": 575, "target": 425, "value": 4}, {"source": 575, "target": 428, "value": 8}, {"source": 575, "target": 513, "value": 8}, {"source": 575, "target": 525, "value": 3}, {"source": 575, "target": 528, "value": 8}, {"source": 575, "target": 563, "value": 8}, {"source": 578, "target": 225, "value": 8}, {"source": 578, "target": 228, "value": 4}, {"source": 578, "target": 249, "value": 8}, {"source": 578, "target": 275, "value": 8}, {"source": 578, "target": 278, "value": 4}, {"source": 578, "target": 303, "value": 10}, {"source": 578, "target": 321, "value": 8}, {"source": 578, "target": 353, "value": 10}, {"source": 578, "target": 365, "value": 10}, {"source": 578, "target": 375, "value": 8}, {"source": 578, "target": 378, "value": 4}, {"source": 578, "target": 399, "value": 8}, {"source": 578, "target": 413, "value": 8}, {"source": 578, "target": 425, "value": 8}, {"source": 578, "target": 428, "value": 4}, {"source": 578, "target": 437, "value": 8}, {"source": 578, "target": 453, "value": 9}, {"source": 578, "target": 503, "value": 8}, {"source": 578, "target": 513, "value": 8}, {"source": 578, "target": 525, "value": 7}, {"source": 578, "target": 528, "value": 3}, {"source": 578, "target": 545, "value": 8}, {"source": 578, "target": 549, "value": 8}, {"source": 578, "target": 563, "value": 9}, {"source": 578, "target": 575, "value": 8}, {"source": 579, "target": 221, "value": 11}, {"source": 579, "target": 233, "value": 12}, {"source": 579, "target": 279, "value": 4}, {"source": 579, "target": 317, "value": 8}, {"source": 579, "target": 333, "value": 11}, {"source": 579, "target": 383, "value": 10}, {"source": 579, "target": 429, "value": 4}, {"source": 579, "target": 483, "value": 10}, {"source": 579, "target": 531, "value": 9}, {"source": 579, "target": 533, "value": 9}, {"source": 581, "target": 221, "value": 11}, {"source": 581, "target": 231, "value": 4}, {"source": 581, "target": 243, "value": 8}, {"source": 581, "target": 273, "value": 11}, {"source": 581, "target": 281, "value": 2}, {"source": 581, "target": 291, "value": 8}, {"source": 581, "target": 293, "value": 8}, {"source": 581, "target": 333, "value": 9}, {"source": 581, "target": 341, "value": 8}, {"source": 581, "target": 369, "value": 8}, {"source": 581, "target": 377, "value": 8}, {"source": 581, "target": 381, "value": 2}, {"source": 581, "target": 393, "value": 8}, {"source": 581, "target": 398, "value": 9}, {"source": 581, "target": 429, "value": 8}, {"source": 581, "target": 431, "value": 2}, {"source": 581, "target": 441, "value": 9}, {"source": 581, "target": 443, "value": 8}, {"source": 581, "target": 489, "value": 7}, {"source": 581, "target": 491, "value": 8}, {"source": 581, "target": 519, "value": 8}, {"source": 581, "target": 531, "value": 3}, {"source": 581, "target": 533, "value": 4}, {"source": 581, "target": 543, "value": 8}, {"source": 585, "target": 243, "value": 9}, {"source": 585, "target": 273, "value": 8}, {"source": 585, "target": 281, "value": 8}, {"source": 585, "target": 285, "value": 2}, {"source": 585, "target": 293, "value": 2}, {"source": 585, "target": 333, "value": 8}, {"source": 585, "target": 335, "value": 4}, {"source": 585, "target": 347, "value": 4}, {"source": 585, "target": 377, "value": 10}, {"source": 585, "target": 381, "value": 8}, {"source": 585, "target": 393, "value": 9}, {"source": 585, "target": 398, "value": 8}, {"source": 585, "target": 431, "value": 8}, {"source": 585, "target": 435, "value": 4}, {"source": 585, "target": 437, "value": 4}, {"source": 585, "target": 443, "value": 9}, {"source": 585, "target": 473, "value": 8}, {"source": 585, "target": 485, "value": 2}, {"source": 585, "target": 489, "value": 9}, {"source": 585, "target": 497, "value": 4}, {"source": 585, "target": 525, "value": 9}, {"source": 585, "target": 531, "value": 7}, {"source": 585, "target": 533, "value": 7}, {"source": 585, "target": 543, "value": 9}, {"source": 585, "target": 573, "value": 8}, {"source": 585, "target": 581, "value": 8}, {"source": 587, "target": 225, "value": 9}, {"source": 587, "target": 263, "value": 9}, {"source": 587, "target": 285, "value": 9}, {"source": 587, "target": 287, "value": 4}, {"source": 587, "target": 291, "value": 8}, {"source": 587, "target": 335, "value": 9}, {"source": 587, "target": 341, "value": 8}, {"source": 587, "target": 363, "value": 9}, {"source": 587, "target": 381, "value": 10}, {"source": 587, "target": 413, "value": 4}, {"source": 587, "target": 425, "value": 8}, {"source": 587, "target": 429, "value": 4}, {"source": 587, "target": 435, "value": 9}, {"source": 587, "target": 437, "value": 4}, {"source": 587, "target": 441, "value": 8}, {"source": 587, "target": 461, "value": 8}, {"source": 587, "target": 485, "value": 9}, {"source": 587, "target": 489, "value": 9}, {"source": 587, "target": 491, "value": 8}, {"source": 587, "target": 513, "value": 4}, {"source": 587, "target": 525, "value": 7}, {"source": 587, "target": 533, "value": 9}, {"source": 587, "target": 563, "value": 8}, {"source": 587, "target": 581, "value": 8}, {"source": 587, "target": 585, "value": 8}, {"source": 591, "target": 291, "value": 4}, {"source": 591, "target": 293, "value": 10}, {"source": 591, "target": 341, "value": 4}, {"source": 591, "target": 365, "value": 8}, {"source": 591, "target": 381, "value": 10}, {"source": 591, "target": 429, "value": 8}, {"source": 591, "target": 441, "value": 4}, {"source": 591, "target": 491, "value": 4}, {"source": 591, "target": 533, "value": 9}, {"source": 591, "target": 581, "value": 8}, {"source": 591, "target": 587, "value": 8}, {"source": 593, "target": 243, "value": 1}, {"source": 593, "target": 245, "value": 10}, {"source": 593, "target": 251, "value": 8}, {"source": 593, "target": 255, "value": 8}, {"source": 593, "target": 281, "value": 8}, {"source": 593, "target": 293, "value": 1}, {"source": 593, "target": 305, "value": 8}, {"source": 593, "target": 317, "value": 2}, {"source": 593, "target": 333, "value": 10}, {"source": 593, "target": 335, "value": 9}, {"source": 593, "target": 339, "value": 9}, {"source": 593, "target": 377, "value": 8}, {"source": 593, "target": 381, "value": 8}, {"source": 593, "target": 393, "value": 1}, {"source": 593, "target": 398, "value": 9}, {"source": 593, "target": 405, "value": 8}, {"source": 593, "target": 429, "value": 8}, {"source": 593, "target": 431, "value": 9}, {"source": 593, "target": 443, "value": 1}, {"source": 593, "target": 455, "value": 8}, {"source": 593, "target": 467, "value": 4}, {"source": 593, "target": 489, "value": 2}, {"source": 593, "target": 533, "value": 8}, {"source": 593, "target": 543, "value": 1}, {"source": 593, "target": 545, "value": 9}, {"source": 593, "target": 555, "value": 8}, {"source": 593, "target": 581, "value": 8}, {"source": 593, "target": 585, "value": 8}, {"source": 603, "target": 227, "value": 8}, {"source": 603, "target": 228, "value": 8}, {"source": 603, "target": 249, "value": 9}, {"source": 603, "target": 278, "value": 8}, {"source": 603, "target": 279, "value": 9}, {"source": 603, "target": 293, "value": 10}, {"source": 603, "target": 303, "value": 2}, {"source": 603, "target": 341, "value": 8}, {"source": 603, "target": 353, "value": 1}, {"source": 603, "target": 377, "value": 8}, {"source": 603, "target": 378, "value": 8}, {"source": 603, "target": 399, "value": 9}, {"source": 603, "target": 405, "value": 9}, {"source": 603, "target": 428, "value": 8}, {"source": 603, "target": 437, "value": 8}, {"source": 603, "target": 441, "value": 8}, {"source": 603, "target": 453, "value": 1}, {"source": 603, "target": 503, "value": 2}, {"source": 603, "target": 527, "value": 8}, {"source": 603, "target": 528, "value": 8}, {"source": 603, "target": 545, "value": 9}, {"source": 603, "target": 549, "value": 9}, {"source": 603, "target": 578, "value": 8}, {"source": 605, "target": 215, "value": 8}, {"source": 605, "target": 231, "value": 8}, {"source": 605, "target": 245, "value": 10}, {"source": 605, "target": 251, "value": 8}, {"source": 605, "target": 255, "value": 2}, {"source": 605, "target": 281, "value": 8}, {"source": 605, "target": 293, "value": 8}, {"source": 605, "target": 305, "value": 2}, {"source": 605, "target": 315, "value": 8}, {"source": 605, "target": 317, "value": 8}, {"source": 605, "target": 353, "value": 8}, {"source": 605, "target": 363, "value": 8}, {"source": 605, "target": 365, "value": 8}, {"source": 605, "target": 377, "value": 11}, {"source": 605, "target": 381, "value": 8}, {"source": 605, "target": 393, "value": 8}, {"source": 605, "target": 405, "value": 2}, {"source": 605, "target": 429, "value": 8}, {"source": 605, "target": 431, "value": 8}, {"source": 605, "target": 453, "value": 4}, {"source": 605, "target": 455, "value": 4}, {"source": 605, "target": 461, "value": 8}, {"source": 605, "target": 465, "value": 9}, {"source": 605, "target": 467, "value": 8}, {"source": 605, "target": 515, "value": 8}, {"source": 605, "target": 531, "value": 8}, {"source": 605, "target": 555, "value": 3}, {"source": 605, "target": 557, "value": 8}, {"source": 605, "target": 581, "value": 8}, {"source": 605, "target": 593, "value": 8}, {"source": 609, "target": 221, "value": 8}, {"source": 609, "target": 261, "value": 10}, {"source": 609, "target": 263, "value": 12}, {"source": 609, "target": 309, "value": 2}, {"source": 609, "target": 321, "value": 8}, {"source": 609, "target": 333, "value": 8}, {"source": 609, "target": 351, "value": 10}, {"source": 609, "target": 363, "value": 11}, {"source": 609, "target": 371, "value": 8}, {"source": 609, "target": 405, "value": 8}, {"source": 609, "target": 413, "value": 10}, {"source": 609, "target": 459, "value": 4}, {"source": 609, "target": 461, "value": 4}, {"source": 609, "target": 471, "value": 8}, {"source": 609, "target": 497, "value": 4}, {"source": 609, "target": 513, "value": 10}, {"source": 609, "target": 521, "value": 8}, {"source": 609, "target": 549, "value": 8}, {"source": 609, "target": 563, "value": 9}, {"source": 611, "target": 219, "value": 8}, {"source": 611, "target": 248, "value": 8}, {"source": 611, "target": 249, "value": 9}, {"source": 611, "target": 257, "value": 8}, {"source": 611, "target": 261, "value": 4}, {"source": 611, "target": 273, "value": 9}, {"source": 611, "target": 285, "value": 8}, {"source": 611, "target": 309, "value": 8}, {"source": 611, "target": 311, "value": 4}, {"source": 611, "target": 323, "value": 9}, {"source": 611, "target": 348, "value": 8}, {"source": 611, "target": 363, "value": 10}, {"source": 611, "target": 369, "value": 9}, {"source": 611, "target": 398, "value": 8}, {"source": 611, "target": 411, "value": 4}, {"source": 611, "target": 423, "value": 9}, {"source": 611, "target": 461, "value": 4}, {"source": 611, "target": 473, "value": 8}, {"source": 611, "target": 498, "value": 8}, {"source": 611, "target": 501, "value": 9}, {"source": 611, "target": 519, "value": 8}, {"source": 611, "target": 548, "value": 8}, {"source": 611, "target": 549, "value": 7}, {"source": 611, "target": 557, "value": 8}, {"source": 611, "target": 561, "value": 3}, {"source": 611, "target": 573, "value": 8}, {"source": 615, "target": 215, "value": 4}, {"source": 615, "target": 219, "value": 8}, {"source": 615, "target": 248, "value": 9}, {"source": 615, "target": 257, "value": 9}, {"source": 615, "target": 263, "value": 8}, {"source": 615, "target": 273, "value": 11}, {"source": 615, "target": 309, "value": 10}, {"source": 615, "target": 315, "value": 4}, {"source": 615, "target": 323, "value": 11}, {"source": 615, "target": 348, "value": 9}, {"source": 615, "target": 353, "value": 8}, {"source": 615, "target": 363, "value": 9}, {"source": 615, "target": 365, "value": 4}, {"source": 615, "target": 369, "value": 8}, {"source": 615, "target": 398, "value": 9}, {"source": 615, "target": 405, "value": 10}, {"source": 615, "target": 413, "value": 9}, {"source": 615, "target": 423, "value": 10}, {"source": 615, "target": 453, "value": 4}, {"source": 615, "target": 461, "value": 8}, {"source": 615, "target": 465, "value": 4}, {"source": 615, "target": 473, "value": 10}, {"source": 615, "target": 498, "value": 8}, {"source": 615, "target": 513, "value": 9}, {"source": 615, "target": 515, "value": 4}, {"source": 615, "target": 519, "value": 8}, {"source": 615, "target": 548, "value": 8}, {"source": 615, "target": 557, "value": 8}, {"source": 615, "target": 563, "value": 8}, {"source": 615, "target": 573, "value": 9}, {"source": 615, "target": 605, "value": 8}, {"source": 615, "target": 611, "value": 8}, {"source": 617, "target": 221, "value": 10}, {"source": 617, "target": 233, "value": 11}, {"source": 617, "target": 243, "value": 9}, {"source": 617, "target": 255, "value": 10}, {"source": 617, "target": 279, "value": 8}, {"source": 617, "target": 293, "value": 4}, {"source": 617, "target": 305, "value": 8}, {"source": 617, "target": 317, "value": 2}, {"source": 617, "target": 333, "value": 10}, {"source": 617, "target": 363, "value": 8}, {"source": 617, "target": 383, "value": 10}, {"source": 617, "target": 393, "value": 4}, {"source": 617, "target": 405, "value": 8}, {"source": 617, "target": 429, "value": 8}, {"source": 617, "target": 443, "value": 9}, {"source": 617, "target": 467, "value": 4}, {"source": 617, "target": 483, "value": 9}, {"source": 617, "target": 489, "value": 10}, {"source": 617, "target": 533, "value": 9}, {"source": 617, "target": 543, "value": 8}, {"source": 617, "target": 557, "value": 8}, {"source": 617, "target": 579, "value": 8}, {"source": 617, "target": 593, "value": 4}, {"source": 617, "target": 605, "value": 8}, {"source": 621, "target": 221, "value": 1}, {"source": 621, "target": 231, "value": 8}, {"source": 621, "target": 233, "value": 8}, {"source": 621, "target": 261, "value": 10}, {"source": 621, "target": 273, "value": 3}, {"source": 621, "target": 279, "value": 8}, {"source": 621, "target": 281, "value": 8}, {"source": 621, "target": 293, "value": 12}, {"source": 621, "target": 309, "value": 8}, {"source": 621, "target": 321, "value": 2}, {"source": 621, "target": 323, "value": 9}, {"source": 621, "target": 333, "value": 8}, {"source": 621, "target": 347, "value": 8}, {"source": 621, "target": 369, "value": 8}, {"source": 621, "target": 371, "value": 4}, {"source": 621, "target": 377, "value": 8}, {"source": 621, "target": 381, "value": 8}, {"source": 621, "target": 383, "value": 8}, {"source": 621, "target": 423, "value": 8}, {"source": 621, "target": 431, "value": 8}, {"source": 621, "target": 461, "value": 9}, {"source": 621, "target": 471, "value": 4}, {"source": 621, "target": 473, "value": 4}, {"source": 621, "target": 483, "value": 8}, {"source": 621, "target": 497, "value": 8}, {"source": 621, "target": 519, "value": 8}, {"source": 621, "target": 521, "value": 2}, {"source": 621, "target": 531, "value": 8}, {"source": 621, "target": 533, "value": 8}, {"source": 621, "target": 573, "value": 2}, {"source": 621, "target": 581, "value": 8}, {"source": 621, "target": 609, "value": 8}, {"source": 623, "target": 219, "value": 9}, {"source": 623, "target": 221, "value": 8}, {"source": 623, "target": 248, "value": 8}, {"source": 623, "target": 257, "value": 8}, {"source": 623, "target": 261, "value": 9}, {"source": 623, "target": 273, "value": 1}, {"source": 623, "target": 309, "value": 8}, {"source": 623, "target": 323, "value": 2}, {"source": 623, "target": 347, "value": 8}, {"source": 623, "target": 348, "value": 8}, {"source": 623, "target": 369, "value": 9}, {"source": 623, "target": 398, "value": 8}, {"source": 623, "target": 413, "value": 10}, {"source": 623, "target": 423, "value": 2}, {"source": 623, "target": 461, "value": 8}, {"source": 623, "target": 473, "value": 1}, {"source": 623, "target": 497, "value": 8}, {"source": 623, "target": 498, "value": 8}, {"source": 623, "target": 519, "value": 9}, {"source": 623, "target": 545, "value": 10}, {"source": 623, "target": 548, "value": 8}, {"source": 623, "target": 557, "value": 8}, {"source": 623, "target": 561, "value": 7}, {"source": 623, "target": 573, "value": 1}, {"source": 623, "target": 611, "value": 7}, {"source": 623, "target": 615, "value": 8}, {"source": 623, "target": 621, "value": 7}, {"source": 633, "target": 221, "value": 4}, {"source": 633, "target": 233, "value": 0}, {"source": 633, "target": 245, "value": 8}, {"source": 633, "target": 248, "value": 8}, {"source": 633, "target": 257, "value": 4}, {"source": 633, "target": 279, "value": 4}, {"source": 633, "target": 285, "value": 10}, {"source": 633, "target": 317, "value": 8}, {"source": 633, "target": 321, "value": 4}, {"source": 633, "target": 333, "value": 1}, {"source": 633, "target": 335, "value": 10}, {"source": 633, "target": 345, "value": 8}, {"source": 633, "target": 348, "value": 8}, {"source": 633, "target": 381, "value": 4}, {"source": 633, "target": 383, "value": 1}, {"source": 633, "target": 395, "value": 8}, {"source": 633, "target": 398, "value": 8}, {"source": 633, "target": 405, "value": 11}, {"source": 633, "target": 407, "value": 4}, {"source": 633, "target": 429, "value": 9}, {"source": 633, "target": 483, "value": 1}, {"source": 633, "target": 485, "value": 9}, {"source": 633, "target": 495, "value": 8}, {"source": 633, "target": 498, "value": 8}, {"source": 633, "target": 521, "value": 8}, {"source": 633, "target": 533, "value": 1}, {"source": 633, "target": 545, "value": 8}, {"source": 633, "target": 548, "value": 8}, {"source": 633, "target": 557, "value": 2}, {"source": 633, "target": 573, "value": 8}, {"source": 633, "target": 579, "value": 9}, {"source": 633, "target": 617, "value": 8}, {"source": 633, "target": 621, "value": 8}, {"source": 635, "target": 273, "value": 9}, {"source": 635, "target": 285, "value": 4}, {"source": 635, "target": 335, "value": 4}, {"source": 635, "target": 377, "value": 10}, {"source": 635, "target": 435, "value": 4}, {"source": 635, "target": 473, "value": 8}, {"source": 635, "target": 485, "value": 4}, {"source": 635, "target": 525, "value": 9}, {"source": 635, "target": 531, "value": 8}, {"source": 635, "target": 573, "value": 8}, {"source": 635, "target": 585, "value": 3}, {"source": 635, "target": 587, "value": 9}, {"source": 639, "target": 243, "value": 12}, {"source": 639, "target": 293, "value": 11}, {"source": 639, "target": 335, "value": 8}, {"source": 639, "target": 339, "value": 4}, {"source": 639, "target": 377, "value": 8}, {"source": 639, "target": 393, "value": 11}, {"source": 639, "target": 413, "value": 8}, {"source": 639, "target": 429, "value": 10}, {"source": 639, "target": 443, "value": 10}, {"source": 639, "target": 461, "value": 10}, {"source": 639, "target": 489, "value": 4}, {"source": 639, "target": 543, "value": 10}, {"source": 639, "target": 593, "value": 9}, {"source": 641, "target": 291, "value": 4}, {"source": 641, "target": 293, "value": 5}, {"source": 641, "target": 303, "value": 8}, {"source": 641, "target": 341, "value": 2}, {"source": 641, "target": 353, "value": 8}, {"source": 641, "target": 365, "value": 8}, {"source": 641, "target": 381, "value": 10}, {"source": 641, "target": 429, "value": 8}, {"source": 641, "target": 441, "value": 2}, {"source": 641, "target": 453, "value": 8}, {"source": 641, "target": 491, "value": 4}, {"source": 641, "target": 503, "value": 8}, {"source": 641, "target": 533, "value": 9}, {"source": 641, "target": 581, "value": 8}, {"source": 641, "target": 587, "value": 7}, {"source": 641, "target": 591, "value": 3}, {"source": 641, "target": 603, "value": 8}, {"source": 645, "target": 233, "value": 4}, {"source": 645, "target": 245, "value": 2}, {"source": 645, "target": 248, "value": 8}, {"source": 645, "target": 251, "value": 8}, {"source": 645, "target": 255, "value": 8}, {"source": 645, "target": 257, "value": 8}, {"source": 645, "target": 285, "value": 10}, {"source": 645, "target": 293, "value": 8}, {"source": 645, "target": 305, "value": 8}, {"source": 645, "target": 333, "value": 8}, {"source": 645, "target": 345, "value": 2}, {"source": 645, "target": 348, "value": 8}, {"source": 645, "target": 393, "value": 8}, {"source": 645, "target": 395, "value": 4}, {"source": 645, "target": 398, "value": 8}, {"source": 645, "target": 405, "value": 8}, {"source": 645, "target": 407, "value": 8}, {"source": 645, "target": 455, "value": 8}, {"source": 645, "target": 485, "value": 9}, {"source": 645, "target": 495, "value": 4}, {"source": 645, "target": 498, "value": 8}, {"source": 645, "target": 503, "value": 8}, {"source": 645, "target": 533, "value": 8}, {"source": 645, "target": 545, "value": 2}, {"source": 645, "target": 548, "value": 8}, {"source": 645, "target": 555, "value": 8}, {"source": 645, "target": 557, "value": 8}, {"source": 645, "target": 593, "value": 7}, {"source": 645, "target": 605, "value": 8}, {"source": 645, "target": 633, "value": 8}, {"source": 647, "target": 221, "value": 9}, {"source": 647, "target": 273, "value": 4}, {"source": 647, "target": 285, "value": 9}, {"source": 647, "target": 293, "value": 8}, {"source": 647, "target": 323, "value": 9}, {"source": 647, "target": 347, "value": 4}, {"source": 647, "target": 423, "value": 9}, {"source": 647, "target": 437, "value": 9}, {"source": 647, "target": 473, "value": 4}, {"source": 647, "target": 485, "value": 8}, {"source": 647, "target": 497, "value": 4}, {"source": 647, "target": 573, "value": 4}, {"source": 647, "target": 585, "value": 4}, {"source": 647, "target": 621, "value": 8}, {"source": 647, "target": 623, "value": 8}, {"source": 648, "target": 219, "value": 8}, {"source": 648, "target": 233, "value": 4}, {"source": 648, "target": 245, "value": 8}, {"source": 648, "target": 248, "value": 4}, {"source": 648, "target": 257, "value": 8}, {"source": 648, "target": 273, "value": 11}, {"source": 648, "target": 285, "value": 10}, {"source": 648, "target": 309, "value": 9}, {"source": 648, "target": 323, "value": 10}, {"source": 648, "target": 333, "value": 9}, {"source": 648, "target": 345, "value": 8}, {"source": 648, "target": 348, "value": 4}, {"source": 648, "target": 369, "value": 8}, {"source": 648, "target": 395, "value": 8}, {"source": 648, "target": 398, "value": 4}, {"source": 648, "target": 423, "value": 10}, {"source": 648, "target": 473, "value": 9}, {"source": 648, "target": 485, "value": 9}, {"source": 648, "target": 495, "value": 8}, {"source": 648, "target": 498, "value": 4}, {"source": 648, "target": 519, "value": 8}, {"source": 648, "target": 533, "value": 8}, {"source": 648, "target": 545, "value": 8}, {"source": 648, "target": 548, "value": 4}, {"source": 648, "target": 557, "value": 7}, {"source": 648, "target": 573, "value": 8}, {"source": 648, "target": 611, "value": 8}, {"source": 648, "target": 615, "value": 8}, {"source": 648, "target": 623, "value": 8}, {"source": 648, "target": 633, "value": 8}, {"source": 648, "target": 645, "value": 8}, {"source": 651, "target": 251, "value": 4}, {"source": 651, "target": 263, "value": 8}, {"source": 651, "target": 341, "value": 10}, {"source": 651, "target": 351, "value": 4}, {"source": 651, "target": 363, "value": 8}, {"source": 651, "target": 401, "value": 2}, {"source": 651, "target": 413, "value": 8}, {"source": 651, "target": 453, "value": 8}, {"source": 651, "target": 455, "value": 9}, {"source": 651, "target": 489, "value": 8}, {"source": 651, "target": 501, "value": 2}, {"source": 651, "target": 503, "value": 9}, {"source": 651, "target": 513, "value": 8}, {"source": 651, "target": 525, "value": 8}, {"source": 651, "target": 551, "value": 4}, {"source": 651, "target": 563, "value": 8}, {"source": 651, "target": 573, "value": 9}, {"source": 653, "target": 215, "value": 8}, {"source": 653, "target": 227, "value": 4}, {"source": 653, "target": 228, "value": 8}, {"source": 653, "target": 249, "value": 8}, {"source": 653, "target": 263, "value": 8}, {"source": 653, "target": 278, "value": 8}, {"source": 653, "target": 279, "value": 3}, {"source": 653, "target": 293, "value": 10}, {"source": 653, "target": 303, "value": 1}, {"source": 653, "target": 315, "value": 8}, {"source": 653, "target": 341, "value": 8}, {"source": 653, "target": 353, "value": 1}, {"source": 653, "target": 363, "value": 8}, {"source": 653, "target": 365, "value": 8}, {"source": 653, "target": 377, "value": 4}, {"source": 653, "target": 378, "value": 8}, {"source": 653, "target": 399, "value": 9}, {"source": 653, "target": 401, "value": 8}, {"source": 653, "target": 405, "value": 4}, {"source": 653, "target": 413, "value": 8}, {"source": 653, "target": 428, "value": 8}, {"source": 653, "target": 429, "value": 8}, {"source": 653, "target": 437, "value": 8}, {"source": 653, "target": 441, "value": 8}, {"source": 653, "target": 453, "value": 0}, {"source": 653, "target": 455, "value": 8}, {"source": 653, "target": 461, "value": 8}, {"source": 653, "target": 465, "value": 8}, {"source": 653, "target": 501, "value": 8}, {"source": 653, "target": 503, "value": 1}, {"source": 653, "target": 513, "value": 8}, {"source": 653, "target": 515, "value": 8}, {"source": 653, "target": 527, "value": 4}, {"source": 653, "target": 528, "value": 8}, {"source": 653, "target": 531, "value": 8}, {"source": 653, "target": 545, "value": 9}, {"source": 653, "target": 549, "value": 9}, {"source": 653, "target": 563, "value": 8}, {"source": 653, "target": 578, "value": 8}, {"source": 653, "target": 579, "value": 8}, {"source": 653, "target": 603, "value": 1}, {"source": 653, "target": 605, "value": 8}, {"source": 653, "target": 615, "value": 8}, {"source": 653, "target": 641, "value": 8}, {"source": 653, "target": 651, "value": 7}, {"source": 663, "target": 221, "value": 8}, {"source": 663, "target": 261, "value": 9}, {"source": 663, "target": 263, "value": 2}, {"source": 663, "target": 287, "value": 8}, {"source": 663, "target": 309, "value": 4}, {"source": 663, "target": 321, "value": 8}, {"source": 663, "target": 351, "value": 9}, {"source": 663, "target": 363, "value": 2}, {"source": 663, "target": 371, "value": 8}, {"source": 663, "target": 401, "value": 8}, {"source": 663, "target": 405, "value": 8}, {"source": 663, "target": 413, "value": 1}, {"source": 663, "target": 437, "value": 8}, {"source": 663, "target": 453, "value": 9}, {"source": 663, "target": 455, "value": 10}, {"source": 663, "target": 459, "value": 9}, {"source": 663, "target": 461, "value": 4}, {"source": 663, "target": 471, "value": 8}, {"source": 663, "target": 489, "value": 8}, {"source": 663, "target": 497, "value": 4}, {"source": 663, "target": 501, "value": 8}, {"source": 663, "target": 513, "value": 1}, {"source": 663, "target": 521, "value": 9}, {"source": 663, "target": 549, "value": 8}, {"source": 663, "target": 563, "value": 2}, {"source": 663, "target": 587, "value": 8}, {"source": 663, "target": 609, "value": 4}, {"source": 663, "target": 615, "value": 8}, {"source": 663, "target": 621, "value": 8}, {"source": 663, "target": 651, "value": 8}, {"source": 663, "target": 653, "value": 8}, {"source": 665, "target": 215, "value": 4}, {"source": 665, "target": 227, "value": 8}, {"source": 665, "target": 315, "value": 4}, {"source": 665, "target": 317, "value": 10}, {"source": 665, "target": 353, "value": 8}, {"source": 665, "target": 365, "value": 2}, {"source": 665, "target": 377, "value": 8}, {"source": 665, "target": 405, "value": 10}, {"source": 665, "target": 453, "value": 4}, {"source": 665, "target": 461, "value": 8}, {"source": 665, "target": 465, "value": 2}, {"source": 665, "target": 515, "value": 4}, {"source": 665, "target": 527, "value": 8}, {"source": 665, "target": 573, "value": 7}, {"source": 665, "target": 605, "value": 8}, {"source": 665, "target": 615, "value": 3}, {"source": 665, "target": 653, "value": 8}, {"source": 669, "target": 219, "value": 4}, {"source": 669, "target": 221, "value": 5}, {"source": 669, "target": 231, "value": 8}, {"source": 669, "target": 233, "value": 10}, {"source": 669, "target": 245, "value": 8}, {"source": 669, "target": 248, "value": 9}, {"source": 669, "target": 257, "value": 9}, {"source": 669, "target": 273, "value": 5}, {"source": 669, "target": 279, "value": 8}, {"source": 669, "target": 281, "value": 8}, {"source": 669, "target": 309, "value": 10}, {"source": 669, "target": 317, "value": 8}, {"source": 669, "target": 321, "value": 10}, {"source": 669, "target": 323, "value": 11}, {"source": 669, "target": 333, "value": 10}, {"source": 669, "target": 345, "value": 8}, {"source": 669, "target": 348, "value": 9}, {"source": 669, "target": 369, "value": 2}, {"source": 669, "target": 377, "value": 8}, {"source": 669, "target": 381, "value": 8}, {"source": 669, "target": 383, "value": 9}, {"source": 669, "target": 395, "value": 8}, {"source": 669, "target": 398, "value": 9}, {"source": 669, "target": 423, "value": 10}, {"source": 669, "target": 429, "value": 8}, {"source": 669, "target": 431, "value": 8}, {"source": 669, "target": 473, "value": 10}, {"source": 669, "target": 483, "value": 9}, {"source": 669, "target": 495, "value": 8}, {"source": 669, "target": 498, "value": 8}, {"source": 669, "target": 519, "value": 2}, {"source": 669, "target": 531, "value": 8}, {"source": 669, "target": 533, "value": 8}, {"source": 669, "target": 545, "value": 8}, {"source": 669, "target": 548, "value": 8}, {"source": 669, "target": 557, "value": 8}, {"source": 669, "target": 573, "value": 9}, {"source": 669, "target": 579, "value": 8}, {"source": 669, "target": 581, "value": 8}, {"source": 669, "target": 611, "value": 9}, {"source": 669, "target": 615, "value": 7}, {"source": 669, "target": 617, "value": 7}, {"source": 669, "target": 621, "value": 8}, {"source": 669, "target": 623, "value": 9}, {"source": 669, "target": 633, "value": 8}, {"source": 669, "target": 645, "value": 8}, {"source": 669, "target": 648, "value": 7}, {"source": 671, "target": 219, "value": 8}, {"source": 671, "target": 221, "value": 4}, {"source": 671, "target": 225, "value": 8}, {"source": 671, "target": 228, "value": 8}, {"source": 671, "target": 261, "value": 10}, {"source": 671, "target": 275, "value": 8}, {"source": 671, "target": 278, "value": 8}, {"source": 671, "target": 293, "value": 12}, {"source": 671, "target": 309, "value": 8}, {"source": 671, "target": 321, "value": 2}, {"source": 671, "target": 365, "value": 10}, {"source": 671, "target": 369, "value": 8}, {"source": 671, "target": 371, "value": 4}, {"source": 671, "target": 375, "value": 8}, {"source": 671, "target": 378, "value": 8}, {"source": 671, "target": 413, "value": 8}, {"source": 671, "target": 425, "value": 8}, {"source": 671, "target": 428, "value": 8}, {"source": 671, "target": 461, "value": 9}, {"source": 671, "target": 471, "value": 4}, {"source": 671, "target": 513, "value": 8}, {"source": 671, "target": 519, "value": 8}, {"source": 671, "target": 521, "value": 4}, {"source": 671, "target": 525, "value": 8}, {"source": 671, "target": 528, "value": 8}, {"source": 671, "target": 563, "value": 9}, {"source": 671, "target": 573, "value": 9}, {"source": 671, "target": 575, "value": 7}, {"source": 671, "target": 578, "value": 8}, {"source": 671, "target": 609, "value": 7}, {"source": 671, "target": 621, "value": 3}, {"source": 671, "target": 663, "value": 8}, {"source": 671, "target": 669, "value": 7}, {"source": 675, "target": 225, "value": 4}, {"source": 675, "target": 228, "value": 8}, {"source": 675, "target": 275, "value": 4}, {"source": 675, "target": 278, "value": 8}, {"source": 675, "target": 321, "value": 8}, {"source": 675, "target": 365, "value": 10}, {"source": 675, "target": 375, "value": 4}, {"source": 675, "target": 377, "value": 10}, {"source": 675, "target": 378, "value": 8}, {"source": 675, "target": 413, "value": 8}, {"source": 675, "target": 425, "value": 4}, {"source": 675, "target": 428, "value": 8}, {"source": 675, "target": 513, "value": 8}, {"source": 675, "target": 525, "value": 4}, {"source": 675, "target": 528, "value": 8}, {"source": 675, "target": 563, "value": 8}, {"source": 675, "target": 575, "value": 3}, {"source": 675, "target": 578, "value": 8}, {"source": 675, "target": 671, "value": 7}, {"source": 677, "target": 225, "value": 8}, {"source": 677, "target": 227, "value": 4}, {"source": 677, "target": 243, "value": 11}, {"source": 677, "target": 279, "value": 10}, {"source": 677, "target": 287, "value": 8}, {"source": 677, "target": 293, "value": 11}, {"source": 677, "target": 303, "value": 9}, {"source": 677, "target": 317, "value": 10}, {"source": 677, "target": 335, "value": 8}, {"source": 677, "target": 339, "value": 8}, {"source": 677, "target": 353, "value": 4}, {"source": 677, "target": 365, "value": 4}, {"source": 677, "target": 377, "value": 2}, {"source": 677, "target": 393, "value": 10}, {"source": 677, "target": 425, "value": 8}, {"source": 677, "target": 429, "value": 4}, {"source": 677, "target": 437, "value": 8}, {"source": 677, "target": 443, "value": 10}, {"source": 677, "target": 453, "value": 4}, {"source": 677, "target": 465, "value": 8}, {"source": 677, "target": 489, "value": 8}, {"source": 677, "target": 503, "value": 9}, {"source": 677, "target": 525, "value": 8}, {"source": 677, "target": 527, "value": 4}, {"source": 677, "target": 543, "value": 9}, {"source": 677, "target": 573, "value": 8}, {"source": 677, "target": 587, "value": 8}, {"source": 677, "target": 593, "value": 9}, {"source": 677, "target": 603, "value": 8}, {"source": 677, "target": 639, "value": 8}, {"source": 677, "target": 653, "value": 4}, {"source": 677, "target": 665, "value": 8}, {"source": 678, "target": 225, "value": 8}, {"source": 678, "target": 228, "value": 4}, {"source": 678, "target": 249, "value": 8}, {"source": 678, "target": 261, "value": 8}, {"source": 678, "target": 273, "value": 8}, {"source": 678, "target": 275, "value": 8}, {"source": 678, "target": 278, "value": 4}, {"source": 678, "target": 303, "value": 10}, {"source": 678, "target": 321, "value": 8}, {"source": 678, "target": 323, "value": 8}, {"source": 678, "target": 353, "value": 10}, {"source": 678, "target": 365, "value": 10}, {"source": 678, "target": 375, "value": 8}, {"source": 678, "target": 378, "value": 4}, {"source": 678, "target": 399, "value": 8}, {"source": 678, "target": 413, "value": 4}, {"source": 678, "target": 423, "value": 8}, {"source": 678, "target": 425, "value": 8}, {"source": 678, "target": 428, "value": 4}, {"source": 678, "target": 437, "value": 8}, {"source": 678, "target": 453, "value": 9}, {"source": 678, "target": 461, "value": 8}, {"source": 678, "target": 473, "value": 8}, {"source": 678, "target": 503, "value": 9}, {"source": 678, "target": 513, "value": 8}, {"source": 678, "target": 525, "value": 8}, {"source": 678, "target": 528, "value": 4}, {"source": 678, "target": 545, "value": 8}, {"source": 678, "target": 549, "value": 8}, {"source": 678, "target": 561, "value": 8}, {"source": 678, "target": 563, "value": 9}, {"source": 678, "target": 573, "value": 8}, {"source": 678, "target": 575, "value": 8}, {"source": 678, "target": 578, "value": 4}, {"source": 678, "target": 603, "value": 8}, {"source": 678, "target": 623, "value": 8}, {"source": 678, "target": 653, "value": 8}, {"source": 678, "target": 671, "value": 7}, {"source": 678, "target": 675, "value": 8}, {"source": 681, "target": 221, "value": 11}, {"source": 681, "target": 231, "value": 4}, {"source": 681, "target": 243, "value": 8}, {"source": 681, "target": 273, "value": 11}, {"source": 681, "target": 281, "value": 2}, {"source": 681, "target": 293, "value": 8}, {"source": 681, "target": 333, "value": 10}, {"source": 681, "target": 369, "value": 8}, {"source": 681, "target": 377, "value": 8}, {"source": 681, "target": 381, "value": 2}, {"source": 681, "target": 393, "value": 8}, {"source": 681, "target": 398, "value": 9}, {"source": 681, "target": 431, "value": 2}, {"source": 681, "target": 443, "value": 8}, {"source": 681, "target": 489, "value": 8}, {"source": 681, "target": 519, "value": 9}, {"source": 681, "target": 531, "value": 4}, {"source": 681, "target": 533, "value": 9}, {"source": 681, "target": 543, "value": 8}, {"source": 681, "target": 581, "value": 2}, {"source": 681, "target": 585, "value": 9}, {"source": 681, "target": 593, "value": 8}, {"source": 681, "target": 605, "value": 8}, {"source": 681, "target": 621, "value": 8}, {"source": 681, "target": 669, "value": 8}, {"source": 683, "target": 221, "value": 4}, {"source": 683, "target": 233, "value": 1}, {"source": 683, "target": 257, "value": 8}, {"source": 683, "target": 279, "value": 4}, {"source": 683, "target": 317, "value": 8}, {"source": 683, "target": 321, "value": 4}, {"source": 683, "target": 333, "value": 1}, {"source": 683, "target": 335, "value": 10}, {"source": 683, "target": 381, "value": 8}, {"source": 683, "target": 383, "value": 2}, {"source": 683, "target": 405, "value": 11}, {"source": 683, "target": 407, "value": 8}, {"source": 683, "target": 429, "value": 9}, {"source": 683, "target": 483, "value": 2}, {"source": 683, "target": 521, "value": 8}, {"source": 683, "target": 533, "value": 1}, {"source": 683, "target": 557, "value": 4}, {"source": 683, "target": 573, "value": 9}, {"source": 683, "target": 579, "value": 9}, {"source": 683, "target": 617, "value": 8}, {"source": 683, "target": 621, "value": 7}, {"source": 683, "target": 633, "value": 1}, {"source": 683, "target": 669, "value": 8}, {"source": 693, "target": 243, "value": 1}, {"source": 693, "target": 245, "value": 10}, {"source": 693, "target": 251, "value": 8}, {"source": 693, "target": 255, "value": 8}, {"source": 693, "target": 281, "value": 9}, {"source": 693, "target": 293, "value": 0}, {"source": 693, "target": 303, "value": 8}, {"source": 693, "target": 305, "value": 8}, {"source": 693, "target": 317, "value": 2}, {"source": 693, "target": 333, "value": 10}, {"source": 693, "target": 335, "value": 8}, {"source": 693, "target": 339, "value": 8}, {"source": 693, "target": 341, "value": 8}, {"source": 693, "target": 353, "value": 8}, {"source": 693, "target": 377, "value": 8}, {"source": 693, "target": 381, "value": 8}, {"source": 693, "target": 393, "value": 1}, {"source": 693, "target": 398, "value": 9}, {"source": 693, "target": 405, "value": 8}, {"source": 693, "target": 429, "value": 8}, {"source": 693, "target": 431, "value": 9}, {"source": 693, "target": 441, "value": 8}, {"source": 693, "target": 443, "value": 1}, {"source": 693, "target": 453, "value": 8}, {"source": 693, "target": 455, "value": 8}, {"source": 693, "target": 467, "value": 4}, {"source": 693, "target": 489, "value": 2}, {"source": 693, "target": 503, "value": 8}, {"source": 693, "target": 533, "value": 9}, {"source": 693, "target": 543, "value": 1}, {"source": 693, "target": 545, "value": 9}, {"source": 693, "target": 555, "value": 8}, {"source": 693, "target": 581, "value": 8}, {"source": 693, "target": 585, "value": 9}, {"source": 693, "target": 593, "value": 1}, {"source": 693, "target": 603, "value": 8}, {"source": 693, "target": 605, "value": 8}, {"source": 693, "target": 617, "value": 4}, {"source": 693, "target": 639, "value": 8}, {"source": 693, "target": 641, "value": 7}, {"source": 693, "target": 645, "value": 8}, {"source": 693, "target": 653, "value": 8}, {"source": 693, "target": 677, "value": 8}, {"source": 693, "target": 681, "value": 8}, {"source": 695, "target": 233, "value": 4}, {"source": 695, "target": 245, "value": 2}, {"source": 695, "target": 248, "value": 8}, {"source": 695, "target": 257, "value": 8}, {"source": 695, "target": 285, "value": 10}, {"source": 695, "target": 333, "value": 8}, {"source": 695, "target": 345, "value": 2}, {"source": 695, "target": 348, "value": 8}, {"source": 695, "target": 395, "value": 4}, {"source": 695, "target": 398, "value": 8}, {"source": 695, "target": 407, "value": 8}, {"source": 695, "target": 485, "value": 9}, {"source": 695, "target": 495, "value": 4}, {"source": 695, "target": 498, "value": 8}, {"source": 695, "target": 503, "value": 8}, {"source": 695, "target": 533, "value": 8}, {"source": 695, "target": 545, "value": 2}, {"source": 695, "target": 548, "value": 8}, {"source": 695, "target": 557, "value": 8}, {"source": 695, "target": 633, "value": 7}, {"source": 695, "target": 645, "value": 2}, {"source": 695, "target": 648, "value": 8}, {"source": 695, "target": 669, "value": 8}, {"source": 698, "target": 219, "value": 8}, {"source": 698, "target": 233, "value": 4}, {"source": 698, "target": 245, "value": 8}, {"source": 698, "target": 248, "value": 4}, {"source": 698, "target": 257, "value": 8}, {"source": 698, "target": 273, "value": 11}, {"source": 698, "target": 285, "value": 11}, {"source": 698, "target": 309, "value": 10}, {"source": 698, "target": 323, "value": 10}, {"source": 698, "target": 333, "value": 9}, {"source": 698, "target": 345, "value": 8}, {"source": 698, "target": 348, "value": 4}, {"source": 698, "target": 369, "value": 8}, {"source": 698, "target": 395, "value": 8}, {"source": 698, "target": 398, "value": 4}, {"source": 698, "target": 423, "value": 10}, {"source": 698, "target": 473, "value": 9}, {"source": 698, "target": 485, "value": 10}, {"source": 698, "target": 495, "value": 8}, {"source": 698, "target": 498, "value": 4}, {"source": 698, "target": 519, "value": 8}, {"source": 698, "target": 533, "value": 8}, {"source": 698, "target": 545, "value": 8}, {"source": 698, "target": 548, "value": 4}, {"source": 698, "target": 557, "value": 8}, {"source": 698, "target": 573, "value": 9}, {"source": 698, "target": 611, "value": 8}, {"source": 698, "target": 615, "value": 8}, {"source": 698, "target": 623, "value": 8}, {"source": 698, "target": 633, "value": 8}, {"source": 698, "target": 645, "value": 7}, {"source": 698, "target": 648, "value": 3}, {"source": 698, "target": 669, "value": 8}, {"source": 698, "target": 695, "value": 8}, {"source": 699, "target": 221, "value": 8}, {"source": 699, "target": 228, "value": 9}, {"source": 699, "target": 249, "value": 4}, {"source": 699, "target": 251, "value": 10}, {"source": 699, "target": 273, "value": 4}, {"source": 699, "target": 278, "value": 9}, {"source": 699, "target": 293, "value": 10}, {"source": 699, "target": 303, "value": 4}, {"source": 699, "target": 321, "value": 11}, {"source": 699, "target": 323, "value": 8}, {"source": 699, "target": 341, "value": 8}, {"source": 699, "target": 347, "value": 8}, {"source": 699, "target": 353, "value": 4}, {"source": 699, "target": 378, "value": 9}, {"source": 699, "target": 399, "value": 4}, {"source": 699, "target": 423, "value": 8}, {"source": 699, "target": 428, "value": 9}, {"source": 699, "target": 437, "value": 8}, {"source": 699, "target": 441, "value": 8}, {"source": 699, "target": 453, "value": 4}, {"source": 699, "target": 473, "value": 4}, {"source": 699, "target": 497, "value": 8}, {"source": 699, "target": 503, "value": 4}, {"source": 699, "target": 528, "value": 8}, {"source": 699, "target": 545, "value": 8}, {"source": 699, "target": 549, "value": 4}, {"source": 699, "target": 573, "value": 2}, {"source": 699, "target": 578, "value": 8}, {"source": 699, "target": 603, "value": 4}, {"source": 699, "target": 621, "value": 8}, {"source": 699, "target": 623, "value": 8}, {"source": 699, "target": 641, "value": 8}, {"source": 699, "target": 647, "value": 8}, {"source": 699, "target": 653, "value": 4}, {"source": 699, "target": 678, "value": 7}, {"source": 699, "target": 693, "value": 8}, {"source": 701, "target": 227, "value": 8}, {"source": 701, "target": 249, "value": 8}, {"source": 701, "target": 251, "value": 4}, {"source": 701, "target": 261, "value": 8}, {"source": 701, "target": 263, "value": 8}, {"source": 701, "target": 279, "value": 10}, {"source": 701, "target": 303, "value": 9}, {"source": 701, "target": 311, "value": 8}, {"source": 701, "target": 341, "value": 10}, {"source": 701, "target": 351, "value": 4}, {"source": 701, "target": 353, "value": 4}, {"source": 701, "target": 363, "value": 8}, {"source": 701, "target": 377, "value": 8}, {"source": 701, "target": 401, "value": 2}, {"source": 701, "target": 411, "value": 8}, {"source": 701, "target": 413, "value": 8}, {"source": 701, "target": 453, "value": 3}, {"source": 701, "target": 455, "value": 9}, {"source": 701, "target": 461, "value": 8}, {"source": 701, "target": 489, "value": 8}, {"source": 701, "target": 501, "value": 2}, {"source": 701, "target": 503, "value": 4}, {"source": 701, "target": 513, "value": 8}, {"source": 701, "target": 525, "value": 8}, {"source": 701, "target": 527, "value": 8}, {"source": 701, "target": 549, "value": 8}, {"source": 701, "target": 551, "value": 4}, {"source": 701, "target": 561, "value": 8}, {"source": 701, "target": 563, "value": 8}, {"source": 701, "target": 573, "value": 10}, {"source": 701, "target": 603, "value": 8}, {"source": 701, "target": 611, "value": 8}, {"source": 701, "target": 651, "value": 2}, {"source": 701, "target": 653, "value": 2}, {"source": 701, "target": 663, "value": 8}, {"source": 701, "target": 677, "value": 8}, {"source": 705, "target": 245, "value": 11}, {"source": 705, "target": 251, "value": 8}, {"source": 705, "target": 255, "value": 2}, {"source": 705, "target": 293, "value": 9}, {"source": 705, "target": 305, "value": 2}, {"source": 705, "target": 317, "value": 8}, {"source": 705, "target": 363, "value": 8}, {"source": 705, "target": 377, "value": 11}, {"source": 705, "target": 393, "value": 8}, {"source": 705, "target": 405, "value": 2}, {"source": 705, "target": 429, "value": 8}, {"source": 705, "target": 455, "value": 4}, {"source": 705, "target": 467, "value": 8}, {"source": 705, "target": 555, "value": 4}, {"source": 705, "target": 557, "value": 9}, {"source": 705, "target": 593, "value": 8}, {"source": 705, "target": 605, "value": 2}, {"source": 705, "target": 617, "value": 8}, {"source": 705, "target": 645, "value": 8}, {"source": 705, "target": 693, "value": 8}, {"source": 707, "target": 233, "value": 4}, {"source": 707, "target": 245, "value": 9}, {"source": 707, "target": 257, "value": 4}, {"source": 707, "target": 333, "value": 4}, {"source": 707, "target": 345, "value": 8}, {"source": 707, "target": 381, "value": 8}, {"source": 707, "target": 383, "value": 9}, {"source": 707, "target": 407, "value": 4}, {"source": 707, "target": 483, "value": 9}, {"source": 707, "target": 503, "value": 8}, {"source": 707, "target": 533, "value": 4}, {"source": 707, "target": 545, "value": 8}, {"source": 707, "target": 557, "value": 4}, {"source": 707, "target": 633, "value": 4}, {"source": 707, "target": 645, "value": 7}, {"source": 707, "target": 683, "value": 8}, {"source": 707, "target": 695, "value": 8}, {"source": 711, "target": 249, "value": 9}, {"source": 711, "target": 261, "value": 4}, {"source": 711, "target": 285, "value": 8}, {"source": 711, "target": 311, "value": 4}, {"source": 711, "target": 363, "value": 10}, {"source": 711, "target": 411, "value": 4}, {"source": 711, "target": 461, "value": 4}, {"source": 711, "target": 501, "value": 9}, {"source": 711, "target": 549, "value": 8}, {"source": 711, "target": 561, "value": 4}, {"source": 711, "target": 611, "value": 4}, {"source": 711, "target": 701, "value": 8}, {"source": 713, "target": 225, "value": 8}, {"source": 713, "target": 228, "value": 8}, {"source": 713, "target": 255, "value": 10}, {"source": 713, "target": 261, "value": 8}, {"source": 713, "target": 263, "value": 1}, {"source": 713, "target": 275, "value": 8}, {"source": 713, "target": 278, "value": 8}, {"source": 713, "target": 285, "value": 8}, {"source": 713, "target": 287, "value": 4}, {"source": 713, "target": 291, "value": 9}, {"source": 713, "target": 293, "value": 8}, {"source": 713, "target": 305, "value": 8}, {"source": 713, "target": 309, "value": 8}, {"source": 713, "target": 311, "value": 8}, {"source": 713, "target": 317, "value": 8}, {"source": 713, "target": 321, "value": 8}, {"source": 713, "target": 341, "value": 9}, {"source": 713, "target": 351, "value": 9}, {"source": 713, "target": 363, "value": 1}, {"source": 713, "target": 365, "value": 4}, {"source": 713, "target": 375, "value": 8}, {"source": 713, "target": 378, "value": 8}, {"source": 713, "target": 401, "value": 8}, {"source": 713, "target": 405, "value": 4}, {"source": 713, "target": 411, "value": 8}, {"source": 713, "target": 413, "value": 1}, {"source": 713, "target": 425, "value": 8}, {"source": 713, "target": 428, "value": 8}, {"source": 713, "target": 437, "value": 4}, {"source": 713, "target": 441, "value": 9}, {"source": 713, "target": 453, "value": 9}, {"source": 713, "target": 455, "value": 10}, {"source": 713, "target": 459, "value": 8}, {"source": 713, "target": 461, "value": 2}, {"source": 713, "target": 467, "value": 8}, {"source": 713, "target": 489, "value": 4}, {"source": 713, "target": 491, "value": 9}, {"source": 713, "target": 497, "value": 4}, {"source": 713, "target": 501, "value": 8}, {"source": 713, "target": 513, "value": 1}, {"source": 713, "target": 525, "value": 8}, {"source": 713, "target": 528, "value": 8}, {"source": 713, "target": 549, "value": 8}, {"source": 713, "target": 557, "value": 9}, {"source": 713, "target": 561, "value": 8}, {"source": 713, "target": 563, "value": 1}, {"source": 713, "target": 575, "value": 8}, {"source": 713, "target": 578, "value": 8}, {"source": 713, "target": 587, "value": 4}, {"source": 713, "target": 591, "value": 9}, {"source": 713, "target": 605, "value": 8}, {"source": 713, "target": 609, "value": 9}, {"source": 713, "target": 611, "value": 8}, {"source": 713, "target": 615, "value": 9}, {"source": 713, "target": 617, "value": 8}, {"source": 713, "target": 641, "value": 9}, {"source": 713, "target": 651, "value": 8}, {"source": 713, "target": 653, "value": 8}, {"source": 713, "target": 663, "value": 1}, {"source": 713, "target": 671, "value": 7}, {"source": 713, "target": 675, "value": 8}, {"source": 713, "target": 678, "value": 8}, {"source": 713, "target": 701, "value": 8}, {"source": 713, "target": 705, "value": 7}, {"source": 713, "target": 711, "value": 7}, {"source": 723, "target": 219, "value": 8}, {"source": 723, "target": 221, "value": 8}, {"source": 723, "target": 248, "value": 8}, {"source": 723, "target": 257, "value": 8}, {"source": 723, "target": 261, "value": 9}, {"source": 723, "target": 273, "value": 1}, {"source": 723, "target": 309, "value": 9}, {"source": 723, "target": 323, "value": 2}, {"source": 723, "target": 347, "value": 8}, {"source": 723, "target": 348, "value": 8}, {"source": 723, "target": 369, "value": 8}, {"source": 723, "target": 398, "value": 8}, {"source": 723, "target": 413, "value": 10}, {"source": 723, "target": 423, "value": 2}, {"source": 723, "target": 461, "value": 8}, {"source": 723, "target": 473, "value": 1}, {"source": 723, "target": 497, "value": 8}, {"source": 723, "target": 498, "value": 8}, {"source": 723, "target": 519, "value": 8}, {"source": 723, "target": 545, "value": 10}, {"source": 723, "target": 548, "value": 8}, {"source": 723, "target": 557, "value": 8}, {"source": 723, "target": 561, "value": 8}, {"source": 723, "target": 573, "value": 1}, {"source": 723, "target": 611, "value": 8}, {"source": 723, "target": 615, "value": 8}, {"source": 723, "target": 621, "value": 8}, {"source": 723, "target": 623, "value": 2}, {"source": 723, "target": 647, "value": 8}, {"source": 723, "target": 648, "value": 8}, {"source": 723, "target": 669, "value": 8}, {"source": 723, "target": 678, "value": 8}, {"source": 723, "target": 698, "value": 8}, {"source": 723, "target": 699, "value": 8}, {"source": 725, "target": 225, "value": 2}, {"source": 725, "target": 228, "value": 8}, {"source": 725, "target": 273, "value": 8}, {"source": 725, "target": 275, "value": 4}, {"source": 725, "target": 278, "value": 8}, {"source": 725, "target": 285, "value": 8}, {"source": 725, "target": 287, "value": 8}, {"source": 725, "target": 321, "value": 8}, {"source": 725, "target": 335, "value": 8}, {"source": 725, "target": 365, "value": 10}, {"source": 725, "target": 375, "value": 4}, {"source": 725, "target": 377, "value": 4}, {"source": 725, "target": 378, "value": 8}, {"source": 725, "target": 413, "value": 8}, {"source": 725, "target": 425, "value": 2}, {"source": 725, "target": 428, "value": 8}, {"source": 725, "target": 429, "value": 10}, {"source": 725, "target": 435, "value": 8}, {"source": 725, "target": 437, "value": 8}, {"source": 725, "target": 473, "value": 8}, {"source": 725, "target": 485, "value": 8}, {"source": 725, "target": 513, "value": 8}, {"source": 725, "target": 525, "value": 2}, {"source": 725, "target": 528, "value": 8}, {"source": 725, "target": 531, "value": 8}, {"source": 725, "target": 563, "value": 9}, {"source": 725, "target": 573, "value": 8}, {"source": 725, "target": 575, "value": 4}, {"source": 725, "target": 578, "value": 8}, {"source": 725, "target": 585, "value": 8}, {"source": 725, "target": 587, "value": 8}, {"source": 725, "target": 635, "value": 8}, {"source": 725, "target": 671, "value": 7}, {"source": 725, "target": 675, "value": 3}, {"source": 725, "target": 677, "value": 8}, {"source": 725, "target": 678, "value": 7}, {"source": 725, "target": 713, "value": 8}, {"source": 728, "target": 225, "value": 8}, {"source": 728, "target": 228, "value": 4}, {"source": 728, "target": 249, "value": 8}, {"source": 728, "target": 275, "value": 8}, {"source": 728, "target": 278, "value": 4}, {"source": 728, "target": 303, "value": 11}, {"source": 728, "target": 321, "value": 8}, {"source": 728, "target": 353, "value": 10}, {"source": 728, "target": 365, "value": 10}, {"source": 728, "target": 375, "value": 8}, {"source": 728, "target": 378, "value": 4}, {"source": 728, "target": 399, "value": 8}, {"source": 728, "target": 413, "value": 8}, {"source": 728, "target": 425, "value": 8}, {"source": 728, "target": 428, "value": 4}, {"source": 728, "target": 437, "value": 8}, {"source": 728, "target": 453, "value": 10}, {"source": 728, "target": 503, "value": 9}, {"source": 728, "target": 513, "value": 8}, {"source": 728, "target": 525, "value": 8}, {"source": 728, "target": 528, "value": 4}, {"source": 728, "target": 545, "value": 8}, {"source": 728, "target": 549, "value": 8}, {"source": 728, "target": 563, "value": 9}, {"source": 728, "target": 575, "value": 8}, {"source": 728, "target": 578, "value": 4}, {"source": 728, "target": 603, "value": 9}, {"source": 728, "target": 653, "value": 8}, {"source": 728, "target": 671, "value": 8}, {"source": 728, "target": 675, "value": 7}, {"source": 728, "target": 678, "value": 3}, {"source": 728, "target": 699, "value": 8}, {"source": 728, "target": 713, "value": 8}, {"source": 728, "target": 725, "value": 8}, {"source": 729, "target": 221, "value": 11}, {"source": 729, "target": 233, "value": 12}, {"source": 729, "target": 279, "value": 4}, {"source": 729, "target": 291, "value": 8}, {"source": 729, "target": 317, "value": 9}, {"source": 729, "target": 333, "value": 11}, {"source": 729, "target": 341, "value": 8}, {"source": 729, "target": 381, "value": 9}, {"source": 729, "target": 383, "value": 11}, {"source": 729, "target": 429, "value": 2}, {"source": 729, "target": 441, "value": 8}, {"source": 729, "target": 483, "value": 10}, {"source": 729, "target": 491, "value": 8}, {"source": 729, "target": 531, "value": 9}, {"source": 729, "target": 533, "value": 4}, {"source": 729, "target": 579, "value": 4}, {"source": 729, "target": 581, "value": 9}, {"source": 729, "target": 587, "value": 8}, {"source": 729, "target": 591, "value": 8}, {"source": 729, "target": 617, "value": 8}, {"source": 729, "target": 633, "value": 9}, {"source": 729, "target": 641, "value": 8}, {"source": 729, "target": 653, "value": 8}, {"source": 729, "target": 669, "value": 8}, {"source": 729, "target": 683, "value": 9}, {"source": 731, "target": 221, "value": 11}, {"source": 731, "target": 231, "value": 4}, {"source": 731, "target": 273, "value": 11}, {"source": 731, "target": 281, "value": 4}, {"source": 731, "target": 369, "value": 8}, {"source": 731, "target": 377, "value": 8}, {"source": 731, "target": 381, "value": 4}, {"source": 731, "target": 431, "value": 4}, {"source": 731, "target": 519, "value": 9}, {"source": 731, "target": 531, "value": 4}, {"source": 731, "target": 581, "value": 4}, {"source": 731, "target": 605, "value": 8}, {"source": 731, "target": 621, "value": 8}, {"source": 731, "target": 669, "value": 7}, {"source": 731, "target": 681, "value": 3}, {"source": 735, "target": 273, "value": 9}, {"source": 735, "target": 285, "value": 4}, {"source": 735, "target": 335, "value": 4}, {"source": 735, "target": 377, "value": 10}, {"source": 735, "target": 435, "value": 4}, {"source": 735, "target": 473, "value": 8}, {"source": 735, "target": 485, "value": 4}, {"source": 735, "target": 525, "value": 9}, {"source": 735, "target": 531, "value": 8}, {"source": 735, "target": 573, "value": 8}, {"source": 735, "target": 585, "value": 4}, {"source": 735, "target": 587, "value": 9}, {"source": 735, "target": 635, "value": 4}, {"source": 735, "target": 725, "value": 8}, {"source": 737, "target": 225, "value": 9}, {"source": 737, "target": 228, "value": 9}, {"source": 737, "target": 249, "value": 8}, {"source": 737, "target": 263, "value": 9}, {"source": 737, "target": 278, "value": 9}, {"source": 737, "target": 287, "value": 4}, {"source": 737, "target": 303, "value": 11}, {"source": 737, "target": 353, "value": 11}, {"source": 737, "target": 363, "value": 9}, {"source": 737, "target": 378, "value": 8}, {"source": 737, "target": 399, "value": 8}, {"source": 737, "target": 413, "value": 4}, {"source": 737, "target": 425, "value": 8}, {"source": 737, "target": 428, "value": 8}, {"source": 737, "target": 429, "value": 10}, {"source": 737, "target": 437, "value": 2}, {"source": 737, "target": 453, "value": 10}, {"source": 737, "target": 461, "value": 8}, {"source": 737, "target": 489, "value": 9}, {"source": 737, "target": 503, "value": 10}, {"source": 737, "target": 513, "value": 4}, {"source": 737, "target": 525, "value": 8}, {"source": 737, "target": 528, "value": 8}, {"source": 737, "target": 545, "value": 8}, {"source": 737, "target": 549, "value": 8}, {"source": 737, "target": 563, "value": 8}, {"source": 737, "target": 578, "value": 8}, {"source": 737, "target": 587, "value": 4}, {"source": 737, "target": 603, "value": 9}, {"source": 737, "target": 653, "value": 8}, {"source": 737, "target": 663, "value": 8}, {"source": 737, "target": 677, "value": 8}, {"source": 737, "target": 678, "value": 7}, {"source": 737, "target": 699, "value": 8}, {"source": 737, "target": 713, "value": 4}, {"source": 737, "target": 725, "value": 7}, {"source": 737, "target": 728, "value": 7}, {"source": 741, "target": 219, "value": 8}, {"source": 741, "target": 228, "value": 8}, {"source": 741, "target": 233, "value": 4}, {"source": 741, "target": 245, "value": 8}, {"source": 741, "target": 248, "value": 8}, {"source": 741, "target": 249, "value": 8}, {"source": 741, "target": 251, "value": 8}, {"source": 741, "target": 278, "value": 8}, {"source": 741, "target": 285, "value": 10}, {"source": 741, "target": 291, "value": 4}, {"source": 741, "target": 293, "value": 5}, {"source": 741, "target": 303, "value": 4}, {"source": 741, "target": 321, "value": 9}, {"source": 741, "target": 333, "value": 8}, {"source": 741, "target": 339, "value": 8}, {"source": 741, "target": 341, "value": 2}, {"source": 741, "target": 345, "value": 8}, {"source": 741, "target": 348, "value": 8}, {"source": 741, "target": 351, "value": 8}, {"source": 741, "target": 353, "value": 4}, {"source": 741, "target": 365, "value": 8}, {"source": 741, "target": 369, "value": 8}, {"source": 741, "target": 378, "value": 8}, {"source": 741, "target": 381, "value": 10}, {"source": 741, "target": 395, "value": 8}, {"source": 741, "target": 398, "value": 8}, {"source": 741, "target": 399, "value": 8}, {"source": 741, "target": 401, "value": 8}, {"source": 741, "target": 413, "value": 8}, {"source": 741, "target": 428, "value": 8}, {"source": 741, "target": 429, "value": 8}, {"source": 741, "target": 437, "value": 8}, {"source": 741, "target": 441, "value": 2}, {"source": 741, "target": 453, "value": 4}, {"source": 741, "target": 461, "value": 9}, {"source": 741, "target": 485, "value": 9}, {"source": 741, "target": 489, "value": 4}, {"source": 741, "target": 491, "value": 4}, {"source": 741, "target": 495, "value": 8}, {"source": 741, "target": 498, "value": 8}, {"source": 741, "target": 501, "value": 8}, {"source": 741, "target": 503, "value": 4}, {"source": 741, "target": 519, "value": 9}, {"source": 741, "target": 528, "value": 8}, {"source": 741, "target": 533, "value": 4}, {"source": 741, "target": 545, "value": 4}, {"source": 741, "target": 548, "value": 8}, {"source": 741, "target": 549, "value": 8}, {"source": 741, "target": 551, "value": 8}, {"source": 741, "target": 578, "value": 8}, {"source": 741, "target": 581, "value": 9}, {"source": 741, "target": 587, "value": 8}, {"source": 741, "target": 591, "value": 4}, {"source": 741, "target": 603, "value": 4}, {"source": 741, "target": 633, "value": 8}, {"source": 741, "target": 639, "value": 8}, {"source": 741, "target": 641, "value": 2}, {"source": 741, "target": 645, "value": 7}, {"source": 741, "target": 648, "value": 8}, {"source": 741, "target": 651, "value": 8}, {"source": 741, "target": 653, "value": 4}, {"source": 741, "target": 669, "value": 8}, {"source": 741, "target": 671, "value": 7}, {"source": 741, "target": 678, "value": 7}, {"source": 741, "target": 693, "value": 8}, {"source": 741, "target": 695, "value": 7}, {"source": 741, "target": 698, "value": 7}, {"source": 741, "target": 699, "value": 4}, {"source": 741, "target": 701, "value": 8}, {"source": 741, "target": 713, "value": 8}, {"source": 741, "target": 728, "value": 8}, {"source": 741, "target": 729, "value": 8}, {"source": 741, "target": 737, "value": 8}, {"source": 743, "target": 243, "value": 2}, {"source": 743, "target": 281, "value": 9}, {"source": 743, "target": 293, "value": 1}, {"source": 743, "target": 317, "value": 4}, {"source": 743, "target": 333, "value": 10}, {"source": 743, "target": 335, "value": 8}, {"source": 743, "target": 339, "value": 8}, {"source": 743, "target": 377, "value": 8}, {"source": 743, "target": 381, "value": 8}, {"source": 743, "target": 393, "value": 1}, {"source": 743, "target": 398, "value": 9}, {"source": 743, "target": 429, "value": 8}, {"source": 743, "target": 431, "value": 9}, {"source": 743, "target": 443, "value": 2}, {"source": 743, "target": 467, "value": 8}, {"source": 743, "target": 489, "value": 2}, {"source": 743, "target": 533, "value": 9}, {"source": 743, "target": 543, "value": 2}, {"source": 743, "target": 545, "value": 9}, {"source": 743, "target": 581, "value": 8}, {"source": 743, "target": 585, "value": 9}, {"source": 743, "target": 593, "value": 1}, {"source": 743, "target": 617, "value": 8}, {"source": 743, "target": 639, "value": 8}, {"source": 743, "target": 677, "value": 8}, {"source": 743, "target": 681, "value": 7}, {"source": 743, "target": 693, "value": 1}, {"source": 753, "target": 215, "value": 8}, {"source": 753, "target": 227, "value": 4}, {"source": 753, "target": 228, "value": 8}, {"source": 753, "target": 249, "value": 8}, {"source": 753, "target": 278, "value": 8}, {"source": 753, "target": 279, "value": 4}, {"source": 753, "target": 293, "value": 10}, {"source": 753, "target": 303, "value": 1}, {"source": 753, "target": 315, "value": 8}, {"source": 753, "target": 341, "value": 9}, {"source": 753, "target": 353, "value": 1}, {"source": 753, "target": 365, "value": 8}, {"source": 753, "target": 377, "value": 4}, {"source": 753, "target": 378, "value": 8}, {"source": 753, "target": 399, "value": 8}, {"source": 753, "target": 405, "value": 4}, {"source": 753, "target": 428, "value": 8}, {"source": 753, "target": 437, "value": 8}, {"source": 753, "target": 441, "value": 8}, {"source": 753, "target": 453, "value": 0}, {"source": 753, "target": 461, "value": 8}, {"source": 753, "target": 465, "value": 8}, {"source": 753, "target": 503, "value": 1}, {"source": 753, "target": 515, "value": 8}, {"source": 753, "target": 527, "value": 4}, {"source": 753, "target": 528, "value": 8}, {"source": 753, "target": 545, "value": 8}, {"source": 753, "target": 549, "value": 8}, {"source": 753, "target": 578, "value": 8}, {"source": 753, "target": 603, "value": 1}, {"source": 753, "target": 605, "value": 9}, {"source": 753, "target": 615, "value": 8}, {"source": 753, "target": 641, "value": 8}, {"source": 753, "target": 653, "value": 1}, {"source": 753, "target": 665, "value": 7}, {"source": 753, "target": 677, "value": 4}, {"source": 753, "target": 678, "value": 8}, {"source": 753, "target": 693, "value": 8}, {"source": 753, "target": 699, "value": 4}, {"source": 753, "target": 701, "value": 3}, {"source": 753, "target": 728, "value": 8}, {"source": 753, "target": 737, "value": 8}, {"source": 753, "target": 741, "value": 4}, {"source": 755, "target": 245, "value": 11}, {"source": 755, "target": 251, "value": 8}, {"source": 755, "target": 255, "value": 4}, {"source": 755, "target": 263, "value": 12}, {"source": 755, "target": 293, "value": 9}, {"source": 755, "target": 303, "value": 8}, {"source": 755, "target": 305, "value": 4}, {"source": 755, "target": 309, "value": 8}, {"source": 755, "target": 351, "value": 10}, {"source": 755, "target": 353, "value": 8}, {"source": 755, "target": 363, "value": 11}, {"source": 755, "target": 377, "value": 11}, {"source": 755, "target": 393, "value": 8}, {"source": 755, "target": 405, "value": 2}, {"source": 755, "target": 413, "value": 10}, {"source": 755, "target": 429, "value": 8}, {"source": 755, "target": 453, "value": 8}, {"source": 755, "target": 455, "value": 4}, {"source": 755, "target": 459, "value": 8}, {"source": 755, "target": 497, "value": 4}, {"source": 755, "target": 503, "value": 8}, {"source": 755, "target": 513, "value": 10}, {"source": 755, "target": 549, "value": 9}, {"source": 755, "target": 555, "value": 4}, {"source": 755, "target": 563, "value": 9}, {"source": 755, "target": 593, "value": 8}, {"source": 755, "target": 603, "value": 8}, {"source": 755, "target": 605, "value": 4}, {"source": 755, "target": 609, "value": 8}, {"source": 755, "target": 645, "value": 8}, {"source": 755, "target": 653, "value": 8}, {"source": 755, "target": 663, "value": 9}, {"source": 755, "target": 693, "value": 7}, {"source": 755, "target": 705, "value": 3}, {"source": 755, "target": 713, "value": 8}, {"source": 755, "target": 753, "value": 7}, {"source": 759, "target": 263, "value": 12}, {"source": 759, "target": 309, "value": 4}, {"source": 759, "target": 333, "value": 8}, {"source": 759, "target": 351, "value": 10}, {"source": 759, "target": 363, "value": 11}, {"source": 759, "target": 405, "value": 8}, {"source": 759, "target": 413, "value": 11}, {"source": 759, "target": 459, "value": 4}, {"source": 759, "target": 461, "value": 9}, {"source": 759, "target": 497, "value": 4}, {"source": 759, "target": 513, "value": 10}, {"source": 759, "target": 549, "value": 9}, {"source": 759, "target": 563, "value": 9}, {"source": 759, "target": 609, "value": 4}, {"source": 759, "target": 663, "value": 9}, {"source": 759, "target": 713, "value": 8}, {"source": 759, "target": 755, "value": 8}, {"source": 761, "target": 249, "value": 9}, {"source": 761, "target": 261, "value": 2}, {"source": 761, "target": 273, "value": 4}, {"source": 761, "target": 285, "value": 8}, {"source": 761, "target": 311, "value": 4}, {"source": 761, "target": 323, "value": 4}, {"source": 761, "target": 363, "value": 10}, {"source": 761, "target": 411, "value": 4}, {"source": 761, "target": 413, "value": 4}, {"source": 761, "target": 423, "value": 4}, {"source": 761, "target": 461, "value": 2}, {"source": 761, "target": 473, "value": 4}, {"source": 761, "target": 501, "value": 9}, {"source": 761, "target": 549, "value": 8}, {"source": 761, "target": 561, "value": 2}, {"source": 761, "target": 573, "value": 4}, {"source": 761, "target": 611, "value": 4}, {"source": 761, "target": 623, "value": 4}, {"source": 761, "target": 678, "value": 4}, {"source": 761, "target": 701, "value": 8}, {"source": 761, "target": 711, "value": 3}, {"source": 761, "target": 713, "value": 8}, {"source": 761, "target": 723, "value": 4}, {"source": 765, "target": 215, "value": 4}, {"source": 765, "target": 225, "value": 8}, {"source": 765, "target": 227, "value": 8}, {"source": 765, "target": 228, "value": 8}, {"source": 765, "target": 275, "value": 8}, {"source": 765, "target": 278, "value": 8}, {"source": 765, "target": 291, "value": 8}, {"source": 765, "target": 293, "value": 10}, {"source": 765, "target": 315, "value": 4}, {"source": 765, "target": 317, "value": 10}, {"source": 765, "target": 321, "value": 8}, {"source": 765, "target": 341, "value": 8}, {"source": 765, "target": 353, "value": 9}, {"source": 765, "target": 365, "value": 1}, {"source": 765, "target": 375, "value": 8}, {"source": 765, "target": 377, "value": 8}, {"source": 765, "target": 378, "value": 8}, {"source": 765, "target": 405, "value": 10}, {"source": 765, "target": 413, "value": 8}, {"source": 765, "target": 425, "value": 8}, {"source": 765, "target": 428, "value": 8}, {"source": 765, "target": 441, "value": 8}, {"source": 765, "target": 453, "value": 4}, {"source": 765, "target": 461, "value": 8}, {"source": 765, "target": 465, "value": 2}, {"source": 765, "target": 491, "value": 8}, {"source": 765, "target": 513, "value": 8}, {"source": 765, "target": 515, "value": 4}, {"source": 765, "target": 525, "value": 8}, {"source": 765, "target": 527, "value": 8}, {"source": 765, "target": 528, "value": 8}, {"source": 765, "target": 563, "value": 8}, {"source": 765, "target": 573, "value": 8}, {"source": 765, "target": 575, "value": 8}, {"source": 765, "target": 578, "value": 8}, {"source": 765, "target": 591, "value": 8}, {"source": 765, "target": 605, "value": 9}, {"source": 765, "target": 615, "value": 4}, {"source": 765, "target": 641, "value": 8}, {"source": 765, "target": 653, "value": 8}, {"source": 765, "target": 665, "value": 2}, {"source": 765, "target": 671, "value": 8}, {"source": 765, "target": 675, "value": 8}, {"source": 765, "target": 677, "value": 7}, {"source": 765, "target": 678, "value": 8}, {"source": 765, "target": 713, "value": 4}, {"source": 765, "target": 725, "value": 8}, {"source": 765, "target": 728, "value": 8}, {"source": 765, "target": 741, "value": 8}, {"source": 765, "target": 753, "value": 7}, {"source": 767, "target": 225, "value": 8}, {"source": 767, "target": 228, "value": 8}, {"source": 767, "target": 243, "value": 9}, {"source": 767, "target": 255, "value": 10}, {"source": 767, "target": 275, "value": 8}, {"source": 767, "target": 278, "value": 8}, {"source": 767, "target": 293, "value": 4}, {"source": 767, "target": 305, "value": 9}, {"source": 767, "target": 317, "value": 4}, {"source": 767, "target": 321, "value": 8}, {"source": 767, "target": 363, "value": 8}, {"source": 767, "target": 365, "value": 9}, {"source": 767, "target": 375, "value": 8}, {"source": 767, "target": 378, "value": 8}, {"source": 767, "target": 393, "value": 4}, {"source": 767, "target": 405, "value": 8}, {"source": 767, "target": 413, "value": 8}, {"source": 767, "target": 425, "value": 8}, {"source": 767, "target": 428, "value": 8}, {"source": 767, "target": 443, "value": 9}, {"source": 767, "target": 467, "value": 4}, {"source": 767, "target": 489, "value": 10}, {"source": 767, "target": 513, "value": 8}, {"source": 767, "target": 525, "value": 8}, {"source": 767, "target": 528, "value": 8}, {"source": 767, "target": 543, "value": 8}, {"source": 767, "target": 557, "value": 9}, {"source": 767, "target": 563, "value": 8}, {"source": 767, "target": 575, "value": 8}, {"source": 767, "target": 578, "value": 8}, {"source": 767, "target": 593, "value": 4}, {"source": 767, "target": 605, "value": 8}, {"source": 767, "target": 617, "value": 4}, {"source": 767, "target": 671, "value": 8}, {"source": 767, "target": 675, "value": 8}, {"source": 767, "target": 678, "value": 8}, {"source": 767, "target": 693, "value": 4}, {"source": 767, "target": 705, "value": 7}, {"source": 767, "target": 713, "value": 4}, {"source": 767, "target": 725, "value": 8}, {"source": 767, "target": 728, "value": 8}, {"source": 767, "target": 743, "value": 8}, {"source": 767, "target": 765, "value": 7}, {"source": 771, "target": 221, "value": 4}, {"source": 771, "target": 261, "value": 11}, {"source": 771, "target": 293, "value": 12}, {"source": 771, "target": 309, "value": 9}, {"source": 771, "target": 321, "value": 4}, {"source": 771, "target": 371, "value": 4}, {"source": 771, "target": 461, "value": 10}, {"source": 771, "target": 471, "value": 4}, {"source": 771, "target": 521, "value": 4}, {"source": 771, "target": 573, "value": 9}, {"source": 771, "target": 609, "value": 8}, {"source": 771, "target": 621, "value": 4}, {"source": 771, "target": 663, "value": 9}, {"source": 771, "target": 671, "value": 4}, {"source": 773, "target": 219, "value": 8}, {"source": 773, "target": 221, "value": 2}, {"source": 773, "target": 233, "value": 8}, {"source": 773, "target": 248, "value": 8}, {"source": 773, "target": 257, "value": 8}, {"source": 773, "target": 261, "value": 9}, {"source": 773, "target": 273, "value": 1}, {"source": 773, "target": 279, "value": 8}, {"source": 773, "target": 285, "value": 8}, {"source": 773, "target": 309, "value": 9}, {"source": 773, "target": 321, "value": 4}, {"source": 773, "target": 323, "value": 1}, {"source": 773, "target": 333, "value": 8}, {"source": 773, "target": 335, "value": 8}, {"source": 773, "target": 347, "value": 4}, {"source": 773, "target": 348, "value": 8}, {"source": 773, "target": 369, "value": 8}, {"source": 773, "target": 377, "value": 10}, {"source": 773, "target": 383, "value": 8}, {"source": 773, "target": 398, "value": 8}, {"source": 773, "target": 413, "value": 10}, {"source": 773, "target": 423, "value": 1}, {"source": 773, "target": 435, "value": 8}, {"source": 773, "target": 461, "value": 8}, {"source": 773, "target": 473, "value": 1}, {"source": 773, "target": 483, "value": 8}, {"source": 773, "target": 485, "value": 8}, {"source": 773, "target": 497, "value": 4}, {"source": 773, "target": 498, "value": 8}, {"source": 773, "target": 519, "value": 8}, {"source": 773, "target": 521, "value": 8}, {"source": 773, "target": 525, "value": 9}, {"source": 773, "target": 531, "value": 8}, {"source": 773, "target": 533, "value": 8}, {"source": 773, "target": 545, "value": 10}, {"source": 773, "target": 548, "value": 8}, {"source": 773, "target": 557, "value": 8}, {"source": 773, "target": 561, "value": 8}, {"source": 773, "target": 573, "value": 0}, {"source": 773, "target": 585, "value": 8}, {"source": 773, "target": 611, "value": 8}, {"source": 773, "target": 615, "value": 8}, {"source": 773, "target": 621, "value": 2}, {"source": 773, "target": 623, "value": 1}, {"source": 773, "target": 633, "value": 8}, {"source": 773, "target": 635, "value": 8}, {"source": 773, "target": 647, "value": 4}, {"source": 773, "target": 648, "value": 8}, {"source": 773, "target": 669, "value": 8}, {"source": 773, "target": 678, "value": 8}, {"source": 773, "target": 683, "value": 8}, {"source": 773, "target": 698, "value": 7}, {"source": 773, "target": 699, "value": 4}, {"source": 773, "target": 723, "value": 1}, {"source": 773, "target": 725, "value": 8}, {"source": 773, "target": 735, "value": 8}, {"source": 773, "target": 761, "value": 4}, {"source": 783, "target": 221, "value": 4}, {"source": 783, "target": 225, "value": 9}, {"source": 783, "target": 231, "value": 8}, {"source": 783, "target": 233, "value": 1}, {"source": 783, "target": 245, "value": 8}, {"source": 783, "target": 248, "value": 8}, {"source": 783, "target": 257, "value": 8}, {"source": 783, "target": 279, "value": 4}, {"source": 783, "target": 281, "value": 8}, {"source": 783, "target": 285, "value": 10}, {"source": 783, "target": 287, "value": 8}, {"source": 783, "target": 317, "value": 8}, {"source": 783, "target": 321, "value": 4}, {"source": 783, "target": 333, "value": 1}, {"source": 783, "target": 335, "value": 10}, {"source": 783, "target": 345, "value": 8}, {"source": 783, "target": 348, "value": 8}, {"source": 783, "target": 381, "value": 4}, {"source": 783, "target": 383, "value": 2}, {"source": 783, "target": 395, "value": 8}, {"source": 783, "target": 398, "value": 8}, {"source": 783, "target": 405, "value": 11}, {"source": 783, "target": 407, "value": 8}, {"source": 783, "target": 425, "value": 8}, {"source": 783, "target": 429, "value": 4}, {"source": 783, "target": 431, "value": 8}, {"source": 783, "target": 437, "value": 8}, {"source": 783, "target": 483, "value": 2}, {"source": 783, "target": 485, "value": 9}, {"source": 783, "target": 495, "value": 8}, {"source": 783, "target": 498, "value": 8}, {"source": 783, "target": 521, "value": 8}, {"source": 783, "target": 525, "value": 8}, {"source": 783, "target": 531, "value": 8}, {"source": 783, "target": 533, "value": 1}, {"source": 783, "target": 545, "value": 8}, {"source": 783, "target": 548, "value": 8}, {"source": 783, "target": 557, "value": 4}, {"source": 783, "target": 573, "value": 9}, {"source": 783, "target": 579, "value": 8}, {"source": 783, "target": 581, "value": 8}, {"source": 783, "target": 587, "value": 8}, {"source": 783, "target": 605, "value": 8}, {"source": 783, "target": 617, "value": 8}, {"source": 783, "target": 621, "value": 8}, {"source": 783, "target": 633, "value": 1}, {"source": 783, "target": 645, "value": 8}, {"source": 783, "target": 648, "value": 8}, {"source": 783, "target": 669, "value": 8}, {"source": 783, "target": 677, "value": 8}, {"source": 783, "target": 681, "value": 8}, {"source": 783, "target": 683, "value": 2}, {"source": 783, "target": 695, "value": 8}, {"source": 783, "target": 698, "value": 8}, {"source": 783, "target": 707, "value": 8}, {"source": 783, "target": 725, "value": 8}, {"source": 783, "target": 729, "value": 8}, {"source": 783, "target": 731, "value": 7}, {"source": 783, "target": 737, "value": 7}, {"source": 783, "target": 741, "value": 8}, {"source": 783, "target": 773, "value": 7}, {"source": 785, "target": 273, "value": 9}, {"source": 785, "target": 285, "value": 2}, {"source": 785, "target": 293, "value": 8}, {"source": 785, "target": 335, "value": 4}, {"source": 785, "target": 347, "value": 8}, {"source": 785, "target": 377, "value": 10}, {"source": 785, "target": 435, "value": 4}, {"source": 785, "target": 437, "value": 9}, {"source": 785, "target": 473, "value": 8}, {"source": 785, "target": 485, "value": 2}, {"source": 785, "target": 497, "value": 8}, {"source": 785, "target": 525, "value": 9}, {"source": 785, "target": 531, "value": 8}, {"source": 785, "target": 573, "value": 8}, {"source": 785, "target": 585, "value": 2}, {"source": 785, "target": 587, "value": 9}, {"source": 785, "target": 635, "value": 4}, {"source": 785, "target": 647, "value": 8}, {"source": 785, "target": 725, "value": 8}, {"source": 785, "target": 735, "value": 3}, {"source": 785, "target": 773, "value": 7}, {"source": 789, "target": 228, "value": 8}, {"source": 789, "target": 243, "value": 12}, {"source": 789, "target": 249, "value": 8}, {"source": 789, "target": 251, "value": 8}, {"source": 789, "target": 278, "value": 8}, {"source": 789, "target": 293, "value": 12}, {"source": 789, "target": 303, "value": 10}, {"source": 789, "target": 335, "value": 8}, {"source": 789, "target": 339, "value": 4}, {"source": 789, "target": 341, "value": 10}, {"source": 789, "target": 351, "value": 8}, {"source": 789, "target": 353, "value": 10}, {"source": 789, "target": 377, "value": 8}, {"source": 789, "target": 378, "value": 8}, {"source": 789, "target": 393, "value": 11}, {"source": 789, "target": 399, "value": 8}, {"source": 789, "target": 401, "value": 8}, {"source": 789, "target": 413, "value": 8}, {"source": 789, "target": 428, "value": 8}, {"source": 789, "target": 429, "value": 10}, {"source": 789, "target": 437, "value": 8}, {"source": 789, "target": 443, "value": 10}, {"source": 789, "target": 453, "value": 9}, {"source": 789, "target": 461, "value": 10}, {"source": 789, "target": 489, "value": 2}, {"source": 789, "target": 501, "value": 8}, {"source": 789, "target": 503, "value": 9}, {"source": 789, "target": 528, "value": 8}, {"source": 789, "target": 543, "value": 10}, {"source": 789, "target": 545, "value": 8}, {"source": 789, "target": 549, "value": 8}, {"source": 789, "target": 551, "value": 8}, {"source": 789, "target": 578, "value": 8}, {"source": 789, "target": 593, "value": 9}, {"source": 789, "target": 603, "value": 8}, {"source": 789, "target": 639, "value": 4}, {"source": 789, "target": 651, "value": 8}, {"source": 789, "target": 653, "value": 8}, {"source": 789, "target": 677, "value": 8}, {"source": 789, "target": 678, "value": 7}, {"source": 789, "target": 693, "value": 9}, {"source": 789, "target": 699, "value": 8}, {"source": 789, "target": 701, "value": 7}, {"source": 789, "target": 728, "value": 7}, {"source": 789, "target": 737, "value": 7}, {"source": 789, "target": 741, "value": 2}, {"source": 789, "target": 743, "value": 8}, {"source": 789, "target": 753, "value": 8}, {"source": 791, "target": 291, "value": 4}, {"source": 791, "target": 293, "value": 10}, {"source": 791, "target": 341, "value": 4}, {"source": 791, "target": 365, "value": 8}, {"source": 791, "target": 381, "value": 10}, {"source": 791, "target": 429, "value": 8}, {"source": 791, "target": 441, "value": 4}, {"source": 791, "target": 491, "value": 4}, {"source": 791, "target": 533, "value": 9}, {"source": 791, "target": 581, "value": 9}, {"source": 791, "target": 587, "value": 8}, {"source": 791, "target": 591, "value": 4}, {"source": 791, "target": 641, "value": 4}, {"source": 791, "target": 713, "value": 9}, {"source": 791, "target": 729, "value": 7}, {"source": 791, "target": 741, "value": 3}, {"source": 791, "target": 765, "value": 8}, {"source": 795, "target": 233, "value": 4}, {"source": 795, "target": 245, "value": 4}, {"source": 795, "target": 248, "value": 8}, {"source": 795, "target": 285, "value": 10}, {"source": 795, "target": 333, "value": 9}, {"source": 795, "target": 345, "value": 4}, {"source": 795, "target": 348, "value": 8}, {"source": 795, "target": 395, "value": 4}, {"source": 795, "target": 398, "value": 8}, {"source": 795, "target": 485, "value": 9}, {"source": 795, "target": 495, "value": 4}, {"source": 795, "target": 498, "value": 8}, {"source": 795, "target": 533, "value": 8}, {"source": 795, "target": 545, "value": 4}, {"source": 795, "target": 548, "value": 8}, {"source": 795, "target": 633, "value": 8}, {"source": 795, "target": 645, "value": 4}, {"source": 795, "target": 648, "value": 7}, {"source": 795, "target": 669, "value": 8}, {"source": 795, "target": 695, "value": 4}, {"source": 795, "target": 698, "value": 7}, {"source": 795, "target": 741, "value": 7}, {"source": 795, "target": 783, "value": 8}, {"source": 797, "target": 221, "value": 9}, {"source": 797, "target": 225, "value": 8}, {"source": 797, "target": 245, "value": 4}, {"source": 797, "target": 251, "value": 8}, {"source": 797, "target": 257, "value": 8}, {"source": 797, "target": 263, "value": 12}, {"source": 797, "target": 273, "value": 3}, {"source": 797, "target": 275, "value": 8}, {"source": 797, "target": 285, "value": 9}, {"source": 797, "target": 293, "value": 8}, {"source": 797, "target": 309, "value": 8}, {"source": 797, "target": 323, "value": 4}, {"source": 797, "target": 341, "value": 10}, {"source": 797, "target": 345, "value": 4}, {"source": 797, "target": 347, "value": 4}, {"source": 797, "target": 351, "value": 4}, {"source": 797, "target": 363, "value": 11}, {"source": 797, "target": 375, "value": 8}, {"source": 797, "target": 377, "value": 9}, {"source": 797, "target": 395, "value": 8}, {"source": 797, "target": 401, "value": 8}, {"source": 797, "target": 405, "value": 8}, {"source": 797, "target": 407, "value": 8}, {"source": 797, "target": 413, "value": 10}, {"source": 797, "target": 423, "value": 4}, {"source": 797, "target": 425, "value": 8}, {"source": 797, "target": 437, "value": 10}, {"source": 797, "target": 459, "value": 8}, {"source": 797, "target": 473, "value": 2}, {"source": 797, "target": 485, "value": 8}, {"source": 797, "target": 489, "value": 8}, {"source": 797, "target": 495, "value": 8}, {"source": 797, "target": 497, "value": 2}, {"source": 797, "target": 501, "value": 8}, {"source": 797, "target": 503, "value": 8}, {"source": 797, "target": 513, "value": 10}, {"source": 797, "target": 525, "value": 8}, {"source": 797, "target": 545, "value": 2}, {"source": 797, "target": 549, "value": 9}, {"source": 797, "target": 551, "value": 8}, {"source": 797, "target": 557, "value": 8}, {"source": 797, "target": 563, "value": 9}, {"source": 797, "target": 573, "value": 2}, {"source": 797, "target": 575, "value": 8}, {"source": 797, "target": 585, "value": 4}, {"source": 797, "target": 609, "value": 8}, {"source": 797, "target": 621, "value": 8}, {"source": 797, "target": 623, "value": 4}, {"source": 797, "target": 645, "value": 4}, {"source": 797, "target": 647, "value": 4}, {"source": 797, "target": 651, "value": 8}, {"source": 797, "target": 663, "value": 9}, {"source": 797, "target": 669, "value": 7}, {"source": 797, "target": 675, "value": 8}, {"source": 797, "target": 695, "value": 4}, {"source": 797, "target": 699, "value": 8}, {"source": 797, "target": 701, "value": 8}, {"source": 797, "target": 707, "value": 8}, {"source": 797, "target": 713, "value": 8}, {"source": 797, "target": 723, "value": 4}, {"source": 797, "target": 725, "value": 8}, {"source": 797, "target": 741, "value": 8}, {"source": 797, "target": 755, "value": 7}, {"source": 797, "target": 759, "value": 8}, {"source": 797, "target": 773, "value": 2}, {"source": 797, "target": 785, "value": 7}, {"source": 797, "target": 789, "value": 7}, {"source": 797, "target": 795, "value": 7}, {"source": 798, "target": 219, "value": 8}, {"source": 798, "target": 233, "value": 5}, {"source": 798, "target": 245, "value": 8}, {"source": 798, "target": 248, "value": 4}, {"source": 798, "target": 257, "value": 8}, {"source": 798, "target": 273, "value": 11}, {"source": 798, "target": 285, "value": 11}, {"source": 798, "target": 309, "value": 10}, {"source": 798, "target": 323, "value": 11}, {"source": 798, "target": 333, "value": 9}, {"source": 798, "target": 345, "value": 8}, {"source": 798, "target": 348, "value": 4}, {"source": 798, "target": 369, "value": 8}, {"source": 798, "target": 395, "value": 8}, {"source": 798, "target": 398, "value": 4}, {"source": 798, "target": 423, "value": 10}, {"source": 798, "target": 473, "value": 10}, {"source": 798, "target": 485, "value": 10}, {"source": 798, "target": 495, "value": 8}, {"source": 798, "target": 498, "value": 4}, {"source": 798, "target": 519, "value": 8}, {"source": 798, "target": 533, "value": 8}, {"source": 798, "target": 545, "value": 8}, {"source": 798, "target": 548, "value": 4}, {"source": 798, "target": 557, "value": 8}, {"source": 798, "target": 573, "value": 9}, {"source": 798, "target": 611, "value": 9}, {"source": 798, "target": 615, "value": 8}, {"source": 798, "target": 623, "value": 9}, {"source": 798, "target": 633, "value": 8}, {"source": 798, "target": 645, "value": 8}, {"source": 798, "target": 648, "value": 4}, {"source": 798, "target": 669, "value": 8}, {"source": 798, "target": 695, "value": 8}, {"source": 798, "target": 698, "value": 4}, {"source": 798, "target": 723, "value": 8}, {"source": 798, "target": 741, "value": 7}, {"source": 798, "target": 773, "value": 8}, {"source": 798, "target": 783, "value": 8}, {"source": 798, "target": 795, "value": 8}, {"source": 801, "target": 251, "value": 4}, {"source": 801, "target": 263, "value": 8}, {"source": 801, "target": 341, "value": 10}, {"source": 801, "target": 351, "value": 4}, {"source": 801, "target": 363, "value": 8}, {"source": 801, "target": 401, "value": 2}, {"source": 801, "target": 413, "value": 8}, {"source": 801, "target": 453, "value": 9}, {"source": 801, "target": 455, "value": 10}, {"source": 801, "target": 489, "value": 8}, {"source": 801, "target": 501, "value": 2}, {"source": 801, "target": 503, "value": 9}, {"source": 801, "target": 513, "value": 8}, {"source": 801, "target": 525, "value": 8}, {"source": 801, "target": 551, "value": 4}, {"source": 801, "target": 563, "value": 8}, {"source": 801, "target": 573, "value": 10}, {"source": 801, "target": 651, "value": 2}, {"source": 801, "target": 653, "value": 8}, {"source": 801, "target": 663, "value": 8}, {"source": 801, "target": 701, "value": 2}, {"source": 801, "target": 713, "value": 7}, {"source": 801, "target": 741, "value": 8}, {"source": 801, "target": 789, "value": 7}, {"source": 801, "target": 797, "value": 8}, {"source": 803, "target": 227, "value": 8}, {"source": 803, "target": 228, "value": 8}, {"source": 803, "target": 249, "value": 8}, {"source": 803, "target": 278, "value": 8}, {"source": 803, "target": 279, "value": 10}, {"source": 803, "target": 293, "value": 10}, {"source": 803, "target": 303, "value": 2}, {"source": 803, "target": 341, "value": 9}, {"source": 803, "target": 353, "value": 1}, {"source": 803, "target": 377, "value": 8}, {"source": 803, "target": 378, "value": 8}, {"source": 803, "target": 399, "value": 8}, {"source": 803, "target": 405, "value": 10}, {"source": 803, "target": 428, "value": 8}, {"source": 803, "target": 437, "value": 8}, {"source": 803, "target": 441, "value": 8}, {"source": 803, "target": 453, "value": 1}, {"source": 803, "target": 503, "value": 2}, {"source": 803, "target": 527, "value": 8}, {"source": 803, "target": 528, "value": 8}, {"source": 803, "target": 545, "value": 8}, {"source": 803, "target": 549, "value": 8}, {"source": 803, "target": 578, "value": 8}, {"source": 803, "target": 603, "value": 2}, {"source": 803, "target": 641, "value": 8}, {"source": 803, "target": 653, "value": 1}, {"source": 803, "target": 677, "value": 8}, {"source": 803, "target": 678, "value": 8}, {"source": 803, "target": 693, "value": 8}, {"source": 803, "target": 699, "value": 4}, {"source": 803, "target": 701, "value": 8}, {"source": 803, "target": 728, "value": 7}, {"source": 803, "target": 737, "value": 8}, {"source": 803, "target": 741, "value": 3}, {"source": 803, "target": 753, "value": 1}, {"source": 803, "target": 755, "value": 8}, {"source": 803, "target": 789, "value": 7}]}
\ No newline at end of file
diff --git a/galaxy/templates/galaxy/user.jinja b/galaxy/templates/galaxy/user.jinja
index 1c6e4c7e7..38abb6de0 100644
--- a/galaxy/templates/galaxy/user.jinja
+++ b/galaxy/templates/galaxy/user.jinja
@@ -1,37 +1,42 @@
{% extends "core/base.jinja" %}
{% block title %}
-{% trans user_name=user.get_display_name() %}{{ user_name }}'s Galaxy{% endtrans %}
+{% trans user_name=object.get_display_name() %}{{ user_name }}'s Galaxy{% endtrans %}
{% endblock %}
{% block content %}
-{% if object.galaxy_user %}
- Reset on {{ object.get_display_name() }}
- Self score: {{ object.galaxy_user.mass }}
-
-
- |
- Citizen |
- Score |
- Distance |
- Family |
- Pictures |
- Clubs |
-
- {% for lane in lanes %}
-
- Locate |
- {{ lane.other_star_name }} |
- {{ lane.other_star_mass }} |
- {{ lane.distance }} |
- {{ lane.family }} |
- {{ lane.pictures }} |
- {{ lane.clubs }} |
-
- {% endfor %}
-
-
-
+{% if object.current_star %}
+
+#{{ object.current_star.galaxy }}#
{% else %}
This citizen has not yet joined the galaxy
{% endif %}
@@ -53,9 +58,31 @@
return Graph.graphData().nodes.find(n => n.id === id);
}
+ function get_links_from_node_id(id) {
+ return Graph.graphData().links.filter(l => l.source.id === id || l.target.id === id);
+ }
+
function focus_node(node) {
+ highlightNodes.clear();
+ highlightLinks.clear();
+
+ hoverNode = node || null;
+ if (node) { // collect neighbors and links for highlighting
+ get_links_from_node_id(node.id).forEach(link => {
+ highlightLinks.add(link);
+ highlightNodes.add(link.source);
+ highlightNodes.add(link.target);
+ });
+ }
+
+ // refresh node and link display
+ Graph
+ .nodeThreeObject(Graph.nodeThreeObject())
+ .linkWidth(Graph.linkWidth())
+ .linkDirectionalParticles(Graph.linkDirectionalParticles());
+
// Aim at node from outside it
- const distance = 200;
+ const distance = 42;
const distRatio = 1 + distance/Math.hypot(node.x, node.y, node.z);
const newPos = node.x || node.y || node.z
@@ -69,25 +96,44 @@
);
}
+ const highlightNodes = new Set();
+ const highlightLinks = new Set();
+ let hoverNode = null;
+
document.addEventListener("DOMContentLoaded", () => {
+ var graph_div = document.getElementById('3d-graph');
Graph = ForceGraph3D();
- Graph(document.getElementById('3d-graph'));
+ Graph(graph_div);
Graph
.jsonUrl('{{ url("galaxy:data") }}')
- .width(1000)
- .height(700)
- .nodeAutoColorBy('id')
- .nodeLabel(node => `${node.name}`)
- .onNodeClick(node => focus_node(node))
- .linkDirectionalParticles(3)
- .linkDirectionalParticleWidth(0.8)
- .linkDirectionalParticleSpeed(0.006)
+ .width(graph_div.parentElement.clientWidth > 1200 ? 1200 : graph_div.parentElement.clientWidth) // Not perfect at all. JS-fu master from the future, please fix this :-)
+ .height(1000)
+ .enableNodeDrag(false) // allow easier navigation
+ .onNodeClick(node => {
+ camera = Graph.cameraPosition();
+ var distance = Math.sqrt(Math.pow(node.x - camera.x, 2) + Math.pow(node.y - camera.y, 2) + Math.pow(node.z - camera.z, 2))
+ if (distance < 120 || highlightNodes.has(node)) {
+ focus_node(node);
+ }
+ })
+ .linkWidth(link => highlightLinks.has(link) ? 0.4 : 0.0)
+ .linkColor(link => highlightLinks.has(link) ? 'rgba(255,160,0,1)' : 'rgba(128,255,255,0.6)')
+ .linkVisibility(link => highlightLinks.has(link))
+ .nodeVisibility(node => highlightNodes.has(node) || node.mass > 4)
+ // .linkDirectionalParticles(link => highlightLinks.has(link) ? 3 : 1) // kinda buggy for now, and slows this a bit, but would be great to help visualize lanes
+ .linkDirectionalParticleWidth(0.2)
+ .linkDirectionalParticleSpeed(-0.006)
.nodeThreeObject(node => {
const sprite = new SpriteText(node.name);
sprite.material.depthWrite = false; // make sprite background transparent
- sprite.color = node.color;
- sprite.textHeight = 5;
+ sprite.color = highlightNodes.has(node) ? node === hoverNode ? 'rgba(200,0,0,1)' : 'rgba(255,160,0,0.8)' : 'rgba(0,255,255,0.2)';
+ sprite.textHeight = 2;
+ sprite.center = new THREE.Vector2(1.2, 0.5);
return sprite;
+ })
+ .onEngineStop( () => {
+ focus_node(get_node_from_id({{ object.id }}));
+ Graph.onEngineStop(() => {}); // don't call ourselves in a loop while moving the focus
});
// Set distance between stars
@@ -98,9 +144,6 @@
Graph.d3Force('positionX', d3.forceX().strength(node => { return 1 - (1 / node.mass); }));
Graph.d3Force('positionY', d3.forceY().strength(node => { return 1 - (1 / node.mass); }));
Graph.d3Force('positionZ', d3.forceZ().strength(node => { return 1 - (1 / node.mass); }));
-
- // Focus current user
- setTimeout(() => focus_node(get_node_from_id({{ object.id }})), 1000);
})
{% endblock %}
diff --git a/galaxy/tests.py b/galaxy/tests.py
index c30ec8cfe..703145747 100644
--- a/galaxy/tests.py
+++ b/galaxy/tests.py
@@ -22,14 +22,19 @@
#
#
-from django.test import TestCase
+import json
+
+from pathlib import Path
+
from django.core.management import call_command
+from django.test import TestCase
+from django.urls import reverse
from core.models import User
from galaxy.models import Galaxy
-class GalaxyTest(TestCase):
+class GalaxyTestModel(TestCase):
def setUp(self):
self.root = User.objects.get(username="root")
self.skia = User.objects.get(username="skia")
@@ -41,6 +46,9 @@ def setUp(self):
self.com = User.objects.get(username="comunity")
def test_user_self_score(self):
+ """
+ Test that individual user scores are correct
+ """
with self.assertNumQueries(8):
self.assertEqual(Galaxy.compute_user_score(self.root), 9)
self.assertEqual(Galaxy.compute_user_score(self.skia), 10)
@@ -52,6 +60,10 @@ def test_user_self_score(self):
self.assertEqual(Galaxy.compute_user_score(self.com), 1)
def test_users_score(self):
+ """
+ Test on the default dataset generated by the `populate` command
+ that the relation scores are correct
+ """
expected_scores = {
"krophil": {
"comunity": {"clubs": 0, "family": 0, "pictures": 0, "score": 0},
@@ -112,33 +124,78 @@ def test_users_score(self):
while len(users) > 0:
user1 = users.pop(0)
for user2 in users:
- score, family, pictures, clubs = Galaxy.compute_users_score(
- user1, user2
- )
+ score = Galaxy.compute_users_score(user1, user2)
u1 = computed_scores.get(user1.username, {})
u1[user2.username] = {
- "score": score,
- "family": family,
- "pictures": pictures,
- "clubs": clubs,
+ "score": sum(score),
+ "family": score.family,
+ "pictures": score.pictures,
+ "clubs": score.clubs,
}
computed_scores[user1.username] = u1
self.maxDiff = None # Yes, we want to see the diff if any
self.assertDictEqual(expected_scores, computed_scores)
+ def test_rule(self):
+ """
+ Test on the default dataset generated by the `populate` command
+ that the number of queries to rule the galaxy is stable.
+ """
+ galaxy = Galaxy.objects.create()
+ with self.assertNumQueries(58):
+ galaxy.rule(0) # We want everybody here
+
+
+class GalaxyTestView(TestCase):
+ @classmethod
+ def setUpTestData(cls):
+ """
+ Generate a plausible Galaxy once for every test
+ """
+ call_command("generate_galaxy_test_data", "-v", "0")
+ galaxy = Galaxy.objects.create()
+ galaxy.rule(26) # We want a fast test
+
def test_page_is_citizen(self):
- Galaxy.rule()
+ """
+ Test that users can access the galaxy page of users who are citizens
+ """
self.client.login(username="root", password="plop")
- response = self.client.get("/galaxy/1/")
+ user = User.objects.get(last_name="n°500")
+ response = self.client.get(reverse("galaxy:user", args=[user.id]))
self.assertContains(
response,
- 'Locate',
+ f'Reset on {user}',
status_code=200,
)
def test_page_not_citizen(self):
- Galaxy.rule()
+ """
+ Test that trying to access the galaxy page of a user who is not
+ citizens return a 404
+ """
self.client.login(username="root", password="plop")
- response = self.client.get("/galaxy/2/")
+ user = User.objects.get(last_name="n°1")
+ response = self.client.get(reverse("galaxy:user", args=[user.id]))
self.assertEquals(response.status_code, 404)
+
+ def test_full_galaxy_state(self):
+ """
+ Test on the more complex dataset generated by the `generate_galaxy_test_data`
+ command that the relation scores are correct, and that the view exposes the
+ right data.
+ """
+ self.client.login(username="root", password="plop")
+ response = self.client.get(reverse("galaxy:data"))
+ state = response.json()
+
+ galaxy_dir = Path(__file__).parent
+
+ # Dump computed state, either for easier debugging, or to copy as new reference if changes are legit
+ (galaxy_dir / "test_galaxy_state.json").write_text(json.dumps(state))
+
+ self.assertEqual(
+ state,
+ json.loads((galaxy_dir / "ref_galaxy_state.json").read_text()),
+ )
diff --git a/galaxy/views.py b/galaxy/views.py
index 472285058..3dda003e1 100644
--- a/galaxy/views.py
+++ b/galaxy/views.py
@@ -45,32 +45,29 @@ class GalaxyUserView(CanViewMixin, UserTabsMixin, DetailView):
def get_object(self, *args, **kwargs):
user: User = super(GalaxyUserView, self).get_object(*args, **kwargs)
- if not hasattr(user, "galaxy_user"):
+ if user.current_star is None:
raise Http404(_("This citizen has not yet joined the galaxy"))
return user
- def get_queryset(self):
- return super(GalaxyUserView, self).get_queryset().select_related("galaxy_user")
-
def get_context_data(self, **kwargs):
kwargs = super(GalaxyUserView, self).get_context_data(**kwargs)
kwargs["lanes"] = (
GalaxyLane.objects.filter(
- Q(star1=self.object.galaxy_user) | Q(star2=self.object.galaxy_user)
+ Q(star1=self.object.current_star) | Q(star2=self.object.current_star)
)
.order_by("distance")
.annotate(
other_star_id=Case(
- When(star1=self.object.galaxy_user, then=F("star2__owner__id")),
+ When(star1=self.object.current_star, then=F("star2__owner__id")),
default=F("star1__owner__id"),
),
other_star_mass=Case(
- When(star1=self.object.galaxy_user, then=F("star2__mass")),
+ When(star1=self.object.current_star, then=F("star2__mass")),
default=F("star1__mass"),
),
other_star_name=Case(
When(
- star1=self.object.galaxy_user,
+ star1=self.object.current_star,
then=Case(
When(
star2__owner__nick_name=None,
@@ -101,4 +98,4 @@ def get_context_data(self, **kwargs):
class GalaxyDataView(FormerSubscriberMixin, View):
def get(self, request, *args, **kwargs):
- return JsonResponse(Galaxy.objects.first().state)
+ return JsonResponse(Galaxy.get_current_galaxy().state)
diff --git a/poetry.lock b/poetry.lock
index debfd0c56..23ef0bdaf 100644
--- a/poetry.lock
+++ b/poetry.lock
@@ -1,10 +1,9 @@
-# This file is automatically @generated by Poetry and should not be changed by hand.
+# This file is automatically @generated by Poetry 1.5.1 and should not be changed by hand.
[[package]]
name = "alabaster"
version = "0.7.12"
description = "A configurable sidebar-enabled Sphinx theme"
-category = "main"
optional = true
python-versions = "*"
files = [
@@ -16,7 +15,6 @@ files = [
name = "appnope"
version = "0.1.3"
description = "Disable App Nap on macOS >= 10.9"
-category = "dev"
optional = false
python-versions = "*"
files = [
@@ -28,7 +26,6 @@ files = [
name = "asgiref"
version = "3.6.0"
description = "ASGI specs, helper code, and adapters"
-category = "main"
optional = false
python-versions = ">=3.7"
files = [
@@ -43,7 +40,6 @@ tests = ["mypy (>=0.800)", "pytest", "pytest-asyncio"]
name = "babel"
version = "2.11.0"
description = "Internationalization utilities"
-category = "main"
optional = true
python-versions = ">=3.6"
files = [
@@ -58,7 +54,6 @@ pytz = ">=2015.7"
name = "backcall"
version = "0.2.0"
description = "Specifications for callback functions passed in to an API"
-category = "dev"
optional = false
python-versions = "*"
files = [
@@ -70,7 +65,6 @@ files = [
name = "black"
version = "23.3.0"
description = "The uncompromising code formatter."
-category = "dev"
optional = false
python-versions = ">=3.7"
files = [
@@ -120,7 +114,6 @@ uvloop = ["uvloop (>=0.15.2)"]
name = "certifi"
version = "2022.12.7"
description = "Python package for providing Mozilla's CA Bundle."
-category = "main"
optional = false
python-versions = ">=3.6"
files = [
@@ -132,7 +125,6 @@ files = [
name = "cffi"
version = "1.15.1"
description = "Foreign Function Interface for Python calling C code."
-category = "main"
optional = false
python-versions = "*"
files = [
@@ -209,7 +201,6 @@ pycparser = "*"
name = "charset-normalizer"
version = "2.1.1"
description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet."
-category = "main"
optional = true
python-versions = ">=3.6.0"
files = [
@@ -224,7 +215,6 @@ unicode-backport = ["unicodedata2"]
name = "click"
version = "8.1.3"
description = "Composable command line interface toolkit"
-category = "dev"
optional = false
python-versions = ">=3.7"
files = [
@@ -239,7 +229,6 @@ colorama = {version = "*", markers = "platform_system == \"Windows\""}
name = "colorama"
version = "0.4.6"
description = "Cross-platform colored terminal text."
-category = "main"
optional = false
python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7"
files = [
@@ -251,7 +240,6 @@ files = [
name = "coverage"
version = "5.5"
description = "Code coverage measurement for Python"
-category = "main"
optional = true
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, <4"
files = [
@@ -316,7 +304,6 @@ toml = ["toml"]
name = "cryptography"
version = "40.0.1"
description = "cryptography is a package which provides cryptographic recipes and primitives to Python developers."
-category = "main"
optional = false
python-versions = ">=3.6"
files = [
@@ -358,7 +345,6 @@ tox = ["tox"]
name = "decorator"
version = "5.1.1"
description = "Decorators for Humans"
-category = "dev"
optional = false
python-versions = ">=3.5"
files = [
@@ -370,7 +356,6 @@ files = [
name = "dict2xml"
version = "1.7.3"
description = "Small utility to convert a python dictionary into an XML string"
-category = "main"
optional = false
python-versions = ">=3.5"
files = [
@@ -385,7 +370,6 @@ tests = ["noseofyeti[black] (==2.4.1)", "pytest (==7.2.1)"]
name = "django"
version = "3.2.18"
description = "A high-level Python Web framework that encourages rapid development and clean, pragmatic design."
-category = "main"
optional = false
python-versions = ">=3.6"
files = [
@@ -406,7 +390,6 @@ bcrypt = ["bcrypt"]
name = "django-ajax-selects"
version = "2.2.0"
description = "Edit ForeignKey, ManyToManyField and CharField in Django Admin using jQuery UI AutoComplete."
-category = "main"
optional = false
python-versions = "*"
files = [
@@ -417,7 +400,6 @@ files = [
name = "django-countries"
version = "7.5.1"
description = "Provides a country field for Django models."
-category = "main"
optional = false
python-versions = "*"
files = [
@@ -439,7 +421,6 @@ test = ["djangorestframework", "graphene-django", "pytest", "pytest-cov", "pytes
name = "django-debug-toolbar"
version = "4.0.0"
description = "A configurable set of panels that display various debug information about the current request/response."
-category = "dev"
optional = false
python-versions = ">=3.8"
files = [
@@ -455,7 +436,6 @@ sqlparse = ">=0.2"
name = "django-haystack"
version = "3.2.1"
description = "Pluggable search for Django."
-category = "main"
optional = false
python-versions = "*"
files = [
@@ -472,7 +452,6 @@ elasticsearch = ["elasticsearch (>=5,<8)"]
name = "django-jinja"
version = "2.10.2"
description = "Jinja2 templating language integrated in Django."
-category = "main"
optional = false
python-versions = ">=3.6"
files = [
@@ -486,21 +465,19 @@ jinja2 = ">=3"
[[package]]
name = "django-ordered-model"
-version = "3.6"
+version = "3.7.4"
description = "Allows Django models to be ordered and provides a simple admin interface for reordering them."
-category = "main"
optional = false
python-versions = "*"
files = [
- {file = "django-ordered-model-3.6.tar.gz", hash = "sha256:62161a6bc51d8b402644854b257605d7b5183d01fd349826682a87e9227c05b5"},
- {file = "django_ordered_model-3.6-py3-none-any.whl", hash = "sha256:0006b111f472a2348f75554a4e77bee2b1f379a0f96726af6b1a3ebf3a950789"},
+ {file = "django-ordered-model-3.7.4.tar.gz", hash = "sha256:f258b9762525c00a53009e82f8b8bf2a3aa315e8b453e281e8fdbbfe2b8cb3ba"},
+ {file = "django_ordered_model-3.7.4-py3-none-any.whl", hash = "sha256:dfcd3183fe0749dad1c9971cba1d6240ce7328742a30ddc92feca41107bb241d"},
]
[[package]]
name = "django-phonenumber-field"
version = "6.4.0"
description = "An international phone number field for django models."
-category = "main"
optional = false
python-versions = ">=3.7"
files = [
@@ -519,7 +496,6 @@ phonenumberslite = ["phonenumberslite (>=7.0.2)"]
name = "django-ranged-response"
version = "0.2.0"
description = "Modified Django FileResponse that adds Content-Range headers."
-category = "main"
optional = false
python-versions = "*"
files = [
@@ -533,7 +509,6 @@ django = "*"
name = "django-simple-captcha"
version = "0.5.17"
description = "A very simple, yet powerful, Django captcha application"
-category = "main"
optional = false
python-versions = "*"
files = [
@@ -553,7 +528,6 @@ test = ["testfixtures"]
name = "djangorestframework"
version = "3.14.0"
description = "Web APIs for Django, made easy."
-category = "main"
optional = false
python-versions = ">=3.6"
files = [
@@ -569,7 +543,6 @@ pytz = "*"
name = "docutils"
version = "0.17.1"
description = "Docutils -- Python Documentation Utilities"
-category = "main"
optional = true
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*"
files = [
@@ -577,11 +550,24 @@ files = [
{file = "docutils-0.17.1.tar.gz", hash = "sha256:686577d2e4c32380bb50cbb22f575ed742d58168cee37e99117a854bcd88f125"},
]
+[[package]]
+name = "freezegun"
+version = "1.2.2"
+description = "Let your Python tests travel through time"
+optional = false
+python-versions = ">=3.6"
+files = [
+ {file = "freezegun-1.2.2-py3-none-any.whl", hash = "sha256:ea1b963b993cb9ea195adbd893a48d573fda951b0da64f60883d7e988b606c9f"},
+ {file = "freezegun-1.2.2.tar.gz", hash = "sha256:cd22d1ba06941384410cd967d8a99d5ae2442f57dfafeff2fda5de8dc5c05446"},
+]
+
+[package.dependencies]
+python-dateutil = ">=2.7"
+
[[package]]
name = "idna"
version = "3.4"
description = "Internationalized Domain Names in Applications (IDNA)"
-category = "main"
optional = true
python-versions = ">=3.5"
files = [
@@ -593,7 +579,6 @@ files = [
name = "imagesize"
version = "1.4.1"
description = "Getting image size from png/jpeg/jpeg2000/gif file"
-category = "main"
optional = true
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
files = [
@@ -605,7 +590,6 @@ files = [
name = "importlib-metadata"
version = "6.0.0"
description = "Read metadata from Python packages"
-category = "main"
optional = true
python-versions = ">=3.7"
files = [
@@ -625,7 +609,6 @@ testing = ["flake8 (<5)", "flufl.flake8", "importlib-resources (>=1.3)", "packag
name = "ipython"
version = "7.34.0"
description = "IPython: Productive Interactive Computing"
-category = "dev"
optional = false
python-versions = ">=3.7"
files = [
@@ -662,7 +645,6 @@ test = ["ipykernel", "nbformat", "nose (>=0.10.1)", "numpy (>=1.17)", "pygments"
name = "jedi"
version = "0.18.2"
description = "An autocompletion tool for Python that can be used for text editors."
-category = "dev"
optional = false
python-versions = ">=3.6"
files = [
@@ -682,7 +664,6 @@ testing = ["Django (<3.1)", "attrs", "colorama", "docopt", "pytest (<7.0.0)"]
name = "jinja2"
version = "3.1.2"
description = "A very fast and expressive template engine."
-category = "main"
optional = false
python-versions = ">=3.7"
files = [
@@ -698,32 +679,22 @@ i18n = ["Babel (>=2.7)"]
[[package]]
name = "libsass"
-version = "0.21.0"
+version = "0.22.0"
description = "Sass for Python: A straightforward binding of libsass for Python."
-category = "main"
optional = false
-python-versions = "*"
+python-versions = ">=3.6"
files = [
- {file = "libsass-0.21.0-cp27-cp27m-macosx_10_14_x86_64.whl", hash = "sha256:06c8776417fe930714bdc930a3d7e795ae3d72be6ac883ff72a1b8f7c49e5ffb"},
- {file = "libsass-0.21.0-cp27-cp27m-win32.whl", hash = "sha256:a005f298f64624f313a3ac618ab03f844c71d84ae4f4a4aec4b68d2a4ffe75eb"},
- {file = "libsass-0.21.0-cp27-cp27m-win_amd64.whl", hash = "sha256:6b984510ed94993708c0d697b4fef2d118929bbfffc3b90037be0f5ccadf55e7"},
- {file = "libsass-0.21.0-cp27-cp27mu-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:1e25dd9047a9392d3c59a0b869e0404f2b325a03871ee45285ee33b3664f5613"},
- {file = "libsass-0.21.0-cp36-abi3-macosx_10_14_x86_64.whl", hash = "sha256:12f39712de38689a8b785b7db41d3ba2ea1d46f9379d81ea4595802d91fa6529"},
- {file = "libsass-0.21.0-cp36-abi3-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:e2b1a7d093f2e76dc694c17c0c285e846d0b0deb0e8b21dc852ba1a3a4e2f1d6"},
- {file = "libsass-0.21.0-cp36-abi3-win32.whl", hash = "sha256:abc29357ee540849faf1383e1746d40d69ed5cb6d4c346df276b258f5aa8977a"},
- {file = "libsass-0.21.0-cp36-abi3-win_amd64.whl", hash = "sha256:659ae41af8708681fa3ec73f47b9735a6725e71c3b66ff570bfce78952f2314e"},
- {file = "libsass-0.21.0-cp38-abi3-macosx_12_0_arm64.whl", hash = "sha256:c9ec490609752c1d81ff6290da33485aa7cb6d7365ac665b74464c1b7d97f7da"},
- {file = "libsass-0.21.0.tar.gz", hash = "sha256:d5ba529d9ce668be9380563279f3ffe988f27bc5b299c5a28453df2e0b0fbaf2"},
+ {file = "libsass-0.22.0-cp36-abi3-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:f1efc1b612299c88aec9e39d6ca0c266d360daa5b19d9430bdeaffffa86993f9"},
+ {file = "libsass-0.22.0-cp37-abi3-macosx_10_15_x86_64.whl", hash = "sha256:081e256ab3c5f3f09c7b8dea3bf3bf5e64a97c6995fd9eea880639b3f93a9f9a"},
+ {file = "libsass-0.22.0-cp37-abi3-win32.whl", hash = "sha256:89c5ce497fcf3aba1dd1b19aae93b99f68257e5f2026b731b00a872f13324c7f"},
+ {file = "libsass-0.22.0-cp37-abi3-win_amd64.whl", hash = "sha256:65455a2728b696b62100eb5932604aa13a29f4ac9a305d95773c14aaa7200aaf"},
+ {file = "libsass-0.22.0.tar.gz", hash = "sha256:3ab5ad18e47db560f4f0c09e3d28cf3bb1a44711257488ac2adad69f4f7f8425"},
]
-[package.dependencies]
-six = "*"
-
[[package]]
name = "markupsafe"
version = "2.1.1"
description = "Safely add untrusted strings to HTML/XML markup."
-category = "main"
optional = false
python-versions = ">=3.7"
files = [
@@ -773,7 +744,6 @@ files = [
name = "matplotlib-inline"
version = "0.1.6"
description = "Inline Matplotlib backend for Jupyter"
-category = "dev"
optional = false
python-versions = ">=3.5"
files = [
@@ -788,7 +758,6 @@ traitlets = "*"
name = "mistune"
version = "0.8.4"
description = "The fastest markdown parser in pure Python"
-category = "main"
optional = false
python-versions = "*"
files = [
@@ -800,7 +769,6 @@ files = [
name = "mypy-extensions"
version = "0.4.3"
description = "Experimental type system extensions for programs checked with the mypy typechecker."
-category = "dev"
optional = false
python-versions = "*"
files = [
@@ -812,7 +780,6 @@ files = [
name = "packaging"
version = "23.0"
description = "Core utilities for Python packages"
-category = "main"
optional = false
python-versions = ">=3.7"
files = [
@@ -824,7 +791,6 @@ files = [
name = "parso"
version = "0.8.3"
description = "A Python Parser"
-category = "dev"
optional = false
python-versions = ">=3.6"
files = [
@@ -840,7 +806,6 @@ testing = ["docopt", "pytest (<6.0.0)"]
name = "pathspec"
version = "0.10.3"
description = "Utility library for gitignore style pattern matching of file paths."
-category = "dev"
optional = false
python-versions = ">=3.7"
files = [
@@ -852,7 +817,6 @@ files = [
name = "pexpect"
version = "4.8.0"
description = "Pexpect allows easy control of interactive console applications."
-category = "dev"
optional = false
python-versions = "*"
files = [
@@ -867,7 +831,6 @@ ptyprocess = ">=0.5"
name = "phonenumbers"
version = "8.13.4"
description = "Python version of Google's common library for parsing, formatting, storing and validating international phone numbers."
-category = "main"
optional = false
python-versions = "*"
files = [
@@ -879,7 +842,6 @@ files = [
name = "pickleshare"
version = "0.7.5"
description = "Tiny 'shelve'-like database with concurrency support"
-category = "dev"
optional = false
python-versions = "*"
files = [
@@ -891,7 +853,6 @@ files = [
name = "pillow"
version = "9.4.0"
description = "Python Imaging Library (Fork)"
-category = "main"
optional = false
python-versions = ">=3.7"
files = [
@@ -982,7 +943,6 @@ tests = ["check-manifest", "coverage", "defusedxml", "markdown2", "olefile", "pa
name = "platformdirs"
version = "2.6.2"
description = "A small Python package for determining appropriate platform-specific dirs, e.g. a \"user data dir\"."
-category = "dev"
optional = false
python-versions = ">=3.7"
files = [
@@ -998,7 +958,6 @@ test = ["appdirs (==1.4.4)", "covdefaults (>=2.2.2)", "pytest (>=7.2)", "pytest-
name = "prompt-toolkit"
version = "3.0.36"
description = "Library for building powerful interactive command lines in Python"
-category = "dev"
optional = false
python-versions = ">=3.6.2"
files = [
@@ -1013,7 +972,6 @@ wcwidth = "*"
name = "psycopg2-binary"
version = "2.9.3"
description = "psycopg2 - Python-PostgreSQL Database Adapter"
-category = "main"
optional = false
python-versions = ">=3.6"
files = [
@@ -1082,7 +1040,6 @@ files = [
name = "ptyprocess"
version = "0.7.0"
description = "Run a subprocess in a pseudo terminal"
-category = "dev"
optional = false
python-versions = "*"
files = [
@@ -1094,7 +1051,6 @@ files = [
name = "pycparser"
version = "2.21"
description = "C parser in Python"
-category = "main"
optional = false
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
files = [
@@ -1106,7 +1062,6 @@ files = [
name = "pygments"
version = "2.14.0"
description = "Pygments is a syntax highlighting package written in Python."
-category = "main"
optional = false
python-versions = ">=3.6"
files = [
@@ -1121,7 +1076,6 @@ plugins = ["importlib-metadata"]
name = "pygraphviz"
version = "1.10"
description = "Python interface to Graphviz"
-category = "main"
optional = false
python-versions = ">=3.8"
files = [
@@ -1132,7 +1086,6 @@ files = [
name = "pyopenssl"
version = "23.1.1"
description = "Python wrapper module around the OpenSSL library"
-category = "main"
optional = false
python-versions = ">=3.6"
files = [
@@ -1151,7 +1104,6 @@ test = ["flaky", "pretend", "pytest (>=3.0.1)"]
name = "python-dateutil"
version = "2.8.2"
description = "Extensions to the standard Python datetime module"
-category = "main"
optional = false
python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,>=2.7"
files = [
@@ -1166,7 +1118,6 @@ six = ">=1.5"
name = "pytz"
version = "2021.3"
description = "World timezone definitions, modern and historical"
-category = "main"
optional = false
python-versions = "*"
files = [
@@ -1178,7 +1129,6 @@ files = [
name = "reportlab"
version = "3.6.12"
description = "The Reportlab Toolkit"
-category = "main"
optional = false
python-versions = ">=3.7,<4"
files = [
@@ -1240,7 +1190,6 @@ rlpycairo = ["rlPyCairo (>=0.1.0)"]
name = "requests"
version = "2.28.1"
description = "Python HTTP for Humans."
-category = "main"
optional = true
python-versions = ">=3.7, <4"
files = [
@@ -1262,7 +1211,6 @@ use-chardet-on-py3 = ["chardet (>=3.0.2,<6)"]
name = "sentry-sdk"
version = "1.21.0"
description = "Python client for Sentry (https://sentry.io)"
-category = "main"
optional = false
python-versions = "*"
files = [
@@ -1304,7 +1252,6 @@ tornado = ["tornado (>=5)"]
name = "setuptools"
version = "65.6.3"
description = "Easily download, build, install, upgrade, and uninstall Python packages"
-category = "dev"
optional = false
python-versions = ">=3.7"
files = [
@@ -1321,7 +1268,6 @@ testing-integration = ["build[virtualenv]", "filelock (>=3.4.0)", "jaraco.envs (
name = "six"
version = "1.16.0"
description = "Python 2 and 3 compatibility utilities"
-category = "main"
optional = false
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*"
files = [
@@ -1333,7 +1279,6 @@ files = [
name = "snowballstemmer"
version = "2.2.0"
description = "This package provides 29 stemmers for 28 languages generated from Snowball algorithms."
-category = "main"
optional = true
python-versions = "*"
files = [
@@ -1345,7 +1290,6 @@ files = [
name = "sphinx"
version = "4.5.0"
description = "Python documentation generator"
-category = "main"
optional = true
python-versions = ">=3.6"
files = [
@@ -1381,7 +1325,6 @@ test = ["cython", "html5lib", "pytest", "pytest-cov", "typed-ast"]
name = "sphinx-copybutton"
version = "0.4.0"
description = "Add a copy button to each of your code cells."
-category = "main"
optional = true
python-versions = ">=3.6"
files = [
@@ -1400,7 +1343,6 @@ rtd = ["ipython", "sphinx", "sphinx-book-theme"]
name = "sphinx-rtd-theme"
version = "1.1.1"
description = "Read the Docs theme for Sphinx"
-category = "main"
optional = true
python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,>=2.7"
files = [
@@ -1419,7 +1361,6 @@ dev = ["bump2version", "sphinxcontrib-httpdomain", "transifex-client", "wheel"]
name = "sphinxcontrib-applehelp"
version = "1.0.3"
description = "sphinxcontrib-applehelp is a Sphinx extension which outputs Apple help books"
-category = "main"
optional = true
python-versions = ">=3.8"
files = [
@@ -1435,7 +1376,6 @@ test = ["pytest"]
name = "sphinxcontrib-devhelp"
version = "1.0.2"
description = "sphinxcontrib-devhelp is a sphinx extension which outputs Devhelp document."
-category = "main"
optional = true
python-versions = ">=3.5"
files = [
@@ -1451,7 +1391,6 @@ test = ["pytest"]
name = "sphinxcontrib-htmlhelp"
version = "2.0.0"
description = "sphinxcontrib-htmlhelp is a sphinx extension which renders HTML help files"
-category = "main"
optional = true
python-versions = ">=3.6"
files = [
@@ -1467,7 +1406,6 @@ test = ["html5lib", "pytest"]
name = "sphinxcontrib-jsmath"
version = "1.0.1"
description = "A sphinx extension which renders display math in HTML via JavaScript"
-category = "main"
optional = true
python-versions = ">=3.5"
files = [
@@ -1482,7 +1420,6 @@ test = ["flake8", "mypy", "pytest"]
name = "sphinxcontrib-qthelp"
version = "1.0.3"
description = "sphinxcontrib-qthelp is a sphinx extension which outputs QtHelp document."
-category = "main"
optional = true
python-versions = ">=3.5"
files = [
@@ -1498,7 +1435,6 @@ test = ["pytest"]
name = "sphinxcontrib-serializinghtml"
version = "1.1.5"
description = "sphinxcontrib-serializinghtml is a sphinx extension which outputs \"serialized\" HTML files (json and pickle)."
-category = "main"
optional = true
python-versions = ">=3.5"
files = [
@@ -1512,21 +1448,24 @@ test = ["pytest"]
[[package]]
name = "sqlparse"
-version = "0.4.3"
+version = "0.4.4"
description = "A non-validating SQL parser."
-category = "main"
optional = false
python-versions = ">=3.5"
files = [
- {file = "sqlparse-0.4.3-py3-none-any.whl", hash = "sha256:0323c0ec29cd52bceabc1b4d9d579e311f3e4961b98d174201d5622a23b85e34"},
- {file = "sqlparse-0.4.3.tar.gz", hash = "sha256:69ca804846bb114d2ec380e4360a8a340db83f0ccf3afceeb1404df028f57268"},
+ {file = "sqlparse-0.4.4-py3-none-any.whl", hash = "sha256:5430a4fe2ac7d0f93e66f1efc6e1338a41884b7ddf2a350cedd20ccc4d9d28f3"},
+ {file = "sqlparse-0.4.4.tar.gz", hash = "sha256:d446183e84b8349fa3061f0fe7f06ca94ba65b426946ffebe6e3e8295332420c"},
]
+[package.extras]
+dev = ["build", "flake8"]
+doc = ["sphinx"]
+test = ["pytest", "pytest-cov"]
+
[[package]]
name = "tomli"
version = "2.0.1"
description = "A lil' TOML parser"
-category = "dev"
optional = false
python-versions = ">=3.7"
files = [
@@ -1538,7 +1477,6 @@ files = [
name = "traitlets"
version = "5.8.1"
description = "Traitlets Python configuration system"
-category = "dev"
optional = false
python-versions = ">=3.7"
files = [
@@ -1554,7 +1492,6 @@ test = ["argcomplete (>=2.0)", "pre-commit", "pytest", "pytest-mock"]
name = "typing-extensions"
version = "4.4.0"
description = "Backported and Experimental Type Hints for Python 3.7+"
-category = "main"
optional = false
python-versions = ">=3.7"
files = [
@@ -1566,7 +1503,6 @@ files = [
name = "urllib3"
version = "1.26.13"
description = "HTTP library with thread-safe connection pooling, file post, and more."
-category = "main"
optional = false
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*"
files = [
@@ -1583,7 +1519,6 @@ socks = ["PySocks (>=1.5.6,!=1.5.7,<2.0)"]
name = "wcwidth"
version = "0.2.5"
description = "Measures the displayed width of unicode strings in a terminal"
-category = "dev"
optional = false
python-versions = "*"
files = [
@@ -1595,7 +1530,6 @@ files = [
name = "xapian-bindings"
version = "0.1.0"
description = "Meta-package to build and install xapian-bindings extension."
-category = "main"
optional = false
python-versions = "*"
files = [
@@ -1606,7 +1540,6 @@ files = [
name = "xapian-haystack"
version = "3.0.1"
description = "A Xapian backend for Haystack"
-category = "main"
optional = false
python-versions = "*"
files = [
@@ -1621,7 +1554,6 @@ django-haystack = ">=2.8.0"
name = "zipp"
version = "3.11.0"
description = "Backport of pathlib-compatible object wrapper for zip files"
-category = "main"
optional = true
python-versions = ">=3.7"
files = [
@@ -1634,10 +1566,10 @@ docs = ["furo", "jaraco.packaging (>=9)", "jaraco.tidelift (>=1.4)", "rst.linker
testing = ["flake8 (<5)", "func-timeout", "jaraco.functools", "jaraco.itertools", "more-itertools", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=1.3)", "pytest-flake8", "pytest-mypy (>=0.9.1)"]
[extras]
-docs = ["Sphinx", "sphinx-rtd-theme", "sphinx-copybutton"]
+docs = ["Sphinx", "sphinx-copybutton", "sphinx-rtd-theme"]
testing = ["coverage"]
[metadata]
lock-version = "2.0"
python-versions = "^3.8"
-content-hash = "5069f58a9ba4b47c16b08e1a4191b0d2be68c20163300fc550b41d68c8e26d73"
+content-hash = "62519616aff5a472dac3dd8071a6404b1ee8eab12a197af717a0520f7ded0331"
diff --git a/pyproject.toml b/pyproject.toml
index 742f7a2e1..cb1a6aaf7 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -35,8 +35,8 @@ reportlab = "^3.6"
django-haystack = "^3.2.1"
xapian-haystack = "^3.0.1"
xapian-bindings = "^0.1.0"
-libsass = "^0.21"
-django-ordered-model = "^3.6"
+libsass = "^0.22"
+django-ordered-model = "^3.7"
django-simple-captcha = "^0.5.17"
python-dateutil = "^2.8.2"
psycopg2-binary = "2.9.3"
@@ -59,6 +59,7 @@ testing = ["coverage"]
docs = ["Sphinx", "sphinx-rtd-theme", "sphinx-copybutton"]
[tool.poetry.dev-dependencies]
+freezegun = "^1.2.2" # used to test time-dependent code
django-debug-toolbar = "^4.0.0"
ipython = "^7.28.0"
black = "^23.3.0"
diff --git a/sith/settings.py b/sith/settings.py
index 26a013a04..5ed279af5 100644
--- a/sith/settings.py
+++ b/sith/settings.py
@@ -327,7 +327,8 @@
# Define the date in the year serving as reference for the subscriptions calendar
# (month, day)
-SITH_START_DATE = (8, 15) # 15th August
+SITH_SEMESTER_START_AUTUMN = (8, 15) # 15 August
+SITH_SEMESTER_START_SPRING = (2, 15) # 15 February
# Used to determine the valid promos
SITH_SCHOOL_START_YEAR = 1999
diff --git a/subscription/models.py b/subscription/models.py
index ee4334f11..f1c2b2d5a 100644
--- a/subscription/models.py
+++ b/subscription/models.py
@@ -114,12 +114,12 @@ def __str__(self):
return "No user - " + str(self.pk)
@staticmethod
- def compute_start(d=None, duration=1, user=None):
+ def compute_start(d: date = None, duration: int = 1, user: User = None) -> date:
"""
This function computes the start date of the subscription with respect to the given date (default is today),
- and the start date given in settings.SITH_START_DATE.
+ and the start date given in settings.SITH_SEMESTER_START_AUTUMN.
It takes the nearest past start date.
- Exemples: with SITH_START_DATE = (8, 15)
+ Exemples: with SITH_SEMESTER_START_AUTUMN = (8, 15)
Today -> Start date
2015-03-17 -> 2015-02-15
2015-01-11 -> 2014-08-15
@@ -135,9 +135,9 @@ def compute_start(d=None, duration=1, user=None):
return get_start_of_semester(d)
@staticmethod
- def compute_end(duration, start=None, user=None):
+ def compute_end(duration: int, start: date = None, user: User = None) -> date:
"""
- This function compute the end date of the subscription given a start date and a duration in number of semestre
+ This function compute the end date of the subscription given a start date and a duration in number of semester
Exemple:
Start - Duration -> End date
2015-09-18 - 1 -> 2016-03-18
@@ -153,7 +153,7 @@ def compute_end(duration, start=None, user=None):
days=math.ceil((6 * duration - round(6 * duration)) * 30),
)
- def can_be_edited_by(self, user):
+ def can_be_edited_by(self, user: User):
return user.is_board_member or user.is_root
def is_valid_now(self):
diff --git a/trombi/models.py b/trombi/models.py
index e4439c1a9..18f365269 100644
--- a/trombi/models.py
+++ b/trombi/models.py
@@ -31,7 +31,7 @@
from datetime import timedelta, date
from core.models import User
-from core.utils import get_start_of_semester, get_semester
+from core.utils import get_start_of_semester, get_semester_code
from club.models import Club
@@ -164,14 +164,14 @@ def make_memberships(self):
if m.description:
role += " (%s)" % m.description
if m.end_date:
- end_date = get_semester(m.end_date)
+ end_date = get_semester_code(m.end_date)
else:
end_date = ""
TrombiClubMembership(
user=self,
club=str(m.club),
role=role[:64],
- start=get_semester(m.start_date),
+ start=get_semester_code(m.start_date),
end=end_date,
).save()