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)