Skip to content

Commit

Permalink
Bring the Whistleblower in
Browse files Browse the repository at this point in the history
  • Loading branch information
cuducos committed Sep 13, 2018
1 parent 9ed1d41 commit 37dc9a6
Show file tree
Hide file tree
Showing 12 changed files with 252 additions and 7 deletions.
3 changes: 3 additions & 0 deletions contrib/crontab
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
0 22 * * 1,3 cd /root/serenata-de-amor && docker-compose -f docker-compose.yml -f docker-compose.prod.yml run --rm django python manage.py tweet >/dev/null 2>&1
0 17 * * 2,4 cd /root/serenata-de-amor && docker-compose -f docker-compose.yml -f docker-compose.prod.yml run --rm django python manage.py tweet >/dev/null 2>&1
0 13 * * 5 cd /root/serenata-de-amor && docker-compose -f docker-compose.yml -f docker-compose.prod.yml run --rm django python manage.py tweet >/dev/null 2>&1
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ class Command(LoadCommand):
BATCH_SIZE = 4096

def add_arguments(self, parser):
super().add_arguments(parser, add_drop_all=False)
super().add_arguments(parser)
parser.add_argument(
'--batch-size', '-b', dest='batch_size', type=int,
default=self.BATCH_SIZE,
Expand All @@ -21,7 +21,10 @@ def handle(self, *args, **options):
self.path = options['dataset']
self.batch_size = options.get('batch_size', self.BATCH_SIZE)
self.batch, self.count = [], 0
self.drop_all(Reimbursement)

if options.get('drop', False):
self.drop_all(Reimbursement)

self.create_batches()

@property
Expand Down
24 changes: 24 additions & 0 deletions jarbas/chamber_of_deputies/management/commands/socialmedia.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import csv
import os

from jarbas.core.management.commands import LoadCommand
from jarbas.chamber_of_deputies.models import SocialMedia


class Command(LoadCommand):
help = 'Load congresspeople social media accounts'
count = 0

def handle(self, *args, **options):
self.path = options['dataset']
if not os.path.exists(self.path):
raise FileNotFoundError(os.path.abspath(self.path))

if options.get('drop', False):
self.drop_all(SocialMedia)

print('Saving social media accounts')
with open(self.path) as fobj:
bulk = (SocialMedia(**line) for line in csv.DictReader(fobj))
SocialMedia.objects.bulk_create(bulk)
print('Done!')
6 changes: 5 additions & 1 deletion jarbas/chamber_of_deputies/management/commands/suspicions.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
from concurrent.futures import ThreadPoolExecutor

from bulk_update.helper import bulk_update
from django.core.exceptions import ObjectDoesNotExist

from jarbas.core.management.commands import LoadCommand
from jarbas.chamber_of_deputies.models import Reimbursement
Expand Down Expand Up @@ -83,9 +84,12 @@ def main(self):

def schedule_update(self, content):
document_id = content.get('document_id')
if not document_id:
return None

try:
reimbursement = Reimbursement.objects.get(document_id=document_id)
except Reimbursement.DoesNotExist:
except (ObjectDoesNotExist, Reimbursement.MultipleObjectsReturned):
pass
else:
reimbursement.suspicions = content.get('suspicions')
Expand Down
17 changes: 17 additions & 0 deletions jarbas/chamber_of_deputies/management/commands/tweet.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
from django.core.management.base import BaseCommand

from jarbas.chamber_of_deputies.twitter import SocialMedia, Twitter


class Command(BaseCommand):
help = 'Tweet the next suspicion at @RosieDaSerenata'

def handle(self, *args, **options):
congressperson_ids_with_twitter = SocialMedia.objects. \
values_list('congressperson_id', flat=True) \
.distinct('congressperson_id') \
.order_by('congressperson_id')

twitter = Twitter(congressperson_ids_with_twitter)
if twitter.reimbursement:
twitter.publish()
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
# Generated by Django 2.0.8 on 2018-09-13 03:25

import django.contrib.postgres.fields
from django.db import migrations, models
import django.db.models.deletion


class Migration(migrations.Migration):

dependencies = [
('chamber_of_deputies', '0004_alter_field_names_following_toolbox_renamings'),
]

operations = [
migrations.CreateModel(
name='SocialMedia',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('congressperson_name', models.CharField(blank=True, default='', max_length=255)),
('congressperson_id', models.IntegerField(blank=True, db_index=True, null=True)),
('twitter_profile', models.CharField(blank=True, default='', max_length=255)),
('secondary_twitter_profile', models.CharField(blank=True, default='', max_length=255)),
('facebook_page', models.CharField(blank=True, default='', max_length=255)),
],
),
migrations.AddField(
model_name='reimbursement',
name='social_media',
field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, to='chamber_of_deputies.SocialMedia'),
),
]
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
# Generated by Django 2.0.8 on 2018-09-13 04:42

