$ cat workspace-template.yaml

M

Monthly Business Review

// Builds a board-ready business review deck for a course business from the organization ontology and live Odoo invoices: revenue by month and by course, enrollment and completion figures, and the actions they imply. Outputs an HTML deck and a downloadable PowerPoint. A second application on the same ontology — it defines no model and ingests nothing.

Automation
#ontology#reporting#presentation#pptx#odoo#erp

// Canvas Preview

canvas.flow

// Instruction

instruction.md

Monthly Business Review

Overview

Builds a board-ready review deck for a course business: revenue by month and by course, enrollment and completion figures, and the actions they imply. Outputs a self-contained HTML deck and a downloadable .pptx.

This is the second application on the same ontology. It defines no object types, ingests nothing, and reuses the console's data-access node unchanged. That is the whole point of the artefact — once the model exists, another app is a rollup and four slide prompts.

Every figure is produced by "Build Review" and passed to the slides as text. The slide generators are instructed to reproduce numbers verbatim; they are laying out figures, not computing them. If a number on a slide looks wrong, the fault is in figures, which is emitted alongside for exactly that reason.

Requires: the ontology already loaded (run Odoo to Ontology Feeder first) and an Odoo connection on the workspace-token owner's account.

Workflow Chain

"Read Ontology"[enrollments] ──┐"Ontology Census"[result] ─────┤"Odoo Invoices (live)"[data] ──┼──► "Build Review" ──┬─[headline]─────► "Slide — Cover""Run Date"[value] ─────────────┘                     ├─[kpiText]──────► "Slide — KPIs"                                                     ├─[revenueText]──► "Slide — Revenue"                                                     └─[actionsText]──► "Slide — Actions"
  four [slide_html] ──► "Deck Slides"[item_0..3] ──► "Compose Deck" ──► "Export PPTX" ──► "Save Deck" (GATED)

Node Reference

  • "Run Date" (String) — the ISO date the reporting window ends on. The executor has no reliable clock. Configurable: value.
  • "Read Ontology" (Custom Code) — the console's data-access node, reused unchanged. Configurable: maxObjects.
  • "Ontology Census" (Ontology Aggregate) — exact object counts per type. Exact, not sampled: it is a census, not a retrieval ranking.
  • "Odoo Invoices (live)" (Odoo RPC) — posted customer invoices. invoice_date is required in params.fields; the monthly rollup buckets on it. Configurable: params.domain, params.limit.
  • "Build Review" (Custom Code) — computes every figure and renders the four slide inputs. Emits figures (auditable JSON) alongside the text. Configurable: periodMonths (default 6), runDate.
  • "Slide — Cover / KPIs / Revenue / Actions" (Slide Generator) — layouts emblema-cover, emblema-kpis, emblema-table, emblema-bullets. Configurable: instruction, template_name, language, llmModelId (leave EMPTY for the workspace default).
  • "Deck Slides" (Make Array) — order matters: cover, KPIs, revenue, actions on item_0..item_3.
  • "Compose Deck" (Presentation Composer) → "Export PPTX" (PPTX Converter) → "Save Deck" (save-file, GATED, destinationFolder ships EMPTY on purpose).

Execution Strategy

You MUST complete all 4 phases in order: Phase 1 → Phase 2 → Phase 3 → Phase 4. Do NOT skip any phase.

There is exactly ONE requestUserDecision, in Phase 4, and only because saving to Drive is a side effect. Phases 1–3 are read-only.

Phase 1: Gather

  1. applyCanvasData to commit the template, then nodeFinder for every id — preview ids are stale.
  2. If the user named a date or a period, upsertNode "Run Date" { value: <date> } and "Build Review" { runDate: <date>, periodMonths: <n> }. Otherwise keep the baked values; do NOT ask.
  3. nodeExecutor "Read Ontology", "Ontology Census" and "Odoo Invoices (live)" together — all three are independent.
  4. getNodeOutput each. If "Read Ontology" reports no Enrollment objects, STOP and report that the Odoo to Ontology Feeder must run first. If "Odoo Invoices (live)" returns record_count: 0, STOP — a revenue deck with no invoices would render every figure as zero and look like a collapsed business rather than a missing feed.

