Skip to content
Projects
Groups
Snippets
Help
This project
Loading...
Sign in / Register
Toggle navigation
E
edx-platform
Overview
Overview
Details
Activity
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
edx
edx-platform
Commits
2cfeb34f
Commit
2cfeb34f
authored
Dec 04, 2015
by
Sarina Canelake
Browse files
Options
Browse Files
Download
Plain Diff
Merge pull request #10825 from edx/kill-psycho
Remove psychometrics app
parents
7429a32b
e932632b
Hide whitespace changes
Inline
Side-by-side
Showing
17 changed files
with
2 additions
and
615 deletions
+2
-615
common/lib/xmodule/xmodule/capa_base.py
+0
-7
common/test/db_cache/bok_choy_schema.sql
+0
-14
lms/djangoapps/courseware/module_render.py
+0
-6
lms/djangoapps/courseware/tests/test_module_render.py
+0
-11
lms/djangoapps/instructor/views/legacy.py
+1
-16
lms/djangoapps/psychometrics/__init__.py
+0
-0
lms/djangoapps/psychometrics/admin.py
+0
-8
lms/djangoapps/psychometrics/management/__init__.py
+0
-0
lms/djangoapps/psychometrics/management/commands/__init__.py
+0
-0
lms/djangoapps/psychometrics/management/commands/init_psychometrics.py
+0
-66
lms/djangoapps/psychometrics/migrations/0001_initial.py
+0
-24
lms/djangoapps/psychometrics/migrations/__init__.py
+0
-0
lms/djangoapps/psychometrics/models.py
+0
-46
lms/djangoapps/psychometrics/psychoanalyze.py
+0
-362
lms/envs/common.py
+0
-3
lms/envs/dev.py
+0
-1
lms/templates/courseware/legacy_instructor_dashboard.html
+1
-51
No files found.
common/lib/xmodule/xmodule/capa_base.py
View file @
2cfeb34f
...
@@ -1125,9 +1125,6 @@ class CapaMixin(CapaFields):
...
@@ -1125,9 +1125,6 @@ class CapaMixin(CapaFields):
self
.
attempts
,
self
.
attempts
,
)
)
if
hasattr
(
self
.
runtime
,
'psychometrics_handler'
):
# update PsychometricsData using callback
self
.
runtime
.
psychometrics_handler
(
self
.
get_state_for_lcp
())
# render problem into HTML
# render problem into HTML
html
=
self
.
get_problem_html
(
encapsulate
=
False
)
html
=
self
.
get_problem_html
(
encapsulate
=
False
)
...
@@ -1375,10 +1372,6 @@ class CapaMixin(CapaFields):
...
@@ -1375,10 +1372,6 @@ class CapaMixin(CapaFields):
event_info
[
'attempts'
]
=
self
.
attempts
event_info
[
'attempts'
]
=
self
.
attempts
self
.
track_function_unmask
(
'problem_rescore'
,
event_info
)
self
.
track_function_unmask
(
'problem_rescore'
,
event_info
)
# psychometrics should be called on rescoring requests in the same way as check-problem
if
hasattr
(
self
.
runtime
,
'psychometrics_handler'
):
# update PsychometricsData using callback
self
.
runtime
.
psychometrics_handler
(
self
.
get_state_for_lcp
())
return
{
'success'
:
success
}
return
{
'success'
:
success
}
def
save_problem
(
self
,
data
):
def
save_problem
(
self
,
data
):
...
...
common/test/db_cache/bok_choy_schema.sql
View file @
2cfeb34f
...
@@ -2528,20 +2528,6 @@ CREATE TABLE `programs_programsapiconfig` (
...
@@ -2528,20 +2528,6 @@ CREATE TABLE `programs_programsapiconfig` (
CONSTRAINT
`programs_programsa_changed_by_id_b7c3b49d5c0dcd3_fk_auth_user_id`
FOREIGN
KEY
(
`changed_by_id`
)
REFERENCES
`auth_user`
(
`id`
)
CONSTRAINT
`programs_programsa_changed_by_id_b7c3b49d5c0dcd3_fk_auth_user_id`
FOREIGN
KEY
(
`changed_by_id`
)
REFERENCES
`auth_user`
(
`id`
)
)
ENGINE
=
InnoDB
DEFAULT
CHARSET
=
utf8
;
)
ENGINE
=
InnoDB
DEFAULT
CHARSET
=
utf8
;
/*!40101 SET character_set_client = @saved_cs_client */
;
/*!40101 SET character_set_client = @saved_cs_client */
;
DROP
TABLE
IF
EXISTS
`psychometrics_psychometricdata`
;
/*!40101 SET @saved_cs_client = @@character_set_client */
;
/*!40101 SET character_set_client = utf8 */
;
CREATE
TABLE
`psychometrics_psychometricdata`
(
`id`
int
(
11
)
NOT
NULL
AUTO_INCREMENT
,
`done`
tinyint
(
1
)
NOT
NULL
,
`attempts`
int
(
11
)
NOT
NULL
,
`checktimes`
longtext
,
`studentmodule_id`
int
(
11
)
NOT
NULL
,
PRIMARY
KEY
(
`id`
),
UNIQUE
KEY
`studentmodule_id`
(
`studentmodule_id`
),
CONSTRAINT
`D758b867e6fa9161734bd9cb58b9a485`
FOREIGN
KEY
(
`studentmodule_id`
)
REFERENCES
`courseware_studentmodule`
(
`id`
)
)
ENGINE
=
InnoDB
DEFAULT
CHARSET
=
utf8
;
/*!40101 SET character_set_client = @saved_cs_client */
;
DROP
TABLE
IF
EXISTS
`self_paced_selfpacedconfiguration`
;
DROP
TABLE
IF
EXISTS
`self_paced_selfpacedconfiguration`
;
/*!40101 SET @saved_cs_client = @@character_set_client */
;
/*!40101 SET @saved_cs_client = @@character_set_client */
;
/*!40101 SET character_set_client = utf8 */
;
/*!40101 SET character_set_client = utf8 */
;
...
...
lms/djangoapps/courseware/module_render.py
View file @
2cfeb34f
...
@@ -56,7 +56,6 @@ from openedx.core.lib.xblock_utils import (
...
@@ -56,7 +56,6 @@ from openedx.core.lib.xblock_utils import (
wrap_xblock
,
wrap_xblock
,
request_token
as
xblock_request_token
,
request_token
as
xblock_request_token
,
)
)
from
psychometrics.psychoanalyze
import
make_psychometrics_data_update_handler
from
student.models
import
anonymous_id_for_user
,
user_by_anonymous_id
from
student.models
import
anonymous_id_for_user
,
user_by_anonymous_id
from
student.roles
import
CourseBetaTesterRole
from
student.roles
import
CourseBetaTesterRole
from
xblock.core
import
XBlock
from
xblock.core
import
XBlock
...
@@ -760,11 +759,6 @@ def get_module_system_for_user(user, student_data, # TODO # pylint: disable=to
...
@@ -760,11 +759,6 @@ def get_module_system_for_user(user, student_data, # TODO # pylint: disable=to
position
=
None
position
=
None
system
.
set
(
'position'
,
position
)
system
.
set
(
'position'
,
position
)
if
settings
.
FEATURES
.
get
(
'ENABLE_PSYCHOMETRICS'
)
and
user
.
is_authenticated
():
system
.
set
(
'psychometrics_handler'
,
# set callback for updating PsychometricsData
make_psychometrics_data_update_handler
(
course_id
,
user
,
descriptor
.
location
)
)
system
.
set
(
u'user_is_staff'
,
user_is_staff
)
system
.
set
(
u'user_is_staff'
,
user_is_staff
)
system
.
set
(
u'user_is_admin'
,
bool
(
has_access
(
user
,
u'staff'
,
'global'
)))
system
.
set
(
u'user_is_admin'
,
bool
(
has_access
(
user
,
u'staff'
,
'global'
)))
...
...
lms/djangoapps/courseware/tests/test_module_render.py
View file @
2cfeb34f
...
@@ -1767,17 +1767,6 @@ class TestRebindModule(TestSubmittingProblems):
...
@@ -1767,17 +1767,6 @@ class TestRebindModule(TestSubmittingProblems):
self
.
assertEqual
(
module
.
scope_ids
.
user_id
,
user2
.
id
)
self
.
assertEqual
(
module
.
scope_ids
.
user_id
,
user2
.
id
)
self
.
assertEqual
(
module
.
descriptor
.
scope_ids
.
user_id
,
user2
.
id
)
self
.
assertEqual
(
module
.
descriptor
.
scope_ids
.
user_id
,
user2
.
id
)
@patch
(
'courseware.module_render.make_psychometrics_data_update_handler'
)
@patch.dict
(
settings
.
FEATURES
,
{
'ENABLE_PSYCHOMETRICS'
:
True
})
def
test_psychometrics_anonymous
(
self
,
psycho_handler
):
"""
Make sure that noauth modules with anonymous users don't have
the psychometrics callback bound.
"""
module
=
self
.
get_module_for_user
(
self
.
anon_user
)
module
.
system
.
rebind_noauth_module_to_user
(
module
,
self
.
anon_user
)
self
.
assertFalse
(
psycho_handler
.
called
)
@attr
(
'shard_1'
)
@attr
(
'shard_1'
)
@ddt.ddt
@ddt.ddt
...
...
lms/djangoapps/instructor/views/legacy.py
View file @
2cfeb34f
...
@@ -48,7 +48,6 @@ from instructor_task.api import (
...
@@ -48,7 +48,6 @@ from instructor_task.api import (
from
instructor_task.views
import
get_task_completion_info
from
instructor_task.views
import
get_task_completion_info
from
edxmako.shortcuts
import
render_to_response
,
render_to_string
from
edxmako.shortcuts
import
render_to_response
,
render_to_string
from
class_dashboard
import
dashboard_data
from
class_dashboard
import
dashboard_data
from
psychometrics
import
psychoanalyze
from
student.models
import
(
from
student.models
import
(
CourseEnrollment
,
CourseEnrollment
,
CourseEnrollmentAllowed
,
CourseEnrollmentAllowed
,
...
@@ -97,7 +96,7 @@ def instructor_dashboard(request, course_id):
...
@@ -97,7 +96,7 @@ def instructor_dashboard(request, course_id):
plots
=
[]
plots
=
[]
datatable
=
{}
datatable
=
{}
# the instructor dashboard page is modal: grades,
psychometrics,
admin
# the instructor dashboard page is modal: grades, admin
# keep that state in request.session (defaults to grades mode)
# keep that state in request.session (defaults to grades mode)
idash_mode
=
request
.
POST
.
get
(
'idash_mode'
,
''
)
idash_mode
=
request
.
POST
.
get
(
'idash_mode'
,
''
)
idash_mode_key
=
u'idash_mode:{0}'
.
format
(
course_id
)
idash_mode_key
=
u'idash_mode:{0}'
.
format
(
course_id
)
...
@@ -320,18 +319,6 @@ def instructor_dashboard(request, course_id):
...
@@ -320,18 +319,6 @@ def instructor_dashboard(request, course_id):
datatable
=
ret
[
'datatable'
]
datatable
=
ret
[
'datatable'
]
#----------------------------------------
#----------------------------------------
# psychometrics
elif
action
==
'Generate Histogram and IRT Plot'
:
problem
=
request
.
POST
[
'Problem'
]
nmsg
,
plots
=
psychoanalyze
.
generate_plots_for_problem
(
problem
)
msg
+=
nmsg
track
.
views
.
server_track
(
request
,
"psychometrics-histogram-generation"
,
{
"problem"
:
unicode
(
problem
)},
page
=
"idashboard"
)
if
idash_mode
==
'Psychometrics'
:
problems
=
psychoanalyze
.
problems_with_psychometric_data
(
course_key
)
#----------------------------------------
# analytics
# analytics
def
get_analytics_result
(
analytics_name
):
def
get_analytics_result
(
analytics_name
):
"""Return data for an Analytic piece, or None if it doesn't exist. It
"""Return data for an Analytic piece, or None if it doesn't exist. It
...
@@ -435,8 +422,6 @@ def instructor_dashboard(request, course_id):
...
@@ -435,8 +422,6 @@ def instructor_dashboard(request, course_id):
'show_email_tab'
:
show_email_tab
,
# email
'show_email_tab'
:
show_email_tab
,
# email
'problems'
:
problems
,
# psychometrics
'plots'
:
plots
,
# psychometrics
'course_errors'
:
modulestore
()
.
get_course_errors
(
course
.
id
),
'course_errors'
:
modulestore
()
.
get_course_errors
(
course
.
id
),
'instructor_tasks'
:
instructor_tasks
,
'instructor_tasks'
:
instructor_tasks
,
'offline_grade_log'
:
offline_grades_available
(
course_key
),
'offline_grade_log'
:
offline_grades_available
(
course_key
),
...
...
lms/djangoapps/psychometrics/__init__.py
deleted
100644 → 0
View file @
7429a32b
lms/djangoapps/psychometrics/admin.py
deleted
100644 → 0
View file @
7429a32b
'''
django admin pages for courseware model
'''
from
psychometrics.models
import
PsychometricData
from
django.contrib
import
admin
admin
.
site
.
register
(
PsychometricData
)
lms/djangoapps/psychometrics/management/__init__.py
deleted
100644 → 0
View file @
7429a32b
lms/djangoapps/psychometrics/management/commands/__init__.py
deleted
100644 → 0
View file @
7429a32b
lms/djangoapps/psychometrics/management/commands/init_psychometrics.py
deleted
100644 → 0
View file @
7429a32b
#!/usr/bin/python
#
# generate pyschometrics data from tracking logs and student module data
import
json
from
courseware.models
import
StudentModule
from
track.models
import
TrackingLog
from
psychometrics.models
import
PsychometricData
from
django.conf
import
settings
from
django.core.management.base
import
BaseCommand
#db = "ocwtutor" # for debugging
#db = "default"
db
=
getattr
(
settings
,
'DATABASE_FOR_PSYCHOMETRICS'
,
'default'
)
class
Command
(
BaseCommand
):
help
=
"initialize PsychometricData tables from StudentModule instances (and tracking data, if in SQL)."
help
+=
"Note this is done for all courses for which StudentModule instances exist."
def
handle
(
self
,
*
args
,
**
options
):
# delete all pmd
#PsychometricData.objects.all().delete()
#PsychometricData.objects.using(db).all().delete()
smset
=
StudentModule
.
objects
.
using
(
db
)
.
exclude
(
max_grade
=
None
)
for
sm
in
smset
:
usage_key
=
sm
.
module_state_key
if
not
usage_key
.
block_type
==
"problem"
:
continue
try
:
state
=
json
.
loads
(
sm
.
state
)
done
=
state
[
'done'
]
except
:
print
"Oops, failed to eval state for
%
s (state=
%
s)"
%
(
sm
,
sm
.
state
)
continue
if
done
:
# only keep if problem completed
try
:
pmd
=
PsychometricData
.
objects
.
using
(
db
)
.
get
(
studentmodule
=
sm
)
except
PsychometricData
.
DoesNotExist
:
pmd
=
PsychometricData
(
studentmodule
=
sm
)
pmd
.
done
=
done
pmd
.
attempts
=
state
[
'attempts'
]
# get attempt times from tracking log
uname
=
sm
.
student
.
username
tset
=
TrackingLog
.
objects
.
using
(
db
)
.
filter
(
username
=
uname
,
event_type__contains
=
'problem_check'
)
tset
=
tset
.
filter
(
event_source
=
'server'
)
tset
=
tset
.
filter
(
event__contains
=
"'
%
s'"
%
usage_key
)
checktimes
=
[
x
.
dtcreated
for
x
in
tset
]
pmd
.
checktimes
=
checktimes
if
not
len
(
checktimes
)
==
pmd
.
attempts
:
print
"Oops, mismatch in number of attempts and check times for
%
s"
%
pmd
#print pmd
pmd
.
save
(
using
=
db
)
print
"
%
d PMD entries"
%
PsychometricData
.
objects
.
using
(
db
)
.
all
()
.
count
()
lms/djangoapps/psychometrics/migrations/0001_initial.py
deleted
100644 → 0
View file @
7429a32b
# -*- coding: utf-8 -*-
from
__future__
import
unicode_literals
from
django.db
import
migrations
,
models
class
Migration
(
migrations
.
Migration
):
dependencies
=
[
(
'courseware'
,
'0001_initial'
),
]
operations
=
[
migrations
.
CreateModel
(
name
=
'PsychometricData'
,
fields
=
[
(
'id'
,
models
.
AutoField
(
verbose_name
=
'ID'
,
serialize
=
False
,
auto_created
=
True
,
primary_key
=
True
)),
(
'done'
,
models
.
BooleanField
(
default
=
False
)),
(
'attempts'
,
models
.
IntegerField
(
default
=
0
)),
(
'checktimes'
,
models
.
TextField
(
null
=
True
,
blank
=
True
)),
(
'studentmodule'
,
models
.
OneToOneField
(
to
=
'courseware.StudentModule'
)),
],
),
]
lms/djangoapps/psychometrics/migrations/__init__.py
deleted
100644 → 0
View file @
7429a32b
lms/djangoapps/psychometrics/models.py
deleted
100644 → 0
View file @
7429a32b
#
# db model for psychometrics data
#
# this data is collected in real time
#
from
django.db
import
models
from
courseware.models
import
StudentModule
class
PsychometricData
(
models
.
Model
):
"""
This data is a table linking student, module, and module performance,
including number of attempts, grade, max grade, and time of checks.
Links to instances of StudentModule, but only those for capa problems.
Note that StudentModule.module_state_key is a :class:`Location` instance.
checktimes is extracted from tracking logs, or added by capa module via psychometrics callback.
"""
class
Meta
(
object
):
app_label
=
"psychometrics"
studentmodule
=
models
.
OneToOneField
(
StudentModule
,
db_index
=
True
)
# contains student, module_state_key, course_id
done
=
models
.
BooleanField
(
default
=
False
)
attempts
=
models
.
IntegerField
(
default
=
0
)
# extracted from studentmodule.state
checktimes
=
models
.
TextField
(
null
=
True
,
blank
=
True
)
# internally stored as list of datetime objects
# keep in mind
# grade = studentmodule.grade
# max_grade = studentmodule.max_grade
# student = studentmodule.student
# course_id = studentmodule.course_id
# location = studentmodule.module_state_key
def
__unicode__
(
self
):
sm
=
self
.
studentmodule
return
"[PsychometricData]
%
s url=
%
s, grade=
%
s, max=
%
s, attempts=
%
s, ct=
%
s"
%
(
sm
.
student
,
sm
.
module_state_key
,
sm
.
grade
,
sm
.
max_grade
,
self
.
attempts
,
self
.
checktimes
)
lms/djangoapps/psychometrics/psychoanalyze.py
deleted
100644 → 0
View file @
7429a32b
#
# File: psychometrics/psychoanalyze.py
#
# generate pyschometrics plots from PsychometricData
from
__future__
import
division
import
datetime
import
logging
import
json
import
math
import
numpy
as
np
from
opaque_keys.edx.locator
import
BlockUsageLocator
from
scipy.optimize
import
curve_fit
from
django.conf
import
settings
from
django.db.models
import
Sum
,
Max
from
psychometrics.models
import
PsychometricData
from
courseware.models
import
StudentModule
from
pytz
import
UTC
log
=
logging
.
getLogger
(
"edx.psychometrics"
)
#db = "ocwtutor" # for debugging
#db = "default"
db
=
getattr
(
settings
,
'DATABASE_FOR_PSYCHOMETRICS'
,
'default'
)
#-----------------------------------------------------------------------------
# fit functions
def
func_2pl
(
x
,
a
,
b
):
"""
2-parameter logistic function
"""
D
=
1.7
edax
=
np
.
exp
(
D
*
a
*
(
x
-
b
))
return
edax
/
(
1
+
edax
)
#-----------------------------------------------------------------------------
# statistics class
class
StatVar
(
object
):
"""
Simple statistics on floating point numbers: avg, sdv, var, min, max
"""
def
__init__
(
self
,
unit
=
1
):
self
.
sum
=
0
self
.
sum2
=
0
self
.
cnt
=
0
self
.
unit
=
unit
self
.
min
=
None
self
.
max
=
None
def
add
(
self
,
x
):
if
x
is
None
:
return
if
self
.
min
is
None
:
self
.
min
=
x
else
:
if
x
<
self
.
min
:
self
.
min
=
x
if
self
.
max
is
None
:
self
.
max
=
x
else
:
if
x
>
self
.
max
:
self
.
max
=
x
self
.
sum
+=
x
self
.
sum2
+=
x
**
2
self
.
cnt
+=
1
def
avg
(
self
):
if
self
.
cnt
is
None
:
return
0
return
self
.
sum
/
1.0
/
self
.
cnt
/
self
.
unit
def
var
(
self
):
if
self
.
cnt
is
None
:
return
0
return
(
self
.
sum2
/
1.0
/
self
.
cnt
/
(
self
.
unit
**
2
))
-
(
self
.
avg
()
**
2
)
def
sdv
(
self
):
v
=
self
.
var
()
if
v
>
0
:
return
math
.
sqrt
(
v
)
else
:
return
0
def
__str__
(
self
):
return
'cnt=
%
d, avg=
%
f, sdv=
%
f'
%
(
self
.
cnt
,
self
.
avg
(),
self
.
sdv
())
def
__add__
(
self
,
x
):
self
.
add
(
x
)
return
self
#-----------------------------------------------------------------------------
# histogram generator
def
make_histogram
(
ydata
,
bins
=
None
):
'''
Generate histogram of ydata using bins provided, or by default bins
from 0 to 100 by 10. bins should be ordered in increasing order.
returns dict with keys being bins, and values being counts.
special: hist['bins'] = bins
'''
if
bins
is
None
:
bins
=
range
(
0
,
100
,
10
)
nbins
=
len
(
bins
)
hist
=
dict
(
zip
(
bins
,
[
0
]
*
nbins
))
for
y
in
ydata
:
for
b
in
bins
[::
-
1
]:
# in reverse order
if
y
>
b
:
hist
[
b
]
+=
1
break
# hist['bins'] = bins
return
hist
#-----------------------------------------------------------------------------
def
problems_with_psychometric_data
(
course_id
):
'''
Return dict of {problems (location urls): count} for which psychometric data is available.
Does this for a given course_id.
'''
pmdset
=
PsychometricData
.
objects
.
using
(
db
)
.
filter
(
studentmodule__course_id
=
course_id
)
plist
=
[
p
[
'studentmodule__module_state_key'
]
for
p
in
pmdset
.
values
(
'studentmodule__module_state_key'
)
.
distinct
()]
problems
=
dict
(
(
p
,
pmdset
.
filter
(
studentmodule__module_state_key
=
BlockUsageLocator
.
from_string
(
p
)
)
.
count
()
)
for
p
in
plist
)
return
problems
#-----------------------------------------------------------------------------
def
generate_plots_for_problem
(
problem
):
pmdset
=
PsychometricData
.
objects
.
using
(
db
)
.
filter
(
studentmodule__module_state_key
=
BlockUsageLocator
.
from_string
(
problem
)
)
nstudents
=
pmdset
.
count
()
msg
=
""
plots
=
[]
if
nstudents
<
2
:
msg
+=
"
%
s nstudents=
%
d --> skipping, too few"
%
(
problem
,
nstudents
)
return
msg
,
plots
max_grade
=
pmdset
[
0
]
.
studentmodule
.
max_grade
agdat
=
pmdset
.
aggregate
(
Sum
(
'attempts'
),
Max
(
'attempts'
))
max_attempts
=
agdat
[
'attempts__max'
]
total_attempts
=
agdat
[
'attempts__sum'
]
# not used yet
msg
+=
"max attempts =
%
d"
%
max_attempts
xdat
=
range
(
1
,
max_attempts
+
1
)
dataset
=
{
'xdat'
:
xdat
}
# compute grade statistics
grades
=
[
pmd
.
studentmodule
.
grade
for
pmd
in
pmdset
]
gsv
=
StatVar
()
for
g
in
grades
:
gsv
+=
g
msg
+=
"<br><p><font color='blue'>Grade distribution:
%
s</font></p>"
%
gsv
# generate grade histogram
ghist
=
[]
axisopts
=
"""{
xaxes: [{
axisLabel: 'Grade'
}],
yaxes: [{
position: 'left',
axisLabel: 'Count'
}]
}"""
if
gsv
.
max
>
max_grade
:
msg
+=
"<br/><p><font color='red'>Something is wrong: max_grade=
%
s, but max(grades)=
%
s</font></p>"
%
(
max_grade
,
gsv
.
max
)
max_grade
=
gsv
.
max
if
max_grade
>
1
:
ghist
=
make_histogram
(
grades
,
np
.
linspace
(
0
,
max_grade
,
max_grade
+
1
))
ghist_json
=
json
.
dumps
(
ghist
.
items
())
plot
=
{
'title'
:
"Grade histogram for
%
s"
%
problem
,
'id'
:
'histogram'
,
'info'
:
''
,
'data'
:
"var dhist =
%
s;
\n
"
%
ghist_json
,
'cmd'
:
'[ {data: dhist, bars: { show: true, align: "center" }} ],
%
s'
%
axisopts
,
}
plots
.
append
(
plot
)
else
:
msg
+=
"<br/>Not generating histogram: max_grade=
%
s"
%
max_grade
# histogram of time differences between checks
# Warning: this is inefficient - doesn't scale to large numbers of students
dtset
=
[]
# time differences in minutes
dtsv
=
StatVar
()
for
pmd
in
pmdset
:
try
:
checktimes
=
eval
(
pmd
.
checktimes
)
# update log of attempt timestamps
except
:
continue
if
len
(
checktimes
)
<
2
:
continue
ct0
=
checktimes
[
0
]
for
ct
in
checktimes
[
1
:]:
dt
=
(
ct
-
ct0
)
.
total_seconds
()
/
60.0
if
dt
<
20
:
# ignore if dt too long
dtset
.
append
(
dt
)
dtsv
+=
dt
ct0
=
ct
if
dtsv
.
cnt
>
2
:
msg
+=
"<br/><p><font color='brown'>Time differences between checks:
%
s</font></p>"
%
dtsv
bins
=
np
.
linspace
(
0
,
1.5
*
dtsv
.
sdv
(),
30
)
dbar
=
bins
[
1
]
-
bins
[
0
]
thist
=
make_histogram
(
dtset
,
bins
)
thist_json
=
json
.
dumps
(
sorted
(
thist
.
items
(),
key
=
lambda
(
x
):
x
[
0
]))
axisopts
=
"""{ xaxes: [{ axisLabel: 'Time (min)'}], yaxes: [{position: 'left',axisLabel: 'Count'}]}"""
plot
=
{
'title'
:
"Histogram of time differences between checks"
,
'id'
:
'thistogram'
,
'info'
:
''
,
'data'
:
"var thist =
%
s;
\n
"
%
thist_json
,
'cmd'
:
'[ {data: thist, bars: { show: true, align: "center", barWidth:
%
f }} ],
%
s'
%
(
dbar
,
axisopts
),
}
plots
.
append
(
plot
)
# one IRT plot curve for each grade received (TODO: this assumes integer grades)
for
grade
in
range
(
1
,
int
(
max_grade
)
+
1
):
yset
=
{}
gset
=
pmdset
.
filter
(
studentmodule__grade
=
grade
)
ngset
=
gset
.
count
()
if
ngset
==
0
:
continue
ydat
=
[]
ylast
=
0
for
x
in
xdat
:
y
=
gset
.
filter
(
attempts
=
x
)
.
count
()
/
ngset
ydat
.
append
(
y
+
ylast
)
ylast
=
y
+
ylast
yset
[
'ydat'
]
=
ydat
if
len
(
ydat
)
>
3
:
# try to fit to logistic function if enough data points
try
:
cfp
=
curve_fit
(
func_2pl
,
xdat
,
ydat
,
[
1.0
,
max_attempts
/
2.0
])
yset
[
'fitparam'
]
=
cfp
yset
[
'fitpts'
]
=
func_2pl
(
np
.
array
(
xdat
),
*
cfp
[
0
])
yset
[
'fiterr'
]
=
[
yd
-
yf
for
(
yd
,
yf
)
in
zip
(
ydat
,
yset
[
'fitpts'
])]
fitx
=
np
.
linspace
(
xdat
[
0
],
xdat
[
-
1
],
100
)
yset
[
'fitx'
]
=
fitx
yset
[
'fity'
]
=
func_2pl
(
np
.
array
(
fitx
),
*
cfp
[
0
])
except
Exception
as
err
:
log
.
debug
(
'Error in psychoanalyze curve fitting:
%
s'
,
err
)
dataset
[
'grade_
%
d'
%
grade
]
=
yset
axisopts
=
"""{
xaxes: [{
axisLabel: 'Number of Attempts'
}],
yaxes: [{
max:1.0,
position: 'left',
axisLabel: 'Probability of correctness'
}]
}"""
# generate points for flot plot
for
grade
in
range
(
1
,
int
(
max_grade
)
+
1
):
jsdata
=
""
jsplots
=
[]
gkey
=
'grade_
%
d'
%
grade
if
gkey
in
dataset
:
yset
=
dataset
[
gkey
]
jsdata
+=
"var d
%
d =
%
s;
\n
"
%
(
grade
,
json
.
dumps
(
zip
(
xdat
,
yset
[
'ydat'
])))
jsplots
.
append
(
'{ data: d
%
d, lines: { show: false }, points: { show: true}, color: "red" }'
%
grade
)
if
'fitpts'
in
yset
:
jsdata
+=
'var fit =
%
s;
\n
'
%
(
json
.
dumps
(
zip
(
yset
[
'fitx'
],
yset
[
'fity'
])))
jsplots
.
append
(
'{ data: fit, lines: { show: true }, color: "blue" }'
)
(
a
,
b
)
=
yset
[
'fitparam'
][
0
]
irtinfo
=
"(2PL: D=1.7, a=
%6.3
f, b=
%6.3
f)"
%
(
a
,
b
)
else
:
irtinfo
=
""
plots
.
append
({
'title'
:
'IRT Plot for grade=
%
s
%
s'
%
(
grade
,
irtinfo
),
'id'
:
"irt
%
s"
%
grade
,
'info'
:
''
,
'data'
:
jsdata
,
'cmd'
:
'[
%
s],
%
s'
%
(
','
.
join
(
jsplots
),
axisopts
),
})
#log.debug('plots = %s' % plots)
return
msg
,
plots
#-----------------------------------------------------------------------------
def
make_psychometrics_data_update_handler
(
course_id
,
user
,
module_state_key
):
"""
Construct and return a procedure which may be called to update
the PsychometricData instance for the given StudentModule instance.
"""
sm
,
status
=
StudentModule
.
objects
.
get_or_create
(
course_id
=
course_id
,
student
=
user
,
module_state_key
=
module_state_key
,
defaults
=
{
'state'
:
'{}'
,
'module_type'
:
'problem'
},
)
try
:
pmd
=
PsychometricData
.
objects
.
using
(
db
)
.
get
(
studentmodule
=
sm
)
except
PsychometricData
.
DoesNotExist
:
pmd
=
PsychometricData
(
studentmodule
=
sm
)
def
psychometrics_data_update_handler
(
state
):
"""
This function may be called each time a problem is successfully checked
(eg on save_problem_check events in capa_module).
state = instance state (a nice, uniform way to interface - for more future psychometric feature extraction)
"""
try
:
state
=
json
.
loads
(
sm
.
state
)
done
=
state
[
'done'
]
except
:
log
.
exception
(
"Oops, failed to eval state for
%
s (state=
%
s)"
,
sm
,
sm
.
state
)
return
pmd
.
done
=
done
try
:
pmd
.
attempts
=
state
.
get
(
'attempts'
,
0
)
except
:
log
.
exception
(
"no attempts for
%
s (state=
%
s)"
,
sm
,
sm
.
state
)
try
:
checktimes
=
eval
(
pmd
.
checktimes
)
# update log of attempt timestamps
except
:
checktimes
=
[]
checktimes
.
append
(
datetime
.
datetime
.
now
(
UTC
))
pmd
.
checktimes
=
checktimes
try
:
pmd
.
save
()
except
:
log
.
exception
(
"Error in updating psychometrics data for
%
s"
,
sm
)
return
psychometrics_data_update_handler
lms/envs/common.py
View file @
2cfeb34f
...
@@ -116,8 +116,6 @@ FEATURES = {
...
@@ -116,8 +116,6 @@ FEATURES = {
# in their emails, and they will have no way to resubscribe.
# in their emails, and they will have no way to resubscribe.
'ENABLE_DISCUSSION_EMAIL_DIGEST'
:
False
,
'ENABLE_DISCUSSION_EMAIL_DIGEST'
:
False
,
'ENABLE_PSYCHOMETRICS'
:
False
,
# real-time psychometrics (eg item response theory analysis in instructor dashboard)
'ENABLE_DJANGO_ADMIN_SITE'
:
True
,
# set true to enable django's admin site, even on prod (e.g. for course ops)
'ENABLE_DJANGO_ADMIN_SITE'
:
True
,
# set true to enable django's admin site, even on prod (e.g. for course ops)
'ENABLE_SQL_TRACKING_LOGS'
:
False
,
'ENABLE_SQL_TRACKING_LOGS'
:
False
,
'ENABLE_LMS_MIGRATION'
:
False
,
'ENABLE_LMS_MIGRATION'
:
False
,
...
@@ -1833,7 +1831,6 @@ INSTALLED_APPS = (
...
@@ -1833,7 +1831,6 @@ INSTALLED_APPS = (
'instructor'
,
'instructor'
,
'instructor_task'
,
'instructor_task'
,
'open_ended_grading'
,
'open_ended_grading'
,
'psychometrics'
,
'licenses'
,
'licenses'
,
'openedx.core.djangoapps.course_groups'
,
'openedx.core.djangoapps.course_groups'
,
'bulk_email'
,
'bulk_email'
,
...
...
lms/envs/dev.py
View file @
2cfeb34f
...
@@ -24,7 +24,6 @@ FEATURES['SUBDOMAIN_COURSE_LISTINGS'] = False # Enable to test subdomains--othe
...
@@ -24,7 +24,6 @@ FEATURES['SUBDOMAIN_COURSE_LISTINGS'] = False # Enable to test subdomains--othe
FEATURES
[
'SUBDOMAIN_BRANDING'
]
=
True
FEATURES
[
'SUBDOMAIN_BRANDING'
]
=
True
FEATURES
[
'FORCE_UNIVERSITY_DOMAIN'
]
=
None
# show all university courses if in dev (ie don't use HTTP_HOST)
FEATURES
[
'FORCE_UNIVERSITY_DOMAIN'
]
=
None
# show all university courses if in dev (ie don't use HTTP_HOST)
FEATURES
[
'ENABLE_MANUAL_GIT_RELOAD'
]
=
True
FEATURES
[
'ENABLE_MANUAL_GIT_RELOAD'
]
=
True
FEATURES
[
'ENABLE_PSYCHOMETRICS'
]
=
False
# real-time psychometrics (eg item response theory analysis in instructor dashboard)
FEATURES
[
'ENABLE_SERVICE_STATUS'
]
=
True
FEATURES
[
'ENABLE_SERVICE_STATUS'
]
=
True
FEATURES
[
'ENABLE_INSTRUCTOR_EMAIL'
]
=
True
# Enable email for all Studio courses
FEATURES
[
'ENABLE_INSTRUCTOR_EMAIL'
]
=
True
# Enable email for all Studio courses
FEATURES
[
'REQUIRE_COURSE_EMAIL_AUTH'
]
=
False
# Give all courses email (don't require django-admin perms)
FEATURES
[
'REQUIRE_COURSE_EMAIL_AUTH'
]
=
False
# Give all courses email (don't require django-admin perms)
...
...
lms/templates/courseware/legacy_instructor_dashboard.html
View file @
2cfeb34f
...
@@ -148,9 +148,6 @@ function goto( mode)
...
@@ -148,9 +148,6 @@ function goto( mode)
%endif
%endif
<h2
class=
"navbar"
>
[
<a
href=
"#"
onclick=
"goto('Grades');"
class=
"${modeflag.get('Grades')}"
>
Grades
</a>
|
<h2
class=
"navbar"
>
[
<a
href=
"#"
onclick=
"goto('Grades');"
class=
"${modeflag.get('Grades')}"
>
Grades
</a>
|
%if settings.FEATURES.get('ENABLE_PSYCHOMETRICS'):
<a
href=
"#"
onclick=
"goto('Psychometrics');"
class=
"${modeflag.get('Psychometrics')}"
>
${_("Psychometrics")}
</a>
|
%endif
<a
href=
"#"
onclick=
"goto('Admin');"
class=
"${modeflag.get('Admin')}"
>
${_("Admin")}
</a>
|
<a
href=
"#"
onclick=
"goto('Admin');"
class=
"${modeflag.get('Admin')}"
>
${_("Admin")}
</a>
|
<a
href=
"#"
onclick=
"goto('Forum Admin');"
class=
"${modeflag.get('Forum Admin')}"
>
${_("Forum Admin")}
</a>
|
<a
href=
"#"
onclick=
"goto('Forum Admin');"
class=
"${modeflag.get('Forum Admin')}"
>
${_("Forum Admin")}
</a>
|
<a
href=
"#"
onclick=
"goto('Enrollment');"
class=
"${modeflag.get('Enrollment')}"
>
${_("Enrollment")}
</a>
|
<a
href=
"#"
onclick=
"goto('Enrollment');"
class=
"${modeflag.get('Enrollment')}"
>
${_("Enrollment")}
</a>
|
...
@@ -268,27 +265,6 @@ function goto( mode)
...
@@ -268,27 +265,6 @@ function goto( mode)
%endif
%endif
##-----------------------------------------------------------------------------
##-----------------------------------------------------------------------------
%if modeflag.get('Psychometrics'):
<p>
${_("Select a problem and an action:")}
</p>
<p>
<select
name=
"Problem"
>
%for problem, count in sorted(problems.items(), key=lambda x: x[0]):
<option
value=
"${problem}"
>
${problem} [${count}]
</option>
%endfor
</select>
</p>
<p>
<input
type=
"submit"
name=
"action"
value=
"Generate Histogram and IRT Plot"
>
</p>
<p></p>
%endif
##-----------------------------------------------------------------------------
%if modeflag.get('Admin'):
%if modeflag.get('Admin'):
%if instructor_access or admin_access:
%if instructor_access or admin_access:
...
@@ -398,7 +374,7 @@ function goto( mode)
...
@@ -398,7 +374,7 @@ function goto( mode)
##-----------------------------------------------------------------------------
##-----------------------------------------------------------------------------
%if datatable
and modeflag.get('Psychometrics') is None
:
%if datatable:
<br/>
<br/>
<br/>
<br/>
...
@@ -492,32 +468,6 @@ function goto( mode)
...
@@ -492,32 +468,6 @@ function goto( mode)
%endif
%endif
##-----------------------------------------------------------------------------
##-----------------------------------------------------------------------------
%if modeflag.get('Psychometrics'):
%for plot in plots:
<br/>
<h3>
${plot['title']}
</h3>
<br/>
<p>
${plot['info']}
</p>
<br/>
<div
id=
"plot_${plot['id']}"
style=
"width:600px;height:300px;"
></div>
<script
type=
"text/javascript"
>
$
(
function
()
{
$
{
plot
[
'data'
]}
$
.
plot
(
$
(
"#plot_${plot['id']}"
),
$
{
plot
[
'cmd'
]}
);
});
</script>
<br/>
<br/>
%endfor
%endif
##-----------------------------------------------------------------------------
## always show msg
##-----------------------------------------------------------------------------
%if modeflag.get('Admin'):
%if modeflag.get('Admin'):
% if course_errors is not UNDEFINED:
% if course_errors is not UNDEFINED:
<h2>
${_("Course errors")}
</h2>
<h2>
${_("Course errors")}
</h2>
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment