איך מתחילים לעבוד עם קריטריונים להערכה

rubric היא תבנית שהמורים יכולים להשתמש בה כדי לתת ציונים לעבודות שהתלמידים הגישו. ‫Classroom API מאפשר לכם לפעול בשם המורה כדי לנהל את קריטריוני ההערכה האלה, וגם לקרוא את הציונים של קריטריוני ההערכה בעבודות של התלמידים.

תצוגה של קריטריון הערכה בממשק המשתמש של Classroom איור 1. תצוגה של קריטריון הערכה לדוגמה במטלה ב-Classroom.

במדריך הזה מוסברים המושגים הבסיסיים והפונקציונליות של Rubrics API. במאמרים הבאים במרכז העזרה מוסבר על המבנה הכללי של קריטריון להערכה ועל השימוש בקריטריון להערכה לצורך מתן ציונים בממשק המשתמש של Classroom.

דרישות מוקדמות

במדריך הזה אנחנו יוצאים מנקודת הנחה שיש לכם:

מאשרים את פרטי הכניסה של האפליקציה למחשב

כדי לבצע אימות כמשתמש קצה ולגשת לנתוני משתמשים באפליקציה, צריך ליצור מזהה לקוח אחד או יותר ב-OAuth 2.0. מזהה הלקוח משמש לזיהוי של אפליקציה אחת בשרתי OAuth של Google. אם האפליקציה פועלת בכמה פלטפורמות, צריך ליצור מזהה לקוח נפרד לכל פלטפורמה.

  1. נכנסים אל דף האישורים במסוף Google Cloud.
  2. לוחצים על Create Credentials (יצירת פרטי כניסה) > OAuth client ID (מזהה לקוח OAuth).
  3. לוחצים על Application type > Desktop app.
  4. בשדה Name, מקלידים שם לפרטי הכניסה. השם הזה מוצג רק במסוף Google Cloud. לדוגמה, "Rubrics client".
  5. לוחצים על יצירה. מופיע מסך עם לקוח OAuth שנוצר, שבו מוצגים מזהה הלקוח וסוד הלקוח החדשים.
  6. לוחצים על הורדת קובץ JSON ואז על אישור. פרטי הכניסה החדשים שנוצרו מופיעים בקטע OAuth 2.0 Client IDs.
  7. שומרים את קובץ ה-JSON שהורדתם בשם credentials.json ומעבירים את הקובץ לספריית העבודה.
  8. לוחצים על Create Credentials (יצירת אמצעי אימות) > API Key (מפתח API) ורושמים את מפתח ה-API.

מידע נוסף זמין במאמר בנושא יצירת פרטי גישה.

הגדרת היקפי הרשאות של OAuth

יכול להיות שתצטרכו להגדיר היקפי הרשאות נוספים, בהתאם להיקפי ההרשאות הקיימים ל-OAuth בפרויקט.

  1. עוברים אל מסך ההסכמה ל-OAuth.
  2. לוחצים על עריכת האפליקציה > שמירה והמשך כדי להגיע למסך ההיקפים.
  3. לוחצים על הוספה או הסרה של היקפים.
  4. אם עדיין לא הוספתם את היקפי ההרשאות הבאים, מוסיפים אותם:
    • https://www.googleapis.com/auth/classroom.coursework.students
    • https://www.googleapis.com/auth/classroom.courses
  5. אחר כך לוחצים על עדכון > שמירה והמשך > שמירה והמשך > חזרה למרכז הבקרה.

למידע נוסף, אפשר לעיין במאמר בנושא הגדרת מסך ההסכמה ל-OAuth.

היקף ההרשאות classroom.coursework.students מאפשר גישת קריאה וכתיבה לקריטריונים להערכה (יחד עם גישה ל-CourseWork), והיקף ההרשאות classroom.courses מאפשר קריאה וכתיבה של קורסים.

ההיקפים הנדרשים לשיטה מסוימת מפורטים במאמרי העזרה של השיטה. אפשר לעיין בדוגמה של courses.courseWork.rubrics.create היקפי הרשאות. במאמר היקפי הרשאות של OAuth 2.0 ל-Google APIs אפשר לראות את כל היקפי ההרשאות של Classroom.

הגדרת הדוגמה

בספריית העבודה, מתקינים את ספריית הלקוח של Google ל-Python:

