Repository navigation
Conversation
Codecov Report❌ Patch coverage is Additional details and impacted files@@ Coverage Diff @@
## main #276 +/- ##
==========================================
+ Coverage 85.54% 88.78% +3.23%
==========================================
Files 29 36 +7
Lines 1986 2845 +859
Branches 175 308 +133
==========================================
+ Hits 1699 2526 +827
- Misses 260 275 +15
- Partials 27 44 +17
Flags with carried forward coverage won't be shown. Click here to find out more. ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
felipemontoya
left a comment
There was a problem hiding this comment.
I used this branch of the project when creating the test data for aspects-dbt#176. It worked beautifully and let me correctly analyze the interplay of the video pairing events.
I was experimenting with a review from OpenWeights model GLM5.3 so I had it upload a bunch of nits into the specific files. Feel free to ignore them or have your agent respond to them in any way. The overall code is working and has been tested to successful results.
| def run(self, output_dir: str) -> Dict: | ||
| """Generate everything into ``output_dir`` and return the manifest.""" | ||
| self.setup() | ||
| os.makedirs(output_dir, exist_ok=True) |
There was a problem hiding this comment.
Overwrite manifest.json with {"complete": false} here before anything else: a crash mid-rerun (Ctrl-C, bad config value) truncates the CSVs but leaves the previous run's manifest in place — a complete-looking dir whose counts lie.
There was a problem hiding this comment.
Good catch. Fixed in 7f61c4e: run() now deletes manifest.json before writing anything, so a run that stops partway leaves no manifest. Added a test that makes a re-run fail partway and checks the old manifest is gone.
| except ValueError as e: | ||
| raise click.UsageError(str(e)) from e | ||
| start = datetime.datetime.now() | ||
| manifest = generator.run(out) |
There was a problem hiding this comment.
Move run() inside the try and type-check required config keys in __init__/from_config: wrong-typed values (e.g. days_between_subsections: "2.0", window_days: "300") escape the except ValueError net and die as raw TypeErrors — some only mid-run, after the CSVs are already truncated.
There was a problem hiding this comment.
Fixed in e960e27. The generator now checks every top-level, course template, course and behavior setting when it's created, before any file is opened. Errors name the setting (for example behavior.days_between_subsections must be a number >= 0.001, got '2.0'), and the CLI shows them as a usage error. Because nothing can fail on config after that point, run() stays outside the try. 14 new test cases cover this.
| verticals_per_sequential: Tuple[int, int] | ||
| # Each vertical gets a random count of these, drawn from the given weights | ||
| # (index = count), e.g. [0.4, 0.4, 0.2] -> 40% none, 40% one, 20% two. | ||
| problems_per_vertical: List[float] |
There was a problem hiding this comment.
These hold probability weights indexed by count, not counts — rename to problem_count_weights/video_count_weights before the schema ships, or a config author writing problems_per_vertical: [0, 4] (like every sibling range key) silently gets a 2-weight distribution instead.
| writers["xapi"].writerow((e["event_id"], _fmt(e["emission_time"]), e["event"])) | ||
|
|
||
| return {"num_enrollments": n_enrollments, "num_xapi_events": n_events, | ||
| "heavy_learners": heavy} |
There was a problem hiding this comment.
heavy_learners is one most-active learner per course run, not a ranking — and when nobody viewed pages the argmax still picks the first enrollment (pages_viewed_fraction: 0.0), so skip empty courses; consider most_active_learner_per_course while the manifest schema is still free.
| max_insert_threads = 1, max_threads = 1``. | ||
|
|
||
| ``window_days`` must stay under 365, because Aspects drops xAPI events older than | ||
| a year. Behavior probabilities (``behavior`` section) are documented in |
There was a problem hiding this comment.
This pointer is a dead end: Behavior's docstring sends readers back to the YAMLs, which document ~5 of 16 keys — the dataclass fields are the accepted behavior: schema, so per-field docstrings there would make it true.
There was a problem hiding this comment.
Fixed in e960e27. Every Behavior field now has a comment saying what it controls, the class docstring says it is the behavior: schema, and the README points there.
|
|
||
| def __init__(self, rng: random.Random, behavior: Behavior, now: datetime.datetime): | ||
| self.rng = rng | ||
| self.b = behavior |
There was a problem hiding this comment.
self.b → self.behavior (25 dereferences, and it collides with the b loop variables in structure/generate).
| return json.dumps(e) | ||
|
|
||
|
|
||
| def video( # pylint: disable=too-many-positional-arguments |
There was a problem hiding this comment.
The only builder not named for its verb — video_statement(), or per-verb builders for symmetry with registered/navigated/problem_evaluated.
There was a problem hiding this comment.
Renamed to video_statement() in d6d95db. I kept it as one builder because the video verbs share every field except the result extensions.
| self.events.append(dict(row)) | ||
| return event_id | ||
|
|
||
| def _is_past(self, when: datetime.datetime) -> bool: |
There was a problem hiding this comment.
Returns True at now, not before it — _has_happened would match the inclusive boundary the docstring already states.
| running_course_fraction: 0.4 | ||
| course_length_days: 120 | ||
| num_organizations: 5 | ||
| num_actors: 100000 |
There was a problem hiding this comment.
[NIT]: the surface splits one person into two words — config says learners, data says actor_id: either rename the schema-level keys toward one term or add one README sentence declaring the split deliberate (keep actor_id; renaming it breaks the aspects-dbt consumers).
There was a problem hiding this comment.
Leaving as is. The config already uses num_actors for people, and learners in a course entry is the number of enrollments, so the two words name different things. actor_id matches Aspects, as you say.
| instead simulates each enrolled learner moving through a properly nested course | ||
| (sections > subsections > units > problems and videos) in time-ordered | ||
| sessions, and writes the expected engagement results alongside the data, so | ||
| report numbers can be checked against a known answer: |
There was a problem hiding this comment.
Use the word "oracle" once here (or rename oracle.py → expected_results.py): today the term exists only in the module name and the journeys_oracle.yaml filename, so a reader going README → package never connects the two.
There was a problem hiding this comment.
Done in e960e27: the README now says the expected results are the "oracle", computed in xapi_db_load/journeys/oracle.py.
- Stop counting a video's actual watched seconds past "now" for sessions still playing. - Report journeys config problems as CLI usage errors. - Check course_length_days fits in window_days. - Add tests for the CLI, video pairing edge cases and unsorted output. - Document the journeys command in the README; fix dataset size comments. - Lint fixes. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- problems/videos_per_vertical -> *_per_vertical_weights: they weight each count, unlike the [low, high] ranges beside them. - manifest heavy_learners -> most_active_learner_per_course. - Simulator.b -> behavior, _is_past -> _has_happened, statements.video -> video_statement. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ge views - A run that stopped partway left the previous run's manifest.json beside the new, partial CSVs. The manifest is now removed before any file is written. - A course where nobody viewed a page listed its first learner as the most active one, with pages_viewed_fraction 0. Such courses are now skipped. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A wrong-typed value, e.g. days_between_subsections: "2.0", used to fail as a raw TypeError partway through a run, after the CSVs were already truncated. Every top-level, course template, course and behavior setting is now checked when the generator is built, and errors name the setting. Each behavior setting is also documented on the Behavior class, and the README explains the weight lists and the "oracle". Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
457b3c4 to
e960e27
Compare
|
@felipemontoya Thanks for the review! I went through all 12 comments: 9 are addressed in d6d95db, 7f61c4e and e960e27, and I've left 3 as they are, with reasons inline. The generated oracle data is byte-identical to before, so existing known answers still hold. |
|
Reopening to try to kick CLA check |
Inserts the event sink rows and expected results straight into ClickHouse and sends the xAPI events through the configured backend: clickhouse, ralph (without duplicate events, which an LRS rejects) or vector. Ralph and Vector insert asynchronously, so it waits until every event id has arrived. A new journeys setting, org_prefix, keeps journeys organizations apart from load-db's. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
expected_video_seconds gains observable_max_views, the count the video watch charts show as total views. The event-derived check in the tests covers it too. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- Reload ClickHouse dictionaries after inserting the event sink rows, so course names and users are found without waiting for the next dictionary refresh. - New journeys setting first_user_id (default 1, which keeps the output unchanged) so journeys users don't share user ids, usernames or emails with load-db's. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
@felipemontoya @saraburns1 sorry to add on here, but I wanted to be able to use the new journey features to be able to check exact row counts in tutor-contrib-aspects CI. This should allow us to have a full integration test that makes sure we haven't broken anything in aspects-dbt, the data pipeline, or xapi-db-load as part of each TCA run. I'm working out the kinks on the current TCA PR for performance: openedx/tutor-contrib-aspects#1355 |
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Printing every event in a few seconds is far faster than an LMS logs, and one log line was lost on its way to Vector at that rate in CI. Pause after every vector_batch_size (default 100) events. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Adds a
journeyscommand that generates learner data with known expected results, so Aspects report numbers can be checked against a known answer instead of only being load-tested.The existing backends pick each event independently. This new mode simulates each enrolled learner moving through a properly nested course (sections > subsections > units > problems and videos) in time-ordered sessions. The simulator knows exactly what each learner did, so it also writes the expected engagement results.
What it generates (gzipped CSV, same layouts as the
csvbackend):completedfired once at 95%;mailto:actors.expected_engagement: per learner and section / subsection, pages / problems / videos done out of total, with the Aspects status label.expected_video_seconds: watched seconds per learner and video. "Observable" is what the events can show (each play paired with the next video event). "Truth" is what the learner actually watched, including tab closes.manifest.json: counts, plus the most active learner per course for the learner dashboard.Edge cases (all on in
journeys_oracle.yaml):mailto:actors, deleted units, duplicate events, pause and resume at the same instant, recent activity inside a refreshable-view lookback window, and still-running courses.Example configs:
journeys_oracle.yaml(~9K events, for correctness),journeys_m.yaml(~15M) andjourneys_l.yaml(~75M) for benchmarks. The same config, seed and--nowproduce identical files.Testing
test_journeys.py. The main check rebuilds every expected page / problem row and every observable watched-seconds value from the generated xAPI events and block structure alone, so the simulator's bookkeeping can't drift from the events it emits. The others cover reproducibility, ordering, nesting, edge-case presence, video pairing edge cases, the CLI and config errors.Usage
Loading instructions are in the README.
🤖 Generated with Claude Code