PlatonOrg / platon

Platform for Learning and Teaching Online: Open Source Education Platform
Other
7 stars 0 forks source link

Elements sauvegardés en base SQL pour chaque WebComponent #12

Open Ofghanirre opened 1 year ago

Ofghanirre commented 1 year ago

I. PRESENTATION

Format d'une réponse sur la base:

Format d'une réponse

Une réponse possèdera toujours des champs tels:

À cela s'ajoute des champs spécifiques aux composants utilisés...

Format d'une réponse en fonction du type de composant

On distingue des composants qui pour l'instant, n'ont pas de champs en plus

Sont concernés:

  • [ ] AutomatonEditor
  • [ ] Drag&Drop
  • [ ] Jsx
  • [ ] Matrix
  • [ ] TextSelect

On distingue ensuite d'autres composants qui stockent des données de type spécifiques, identifiables comme des familles

  1. Format Input

Sont concernés:

  • [ ] InputBox
  • [ ] MathLive

  1. Format Input Code

Est concerné:

  • [ ] CodeEditor

  1. Format Input Select

Sont concernés:

  • [ ] RadioGroup
  • [ ] SortList
  • [ ] CheckboxGroup
  1. Format Select Assign

II. ARGUMENTATION

L'idée derrière ces structures est de pouvoir représenter facilement les données des réponses d'exercices en base afin de pouvoir plus tard, traiter facilement les données à l'aide de graphe, csv et analytics. Le format des données étant variable, il sera nécéssaire de créer des mappers permettant d'uniformiser le format des données et de les adapters pour chaque type de graphe.

image

Format:

[
{
"name"?: string, // Nom de la colonne
"series": [
{
"name": string, // Nom de la série
"value": number // Valeur de la série
},
... // Prochains objets...
]
}
...
]

image

Format:

[
{
"name": string, // Nom de la série
"value": number // Valeur de la série
}
...
]

Il sera donc plus en amont nécessaire de réaliser des mappers (sur des fichiers PL / Python) permettant la traduction de données BDD en données graphe

Si vous avez des éléments à ajouter ou des questions, n'hésitez pas à les proposers!

NewMeeh commented 1 year ago

Il faudrait faire un lien avec l'existant pour faire le format de réponse sur la base de donnée.

https://github.com/PlatonOrg/platon/blob/6d4a00180d3ce45decc71414d85fa20c825f9ded/libs/feature/player/common/src/lib/player.model.ts#L99C1-L124C2

https://github.com/PlatonOrg/platon/blob/main/libs/feature/player/server/src/lib/player.dto.ts

Ofghanirre commented 1 year ago

Hello ! Du coup voici le code produis jusque là:

Il y a une version tirée des interfaces PLaTon Une version réalisé originellement sur PL, améliorée à l'occasion

Le schéma des inclusions est le suivant: image

On a toujours besoin d'aborder là façon d'obtenir l'activity_id, il me semble que sur platon, c'est plus facile que sur PL

from typing import List
from sqlalchemy import create_engine, inspect
from sqlalchemy.orm import sessionmaker
from sqlalchemy.orm import declarative_base
from sqlalchemy import Text, Integer, ForeignKey, String, PickleType, Date
from sqlalchemy.orm import Mapped
from sqlalchemy.orm import mapped_column, relationship

__Base__ = declarative_base()
# TODO replace this value ?
activity_id = 42

def get_session(table_class, base, db_url = "activities-db", db_name = "activity_db", db_user = "activity_user", db_password = "Dimz80k7X97!"):
    engine = create_engine(f"postgresql://{db_user}:{db_password}@{db_url}/{db_name}")
    if not inspect(engine).has_table(table_class.__tablename__):
        base.metadata.create_all(engine)
    return sessionmaker(bind=engine)()

# --------------------------------------------------------------
# BASE COMPONENT 
# --------------------------------------------------------------

# Version based off PLaTon ts interface
class ExercisePLayer(__Base__):
    __tablename__ = f"db_wc_response_default_{activity_id}"
    ressource_type : Mapped[str]= mapped_column(String)
    answerId : Mapped[str]      = mapped_column(String)
    sessionId : Mapped[str]     = mapped_column(String)
    startedAt : Mapped[Date]    = mapped_column(Date)
    lastGradedAt : Mapped[Date] = mapped_column(Date)
    form : Mapped[str]          = mapped_column(String)
    title : Mapped[str]         = mapped_column(String)
    statement: Mapped[str]      = mapped_column(String)
    hints: Mapped[PickleType]   = mapped_column(PickleType)     # List[String]
    author: Mapped[str]         = mapped_column(String)
    correction: Mapped[PickleType] = mapped_column(PickleType)  # {grade: number, authorId: string, createdAt: Date, updatedAt: Date}
    remainingAttemps: Mapped[int] = mapped_column(Integer)
    solution: Mapped[str]       = mapped_column(String)
    settings: Mapped[str]       = mapped_column(String)
    feedbacks: Mapped[PickleType] = mapped_column(PickleType)   # List[{type: string, content: string}]
    theories: Mapped[PickleType] = mapped_column(PickleType)    # {url : string, title: string}

    # For polymorphism
    type: Mapped[str] = mapped_column(String)

    __mapper_args__ = {
        "polymorphic_identity": "WCResponse",
        "polymorphic_on": "type",
    }