pip install --upgrade google-api-python-client google-auth-httplib2 google-auth-oauthlib

יוצרים קובץ בשם main.py שבונה את ספריית הלקוח ומאשר את המשתמש, באמצעות מפתח ה-API במקום YOUR_API_KEY:

import json
import os.path

from google.auth.transport.requests import Request
from google.oauth2.credentials import Credentials
from google_auth_oauthlib.flow import InstalledAppFlow
from googleapiclient.discovery import build
from googleapiclient.errors import HttpError

# If modifying these scopes, delete the file token.json.
SCOPES = ['https://www.googleapis.com/auth/classroom.courses',
          'https://www.googleapis.com/auth/classroom.coursework.students']

def build_authenticated_service(api_key):
    """Builds the Classroom service."""
    creds = None
    # The file token.json stores the user's access and refresh tokens, and is
    # created automatically when the authorization flow completes for the first
    # time.
    if os.path.exists('token.json'):
        creds = Credentials.from_authorized_user_file('token.json', SCOPES)
    # If there are no (valid) credentials available, let the user log in.
    if not creds or not creds.valid:
        if creds and creds.expired and creds.refresh_token:
            creds.refresh(Request())
        else:
            flow = InstalledAppFlow.from_client_secrets_file(
                'credentials.json', SCOPES)
            creds = flow.run_local_server(port=0)
        # Save the credentials for the next run.
        with open('token.json', 'w') as token:
            token.write(creds.to_json())

    try:
        # Build the Classroom service.
        service = build(
            serviceName="classroom",
            version="v1",
            credentials=creds,
            discoveryServiceUrl=f"https://classroom.googleapis.com/$discovery/rest?labels=DEVELOPER_PREVIEW&key={api_key}")

        return service

    except HttpError as error:
        print('An error occurred: %s' % error)

if __name__ == '__main__':
    service = build_authenticated_service(YOUR_API_KEY)

מריצים את הסקריפט באמצעות python main.py. תתבקשו להיכנס לחשבון ולהסכים להיקף ההרשאות של OAuth.

יצירת מטלה

קריטריון הערכה משויך למטלה, או CourseWork, והוא רלוונטי רק בהקשר של אותו CourseWork. אפשר ליצור קריטריונים להערכה רק באמצעות פרויקט בענן של Google שיצר את פריט האב CourseWork. לצורך המדריך הזה, יוצרים מטלה חדשה CourseWork עם סקריפט.

מי יצורף למרחב main.py:

def get_latest_course(service):
    """Retrieves the last created course."""
    try:
        response = service.courses().list(pageSize=1).execute()
        courses = response.get("courses", [])
        if not courses:
            print("No courses found. Did you remember to create one in the UI?")
            return
        course = courses[0]
        return course

    except HttpError as error:
        print(f"An error occurred: {error}")
        return error

def create_coursework(service, course_id):
    """Creates and returns a sample coursework."""
    try:
        coursework = {
            "title": "Romeo and Juliet analysis.",
            "description": """Write a paper arguing that Romeo and Juliet were
                                time travelers from the future.""",
            "workType": "ASSIGNMENT",
            "state": "PUBLISHED",
        }
        coursework = service.courses().courseWork().create(
            courseId=course_id, body=coursework).execute()
        return coursework

    except HttpError as error:
        print(f"An error occurred: {error}")
        return error

עכשיו מעדכנים את main.py כדי לאחזר את course_id של מחלקת הבדיקה שיצרתם, יוצרים מטלה לדוגמה ומאחזרים את coursework_id של המטלה:

if __name__ == '__main__':
    service = build_authenticated_service(YOUR_API_KEY)

    course = get_latest_course(service)
    course_id = course.get("id")
    course_name = course.get("name")
    print(f"'{course_name}' course ID: {course_id}")

    coursework = create_coursework(service, course_id)
    coursework_id = coursework.get("id")
    print(f"Assignment created with ID {coursework_id}")

    #TODO(developer): Save the printed course and coursework IDs.

שומרים את course_id ואת coursework_id. הם נדרשים לכל פעולות ה-CRUD של קריטריוני הערכה.

עכשיו אמורה להיות לכם דוגמה של CourseWork ב-Classroom.

תצוגה של מטלה בממשק המשתמש של Classroom איור 2. תצוגה של מטלה לדוגמה ב-Classroom.