import django.contrib.postgres.fields
from django.db import migrations, models
import django.db.models.deletion


class Migration(migrations.Migration):

dependencies = [
('chamber_of_deputies', '0005_create_social_media_model'),
]

operations = [
migrations.AlterField(
model_name='reimbursement',
name='social_media',
field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, to='chamber_of_deputies.SocialMedia'),
)
]
34 changes: 34 additions & 0 deletions jarbas/chamber_of_deputies/migrations/0007_auto_20180913_0209.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
# Generated by Django 2.0.8 on 2018-09-13 05:09

import django.contrib.postgres.fields
from django.db import migrations, models


class Migration(migrations.Migration):

dependencies = [
('chamber_of_deputies', '0006_change_on_delete_social_media_to_set_null'),
]

operations = [
migrations.AlterField(
model_name='reimbursement',
name='document_id',
field=models.IntegerField(db_index=True, verbose_name='Número do Reembolso'),
),
migrations.AlterField(
model_name='reimbursement',
name='issue_date',
field=models.DateField(blank=True, db_index=True, null=True, verbose_name='Data de Emissão'),
),
migrations.AlterField(
model_name='reimbursement',
name='numbers',
field=django.contrib.postgres.fields.ArrayField(base_field=models.CharField(max_length=128, verbose_name='Números dos Ressarcimentos'), default=list, size=None),
),
migrations.AlterField(
model_name='reimbursement',
name='supplier',
field=models.CharField(max_length=256, verbose_name='Fornecedor'),
),
]
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
# Generated by Django 2.0.8 on 2018-09-13 05:46

from django.db import migrations, models


class Migration(migrations.Migration):

dependencies = [
('chamber_of_deputies', '0007_auto_20180913_0209'),
]

operations = [
migrations.RemoveField(
model_name='reimbursement',
name='social_media',
),
migrations.AlterField(
model_name='reimbursement',
name='congressperson_id',
field=models.IntegerField(blank=True, db_index=True, null=True, verbose_name='Identificador Único do Parlamentar'),
),
]
16 changes: 12 additions & 4 deletions jarbas/chamber_of_deputies/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,14 @@
from jarbas.chamber_of_deputies.querysets import ReimbursementQuerySet


class SocialMedia(models.Model):
congressperson_name = models.CharField(max_length=255, blank=True, default='')
congressperson_id = models.IntegerField(blank=True, null=True, db_index=True)
twitter_profile = models.CharField(max_length=255, blank=True, default='')
secondary_twitter_profile = models.CharField(max_length=255, blank=True, default='')
facebook_page = models.CharField(max_length=255, blank=True, default='')


class Receipt:

def __init__(self, year, applicant_id, document_id):
Expand All @@ -29,7 +37,7 @@ def exists(self):


class Reimbursement(models.Model):
document_id = models.IntegerField('Número do Reembolso', db_index=True, unique=True)
document_id = models.IntegerField('Número do Reembolso', db_index=True)
last_update = models.DateTimeField('Atualizado no Jarbas em', db_index=True, auto_now=True)

year = models.IntegerField('Ano', db_index=True)
Expand All @@ -39,7 +47,7 @@ class Reimbursement(models.Model):
total_net_value = models.DecimalField('Valor Líquido', max_digits=10, decimal_places=3)
numbers = ArrayField(models.CharField('Números dos Ressarcimentos', max_length=128), default=list)

