jb-1980 / khanimport

Use Khan Academy's API to import progress into Moodle gradebook
5 stars 1 forks source link

API Skills Question #3

Open matt-green-ra opened 7 years ago

matt-green-ra commented 7 years ago

Hello there! This is fantastic. I'm building my own importer with the Wolfram Language. I've got my OAuth flow working and I can see some student data. I'm really interested in getting access to each of my student's mastery levels. I can see that you've got that working, but I'm having trouble reading the URL scheme... I've not used much php. Can you share the API endpoints I'd need to hit to get each student's mastery data? Also, do you know if I can the mastery data for all my students (about 170 kiddos) in a single call, or do have to call once for each student? Thank you!

matt-green-ra commented 7 years ago

I've made a call to: https://www.khanacademy.org/api/internal/user/KHANIDHERE/progress?dt_start=1970-07-23T07:00:00.000Z&\ dt_end=2017-08-01T06:59:59.999Z&tz_offset=-420&lang=en

And I can get the mastery data for all the exercises a given student has worked on... I'd like to calculate their percentage based only on the targets associated with the course in which they are enrolled, for example: Algebra 1. Is there a way to filter the student mastery data based on targets only associated with a given course?

jb-1980 commented 7 years ago

In general, you have to maintain a list of the exercise names you are tracking. You can get them from https://www.khanacademy.org/api/v1/exercises

but that is a lot of data. The quicker, more limited, exercise endpoint I have been using is https://www.khanacademy.org/api/internal/exercises/math_topics_and_exercises

I have also found you can break it down by mission with

https://www.khanacademy.org/api/internal/user/mission/

We have not used moodle for a while, but the idea is that you have a list of the names and check their progress on them similar to this python implementation:

# dict to store the students' grades
posting_grades_dict = {}

#a list of names that are associated with the course
exercises = ['logarithms_1', 'functions_1']

# Now we fetch the students data from Khan Academy using
# /api/internal/user/{kaid}/progress endpoint
progress_data = kapi.get_student_progress(kaid,kapi_params) #oauth method to fetch data from KA

# parse the different progress levels from the requested data
practiced = progress_data['practiced']
mastery1  = progress_data['mastery1']
mastery2  = progress_data['mastery2']
mastery3  = progress_data['mastery3']

def set_score(mastery_level_skills,score):
     """
    This is a  method to check what the current skill progress level is for
    the skill from Khan Academy, and to add it to the posting_grades_dict.

    :param: mastery_level_skills: a list of progress dicts from the khan mastery levels
    :param: score: an int that is the point value associated with the mastery level
    :param: exercises: a list of exercises slugs
    :return: None
    """
    for skill in mastery_level_skills:
        # We are checking if the specific skill that has been progressed is part
        # of the course skill. If so, award the given grade.
        if skill['slug'] in exercises: #exercises is a list of names like ['logarithms_1', 'functions_1']
            if grade_id not in .posting_grades_dict:
                # This is the first student we have found for this skill,
                # so we will set it up with its type, grademax, and create a
                # list of studentgrades that can be appended to.
                posting_grades_dict[skill] = {
                   "studentgrades":[{
                        "id": studentid,
                        "score": score
                    }]
                }
            else:
                # There has already been a grade found for a student for this
                # particular skill. So we just need to append the current student's
                # grade to the list.
                posting_grades_dict[skill]['studentgrades'].append({
                    "id":studentid,
                    "score":score,
                })

set_score(practiced,70)
set_score(mastery1,80)
set_score(mastery2,90)
set_score(mastery3,100)

I like the internal api endpoint you have referenced because it is generally faster, but I have noticed that there is a limit of around 100 skills. Once students began to pass that I began to notice a timeout error and I cannot use the data.

When I cannot use the faster endpoint, I concatenate the progress from several fetches with:

def get_many_exercises(self,exercises,kaid):
        """
        Since the api restricts the url length to 2048 characters, and making a
        request for many exercises will often exceed this limit, this function will
        truncate the url below the limit, and tie the responses together.
        """
        exercises.sort()
        out = []
        tmp_lst = []
        params = {'kaid': kaid,'exercises':[]}
        while exercises:
            s=''
            tmp_lst = []
            for exercise in exercises:
                t=s+'&exercises='+exercise
                if len(t)<1500:
                    s+='&exercises='+exercise
                    tmp_lst.append(exercise)
                else:
                    break
            exercises = [ x for x in exercises if x not in tmp_lst]
            url = '/api/v1/user/exercises'
            params['exercises']=tmp_lst
            response = self.get_resource(url,params)
            data = response
            for datum in data:
                out.append(datum)
        return out

Hopefully that gives you enough ideas on how to move forward with your project!

matt-green-ra commented 7 years ago

This is SO VERY HELPFUL! Thank you Joseph!

PierrotLaLune commented 3 years ago

Hi there,

I am working on a project to get students grades into Moodle. Since KA does not officially supports API now, I guess the only way would be to rely on unsupported APIs and fix it when it is broken ? Or would you have a better idea on how to do it ?

jb-1980 commented 3 years ago

Hi @PierrotLaLune,

I have been using the unsupported API for a few years, and it has been stable for a while. I have created another node library that I use, https://github.com/jb-1980/khan-graphql. You could either port that over to to PHP for use with Moodle, or create a microservice that wraps it and then query that service in your Moodle app.

The main idea is just storing the cookies so you can authenticate and then make requests to the unsupported APIs. I would be happy to help, but open another issue so we are not sending matt-green-ra a lot of emails.