בדיקת הזכאות של המשתמשים

כדי ליצור ולעדכן קריטריונים להערכה, גם למשתמש ששולח את הבקשה וגם לבעלים של הקורס הרלוונטי צריך להיות רישיון ל-Google Workspace for Education Plus. ‫Classroom תומך בנקודת קצה (endpoint) לבדיקת זכאות של משתמש, כדי לאפשר למפתחים לקבוע את היכולות שלמשתמש יש גישה אליהן.

מעדכנים ומריצים את main.py כדי לוודא שלחשבון הבדיקה יש גישה לתכונה 'קריטריונים להערכה':

if __name__ == '__main__':
    service = build_authenticated_service(YOUR_API_KEY)

    capability = service.userProfiles().checkUserCapability(
        userId='me',
        # Specify the preview version. checkUserCapability is
        # supported in V1_20240930_PREVIEW and later.
        previewVersion="V1_20240930_PREVIEW",
        capability="CREATE_RUBRIC").execute()

    if not capability.get('allowed'):
      print('User ineligible for rubrics creation.')
      # TODO(developer): in a production app, this signal could be used to
      # proactively hide any rubrics related features from users or encourage
      # them to upgrade to the appropriate license.
    else:
      print('User eligible for rubrics creation.')

יצירת קריטריון הערכה

עכשיו אפשר להתחיל לנהל קריטריונים להערכה.

אפשר ליצור קריטריון הערכה ב-CourseWork באמצעות קריאה ל-create() שמכילה את אובייקט קריטריון ההערכה המלא, כשמאפייני המזהה של הקריטריונים והרמות מושמטים (הם נוצרים בזמן היצירה).

מוסיפים את הפונקציה הבאה ל-main.py:

def create_rubric(service, course_id, coursework_id):
    """Creates an example rubric on a coursework."""
    try:
        body = {
            "criteria": [
                {
                    "title": "Argument",
                    "description": "How well structured your argument is.",
                    "levels": [
                        {"title": "Convincing",
                         "description": "A compelling case is made.", "points": 30},
                        {"title": "Passable",
                         "description": "Missing some evidence.", "points": 20},
                        {"title": "Needs Work",
                         "description": "Not enough strong evidence..", "points": 0},
                    ]
                },
                {
                    "title": "Spelling",
                    "description": "How well you spelled all the words.",
                    "levels": [
                        {"title": "Perfect",
                         "description": "No mistakes.", "points": 20},
                        {"title": "Great",
                         "description": "A mistake or two.", "points": 15},
                        {"title": "Needs Work",
                         "description": "Many mistakes.", "points": 5},
                    ]
                },
                {
                    "title": "Grammar",
                    "description": "How grammatically correct your sentences are.",
                    "levels": [
                        {"title": "Perfect",
                         "description": "No mistakes.", "points": 20},
                        {"title": "Great",
                         "description": "A mistake or two.", "points": 15},
                        {"title": "Needs Work",
                         "description": "Many mistakes.", "points": 5},
                    ]
                },
            ]
        }

        rubric = service.courses().courseWork().rubrics().create(
            courseId=course_id, courseWorkId=coursework_id, body=body
            ).execute()
        print(f"Rubric created with ID {rubric.get('id')}")
        return rubric

    except HttpError as error:
        print(f"An error occurred: {error}")
        return error

לאחר מכן מעדכנים ומריצים את main.py כדי ליצור את קריטריון ההערכה לדוגמה, באמצעות המזהים Course ו-CourseWork שצוינו קודם:

if __name__ == '__main__':
    service = build_authenticated_service(YOUR_API_KEY)

    capability = service.userProfiles().checkUserCapability(
        userId='me',
        # Specify the preview version. checkUserCapability is
        # supported in V1_20240930_PREVIEW and later.
        previewVersion="V1_20240930_PREVIEW",
        capability="CREATE_RUBRIC").execute()

    if not capability.get('allowed'):
      print('User ineligible for rubrics creation.')
      # TODO(developer): in a production app, this signal could be used to
      # proactively hide any rubrics related features from users or encourage
      # them to upgrade to the appropriate license.
    else:
      rubric = create_rubric(service, YOUR_COURSE_ID, YOUR_COURSEWORK_ID)
      print(json.dumps(rubric, indent=4))

