Owner Nikunj

Flowchart PRD

Data Model

FieldTypeNotes
idstringSystem generated.
short_uidstringSystem generated.
titlestringRequired.
hide_titlebooleanIf true, the title is not shown as a caption when rendered.
descriptionstringOptional.
thumbnailobjectRequired. The flowchart image.
mermaid_codestringOptional. The Mermaid source related to the thumbnail.
licenseenumRequired. Same list of licenses used for images.
needs_reviewboolean”Mark for review” flag.
confidence_scorenumber0–100.
source_urlstring (URL)Optional.
linked_entitiesarrayList of { content_id, content_type }. Where this flowchart is attached.
used_in_l1_ids / used_in_l2_ids / used_in_l3_idsarrayDenormalized taxonomy usage, derived from each linked entity’s chain (linked_entities → Block/MCQ → Docket → L3 → L2 → L1). Powers the admin-listing filter by L1/L2/L3.
statusenumpublished or archived.

Step 1 — Create a Flowchart

An editor creates a flowchart by filling out a form.

Required fields:

  • Title
  • Thumbnail
  • License (pick from the same license list as images)

Optional fields:

  • Description
  • Mermaid code (related to the thumbnail)
  • Mark for review (yes/no)
  • Confidence score (0–100)
  • Source URL
  • Hide title (yes/no) — controls whether the title shows as a caption later

Default behavior: When a flowchart is created, its status is automatically set to published.


Step 2 — Attach a Flowchart

In the Admin Panel, an editor can attach a flowchart inside the editor, either in an MCQ Solution or in Block Content.

When a flowchart is attached:

  • The flowchart’s linked_entities is updated with the content it was attached to, based on the content’s type (MCQ or Block) and its content_id (_id).

For a Block, the data stored is:

  • content — In the PlateJS editor, attaching a flowchart inserts a node: { id: xyz, type: flowchart }.
  • flowchart_ids — array of attached flowchart IDs.
  • flowchart_objects — array of the full flowchart objects.

Step 3 — Admin Listing & Rendering

Make the admin listing the same as the Image Bank.

Filter by taxonomy (L1 / L2 / L3). The listing can be filtered by L1 / L2 / L3, matching the Table Repository. The filter reads the denormalized used_in_l1_ids / used_in_l2_ids / used_in_l3_ids on the flowchart directly — no join through linked entities. A flowchart attached under multiple taxonomies appears under each: it matches when used under any of the selected L1/L2/L3 values.


Step 4 — Rendering (Client & Admin Preview)

When a flowchart needs to be shown:

  1. Fetch the flowchart by its id.
  2. Render the flowchart inline.
    • Note: “Render inline” needs more detailing. For now, render it the same way an image is rendered.
  3. If hide_title is not true, show the title as a caption below it.

Step 5 — Archive & Edit (Cascading Updates)

Changes to a flowchart or to a linked item must keep linkage in sync in both directions.

  • If a linked entity (Block or MCQ) is archived: remove that linkage from the flowchart.
  • Constraint — archiving a flowchart: archive is blocked while the flowchart has any linked entity (linked_entities non-empty). Ask the editor to manually remove all links first, and show them everywhere it is currently linked. (Matches the shipped code: FlowchartService.archive raises FLOWCHART_ARCHIVE_BLOCKED for ≥1 link; 0 links → archive.)
  • Keep taxonomy usage in sync: whenever linked_entities changes — on attach (Step 2), on manual detach, and on the archive-cascade detach above — re-derive used_in_l1_ids / used_in_l2_ids / used_in_l3_ids through the same single write path that updates linked_entities. Block→Docket moves and Docket re-parenting do not happen (disallowed by the platform), so a linked entity’s taxonomy chain is immutable and there is no other recompute trigger.

Addendum — Taxonomy Filter (delta on executed PRD)

Added 2026-06-03. The flowchart entity shipped before this filter, so this section is a delta on the already-executed PRD.

  • Backfill. Existing flowcharts need used_in_l1_ids / used_in_l2_ids / used_in_l3_ids populated once from their current linked_entities (one-time migration), then maintained going forward by the Step 5 sync.
  • Archive rule — aligned (resolved 2026-06-08). Flowchart, Table, and the Image Bank all block archive while any linkage exists (linked_entities non-empty); the editor must unlink all references first. This matches the shipped code (FlowchartService.archive blocks on ≥1), and supersedes the earlier draft text in Step 5 that allowed archiving a flowchart linked to ≤ 1 entity.

Technical Details (as built)

Implemented in PR #747 (feature/flowchart) and PR #750 (bug/flowchart-search). Design spec: keystone/docs/superpowers/specs/2026-06-02-flowchart-design.md; backend plan: keystone/docs/superpowers/plans/2026-06-02-flowchart-backend.md. Backend only — the PlateJS flowchart editor node, admin screens, and inline rendering live in keystone-web / parixa (this repo exposes the API contract).

Naming: PRD vs code

PRD termCode
Flowchart entityFlowchartDBModel (collection flowchart) / FlowchartProjection
linked_entities[].content_idFlowchartLinkedEntity.content_id — the linked Block/MCQ _id
linked_entities[].content_typeFlowchartLinkedContentTypeEnum (MCQ=1, BLOCK=2)
status (published/archived)FlowchartStatusEnum (PUBLISHED=1, ARCHIVED=2), default PUBLISHED
thumbnailthumbnail: ImageVariant + variants: Dict[str, ImageVariant] (Image-Bank S3 pipeline)
Block/MCQ “flowchart IDs”flowchart_ids: List[str] storing flowchart short_uids