Phase 1 complete. Now proceed to Phase 2.

Phase 2: Compute

  1. nodeExecutor "Build Review". getNodeOutput and read summary and figures.
  2. Report the headline figures to the user in prose before any slide is rendered: collected, outstanding, enrollments, completion rate, stalled count. Rendering four slides from numbers the user has not seen wastes a full LLM pass when a figure is wrong.
  3. If any enrollment carries engagementSource: "synthetic", say that completion and progress figures are derived rather than measured, because no Moodle completion feed is wired. Never let a synthetic figure reach a board slide unlabelled.

Phase 2 complete. Now proceed to Phase 3.

Phase 3: Render

  1. nodeExecutor the four slide nodes together — they are independent of each other.
  2. getNodeOutput each and confirm slide_html is non-empty. If one is empty, re-run that node alone; do not compose a deck with a hole in it.
  3. nodeExecutor "Deck Slides", then "Compose Deck", then "Export PPTX", in that order.
  4. getNodeOutput "Compose Deck" and spot-check that the figures in the HTML match figures from step 5. The slide generators are instructed to copy numbers verbatim, and this is where that gets verified rather than assumed.

Phase 3 complete. Now proceed to Phase 4.

Phase 4: Deliver

  1. Call requestUserDecision ONCE: "Save the deck to Drive?", allowFreeText: false, options save / skip.
  2. If save: call selectDriveItem (type=folder), then upsertNode "Save Deck" { destinationFolder: <picked id>, name: "business-review-<runDate>.pptx" }, then nodeExecutor it. Never invent a folder id and never reuse the empty default. If skip or cancelled: true, write nothing.
  3. Call verification with the headline figures and whether the deck was saved. Then followUp offering: (a) a monthly schedule, (b) re-run for a different period, (c) drill into one course in the console.

Configuration Tips

User saysConfigure
"make it quarterly""Build Review" periodMonths3
"full year""Build Review" periodMonths12
"in Italian"languageit on all four slide nodes
"different look"template_name on the slide nodes, or "Compose Deck" theme
"add a slide"New Slide Generator + a Build Review text output + item_4 on "Deck Slides"
"only paid revenue""Odoo Invoices (live)" params.domain → add ["payment_state","=","paid"]

Error Handling

ErrorCauseFix
no Enrollment objects are visibleOntology not loadedRun the Odoo to Ontology Feeder first
All revenue reads 0invoice_date missing from params.fields, or invoices fall outside the windowConfirm the field is requested; widen periodMonths
runDate ... is not an ISO date"Run Date" holds a labelSet value to YYYY-MM-DD
A slide renders emptySlide generator returned nothing for that blockRe-run that node alone; check its content edge is wired
Figures on a slide disagree with figuresA slide generator paraphrased a numberTighten that node's instruction — the rule is verbatim reproduction
"Save Deck" 422sdestinationFolder still emptyAlways pick with selectDriveItem; never bake one
Odoo is not connectedNo Odoo connection on the workspace-token OWNERAdd it in account settings → Connections

// Dependencies

requirements.py
1from input import String2from process import CustomCode3from process import OntologyAggregate4from process import OdooRPC5from process import SlideGenerator6from process import MakeArray7from output import PresentationComposer8from output import PPTXConverter9from output import SaveFile

// Variables

variables.yaml
1runDate:2  type: undefined3  label: "Run date"4  description: "ISO date the reporting window ends on."5  required: undefined67periodMonths:8  type: undefined9  label: "Period (months)"10  description: "How many months the review covers."11  required: undefined

$ git log --oneline

v1.0.6
HEAD
2026-08-28
v1.0.32026-08-28
v1.0.22026-08-28
v1.0.12026-08-28
v1.0.02026-08-28