כמה נקודות לגבי ייצוג הקריטריונים:

  • סדר הקריטריונים והרמות משתקף בממשק המשתמש של Classroom.
  • רמות עם ניקוד (אלה עם המאפיין points) חייבות להיות ממוינות לפי נקודות בסדר עולה או בסדר יורד (אי אפשר למיין אותן באופן אקראי).
  • המורים יכולים למיין מחדש את הקריטריונים ואת הרמות עם הניקוד (אבל לא את הרמות ללא הניקוד) בממשק המשתמש, והפעולה הזו משנה את הסדר שלהם בנתונים.

בקטע מגבלות מפורטות אזהרות נוספות לגבי מבנה הקריטריונים.

בממשק המשתמש, אמור להופיע קריטריון ההערכה במטלה.

תצוגה של קריטריון הערכה בממשק המשתמש של Classroom איור 3. תצוגה של קריטריון הערכה לדוגמה במטלה ב-Classroom.

קריאת קריטריון הערכה

אפשר לקרוא קריטריונים להערכה באמצעות השיטות הרגילות list() ו-get().

יכול להיות שיש רק קריטריון הערכה אחד במטלה, ולכן יכול להיות שהשימוש ב-list() לא נראה אינטואיטיבי, אבל הוא שימושי אם עדיין אין לכם את מזהה הקריטריון. אם אין קריטריון הערכה שמשויך ל-CourseWork, התשובה list() ריקה.

מוסיפים את הפונקציה הבאה ל-main.py:

def get_rubric(service, course_id, coursework_id):
    """
    Get the rubric on a coursework. There can only be at most one.
    Returns null if there is no rubric.
    """
    try:
        response = service.courses().courseWork().rubrics().list(
            courseId=course_id, courseWorkId=coursework_id
            ).execute()

        rubrics = response.get("rubrics", [])
        if not rubrics:
            print("No rubric found for this assignment.")
            return
        rubric = rubrics[0]
        return rubric

    except HttpError as error:
        print(f"An error occurred: {error}")
        return error

מעדכנים ומריצים את main.py כדי לאחזר את קריטריון ההערכה שהוספתם:

if __name__ == '__main__':
    service = build_authenticated_service(YOUR_API_KEY)

    rubric = get_rubric(service, YOUR_COURSE_ID, YOUR_COURSEWORK_ID)
    print(json.dumps(rubric, indent=4))

    #TODO(developer): Save the printed rubric ID.

חשוב לשים לב לנכס id בקריטריון ההערכה, כי תצטרכו אותו בשלבים הבאים.

Get() פועל בצורה טובה כשמזינים את מזהה קריטריון ההערכה. השימוש ב-get() בפונקציה עשוי להיראות כך:

def get_rubric(service, course_id, coursework_id, rubric_id):
    """
    Get the rubric on a coursework. There can only be at most one.
    Returns a 404 if there is no rubric.
    """
    try:
        rubric = service.courses().courseWork().rubrics().get(
            courseId=course_id,
            courseWorkId=coursework_id,
            id=rubric_id
        ).execute()

        return rubric

    except HttpError as error:
        print(f"An error occurred: {error}")
        return error

ההטמעה הזו מחזירה 404 אם אין קריטריון.

עדכון קריטריון הערכה

עדכונים לקריטריון הערכה מתבצעים באמצעות קריאות ל-patch(). בגלל המבנה המורכב של קריטריון הערכה, צריך לבצע עדכונים באמצעות תבנית של קריאה-שינוי-כתיבה, שבה מחליפים את כל מאפיין criteria.

אלה כללי העדכון:

  1. קריטריונים או רמות שנוספו ללא מזהה נחשבים תוספות.
  2. קריטריונים או רמות שלא מופיעים יותר נחשבים למחיקות.
  3. קריטריונים או רמות עם מזהה קיים אבל נתונים ששונו נחשבים עריכות. נכסים שלא משתנים נשארים כמו שהם.
  4. קריטריונים או רמות שסופקו עם מזהים חדשים או לא ידועים נחשבים לשגיאות.
  5. הסדר של הקריטריונים והרמות החדשים הוא הסדר החדש בממשק המשתמש (עם המגבלות שצוינו למעלה).

מוסיפים פונקציה לעדכון קריטריון הערכה:

