diff --git a/agenda/migrations/0005_auto_20180905_1922.py b/agenda/migrations/0005_auto_20180905_1922.py
new file mode 100644
index 00000000..593192ef
--- /dev/null
+++ b/agenda/migrations/0005_auto_20180905_1922.py
@@ -0,0 +1,19 @@
+# -*- coding: utf-8 -*-
+# Generated by Django 1.11.9 on 2018-09-05 19:22
+from __future__ import unicode_literals
+
+from django.db import migrations
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('agenda', '0004_auto_20180525_1552'),
+ ]
+
+ operations = [
+ migrations.AlterModelOptions(
+ name='activity',
+ options={'ordering': ['date']},
+ ),
+ ]
diff --git a/elections/migrations/0034_auto_20180903_1614.py b/elections/migrations/0034_auto_20180903_1614.py
new file mode 100644
index 00000000..212eb078
--- /dev/null
+++ b/elections/migrations/0034_auto_20180903_1614.py
@@ -0,0 +1,20 @@
+# -*- coding: utf-8 -*-
+# Generated by Django 1.11.9 on 2018-09-03 16:14
+from __future__ import unicode_literals
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('elections', '0033_auto_20180717_2345'),
+ ]
+
+ operations = [
+ migrations.AlterField(
+ model_name='election',
+ name='extra_info_content',
+ field=models.TextField(blank=True, help_text='Voc\xea pode usar Markdown.
Markdown syntax allowed, but no raw HTML. Examples: **bold**, *italic*, indent 4 spaces for a code block.', max_length=3000, null=True),
+ ),
+ ]
diff --git a/merepresenta/match/__init__.py b/merepresenta/match/__init__.py
new file mode 100644
index 00000000..e69de29b
diff --git a/merepresenta/match/matrix_builder.py b/merepresenta/match/matrix_builder.py
new file mode 100644
index 00000000..d1640e2b
--- /dev/null
+++ b/merepresenta/match/matrix_builder.py
@@ -0,0 +1,97 @@
+import numpy as np
+from candidator.models import Position
+from elections.models import QuestionCategory
+from merepresenta.models import Candidate
+
+
+class MatrixBuilder(object):
+ def __init__(self, *args, **kwargs):
+ self.positions = Position.objects.all().order_by('id')
+ self.candidates = Candidate.objects.filter(candidatequestioncategory__isnull=False).order_by('id')
+ self.categories = QuestionCategory.objects.all().order_by('id')
+ self.positions_id = self.set_index_of(self.positions)
+ self.categories_id = self.set_index_of(self.categories)
+ self.candidates_id = self.set_index_of(self.candidates)
+ self.electors_categories = np.ones(self.categories.count())
+ self.coalicagaos_nota = self.get_coaligacao_marks()
+
+ def set_index_of(self, variable):
+ index = 0
+ result = {}
+ for v in variable:
+ result[v.id] = index
+ index +=1
+ return result
+
+ def get_coaligacao_marks(self):
+ coalicagaos_nota = np.ones(len(self.candidates_id))
+ for c in self.candidates:
+ index = self.candidates_id[c.id]
+ try:
+ mark = c.partido.coaligacao.mark
+ coalicagaos_nota[index] = mark
+ except:
+ pass
+ return coalicagaos_nota
+
+ def get_positions_vector_for_category(self, cat):
+ result = np.zeros(self.positions.count())
+ for topic in cat.topics.all():
+ i = self.positions_id[topic.right_answer.position.id]
+ result[i] = 1
+ return result
+
+ def get_matrix_positions_and_categories(self):
+ r = []
+ for c in self.categories:
+ r.append(self.get_positions_vector_for_category(c))
+ return np.vstack(r).T
+
+ def get_positions_vector_for_candidate(self, cand):
+ r = np.zeros(self.positions.count())
+ for p in self.positions:
+ if cand.candidatequestioncategory_set.filter(category=p.topic.category).exists():
+ multiplier = 2
+ else:
+ multiplier = 1
+ if cand.taken_positions.filter(position=p).exists():
+ index = self.positions_id[p.id]
+ r[index] = 1 * multiplier
+ return r.T
+
+ def get_matrix_positions_and_candidates(self):
+ r = []
+ for c in self.candidates:
+ r.append(self.get_positions_vector_for_candidate(c))
+ return np.vstack(r)
+
+ def get_candidates_right_positions_matrix(self):
+ C = self.get_matrix_positions_and_candidates()
+ P = self.get_matrix_positions_and_categories()
+ return np.dot(C ,P)
+
+ def set_electors_categories(self, categories):
+ for c in categories:
+ index = self.categories_id[c.id]
+ self.electors_categories[index] = 3
+
+ def get_candidates_result(self):
+ # Candidates right answers multiplied by 2 if she chooses
+ # the given TEMA
+ CPR = self.get_candidates_right_positions_matrix()
+ return np.dot(CPR, self.electors_categories)
+
+ def get_result(self):
+ C = self.get_candidates_result()
+ notas = self.coalicagaos_nota.T
+ return C * notas
+
+ def get_result_as_array(self):
+ r = self.get_result()
+ as_array = []
+ index = 0
+ for c in self.candidates:
+ i = self.candidates_id[c.id]
+ mark = r[i]
+ as_array.append({'candidato': c, 'nota': mark})
+ return as_array
diff --git a/merepresenta/match/tests/match_tests.py b/merepresenta/match/tests/match_tests.py
new file mode 100644
index 00000000..ca46e9be
--- /dev/null
+++ b/merepresenta/match/tests/match_tests.py
@@ -0,0 +1,207 @@
+# coding=utf-8
+from django.test import TestCase
+from elections.tests import VotaInteligenteTestCase
+from popular_proposal.models import PopularProposal, Commitment
+from merepresenta.models import (MeRepresentaPopularProposal,
+ MeRepresentaCommitment,
+ Candidate,
+ Coaligacao,
+ Partido,
+ VolunteerInCandidate,
+ CandidateQuestionCategory,
+ LGBTQDescription,
+ RightAnswer,
+ QuestionCategory)
+from django.contrib.auth.models import User
+from elections.models import Election, Topic
+from candidator.models import Position, TakenPosition
+from django.utils import timezone
+import datetime
+from django.core.urlresolvers import reverse
+from merepresenta.voluntarios.models import VolunteerProfile
+from backend_candidate.models import Candidacy
+from django.test import override_settings
+import numpy as np
+from merepresenta.match.matrix_builder import MatrixBuilder
+from numpy.testing import assert_equal
+
+
+class QuestionCategoryVectors(TestCase):
+ def setUp(self):
+ super(QuestionCategoryVectors, self).setUp()
+ self.c1 = Candidate.objects.create(name='c1', cpf='1')
+ self.c2 = Candidate.objects.create(name='c2', cpf='2')
+ self.c3 = Candidate.objects.create(name='c3', cpf='3')
+
+ self.cat1 = QuestionCategory.objects.create(name="Pautas LGBT")
+ topic = Topic.objects.create(label=u"Adoção de crianças por famílias LGBTs", category=self.cat1)
+ yes = Position.objects.create(topic=topic, label=u"Sou a FAVOR da adoção de crianças por famílias LGBTs")
+ no = Position.objects.create(topic=topic, label=u"Sou CONTRA a adoção de crianças por famílias LGBTs")
+ RightAnswer.objects.create(topic=topic,position=yes)
+ TakenPosition.objects.create(topic=topic, person=self.c1, position=yes)
+ TakenPosition.objects.create(topic=topic, person=self.c2, position=no)
+ TakenPosition.objects.create(topic=topic, person=self.c3, position=yes)
+ CandidateQuestionCategory.objects.create(category=self.cat1, candidate=self.c1)
+
+ topic2 = Topic.objects.create(label=u"é A favor?", category=self.cat1)
+ yes2 = Position.objects.create(topic=topic2, label=u"Sou a FAVOR")
+ no2 = Position.objects.create(topic=topic2, label=u"Sou CONTRA")
+ RightAnswer.objects.create(topic=topic2, position=yes2)
+
+ TakenPosition.objects.create(topic=topic2, person=self.c1, position=no2)
+ TakenPosition.objects.create(topic=topic2, person=self.c2, position=yes2)
+ TakenPosition.objects.create(topic=topic2, person=self.c3, position=yes2)
+
+ self.cat2 = QuestionCategory.objects.create(name="Genero")
+ CandidateQuestionCategory.objects.create(category=self.cat2, candidate=self.c2)
+ topic3 = Topic.objects.create(label=u"Aborto é A favor?", category=self.cat2)
+ yes3 = Position.objects.create(topic=topic3, label=u"Sou a FAVOR")
+ no3 = Position.objects.create(topic=topic3, label=u"Sou CONTRA")
+ RightAnswer.objects.create(topic=topic3, position=yes3)
+ TakenPosition.objects.create(topic=topic3, person=self.c1, position=yes3)
+ TakenPosition.objects.create(topic=topic3, person=self.c2, position=no3)
+ TakenPosition.objects.create(topic=topic3, person=self.c3, position=no3)
+
+ topic4 = Topic.objects.create(label=u"Monitoramento da Lei do feminicídio", category=self.cat2)
+ yes4 = Position.objects.create(topic=topic4, label=u"Sou a FAVOR")
+ no4 = Position.objects.create(topic=topic4, label=u"Sou CONTRA")
+ RightAnswer.objects.create(topic=topic4, position=yes4)
+ TakenPosition.objects.create(topic=topic4, person=self.c1, position=yes4)
+ TakenPosition.objects.create(topic=topic4, person=self.c2, position=no4)
+ TakenPosition.objects.create(topic=topic4, person=self.c3, position=yes4)
+
+ self.cat3 = QuestionCategory.objects.create(name=u"Corrupção")
+ CandidateQuestionCategory.objects.create(category=self.cat3, candidate=self.c3)
+ topic5 = Topic.objects.create(label=u"Políticos serem donos de emissoras de rádio e TV", category=self.cat3)
+ yes5 = Position.objects.create(topic=topic5, label=u"Sou a FAVOR")
+ no5 = Position.objects.create(topic=topic5, label=u"Sou CONTRA")
+ RightAnswer.objects.create(topic=topic5, position=no5)
+ TakenPosition.objects.create(topic=topic5, person=self.c1, position=yes5)
+ TakenPosition.objects.create(topic=topic5, person=self.c2, position=yes5)
+ TakenPosition.objects.create(topic=topic5, person=self.c3, position=yes5)
+
+
+ def test_get_positions_vector_of_categories(self):
+ '''
+ Aqui o que eu quero lograr é um vector de dimensiones (Nao sei falar portugues)
+ Dx1 onde D é a quantidade de possivels respostas, asim:
+
+ yes1 | 1 |
+ no1 | 0 |
+ yes2 | 1 |
+ no2 | 0 |
+ yes3 | 0 |
+ no3 | 0 |
+ yes4 | 0 |
+ no4 | 0 |
+ yes5 | 0 |
+ no5 | 0 |
+
+ Más que só tem os dados do Tema
+ '''
+ builder = MatrixBuilder()
+ vector = builder.get_positions_vector_for_category(self.cat1)
+ self.assertEquals(vector.shape, (Position.objects.count(),))
+ expected_vector = np.array([1,0,1,0,0,0,0,0,0,0])
+
+ assert_equal(vector, expected_vector)
+
+ def test_get_matrix_of_positions_and_categories(self):
+ builder = MatrixBuilder()
+ matrix = builder.get_matrix_positions_and_categories()
+ expected_mat = np.array([[1,0,1,0,0,0,0,0,0,0],
+ [0,0,0,0,1,0,1,0,0,0],
+ [0,0,0,0,0,0,0,0,0,1]
+ ]).T
+ assert_equal(matrix, expected_mat)
+
+ def test_get_zeros_if_not_right_answers_selected(self):
+ builder = MatrixBuilder()
+ vector = builder.get_positions_vector_for_category(self.cat1)
+ RightAnswer.objects.all().delete()
+ expected_vector = np.array([0,0,0,0,0,0,0,0,0,0])
+
+ def test_get_position_vector_respect_with_candidate(self):
+ builder = MatrixBuilder()
+ vector = builder.get_positions_vector_for_candidate(self.c1)
+ expected_vector = np.array([2,0,0,2,1,0,1,0,1,0])
+ assert_equal(vector, expected_vector)
+
+ def test_get_matrix_of_candidates_with_positions(self):
+ builder = MatrixBuilder()
+ matrix = builder.get_matrix_positions_and_candidates()
+ expected_mat = np.array([[2,0,0,2,1,0,1,0,1,0],
+ [0,1,1,0,0,2,0,2,1,0],
+ [1,0,1,0,0,1,1,0,2,0]
+ ])
+ assert_equal(matrix, expected_mat)
+
+ def test_get_matrix_of_candidates_and_positions_and_right_positions(self):
+ builder = MatrixBuilder()
+ matrix = builder.get_candidates_right_positions_matrix()
+ self.assertEquals(matrix.shape, (3 ,3))
+ self.assertEquals(matrix[0][0], 2)
+ self.assertEquals(matrix[0][1], 2)
+ self.assertEquals(matrix[0][2], 0)
+
+ def test_set_electors_categories(self):
+ builder = MatrixBuilder()
+ builder.set_electors_categories([self.cat1, self.cat2])
+ electors_choices = builder.electors_categories
+ self.assertEquals(electors_choices.shape, (3,))
+ self.assertEquals(electors_choices[0], 3)
+ self.assertEquals(electors_choices[1], 3)
+ self.assertEquals(electors_choices[2], 1)
+
+ def test_get_candidates_right_answers_vs_electors(self):
+ builder = MatrixBuilder()
+ builder.set_electors_categories([self.cat1, self.cat2])
+ r = builder.get_candidates_result()
+ self.assertEquals(r.shape, (Candidate.objects.count(), ))
+
+ def test_get_candidates_full_answer_including_partido(self):
+ coaligacao = Coaligacao.objects.create(name=u"Coaligacao a", initials='CA', number='1234')
+ Partido.objects.create(name=u"Partido de los trabalhadores",
+ initials='PT',
+ number='12345',
+ mark=3.5,
+ coaligacao=coaligacao)
+ peta = Partido.objects.create(name=u"Petronila",
+ initials='PeTa',
+ number='1232',
+ mark=4.5,
+ coaligacao=coaligacao)
+ self.c1.partido = peta
+ self.c1.save()
+ builder = MatrixBuilder()
+ builder.set_electors_categories([self.cat1, self.cat2])
+ r = builder.get_result()
+ self.assertEquals(r.shape, (Candidate.objects.count(), ))
+ self.assertEquals(r[0], 48)
+ self.assertEquals(r[1], 3)
+ self.assertEquals(r[2], 9)
+
+ def test_get_result_as_dict(self):
+ coaligacao = Coaligacao.objects.create(name=u"Coaligacao a", initials='CA', number='1234')
+ Partido.objects.create(name=u"Partido de los trabalhadores",
+ initials='PT',
+ number='12345',
+ mark=3.5,
+ coaligacao=coaligacao)
+ peta = Partido.objects.create(name=u"Petronila",
+ initials='PeTa',
+ number='1232',
+ mark=4.5,
+ coaligacao=coaligacao)
+
+ self.c1.partido = peta
+ self.c1.save()
+ builder = MatrixBuilder()
+ builder.set_electors_categories([self.cat1, self.cat2])
+ r = builder.get_result_as_array()
+ self.assertEquals(r[0]['candidato'], self.c1)
+ self.assertEquals(r[0]['nota'], 48)
+ self.assertEquals(r[1]['candidato'], self.c2)
+ self.assertEquals(r[1]['nota'], 3)
+ self.assertEquals(r[2]['candidato'], self.c3)
+ self.assertEquals(r[2]['nota'], 9)
\ No newline at end of file
diff --git a/merepresenta/migrations/0019_rightanswer.py b/merepresenta/migrations/0019_rightanswer.py
new file mode 100644
index 00000000..9e62dc11
--- /dev/null
+++ b/merepresenta/migrations/0019_rightanswer.py
@@ -0,0 +1,25 @@
+# -*- coding: utf-8 -*-
+# Generated by Django 1.11.9 on 2018-09-03 16:14
+from __future__ import unicode_literals
+
+from django.db import migrations, models
+import django.db.models.deletion
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('elections', '0034_auto_20180903_1614'),
+ ('merepresenta', '0018_auto_20180830_1923'),
+ ]
+
+ operations = [
+ migrations.CreateModel(
+ name='RightAnswer',
+ fields=[
+ ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
+ ('position', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, to='candidator.Position')),
+ ('topic', models.OneToOneField(null=True, on_delete=django.db.models.deletion.CASCADE, related_name='right_answer', to='elections.Topic')),
+ ],
+ ),
+ ]
diff --git a/merepresenta/migrations/0020_auto_20180905_1922.py b/merepresenta/migrations/0020_auto_20180905_1922.py
new file mode 100644
index 00000000..4d5f4feb
--- /dev/null
+++ b/merepresenta/migrations/0020_auto_20180905_1922.py
@@ -0,0 +1,20 @@
+# -*- coding: utf-8 -*-
+# Generated by Django 1.11.9 on 2018-09-05 19:22
+from __future__ import unicode_literals
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('merepresenta', '0019_rightanswer'),
+ ]
+
+ operations = [
+ migrations.AlterField(
+ model_name='partido',
+ name='mark',
+ field=models.FloatField(null=True),
+ ),
+ ]
diff --git a/merepresenta/migrations/0021_remove_coaligacao_mark.py b/merepresenta/migrations/0021_remove_coaligacao_mark.py
new file mode 100644
index 00000000..070a878d
--- /dev/null
+++ b/merepresenta/migrations/0021_remove_coaligacao_mark.py
@@ -0,0 +1,19 @@
+# -*- coding: utf-8 -*-
+# Generated by Django 1.11.9 on 2018-09-05 19:25
+from __future__ import unicode_literals
+
+from django.db import migrations
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('merepresenta', '0020_auto_20180905_1922'),
+ ]
+
+ operations = [
+ migrations.RemoveField(
+ model_name='coaligacao',
+ name='mark',
+ ),
+ ]
diff --git a/merepresenta/models.py b/merepresenta/models.py
index e86901d8..202ece54 100644
--- a/merepresenta/models.py
+++ b/merepresenta/models.py
@@ -11,10 +11,11 @@
from votai_utils.send_mails import send_mail
from django.utils import timezone
import datetime
-from elections.models import QuestionCategory as OriginalQuestionCategory
+from elections.models import QuestionCategory as OriginalQuestionCategory, Topic
from django.utils.encoding import python_2_unicode_compatible
from django.dispatch import receiver
from django.db.models.signals import post_save
+from candidator.models import Position
class MeRepresentaPopularProposal(PopularProposal):
@@ -186,13 +187,21 @@ class Coaligacao(models.Model):
name = models.CharField(max_length=1024, null=True)
initials = models.CharField(max_length=1024, null=True)
number = models.CharField(max_length=1024, null=True)
- mark = models.IntegerField(null=True)
+
+ @property
+ def mark(self):
+ final_sum = 0.0
+ counter = 0
+ for p in self.partido_set.all():
+ final_sum += p.mark
+ counter += 1
+ return final_sum/counter
class Partido(models.Model):
name = models.CharField(max_length=1024, null=True)
initials = models.CharField(max_length=1024, null=True)
number = models.CharField(max_length=1024, null=True)
- mark = models.IntegerField(null=True)
+ mark = models.FloatField(null=True)
coaligacao = models.ForeignKey(Coaligacao, null=True)
@@ -224,6 +233,11 @@ def say_thanks_to_the_volunteer(sender, instance, created, raw, **kwargs):
except VolunteerGetsCandidateEmailLog.DoesNotExist:
pass
+
+class RightAnswer(models.Model):
+ topic = models.OneToOneField(Topic, related_name='right_answer', null=True)
+ position = models.OneToOneField(Position)
+
##### VOLUNTEERS PART!!!
## I wrote this as part of #MeRepresenta, this means that we haven't needed volunteers doing research on candidates before
## This is why I kept it here until now
diff --git a/merepresenta/tests/model_tests.py b/merepresenta/tests/model_tests.py
index 5400cf83..f4e4349b 100644
--- a/merepresenta/tests/model_tests.py
+++ b/merepresenta/tests/model_tests.py
@@ -1,6 +1,7 @@
# coding=utf-8
from django.test import TestCase
from elections.tests import VotaInteligenteTestCase
+from candidator.models import TakenPosition, Position
from popular_proposal.models import PopularProposal, Commitment
from merepresenta.models import (MeRepresentaPopularProposal,
MeRepresentaCommitment,
@@ -10,9 +11,10 @@
VolunteerInCandidate,
CandidateQuestionCategory,
LGBTQDescription,
+ RightAnswer,
QuestionCategory)
from django.contrib.auth.models import User
-from elections.models import Election
+from elections.models import Election, Topic
from django.utils import timezone
import datetime
from django.core.urlresolvers import reverse
@@ -198,13 +200,18 @@ def test_instanciate(self):
class CoaligacaoTestCase(TestCase):
def test_instanciate(self):
- coaligacao = Coaligacao.objects.create(name=u"Coaligacao a", initials='CA', number='1234', mark=3)
+ coaligacao = Coaligacao.objects.create(name=u"Coaligacao a", initials='CA', number='1234')
self.assertTrue(coaligacao)
+ def test_get_mark_for_coaligacao(self):
+ coaligacao = Coaligacao.objects.create(name=u"Coaligacao a", initials='CA', number='1234')
+ Partido.objects.create(name=u"Partido de los trabalhadores", initials='PT', number='12345', mark=3.5, coaligacao=coaligacao)
+ Partido.objects.create(name=u"Petronila", initials='PeTa', number='1232', mark=4.5, coaligacao=coaligacao)
+ self.assertEquals(coaligacao.mark, 4.0)
class PartidoTestCase(TestCase):
def test_instanciate(self):
- coaligacao = Coaligacao.objects.create(name=u"Coaligacao a", initials='CA', number='1234', mark=3)
+ coaligacao = Coaligacao.objects.create(name=u"Coaligacao a", initials='CA', number='1234')
partido = Partido.objects.create(name=u"Partido de los trabalhadores", initials='PT', number='12345', mark=3, coaligacao=coaligacao)
self.assertTrue(partido)
@@ -228,3 +235,12 @@ def test_instanciate(self):
u = User.objects.create_user(username='user', is_staff=True)
i = VolunteerProfile.objects.create(user=u)
self.assertIsNone(i.area)
+
+
+class RightAnswerTestCase(TestCase):
+ def test_instanciate(self):
+ topic4 = Topic.objects.create(label=u"Monitoramento da Lei do feminicídio")
+ yes4 = Position.objects.create(topic=topic4, label=u"Sou a FAVOR")
+ no4 = Position.objects.create(topic=topic4, label=u"Sou CONTRA")
+ a = RightAnswer.objects.create(topic=topic4, position=yes4)
+ self.assertTrue(a)