Commit ae3891ee authored by legton's avatar legton

Merge branch 'development' into 135-rep-por-frequencia

parents d15afbdd 3c3627ad
# info de contas criadas no sistema
login_info
# base de dados
*.csv
......@@ -8,14 +10,17 @@ src/cache
*.json
base_dados/*
base_dados
*/~lock.*
!./src/submission/analysis/test/historico.xls
!./src/submission/analysis/test/matricula.xls
src/.coverage
# lixo
static/*
# lixo
src/static
**/migrations/**
*~
*.swp
......@@ -26,3 +31,94 @@ src/static
**/__pycache__
*.ipynb
# Created by https://www.gitignore.io
### OSX ###
.DS_Store
.AppleDouble
.LSOverride
# Icon must end with two \r
Icon
# Thumbnails
._*
# Files that might appear on external disk
.Spotlight-V100
.Trashes
# Directories potentially created on remote AFP share
.AppleDB
.AppleDesktop
Network Trash Folder
Temporary Items
.apdisk
### Python ###
# Byte-compiled / optimized / DLL files
__pycache__/
*.py[cod]
# C extensions
*.so
# Distribution / packaging
.Python
env/
build/
develop-eggs/
dist/
downloads/
eggs/
lib/
lib64/
parts/
sdist/
var/
*.egg-info/
.installed.cfg
*.egg
# PyInstaller
# Usually these files are written by a python script from a template
# before PyInstaller builds the exe, so as to inject date/other infos into it.
*.manifest
*.spec
# Installer logs
pip-log.txt
pip-delete-this-directory.txt
# Unit test / coverage reports
htmlcov/
.tox/
.coverage
.cache
nosetests.xml
coverage.xml
# Translations
*.mo
*.pot
# Sphinx documentation
docs/_build/
# PyBuilder
target/
### Django ###
*.log
*.pot
*.pyc
__pycache__/
local_settings.py
.env
db.sqlite3
#image: ubuntu:16.04
variables:
POSTGRES_USER: "adega"
POSTGRES_PASSWORD: "adega"
POSTGRES_DB: "adega"
POSTGRES_HOST: "postgres"
POSTGRES_PORT: "5432"
VERSION: "DEVELOPMENT"
services:
- postgres:9.6
before_script:
- export LC_ALL=C.UTF-8
- export LANG=C.UTF-8
# - apt-get update -qq
# - apt-get install -y make
# - make install
# - make install-user
#- pip3 install -U pip setuptools pipenv==9.0.3
## pip3 --version
#- pipenv install
#- source $(pipenv --venv)/bin/activate
# - pipenv shell
# - python --version
# - pip3 show django | grep Version
django-tests:
tags:
- ubuntu
- regular
script:
- cd src
# - python manage.py makemigrations degree
# - python manage.py makemigrations educator
# - python manage.py makemigrations upload
# - python manage.py migrate
#- python manage.py test
# apt-get --quiet=2 não é efetivo, com >> /dev/null só mostra erros da saída
# stderr
- apt-get update --assume-yes >> /dev/null
- apt-get install --assume-yes python3-pip libpq-dev postgresql-client >> /dev/null
- pip3 install --quiet -r requirements.txt
- pip3 show django | grep Version
django-tests:
tags:
- ubuntu
- regular
script:
- cd src
- python3 manage.py makemigrations degree
- python3 manage.py makemigrations educator
- python3 manage.py makemigrations submission
- python3 manage.py makemigrations student
- python3 manage.py makemigrations admission
- python3 manage.py migrate
- python3 manage.py test
# ADEGA
Este software faz parte de um projeto do PET Computação UFPR para
Este software faz parte de um projeto do PET Computação UFPR para
análise de dados dos cursos de graduação da UFPR. Veja a [wiki](http://gitlab.c3sl.ufpr.br/adega/adega/wikis/home).
......@@ -60,7 +60,7 @@ Enquanto o `sudo make docker-up` estiver sendo executado, as alterações feitas
Assim como é possível realizar qualquer comando como seria feito no com o manage.py, também é possível por meio do comando `sudo make docker-manage`. Por exemplo:
```bash
$ sudo make docker-manage makemigrations uploads
$ sudo make docker-manage makemigrations submission
$ sudo make docker-manage migrate
$ sudo make docker-manage createsuperuser
```
......@@ -80,6 +80,37 @@ $ sudo make docker-remove-all
```
*Observação*: Esse comando **não** irá deletar qualquer arquivo do projeto / diretório local, apenas os containers.
## Versão de produção
Para fazer o deploy do adega na versão de produção primeiro verifique se settings.py está com a seguinte linha:
```python
DEBUG = False
```
Então execute:
```bash
$ sudo make docker-production
```
Este comando funciona da mesma forma que `make docker-up` e portando também funciona com os comandos `make docker-manage`.
O aplicativo vai rodar na porta 8000, para alterar mude a porta do container nginx no arquivo `docker-production.yml`.
### Observações do servidor
Se não for possível construir as imgens do docker no servidor será necessário copia-las manualmente por scp.
Para salvar uma imagem execute:
```bash
$ sudo docker save imagem -o imagem_destino
```
Para carregar:
```bash
$ sudo docker load -i imagem
```
É necessário carregar as imagens `adega_web`, `adega_db` e `adega_nginx`.
Se alterações forem feitas no código do adega elas serão automaticamente refletidas no servidor, mesmo na versão de produção.
Porém se mudanças forem feitas no container do nginx a imagem deste deverá ser refeita.
Para manter o aplicativo atualizado só é necessário dar pull na branch production.
## Instalação e dependências manuais (não recomendado)
......@@ -96,8 +127,8 @@ sudo -u postgres psql < postgres/create.sql
```
se você possui o arquivo do banco de dados compartilhado internamente pelos
desenvolvedores do projeto coloque-o na home do projeto, ele vem com um usuário
se você possui o arquivo do banco de dados compartilhado internamente pelos
desenvolvedores do projeto coloque-o na home do projeto, ele vem com um usuário
`pet` com senha `pet` pré-configurado para testes.
......@@ -130,7 +161,7 @@ Ao sair do projeto execute `exit` para sair do virtualenv e evitar polui-lo
## Transformando o seu usuário em um professor
Após você logar no sistema com o seu super usuário você terá acesso ao `URL_DO_SITE/admin`, graças ao [Django admin](https://docs.djangoproject.com/en/1.10/ref/contrib/admin/) nesta tela você é capaz de gerenciar os dados salvos nas models do projeto.
Para transformar o seu usuário em professor basta clicar em `professor`e então selecionar o seu usuário e o curso. Agora se você voltar para a página inicial do sistema você deve ver uma listagem dos seus cursos.
Para transformar o seu usuário em professor basta clicar em `professor`e então selecionar o seu usuário e o curso. Agora se você voltar para a página inicial do sistema você deve ver uma listagem dos seus cursos.
## Executando análises (se vocẽ está usando docker)
......
# -*- coding: utf-8 -*-
import pandas
import json
import numpy
from script.utils.situations import Situation as sit
from script.analysis.analysis import Analysis, rate, mean
from collections import namedtuple
class Course(Analysis):
"""
Classe que calcula analises relacionada a disciplina.
"""
__data = {}
__build_analyze = False
analysis = dict.fromkeys([
"courses",
"general_rate",
"semestral_rate",
"general_mean",
"semestral_mean",
"semestral_count_application",
"general_count_application",
"coursed_count",
"graph_course"
], None)
__rates = [
rate(
"taxa_reprovacao_absoluta",
"SITUACAO",
list(sit.SITUATION_FAIL),
list(sit.SITUATION_COURSED)
),
rate(
"taxa_aprovacao",
"SITUACAO",
list(sit.SITUATION_PASS),
list(sit.SITUATION_COURSED)
),
rate(
"taxa_trancamento",
"SITUACAO",
[sit.SIT_CANCELADO],
list(sit.SITUATION_COURSED)
),
rate(
"taxa_conhecimento",
"SITUACAO",
[sit.SIT_CONHECIMENTO_APROVADO],
list(sit.SITUATION_KNOWLDGE)
),
rate(
"taxa_reprovacao_frequencia",
"SITUACAO",
[sit.SIT_REPROVADO_FREQ],
list(sit.SITUATION_COURSED)
)
]
__mean_set = [
mean("nota",
"SITUACAO",
list(sit.SITUATION_AFFECT_IRA),
"MEDIA_FINAL")
]
def __init__(self, df):
df_filted = df[df['SITUACAO'].isin(sit.SITUATION_COURSED)]
dict_df = {
"normal_dataframe": df,
"filted_dataframe": df_filted
}
for df in dict_df:
type_df = df.split("_")[0] + "_"
general_tmp = dict_df[df].groupby(["COD_ATIV_CURRIC"])
semestral_tmp = dict_df[df].groupby(
["COD_ATIV_CURRIC", "ANO", "PERIODO"])
self.__data[type_df + "general_groupby"] = general_tmp
self.__data[type_df + "semestral_groupby"] = semestral_tmp
self.__data[df] = dict_df[df]
def __str__(self):
"""
Retorna uma string com todas as analises feita no momento.
"""
analysis = []
for analyze in self.analysis:
if self.analysis[analyze] is not None:
analysis.append(self.analysis[analyze])
return "Analises: {} \n".format(analysis)
def build_analysis(self):
"""
Chama todos os metodos de analises.
O metodo é responsável por fazer todas analises necessarias
para disciplina, ao final de fazer todas as analises o metodo muda o
valor do atributo self.__build_analyze para True, desta maneira não é
necessario executar uma analises, se ela já foi feita.
"""
self.courses_list()
self.general_rate()
self.semestral_rate()
self.general_count_submission()
self.semestral_count_submission()
self.graph_course()
self.coursed_count()
self.__build_analyze = True
def courses_list(self):
"""
Obtém a lista de disciplina de um curso.
A lista de disciplina de um curso se resume em uma serie (pandas),
no qual o valor é o nome da disciplina e o index é o código da
disciplina.
A serie é obtida por meio do dataframe df, em que é retirado todas as
linhas em que o valor da coluna COD_ATIV_CURRIC é duplicado, e redinido
o index para a coluna COD_ATIV_CURRIC, assim é possivel obter a serie.
"""
df = self.__data["normal_dataframe"]
df = df[["COD_ATIV_CURRIC", "NOME_ATIV_CURRIC"]].drop_duplicates()
df = df.set_index("COD_ATIV_CURRIC")
self.analysis["courses"] = df["NOME_ATIV_CURRIC"]
def general_rate(self):
"""
Calcula as taxas gerais para cada disciplina e a media das taxas.
O calculo das taxas para cada disciplina utiliza o metodo da super
classe Analysis calc_rate, no qual é passado um groupby object e Uma
lista de taxas a serem calculadas. O calculo da media das taxas é feito
calculando a media das taxas de cada dataframes do
groupby object groups.
"""
groups = self.__data["normal_general_groupby"]
rates = self.calc_rate(groups, self.__rates)
self.analysis["general_rates"] = rates
for rate_it in self.__rates:
rate_mean = self.analysis["general_rates"][rate_it.name][0].mean()
rate_std = self.analysis["general_rates"][rate_it.name][0].std()
self.analysis[rate_it.name] = [rate_mean, rate_std]
def semestral_rate(self):
"""
Calcula as taxas de modo semestrais para cada disciplina.
O calculo das taxas semestrais utiliza o metodo da super classe
Analysis calc_rate, no qual é passado um groupby object e uma lista
de taxas a serem calculadas.
"""
groups = self.__data["normal_semestral_groupby"]
rates = self.calc_rate(groups, [self.__rates[1]])
self.analysis["semestral_rate"] = rates
def semestral_count_submission(self):
"""
calcula a quantidade de matriculas por semestre.
calcula quantos alunos se matricularam na disciplina em cada
semestre.
"""
serie_count = self.count(self.__data["normal_semestral_groupby"])
self.analysis["semestral_count_application"] = serie_count.to_dict()
def general_note_statistic(self):
"""
Calcula algumas estatisticas sobre MEDIA_FINAL relacionada a course.
"""
group = self.__data["filted_general_groupby"]
serie_mean = group.apply(lambda x: x["MEDIA_FINAL"].mean())
serie_std = group.apply(lambda x: x["MEDIA_FINAL"].std())
general_mean = serie_mean.mean()
general_std = serie_mean.mean()
self.analysis["general_note_statistic"] = [serie_mean, serie_std]
self.analysis["general_note_mean"] = general_mean
self.analysis["general_note_std"] = general_std
def general_count_submission(self):
"""
Conta a quantidade de matriculas que cada disciplina tem.
"""
serie_count = self.count(self.__data["normal_general_groupby"])
self.analysis["general_count_submission"] = serie_count.to_dict()
def __calc_graph_mean(self, group, min_v, max_v, graph):
"""
Calcula a media que está entre um intervalo.
Calcula a media das notas que estão ente o intervalo min_v e max_v e
groupby.
"""
interval_key = str(min_v) + "-" + str(max_v)
col = "MEDIA_FINAL"
# para ficar mais legivel
def f(x, min_v, max_v):
return self.sum_interval(x, col, min_v, max_v)
graph_serie = group.apply(lambda x: f(x, min_v, max_v) / x.shape[0])
graph_dict = graph_serie.to_dict() # transforma em dicionario
for course in graph_dict: # coloca a media e o intervalo no dicionario
graph[course].append([interval_key, graph_dict[course]])
def graph_course(self):
"""
Calcula o grafico de nota disciplina.
"""
group = self.__data["filted_general_groupby"]
graph = {}
if self.analysis["courses"] is None:
self.courses_list()
# inicializa o dicionario que vai guardar o grafico
for course in self.analysis["courses"].index:
graph[course] = []
for i in range(18):
min_v = i * 5
max_v = min_v + 4.99
self.__calc_graph_mean(group, min_v, max_v, graph)
min_v = 95
max_v = 100
self.__calc_graph_mean(group, min_v, max_v, graph)
self.analysis["graph_course"] = graph
def coursed_count(self):
"""
Calcula a quandidade de vezes que cada aluno cursou a disciplina.
"""
dict_name = "filted_course_student_groupby"
self.__data[dict_name] = self.__data["normal_dataframe"].groupby([
"COD_ATIV_CURRIC",
"MATR_ALUNO"
])
course_dict = {}
for df in self.__data[dict_name]:
if df[0][0] not in course_dict:
course_dict[df[0][0]] = dict.fromkeys(
[str(i) for i in range(1, 6)], 0)
count = df[1].shape[0] if df[1].shape[0] <= 5 else 5
course_dict[df[0][0]][str(count)] += 1
self.analysis["coursed_count"] = course_dict
def build_courses(self):
"""
Cria o dicionario para o json chamado 'disciplina.json'
"""
courses = {}
if self.__build_analyze is False:
self.build_analysis()
courses["taxa_conhecimento"] = self.analysis["taxa_conhecimento"]
courses["taxa_reprovacao"] = self.analysis["taxa_reprovacao_absoluta"]
courses["taxa_trancamento"] = self.analysis["taxa_trancamento"]
# cria cache
cache = {}
for rate_it in self.__rates:
rate_calc = self.analysis["general_rates"][rate_it.name][0]
for course in self.analysis["courses"].index:
if course not in cache:
cache[course] = {}
cache[course][rate_it.name] = rate_calc[course]
courses["cache"] = cache
# cria o campo compara_aprov
courses["compara_aprov"] = self.analysis["graph_course"]
# cria o campo courses
courses["disciplinas"] = self.analysis["courses"].to_dict()
return courses
def build_course(self):
"""
Cria o dicionario para cada json de disciplina, ex 'CI055.json'.
"""
courses = []
for course in self.analysis["courses"].index:
course_dict = {}
course_dict["disciplina_codigo"] = course
course_dict["disciplina_nome"] = self.analysis["courses"][course]
# quantidade de matriculas
count = self.analysis["general_count_submission"][course]
course_dict["qtd_alunos"] = count
# taxas
for rate_it in self.__rates:
rate_data = self.analysis["general_rates"][rate_it.name]
course_dict[rate_it.name] = rate_data[0][course]
course_str = rate_it.name.replace("taxa", "qtd")
course_dict[course_str] = rate_data[1][course]
course_dict["grafico_qtd_cursada_aprov"] = \
self.analysis["coursed_count"][course]
if(course == "CI055"):
print(course_dict)
courses.append(course_dict)
return courses
......@@ -6,7 +6,7 @@
if ! sudo -u postgres psql adega
then
sudo -u postgres psql < postgres/create.sql
sudo -u postgres psql < postgres/create.sql
fi
......
version: '3'
services:
db:
container_name: adega_db
image: postgres
environment:
- POSTGRES_USER=adega
- POSTGRES_PASSWORD=adega
- POSTGRES_DB=adega
web:
build:
container_name: adega_web
build:
context: .
dockerfile: ./docker_scripts/Dockerfile
command: bash "./docker_scripts/on_docker_init.sh"
......@@ -23,4 +25,5 @@ services:
- POSTGRES_USER=adega
- POSTGRES_PASSWORD=adega
- POSTGRES_DB=adega
- POSTGRES_HOST=adega_db_1
- POSTGRES_HOST=adega_db
- VERSION=DEVELOPMENT
version: '3'
services:
db:
image: postgres
environment:
- POSTGRES_USER=adega
- POSTGRES_PASSWORD=adega
- POSTGRES_DB=adega
web:
build:
context: .