def update_rubric(service, course_id, coursework_id, rubric_id, body):
    """
    Updates the rubric on a coursework.
    """
    try:
        rubric = service.courses().courseWork().rubrics().patch(
            courseId=course_id,
            courseWorkId=coursework_id,
            id=rubric_id,
            body=body,
            updateMask='criteria'
        ).execute()

        return rubric

    except HttpError as error:
        print(f"An error occurred: {error}")
        return error

בדוגמה הזו, השדה criteria מוגדר לשינוי עם updateMask.

לאחר מכן משנים את main.py כדי לבצע שינוי בכל אחד מכללי העדכון שצוינו למעלה:

if __name__ == '__main__':
    service = build_authenticated_service(YOUR_API_KEY)

    capability = service.userProfiles().checkUserCapability(
        userId='me',
        # Specify the preview version. checkUserCapability is
        # supported in V1_20240930_PREVIEW and later.
        previewVersion="V1_20240930_PREVIEW",
        capability="CREATE_RUBRIC").execute()

    if not capability.get('allowed'):
      print('User ineligible for rubrics creation.')
      # TODO(developer): in a production app, this signal could be used to
      # proactively hide any rubrics related features from users or encourage
      # them to upgrade to the appropriate license.
    else:
        # Get the latest rubric.
        rubric = get_rubric(service, YOUR_COURSE_ID, YOUR_COURSEWORK_ID)
        criteria = rubric.get("criteria")
        """
        The "criteria" property should look like this:
        [
            {
                "id": "NkEyMdMyMzM2Nxkw",
                "title": "Argument",
                "description": "How well structured your argument is.",
                "levels": [
                    {
                        "id": "NkEyMdMyMzM2Nxkx",
                        "title": "Convincing",
                        "description": "A compelling case is made.",
                        "points": 30
                    },
                    {
                        "id": "NkEyMdMyMzM2Nxky",
                        "title": "Passable",
                        "description": "Missing some evidence.",
                        "points": 20
                    },
                    {
                        "id": "NkEyMdMyMzM2Nxkz",
                        "title": "Needs Work",
                        "description": "Not enough strong evidence..",
                        "points": 0
                    }
                ]
            },
            {
                "id": "NkEyMdMyMzM2Nxk0",
                "title": "Spelling",
                "description": "How well you spelled all the words.",
                "levels": [...]
            },
            {
                "id": "NkEyMdMyMzM2Nxk4",
                "title": "Grammar",
                "description": "How grammatically correct your sentences are.",
                "levels": [...]
            }
        ]
        """

        # Make edits. This example will make one of each type of change.

        # Add a new level to the first criteria. Levels must remain sorted by
        # points.
        new_level = {
            "title": "Profound",
            "description": "Truly unique insight.",
            "points": 50
        }
        criteria[0]["levels"].insert(0, new_level)

        # Remove the last criteria.
        del criteria[-1]

        # Update the criteria titles with numeric prefixes.
        for index, criterion in enumerate(criteria):
            criterion["title"] = f"{index}: {criterion['title']}"

        # Resort the levels from descending to ascending points.
        for criterion in criteria:
            criterion["levels"].sort(key=lambda level: level["points"])

        # Update the rubric with a patch call.
        new_rubric = update_rubric(
            service, YOUR_COURSE_ID, YOUR_COURSEWORK_ID, YOUR_RUBRIC_ID, rubric)

        print(json.dumps(new_rubric, indent=4))

השינויים אמורים להשתקף עכשיו אצל המורה ב-Classroom.

תצוגה של קריטריון הערכה מעודכן בממשק המשתמש של Classroom איור 4. תצוגה של הקריטריון המעודכן.

צפייה במטלות שהוגשו וקיבלו ציון לפי קריטריון הערכה

בשלב הזה, אי אפשר לתת ציונים להגשות של תלמידים באמצעות קריטריון הערכה דרך ה-API, אבל אפשר לקרוא ציונים של קריטריון הערכה להגשות שנבדקו באמצעות קריטריון הערכה בממשק המשתמש של Classroom.

בתור תלמידים בממשק המשתמש של Classroom, משלימים את המטלה לדוגמה ומגישים אותה. לאחר מכן, המורה יכול לתת ציון לעבודה באופן ידני באמצעות קריטריון ההערכה.

