Skip to content

Dynamic Content Brainstorming #344

Open
0 of 4 issues completed
Open

Description

@kdmccormick

Strawman Model

Imagining we adopt https://cel.dev/ and trust a Python binding for it...

class Selector(Model):
    cel_query = TextField(...)
    @property
    def compiled_cel_query(self) -> CELQuery:
        return CELQuery.parse(self.cel_query)
class ContainerVersionSelector(Model):
    selector = FK(Selector)
    container_version = FK(ContainerVersion, related_name="cv_selectors")
def get_children(container_version) -> list[PubEntVersion]:
    children = container_version.entity_list.entities
    params = ... 
    for cv_selector in container_version.cv_selectors:
        children = cv_selector.selector.compiled_cel_query.evaluate(
            context=dict(children=children, params=params)
        )
    return children
class ItemBank(Selector):
    count = IntegerField(...)
    
    def __init__(self, count):
        super().__init__(count=count, cel_query=(
            "random_shuffle(params.seed, random_choice(params.seed, children, count))"
        )

Activity

  1. changed the title [-]Selectors Brainstorming[/-] [+]Dynamic Content Brainstorming[/+] on Jul 29, 2025
  2. self-assigned this
    on Aug 5, 2025
  3. kdmccormick commented on Aug 21, 2025

    @kdmccormick
    MemberAuthor

    Let's pick apart two properties of dynamic content:

    • Variant space: Is the set of possible content based on predefined variants? If not, can the variants be generated from a predefined pool of content? Or, is the space completely undefined, generated on-the-fly at learning time?
    • Assignment stability: Once a user is assigned a variant, if the content remains stable, does the user's variant assignment generally remain stable too, or is it subject to change based on LMS-side conditions (unstable)? This might actually be a spectrum rather than a dichotomy. Perhaps a more precise question is: What are the conditions under which a user's assignment to a variant could change, and can we listen for the changes?

    Analyzing known dynamic content blocks:

    • randomize: Predefined variants, stable assignment.
    • split test: Predefined variants, mostly-stable assignment (other than: track upgrade, team membership change, et al)
    • item_bank: Predefined pool (but innumerably large variant space), stable assignment (other than: content sync from library that causes assigned blocks to be removed).
    • conditional: Predefined variants, unstable assignment (changing an answer to a poll or a problem can update the assignment).

    Still need to consider dynamic content which occurs outside of the scope of those blocks.

  4. kdmccormick commented on Aug 26, 2025

    @kdmccormick
    MemberAuthor

    @ormsbee , @bradenmacdonald We had discussed a "flat" data model, where any given container's dynamic children would be rendered inline with its static children, and some "rules" (defined at the container level or the course level) would decide which dynamic children to hide for any given user. There's appeal to this, but as an authoring data model, I think it falls short in two areas:

    • Ordering: Dynamic children, particularly for an ItemBank, might need to appear in a different order for different users. But, the dynamic children may be surrounded on either side by static children with a constant order. Imagine a unit like this, where each B is an ItemBank configured to show 2 ordered random items, and each C is a regular component:

      • C0, B1, C2, B3, C4 <- author view
      • C0, B1.3, B1.7, C2, B3.9, B3.1, C4 <- learner view

      If each B were not a node in the authored hierarchy, then I'm not sure how we'd encode that fact that each B1.$x needs to appear in a random position between C0 and C1 and that each B3.$y needs to appear at a random position between C2 and C4.

    • UI and OLX correspondence: Now, I know that we are in no obligation to make our authored data model match OLX and the Studio UI, both of which currently encode several types of dynamic content as container-like things (<split_test>, <itembank>, etc.). But, I think that we are introducing a certain amount of complexity and surprise by not choosing to make the authoring data model correspond to OLX and the UI. And that additional complexity would need to be seriously justified by some other data modelling benefits, and I'm just not seeing those concrete benefits right now.

    With that said, I do think it's natural to encode dynamic children flatly for a learner outline. Particularly, the outline model that @ormsbee is describing in #360 could exclude "dynamic containers", and instead just encode a static version of every outline that any user has seen.

    For this reason, I am considering the idea of Pool, which would be a new type of PublishableEntity, but neither a Component nor a Container. It would appear as a node in the content hierarchy for authoring purposes, and it would have an orderless EntityList (or EntityList-like thing) for all its possible children. But, it would not manifest in the content hierarchy for learning purposes; we would have some pluggable subsystem that picks an ordered sub-list for every learner, and concatenates that sub-list into the parent container. (I have more semi-formed thoughts on this that I'd love to talk through today)

  5. bradenmacdonald commented on Aug 26, 2025

    @bradenmacdonald
    Contributor

    @kdmccormick I'm a bit reluctant to add a new "type" alongside container and component, but your points make a lot of sense.

    Also, your Pool in the container hierarchy seems to play the exact same role as the DynamicContent placeholder that I had been starting to think about for the purposes of materializing the entire authoring outline hierarchy.

  6. bradenmacdonald commented on Sep 5, 2025

    @bradenmacdonald
    Contributor

    As mentioned on another issue, I recently learned about three more examples of dynamic selector use cases:

  7. kdmccormick commented on Sep 5, 2025

    @kdmccormick
    MemberAuthor

    Thanks, those are great to know about.

    Thinking out loud, I wonder if replacing non-leaf block types in a backwards compatible way would require 3 separate plugin systems:

    1. A way for operators to plug in rules for selection/restriction of children
    2. A way for operators to plug in custom editors for things above the component level
    3. A way for operators to specify custom parsing logic for a given olx tag (or, a way for operators to specify how to translate legacy olx tags into a "new" well-specified olx schema)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

No labels
No labels

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions