Commit a9b9a467 authored by Luis Floriano's avatar Luis Floriano 🍍

Merge branch 'development' into 'master'

Development

See merge request !115
parents fbb5af39 e4f535e4
Pipeline #22338 passed with stage
in 5 minutes and 1 second
......@@ -198,6 +198,7 @@ python manage.py graph_models -a -o diagrama.png
* [Django](https://www.djangoproject.com/) - Framework base. Trata a requisição dos clientes e chama as devidas rotinas.
* [Bootstrap](http://getbootstrap.com/) - Framework css. Usamos os seus componentes para deixar as telas bonitas
* [Charts.js](http://www.chartjs.org/) - Biblioteca javascript para desenhar gráficos.
* [chroma.js](https://gka.github.io/chroma.js/#color-scales) - Biblioteca javascript para escala de cores do heatmap.
* [Pandas](http://pandas.pydata.org/) - usada para importação dos dados
* [django-extensions](https://django-extensions.readthedocs.io/en/latest/) - Várias extensões para o django. Estamos usando para gerar o diagrama do projeto.
......
......@@ -290,18 +290,18 @@ span.data {
.materia_selected {
outline-style:solid;
outline-color:rgb(138, 64, 207);
outline-width:1px;
outline-color:#c657e9;
outline-width:3px;
}
.materia_prerequisite {
outline-style:solid;
outline-color:#FFD700;
outline-width:1px;
outline-color:#0867b8;
outline-width:2px;
}
.materia_posrequisite {
outline-style:solid;
outline-color:#6B8E23;
outline-width:1px;
outline-color:#f11de0;
outline-width:2px;
}
/* ============================================== MATERIA */
......@@ -390,6 +390,59 @@ span.data {
align-content: flex-start;
align-items: stretch; }
/* ======================================= TOGGLE HEATMAP */
.switch {
position: relative;
display: inline-block;
vertical-align: middle;
width: 30px;
height: 17px;
margin-right: 7px;
}
.slider {
position: absolute;
cursor: pointer;
top: 0;
left: 0;
right: 0;
bottom: 0;
background-color: #ccc;
-webkit-transition: .4s;
transition: .4s;
}
.slider:before {
position: absolute;
content: "";
height: 13px;
width: 13px;
left: 2px;
bottom: 2px;
background-color: white;
-webkit-transition: .4s;
transition: .4s;
}
.toggleheat:checked + .slider {
background-color: #18bc9c;
}
.toggleheat:checked + .slider:before {
-webkit-transform: translateX(13px);
-ms-transform: translateX(13px);
transform: translateX(13px);
}
/* Rounded sliders */
.slider.round {
border-radius: 17px;
}
.slider.round:before {
border-radius: 50%;
}
/* ============================================== HISTORICO */
#desenvolvimento {
overflow-x: auto;
......
This diff is collapsed.
......@@ -91,6 +91,7 @@
<script src="{% static 'adega/js/plotly-latest.min.js' %}"></script>
<script src="{% static 'adega/js/chroma.min.js' %}"></script>
<script src="https://cdn.datatables.net/1.10.19/js/dataTables.bootstrap4.min.js"></script>
......
......@@ -156,11 +156,12 @@
div_target: "aprovacao_semestre",
title: "Relação entre quantidade de alunos matriculados e taxa de aprovação",
fill: "none",
legend: ["Taxa de aprovação","Quantidade de alunos"],
type:["scatter", "bar"],
data_axis_y: ["y2", "y"],
legend: ["Taxa de aprovação", "Quantidade de alunos aprovados", "Quantidade de alunos matriculados"],
type:["scatter", "bar", "bar"],
data_axis_y: ["y2", "y", "y"],
barmode:"group",
xaxis_title: "Período",
yaxis_title: "Quantidade de matrículas",
yaxis_title: "Quantidade de alunos",
yaxis2_title: "Taxa de aprovação",
});
......
......@@ -49,6 +49,7 @@ def index(request, submission_id):
"submission": submission
})
@permission_required_or_403('view_course', (Submission, 'id', 'submission_id'))
def compare(request, submission_id):
print(request,submission_id)
submission_id = int(submission_id)
......@@ -57,9 +58,6 @@ def compare(request, submission_id):
submission = Submission.objects.get(id=submission_id)
degree = submission.degree
if not (degree in request.user.educator.degree.all()):
return redirect("adega:dashboard")
analysis_result = get_list_courses(request.session, degree, submission_id)
courses_list = analysis_result["cache"]
code_to_name = analysis_result["disciplinas"]
......
This diff is collapsed.
from submission.analysis.utils.situations import Situation
from submission.analysis.utils.situations import Situation, PeriodType
import numpy as np
......@@ -7,8 +7,9 @@ class CourseGrid:
self.situation = obj["situacao"]
self.code = obj["codigo"]
self.name = obj["nome"]
self.year = obj["ano"]
self.semester = obj["semestre"]
self.year = int(obj["ano"])
# the code that subtitutes the strings are sorting the period types by time (look situations.py)
self.semester_code = PeriodType.str_to_code(obj["semestre"])
self.grade = obj["nota"]
def is_approved(self):
......@@ -36,6 +37,22 @@ class CourseGrid:
def is_coursed(self):
return self.is_approved() or self.is_failed()
def __gt__(self, other):
if self.year == other.year:
return self.semester_code > other.semester_code
else:
return self.year > other.year
def __lt__(self, other):
if self.year == other.year:
return self.semester_code < other.semester_code
else:
return self.year < other.year
def __eq__(self, other):
return (self.year == other.year) and (self.semester_code == other.semester_code)
class CourseGridCollection:
def __init__(self, code, grid):
......@@ -102,7 +119,11 @@ class CourseGridCollection:
def mean_grade(self):
grades = [x.grade for x in self.get_coursed_historic()]
return np.mean(grades)
def last_grade(self):
courses = self.get_coursed_historic()
courses = sorted(courses)
return courses[-1].grade
def get_prevalent_situation(self):
# If this is an course that repeat on grid, then this doesnt have any
......@@ -140,12 +161,12 @@ class CourseGridCollection:
"name": self.name,
"code": self.code,
"situation": p_sit,
"is_real_code": self.is_real_code
"is_real_code": self.is_real_code,
}
if self.has_detail():
info["detail"] = {
"count": self.count_coursed(),
"mean_grade": self.mean_grade(),
"last_grade": self.last_grade(),
}
return info
......@@ -176,7 +197,7 @@ class DegreeGridDescription:
def is_repeated_code(self, code):
return code in self.repeated_codes
def is_real_code(self, code):
return not code in self.fake_codes
......@@ -188,12 +209,12 @@ class DegreeGridDescription:
if(code1 == code2):
return True
return False
class DegreeGrid:
def __init__(self, grid_detail):
self.grid_detail = grid_detail
self.cgc = {}
def compute_cgc(self, hist):
# Create an instance for each cell in grid
cgc = {}
......@@ -217,12 +238,47 @@ class DegreeGrid:
return cgc
def get_prerequisites(self, code):
if code in self.bcc_grid_2011.prerequisites.keys():
return [x for x in self.bcc_grid_2011.prerequisites[code]]
else:
return ['none']
def get_list_approvations(self, grid):
approvations=[]
for period in grid:
for course in period:
if course['situation'] == "approved" or course['situation'] == "equivalence":
approvations.append(course['code'])
return approvations
def is_blocked(self, code, prerequisites, approvations):
if code in approvations:
return False
prerequisites_completed = True
for prerequisite in prerequisites:
if prerequisite == 'none':
return False
if prerequisite not in approvations:
prerequisites_completed = False
return not prerequisites_completed
def get_blocked_courses(self, grid):
approvations = self.get_list_approvations(grid)
for i, period in enumerate(grid):
for j, course in enumerate(period):
code = course['code']
prerequisites = self.get_prerequisites(code)
grid[i][j]["is_blocked"] = self.is_blocked(code, prerequisites, approvations);
return grid
def get_grid(self, cgc):
new_grid = np.array(self.grid_detail.grid, dtype=np.dtype(object))
for i, line in enumerate(self.grid_detail.grid):
for j, course_code in enumerate(line):
# TODO: Add possibility to insert others grids
new_grid[i, j] = cgc[course_code].get_info()
new_grid = self.get_blocked_courses(new_grid)
return new_grid
def get_repeated_course_info(self, cgc):
......@@ -244,6 +300,12 @@ class DegreeGrid:
def get_situation(self, hist):
cgc = self.compute_cgc(hist)
return self.get_grid(cgc), self.get_repeated_course_info(cgc)
@staticmethod
def get_degree_grid(code):
if code == "21A":
return DegreeGrid.bcc_grid_2011
def get_degree_situation(self, courses_hist):
'''
......@@ -537,7 +599,7 @@ class DegreeGrid:
"CI064": ["CI055"],
"CI067": ["CI055"],
"CI237": ["CM046"],
"CM005": ["CI045"],
"CM005": ["CM045"],
"CM202": ["CM201"],
##
......
......@@ -40,17 +40,20 @@
{% for semester in analysis_result.grid%}
<div class="semestre">
<div class="grade-head">{{forloop.counter}}º</div>
{% for course in semester %}
<div data-toggle="tooltip" data-placement="top" title="{{course.name}}" class="materia {{ course.situation}}">
<div class="info">
{% if course.is_real_code %}
<span class="name">
<a href="{% url 'course:detail' submission_id=submission.id codigo_disciplina=course.code|remove_spaces %}">
{{ course.code }}</a>
{% for course in semester %}
<div data-toggle="tooltip" data-placement="top" title="{{course.name}}" class="materia {{ course.situation}}">
<div class="info">
<span class="name">
{% if course.is_real_code %}
<a href="{% url 'course:detail' submission_id=submission.id codigo_disciplina=course.code|remove_spaces %}">
{{ course.code }}</a>
{% else %}
{{ course.code }}
{% endif %}
{% if course.is_blocked %}
<i class="fa fa-lock pl-3" title="Faltando pré-requisitos. Disciplina bloqueada."></i>
{% endif %}
</span>
{% else %}
<span class="name">{{ course.code }}</span>
{% endif %}
</div>
<div class="details">
{% if course.detail %}
......@@ -59,16 +62,16 @@
<span class="detail-value">{{course.detail.count}}</span>
</div>
<div class="detail">
<span class="detail-name">Nota média</span>
<span class="detail-value">{{course.detail.mean_grade|fix_2digit}}</span>
<span class="detail-name">Última nota</span>
<span class="detail-value">{{course.detail.last_grade|fix_2digit}}</span>
</div>
{% endif %}
</div>
</div>
{% endfor %}
</div>
{% endfor %}
</div>
{% endfor %}
</div>
<br>
......
......@@ -31,6 +31,7 @@
<a class="nav-item nav-link" href="#abandono" aria-controls="abandono" data-toggle="tab">Abandono</a>
<a class="nav-item nav-link" href="#desistencia" aria-controls="desistencia" data-toggle="tab">Desistência</a>
<a class="nav-item nav-link" href="#outras" aria-controls="outras" data-toggle="tab">Outras Formas de Evasão</a>
<a class="nav-item nav-link" href="#formandos" aria-controls="formandos" data-toggle="tab">Possíveis Formandos</a>
{% for phase_name, phase_value in grid_phases %}
<a class="nav-item nav-link" href="#phase_{{phase_name|remove_spaces}}" aria-controls="{{phase_name}}" data-toggle="tab">{{phase_name}}</a>
{% endfor %}
......@@ -148,7 +149,28 @@
</tbody>
</table>
</div>
<div role="tabpanel" class="tab-pane" id="formandos">
<table class="table table-striped table-bordered" id="formandos_table">
<thead>
<tr>
<th class="col-md-2">GRR</th>
<th class="col-md-4">Nome</th>
<th class="col-md-2">IRA</th>
<th class="col-md-2">{{formandos.description_name}}</th>
</tr>
</thead>
<tbody>
{% for l in formandos.student_list %}
<tr>
<td class="col-md-2"><a href="{% url 'student:detail' grr=l.grr submission_id=submission.id %}">{{ l.grr }}</a></td>
<td class="col-md-4">{{ l.nome }}</td>
<td class="col-md-2">{{ l.ira|floatformat:2 }}</td>
<td class="col-md-2">{{ l.description_value }}</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
{% for phase_name, phase_value in grid_phases %}
<div role="tabpanel" class="tab-pane" id="phase_{{phase_name|remove_spaces}}">
......@@ -188,6 +210,7 @@
$("#abandono_table"),
$("#desistencia_table"),
$("#outros_table"),
$("#formandos_table"),
{% for phase_name, phase_value in grid_phases %}
$("#phase_{{phase_name|remove_spaces}}_table"),
{% endfor %}
......
......@@ -41,8 +41,8 @@ def detail(request, submission_id, grr):
grid_info, grid_info_extra = dg.get_situation(hist)
grid_phases = dg.grid_detail.phases # Dictionary
# Parse to list of tuples
# Parse to list of tuple
grid_phases_values = []
for phase_name in grid_phases:
list_phase_val = get_list_students(
......@@ -128,6 +128,13 @@ def index(request, submission_id):
"Outro",
submission_id
)
formandos = get_list_students(
request.session,
degree,
"Formandos",
submission_id
)
dg = DegreeGrid(DegreeGrid.bcc_grid_2011)
......@@ -156,6 +163,7 @@ def index(request, submission_id):
'abandono': abandono,
'desistencia': desistencia,
'outros': outros,
'formandos': formandos,
"submission": submission,
"situations_pass": situations_pass,
"situations_fail": situations_fail,
......
......@@ -344,11 +344,12 @@ class Course(Analysis):
for i in rate_data[0].index:
if i[0] not in aprovacao_d:
aprovacao_d[i[0]] = {}
periodo = str(i[1]) + "/" + str(i[2])
aprovacao_d[i[0]][periodo] = [
float(rate_data[0][i]),
int(rate_data[rate_it.count_sel][i])]
int(rate_data[1][i]),
int(rate_data[2][i]),]
note = self.analysis["general_note_statistic"]
note_last_year = self.analysis["last_year_statistic"]
......
......@@ -6,6 +6,7 @@ from submission.analysis.utils.situations import Situation, EvasionForm
from submission.analysis.utils.utils import IntervalCount, save_json
from submission.analysis.analysis.student_analysis import *
from submission.analysis.analysis.student_analysis import StudentAnalysis
def average_graduation(df):
......@@ -234,6 +235,56 @@ def period_evasion_graph(df):
for di in di_qtd:
qtd = di_qtd[di]
dic[di] = {'qtd': qtd, 'taxa': (float(qtd)/evasions_total)*100}
return dic
def evasion_per_period_graph(df):
"""
Build the dict for the graph that displays how many people evaded in each period of the grid
Filter df for evaded people and the needed columns
apply current_period() and counts how many times each period is returned
Returns
-------
dict of {int: int}
evasions_period = {
period: number of people evaded,
...
}
Examples
--------
{8: 3, 1: 69, 2: 48, 3: 21, 4: 14}
"""
rows = (df.FORMA_EVASAO != EvasionForm.EF_ATIVO) & (df.FORMA_EVASAO != EvasionForm.EF_FORMATURA) & (df.FORMA_EVASAO != EvasionForm.EF_REINTEGRACAO)
cols = ["MATR_ALUNO", "NUM_VERSAO_x", "COD_ATIV_CURRIC", "SITUACAO"]
evaded_students = df.loc[rows, cols]
periods = StudentAnalysis.current_period(evaded_students).values()
evasions_period = defaultdict(int)
for number in periods:
evasions_period[number] += 1
return evasions_period
def build_dict_ira_medio(alunos):
dic = {"00-4.9":0, "05-9.9":0, "10-14.9":0, "15-19.9":0, "20-24.9":0, "25-29.9":0, "30-34.9":0,
"35-39.9":0, "40-44.9":0, "45-49.9":0, "50-54.9":0, "55-59.9":0, "60-64.9":0, "65-69.9":0,
"70-74.9":0, "75-79.9":0, "80-84.9":0, "85-89.9":0, "90-94.9": 0,"95-100":0}
iras = []
for index, row in alunos.iterrows():
if(row['MEDIA_FINAL'] is not None):
iras.append(row['MEDIA_FINAL'])
for d in dic:
aux = d.split('-')
v1 = float(aux[0])
if (v1 == 0.0):
v1 += 0.01
v2 = float(aux[1])
dic[d] = sum((float(num) >= v1) and (float(num) < v2) for num in iras)
return dic
......@@ -328,6 +379,7 @@ def build_degree_json(path,df,student_analysis):
"taxa_reprovacao": general_failure(df),
"taxa_reprovacao_atual": current_students_failure(df),
"tempo_formatura": average_graduation_time(df),
"evasao_grafico2": evasion_per_period_graph(df),
}
save_json(path+"/degree.json", degree_json)
......
import numpy as np
from submission.analysis.utils.situations import *
from submission.analysis.utils.utils import memoize
import pandas as pd
from collections import defaultdict
from student.grid import DegreeGrid
......@@ -117,6 +119,76 @@ class StudentAnalysis:
list_phases[phase_name]["description_name"] = "Disciplinas restantes"
return list_phases
def list_students_trainees(self, df=None):
df = df if df is not None else self.data_frame
iras = self.ira_alunos()
df = df[df["FORMA_EVASAO"] == EvasionForm.EF_ATIVO]
groups = df.groupby("MATR_ALUNO")
# Parse phases lists to sets before start the checkage
phases = self.dg.grid_detail.phases
# Transforme grid matrix into list
flatten = lambda l: [item for sublist in l for item in sublist]
list_phases = defaultdict(dict)
intended_semester = self.periodo_pretendido(df)
SITUATION_PASS_OR_MATR = (
Situation.SIT_APROVADO,
Situation.SIT_CONHECIMENTO_APROVADO,
Situation.SIT_DISPENSA_COM_NOTA,
Situation.SIT_APROV_ADIANTAMENTO,
Situation.SIT_EQUIVALENCIA,
Situation.SIT_MATRICULA
)
student_list = []
for grr,group in groups:
to_do = flatten(self.dg.grid_detail.grid)
# Each row of sub dataframe have the same "NOME_PESSOA" value
people_name = group["NOME_PESSOA"][0]
group = group[ group['SITUACAO'].isin(SITUATION_PASS_OR_MATR) ]
approved_matr_courses = group["COD_ATIV_CURRIC"].values
# Replace real codes for "fake codes" from grid e.g.: CI204 -> OPT
for fake_code in self.dg.grid_detail.fake_codes:
approved_matr_courses = [fake_code if self.dg.grid_detail.is_equivalence(code,fake_code)
else code for code in approved_matr_courses ]
# Total if courses needed fot a student complete a phase
for code1 in approved_matr_courses:
# If the student did the course then remove it from to do list
if (code1 in to_do):
i = to_do.index(code1)
del to_do[i]
# Total courses that is left to do to graduate
debpt = len(to_do)
if (not debpt):
student_list.append({
"grr":grr,
"nome": people_name,
"ira": iras[grr],
"description_value":intended_semester[grr]
})
return {
"student_list": student_list,
"description_name": "Semestres de curso"
}
def ira_alunos(self, df=None):
......@@ -238,7 +310,86 @@ class StudentAnalysis:
students[x[0]] = None
return students
def current_period(df):
"""
Calculate someone's current period
Filter df for approved courses and group by student
For every student:
Attribute the followed grid
Checks if courses of period p are completed:
do this for obligatory and optatives
TO DO check for equivalents courses too
stops when a period is incompleted
(the current period is the first incompleted one)
Returns:
---------
dict of {string: int}
{"GRR": current period, "GRR": current period, ...}
"""
# filter for approved situtations and group df by student
df = df[df['SITUACAO'].isin(Situation.SITUATION_PASS)]
students_df = df.groupby("MATR_ALUNO")
student_period = {}
for student, dataframe in students_df:
# TO DO: grid recebe a grade que a pessoa segue (curso e ano)
if dataframe.iloc[0]["NUM_VERSAO_x"] == 2011:
# the academic grid is a list of lists from src/student/grid.py
grid = DegreeGrid.get_degree_grid("21A").grid
fake_codes = DegreeGrid.get_degree_grid("21A").fake_codes
opts_tgs = list(DegreeGrid.get_degree_grid("21A").equiv_codes)
else:
continue
max_period = len(grid)-1
p = 0
period_completed = 1
checked = []
while (p < max_period):
c = 0
while c < len(grid[p]):
course = grid[p][c]
coursed = 0
# course is a normal obligatory code
if course in dataframe['COD_ATIV_CURRIC'].values:
coursed = 1
# course is a optative or tg
elif course in fake_codes:
for item in opts_tgs:
if item not in checked:
if item in dataframe['COD_ATIV_CURRIC'].values:
checked.append(item)
coursed = 1
break
# to do: caso em que recebeu equivalencia na disciplina
# equivs =
# for equiv in equivs:
# if equiv in dataframe['COD_ATIV_CURRIC'].values:
# checked.append(item)