תצוגה של ציון לפי קריטריון הערכה בממשק המשתמש של Classroom איור 5. תצוגת הקריטריונים למורה במהלך מתן הציונים.

StudentSubmissions שקיבלו ציון באמצעות קריטריון הערכה, יש שתי מאפיינים חדשים: draftRubricGrades ו-assignedRubricGrades. המאפיינים האלה מייצגים את הנקודות והרמות שנבחרו על ידי המורה במהלך הטיוטה ומצבי הציון שהוקצו, בהתאמה.

אפשר להשתמש בשיטות הקיימות studentSubmissions.get() ו-studentSubmissions.list() כדי לראות עבודות עם ציון.

מוסיפים את הפונקציה הבאה ל-main.py כדי להציג רשימה של מטלות שהתלמידים הגישו:

def get_latest_submission(service, course_id, coursework_id):
    """Retrieves the last submission for an assignment."""
    try:
        response = service.courses().courseWork().studentSubmissions().list(
            courseId = course_id,
            courseWorkId = coursework_id,
            pageSize=1
        ).execute()
        submissions = response.get("studentSubmissions", [])
        if not submissions:
            print(
                """No submissions found. Did you remember to turn in and grade
                   the assignment in the UI?""")
            return
        submission = submissions[0]
        return submission

    except HttpError as error:
        print(f"An error occurred: {error}")
        return error

אחר כך מעדכנים ומריצים את main.py כדי לראות את הציונים של העבודות.

if __name__ == '__main__':
    service = build_authenticated_service(YOUR_API_KEY)

    submission = get_latest_submission(
        service, YOUR_COURSE_ID, YOUR_COURSEWORK_ID)
    print(json.dumps(submission, indent=4))

השדות draftRubricGrades ו-assignedRubricGrades מכילים:

  • ה-criterionId של קריטריון ההערכה המתאים.
  • points הציון שהמורה הקצה לכל קריטריון. יכול להיות שההגדרות האלה מגיעות מהרמה שנבחרה, אבל יכול להיות שהמורה שינה אותן.
  • הציון levelId של הרמה שנבחרה לכל קריטריון. אם המורה לא בחר רמה, אבל עדיין הקצה נקודות לקריטריון, השדה הזה לא יופיע.

הרשימות האלה מכילות רק רשומות של הקריטריונים שבהם המורה בחר רמה או הגדיר נקודות. לדוגמה, אם מורה בוחר ליצור אינטראקציה רק עם קריטריון אחד במהלך מתן ציונים, ב-draftRubricGrades וב-assignedRubricGrades יהיה רק פריט אחד, גם אם בקריטריון ההערכה יש קריטריונים רבים.

מחיקת קריטריון הערכה

אפשר למחוק קריטריון הערכה באמצעות בקשת delete() רגילה. בדוגמה הבאה מוצג קוד של פונקציה שלמה, אבל מכיוון שהתחלתם לתת ציונים, אי אפשר למחוק את קריטריון ההערכה הנוכחי:

def delete_rubric(service, course_id, coursework_id, rubric_id):
    """Deletes the rubric on a coursework."""
    try:
        service.courses().courseWork().rubrics().delete(
            courseId=course_id,
            courseWorkId=coursework_id,
            id=rubric_id
        ).execute()

    except HttpError as error:
        print(f"An error occurred: {error}")
        return error

ייצוא וייבוא של קריטריוני הערכה

מורים יכולים לייצא קריטריונים להערכה לגיליונות אלקטרוניים של Google כדי לעשות בהם שימוש חוזר.

בנוסף לאפשרות לציין קריטריונים להערכה בקוד, אפשר ליצור ולעדכן קריטריונים להערכה מגיליונות שיוצאו, על ידי ציון sourceSpreadsheetId בגוף הקריטריון במקום criteria:

def create_rubric_from_sheet(service, course_id, coursework_id, sheet_id):
    """Creates an example rubric on a coursework."""
    try:
        body = {
            "sourceSpreadsheetId": sheet_id
        }

        rubric = service.courses().courseWork().rubrics().create(
            courseId=course_id, courseWorkId=coursework_id, body=body
            ).execute()

        print(f"Rubric created with ID {rubric.get('id')}")
        return rubric

    except HttpError as error:
        print(f"An error occurred: {error}")
        return error