# Custom version
class WCResponse(__Base__):
    """
    This class is the super class embodying the response at a db format
    It contains all properties that are shared through out the webComponents
    of PLaTon
    """
    base = __Base__ 
    __tablename__ = f"db_wc_response_default_{activity_id}"
    id : Mapped[int] = mapped_column(primary_key=True)
    usr_id : Mapped[int] = mapped_column(Integer)
    exc_id : Mapped[int] = mapped_column(Integer)

    # User Informations:
    usr_username : Mapped[str] = mapped_column(String)
    usr_firstname : Mapped[str] = mapped_column(String)
    usr_lastname : Mapped[str] = mapped_column(String)
    usr_email : Mapped[str] = mapped_column(String)

    # Exercise Informations:
    exc_title : Mapped[str] = mapped_column(String)
    exc_text : Mapped[Text] = mapped_column(Text)   

    # Grade value:
    grade : Mapped[int] = mapped_column(Integer)
    feedback : Mapped[Text] = mapped_column(Text)

    # For polymorphism
    __type__: Mapped[str] = mapped_column(String)

    __mapper_args__ = {
        "polymorphic_identity": "Response",
        "polymorphic_on": "__type__",
    }
    ######################

    def __str__(self):
        return f"{self.usr_username}[{self.usr_id}] a obtenu un score de {self.grade} sur l'exercice {self.exc_id}."

# --------------------------------------------------------------
# FORMAT RESPONSE COMPONENT
# --------------------------------------------------------------

class WCResponseInput(WCResponse):
    __tablename__ = f"db_wc_response_input_{activity_id}"
    __mapper_args__ = {
        "polymorphic_identity": "ResponseInput",
    }

    id: Mapped[int] = mapped_column(ForeignKey(f"db_wc_response_default_{activity_id}.id"), primary_key=True)

    input : Mapped[str] = mapped_column(String)

class WCResponseInputSelect(WCResponse):
    __tablename__ = f"db_wc_response_inputSelect_{activity_id}"
    __mapper_args__ = {
        "polymorphic_identity": "ResponseInput",
    }

    id: Mapped[int] = mapped_column(ForeignKey(f"db_wc_response_default_{activity_id}.id"), primary_key=True)

    input : Mapped[PickleType] = mapped_column(PickleType) # List[{label: string, score: number}]

class WCResponseSelectAssign(WCResponse):
    __tablename__ = f"db_wc_response_selectAssign_{activity_id}"
    __mapper_args__ = {
        "polymorphic_identity": "ResponseInput",
    }

    id: Mapped[int] = mapped_column(ForeignKey(f"db_wc_response_default_{activity_id}.id"), primary_key=True)

    input : Mapped[PickleType] = mapped_column(PickleType) # List[{label: string, score: string}]

# --------------------------------------------------------------
# WEB COMPONENT RESPONSE
# --------------------------------------------------------------

class WCResponseAutomatonEditor(WCResponse):
    __tablename__ = f"db_wc_response_AutomatonEditor_{activity_id}"

class WCResponseDragDrop(WCResponse):
    __tablename__ = f"db_wc_response_DragDrop_{activity_id}"

class WCResponseJsx(WCResponse):
    __tablename__ = f"db_wc_response_Jsx_{activity_id}"

class WCResponseMatrix(WCResponse):
    __tablename__ = f"db_wc_response_Matrix_{activity_id}"

class WCResponseTextSelect(WCResponse):
    __tablename__ = f"db_wc_response_TextSelect_{activity_id}"

# --------

class WCResponseInputBox(WCResponseInput):
    __tablename__ = f"db_wc_response_InputBox_{activity_id}"

class WCResponseMathLive(WCResponseInput):
    __tablename__ = f"db_wc_response_MathLive_{activity_id}"

# --------

class WCResponseCodeEditor(WCResponseInput):
    __tablename__ = f"db_wc_response_CodeEditor_{activity_id}"
    __mapper_args__ = {
        "polymorphic_identity": "ResponseCodeEditor",
    }

    id: Mapped[int] = mapped_column(ForeignKey(f"db_wc_response_input_{activity_id}.id"), primary_key=True)

    language : Mapped[str] = mapped_column(String)

# --------

class WCResponseRadioGroup(WCResponseInputSelect):
    __tablename__ = f"db_wc_response_InputSelect_{activity_id}"

class WCResponseSortList(WCResponseInputSelect):
    __tablename__ = f"db_wc_response_InputSelect_{activity_id}"

class WCResponseCheckboxGroup(WCResponseInputSelect):
    __tablename__ = f"db_wc_response_CheckboxGroup_{activity_id}"

# --------

class WCResponseMatchList(WCResponseSelectAssign):
    __tablename__ = f"db_wc_response_MatchList_{activity_id}"
Ofghanirre commented 1 year ago

Pour les mappers, je propose ce format pour les mappers version Python On pourra ainsi laisser chacun créer ses propres mappers, avec un nom, une description, et une fonction de mapping qui peut être appliqué sur une méthode map_data

class WCMapper():
    def __init__(self, name: str, description: str, mapping_function):
        """
        Constructor for a WCMapper, need to be defined with a name, a description (markdown format), and a mapping function
        taking a single argument

        :param name: The mapper name
        :param description: The mapper description (markdown allowed)
        :param mapping_function: The mapping function, taking a single parameter
        """
        self.mapping_function = mapping_function
        self.description = description
        self.name = name

    def call(self, x):
        """
        Function to be called upon a mapping operation for a webComponent
        Default is identity: x -> x

        :param x: the element for the mapping
        :return: the mapped element
        """
        return self.mapping_function(x)

    def __repr__(self):
        return (self.name, self.mapping_function)

    def __str__(self):
        return f"<WebComponentMapper: {self.name}>\n\nDescription:\n{self.description}"

def map_data(wcmapper: WCMapper, data: list | set):
    return list(map(wcmapper.call, data))
nimdanor commented 1 month ago

Deux problèmatique diférentes: -> la génération de données pour les stats -> l'affichage des réponses pour les corrections.