Deviations from the PRD (as shipped):

  • flowchart_objects was not implemented. PRD Step 2 lists flowchart_objects on the Block; the content side stores IDs only (flowchart_ids) and the client fetches each flowchart by id. Because the reference is a stable short_uid (not an embedded snapshot), editing a flowchart’s metadata never goes stale — so there is no content-side re-sync on edit; the only flowchart-side linkage edit is archive.
  • ID asymmetry (intentional). flowchart_ids stores flowchart short_uids (matches the image-thumbnail precedent), whereas linked_entities[].content_id stores the Block/MCQ Mongo _id.
  • confidence_score is an int validated to 0–100 (PRD said “number”).
  • Images always private. Uploads run through the Image Bank S3 pipeline with is_public=False and create_variants=True (medium/large; SVG skips variants); there is no is_public model field.

Entity (src/models/flowchart_models/)

FlowchartDBModel(BaseBeanieDocumentModel), collection flowchart, short_uid prefix FLW. Own fields: course_id (CourseEnum), title, hide_title (False), description, thumbnail, variants, mermaid_code (stored only, never server-rendered), license (LicenseType, same list as images), needs_review, confidence_score, source_url, linked_entities, used_in_l1_ids/l2_ids/l3_ids (denormalized taxonomy), status — plus inherited audit fields (created_at/updated_at/is_deleted/created_by/updated_by).

8 indexes (flw_ prefix): unique short_uid; course_id; (course_id, is_deleted, updated_at DESC, _id DESC) (bidirectional listing); (course_id, status); linked_entities.content_id (archive-cascade reverse lookup); (course_id, used_in_l{1,2,3}_ids) (L1/L2/L3 filter).

Cross-entity linkage (Block & MCQ)

  • Block and MCQ each carry top-level flowchart_ids: List[str] (flowchart short_uids, default_factory=list). The PlateJS { id, type: "flowchart" } node lives inside free-form content (Block.content / MCQ solution_client) and is never parsed server-side — the client sends flowchart_ids explicitly through the av1 Block/MCQ request schemas.
  • Single synchronous write path. FlowchartService.sync_linked_entities_for_content(course_id, content_id, content_type, old_flowchart_ids, new_flowchart_ids) — invoked by Block/MCQ create+update via the unified service container (services don’t import each other) — diffs added/removed short_uids and appends/pulls {content_id, content_type} on each flowchart’s linked_entities. No Celery.
  • In the same update_by_id, used_in_l*_ids is re-derived: MCQ → mcq.taxonomy_ids; Block → block.docket_iddocket.taxonomy_ids ([L1, L2, L3], deduped). Re-derivation triggers only when linked_entities changes — taxonomy chains are immutable (no Block→Docket move, no Docket re-parent), so there is no other recompute path.

Archive & cascade

  • Archive rule resolved → block whenever ≥1 link exists. (Resolves the open question in the addendum above: flowchart was aligned to the stricter Table rule.) FlowchartService.archive raises FLOWCHART_ARCHIVE_BLOCKED while linked_entities is non-empty; only 0 links archive (defensively clearing used_in_*). No auto-unlink — the editor detaches all references first via the normal Block/MCQ save.
  • Reverse cascade. When a linked Block/MCQ is archived or deleted, unlink_content_from_flowcharts pulls that content_id from every flowchart and recomputes used_in_*. MCQ archive additionally zeroes the MCQ’s own flowchart_ids (and linked_table_ids) in the same status write.
  • Known gap. The archive-blocked error currently reports only the link count, not the per-entity list that PRD Step 5 asks to “show … everywhere it is currently linked” (the list is computed in the service but dropped from the message). keystone-web’s “where used” panel covers this on the UI side.

Surface & semantics

SurfaceRoutesNotes
av1 (admin, write)POST /upload (multipart), GET /, GET /list-updated-at-bidirectional, GET /filters, GET /{id}, PATCH /{id}, POST /{id}/archiveJWT + editor/admin role; admin GET can preview archived.
v1 (mobile, read)GET /v1/flowchart/{id}published_only=True; wrapped in v1 client-payload encryption.
v2 (web, read)GET /v2/flowchart/{id}published_only=True; plain response.
  • Archived flowcharts are hidden from clients via published_only=True on the client get path; admin omits it.
  • v1/v2 schema modules re-export av1’s FlowchartDetailsResponse (one shared shape; URLs resolved with is_public=False).
  • Listing/search is MongoDB-filter based, no Typesense: bidirectional keyset cursor on (updated_at, _id) newest-first (FlowchartTimestampCursor); filter params short_uid (exact), title, license, source_url, needs_review, status, used_in_l{1,2,3}_ids, updated_at__gte/__lte.
  • New error codes: FLOWCHART_NOT_FOUND=9300, FLOWCHART_OPERATION_FAILED=9303, FLOWCHART_ARCHIVE_BLOCKED=9304.

Migration

One-time, idempotent backfill src/core/migrations/jun_03_26/backfill_flowchart_taxonomy_usage.py populates used_in_l*_ids on existing flowcharts (reuses _derive_taxonomy_usage; supports --dry-run). Block/MCQ flowchart_ids need no backfill (default []).