congressperson_id = models.IntegerField('Identificador Único do Parlamentar', blank=True, null=True)
congressperson_id = models.IntegerField('Identificador Único do Parlamentar', blank=True, null=True, db_index=True)
congressperson_name = models.CharField('Nome do Parlamentar', max_length=140, db_index=True, blank=True, null=True)
congressperson_document = models.IntegerField('Número da Carteira Parlamentar', blank=True, null=True)

Expand All @@ -54,14 +62,14 @@ class Reimbursement(models.Model):
subquota_group_id = models.IntegerField('Número da Especificação da Subcota', blank=True, null=True)
subquota_group_description = models.CharField('Descrição da Especificação da Subcota', max_length=140, blank=True, null=True)

supplier = models.CharField('Fornecedor', max_length=140)
supplier = models.CharField('Fornecedor', max_length=256)
cnpj_cpf = models.CharField('CNPJ ou CPF', max_length=14, db_index=True, blank=True, null=True)

document_type = models.IntegerField('Indicativo de Tipo de Documento Fiscal')
document_number = models.CharField('Número do Documento', max_length=140, blank=True, null=True)
document_value = models.DecimalField('Valor do Documento', max_digits=10, decimal_places=3)

issue_date = models.DateField('Data de Emissão', db_index=True)
issue_date = models.DateField('Data de Emissão', blank=True, null=True, db_index=True)
month = models.IntegerField('Mês', db_index=True)
remark_value = models.DecimalField('Valor da Glosa', max_digits=10, decimal_places=3, blank=True, null=True)
installment = models.IntegerField('Número da Parcela', blank=True, null=True)
Expand Down
16 changes: 16 additions & 0 deletions jarbas/chamber_of_deputies/querysets.py
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,22 @@ def search_vector(self, search_term):

return self

def last_term(self):
return self.exclude(term=None) \
.distinct('term') \
.order_by('-term') \
.values_list('term', flat=True) \
.first()

def next_tweet(self, congressperson_ids_with_twitter):
kwargs = {
'term': self.last_term(),
'suspicions__meal_price_outlier': True,
'tweet': None,
'congressperson_id__in': congressperson_ids_with_twitter
}
return self.filter(**kwargs).order_by('issue_date').first()


def _str_to_tuple(filters):
"""
Expand Down
63 changes: 63 additions & 0 deletions jarbas/chamber_of_deputies/twitter.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
import twitter

from django.conf import settings
from django.core.exceptions import ObjectDoesNotExist

from jarbas.chamber_of_deputies.models import Reimbursement, SocialMedia, Tweet


class Twitter:
"""Twitter target. Maintains the logic for posting suspicious
reimbursements in a Twitter account.
"""

LINK = 'https://jarbas.serenata.ai/layers/#/documentId/{}'
TEXT = (
'🚨Gasto suspeito de Dep. @{} ({}). '
'Você pode me ajudar a verificar? '
'{} #SerenataDeAmor na @CamaraDeputados'
)

def __init__(self, congressperson_ids_with_twitter):
self.credentials = (
settings.TWITTER_CONSUMER_KEY,
settings.TWITTER_CONSUMER_SECRET,
settings.TWITTER_ACCESS_TOKEN,
settings.TWITTER_ACCESS_SECRET
)
self.reimbursement = Reimbursement.objects \
.next_tweet(congressperson_ids_with_twitter)
self._message = ''

@property
def message(self):
"""Proper tweet message for the given reimbursement."""
if self._message:
return self._message

try:
social_media = SocialMedia.objects \
.get(congressperson_id=self.reimbursement.congressperson_id)
except (ObjectDoesNotExist, SocialMedia.MultipleObjectsReturned):
return None

profile = social_media.twitter_profile or \
social_media.secondary_twitter_profile
if not profile:
return None

self._message = self.TEXT.format(
profile,
self.reimbursement.state,
self.LINK.format(self.reimbursement.document_id)
)
return self.message

def publish(self):
"""Post the update to Twitter's timeline."""
if not self.reimbursement:
return None

api = twitter.Api(*self.credentials)
tweet = api.PostUpdate(self.message)
Tweet.objects.create(reimbursement=self.reimbursement, status=tweet.id)

0 comments on commit 37dc9a6

Please sign in to comment.