> ## Documentation Index
> Fetch the complete documentation index at: https://documentation.kodelabs.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Build an annual energy budget for a building

> Set a budget model, choose a target method, configure accruals, and track predicted spend against actual for one building.

export const TutorialStep = ({tutorialId, id, number, title, stepIds = "", subStepIds = "", mediaLabel, children}) => {
  const progressEvent = "kode-tutorial-progress";
  const brandBlues = ["#015BFA", "#3A9AFF", "#7BB3FF", "#C5DBFF", "#E8F1FF"];
  const [complete, setComplete] = useState(false);
  const parseIds = value => {
    if (Array.isArray(value)) return value;
    if (typeof value === "string" && value.length > 0) {
      return value.split(",").map(stepId => stepId.trim()).filter(Boolean);
    }
    return [];
  };
  const hasSubsteps = parseIds(subStepIds).length > 0;
  const readProgress = () => {
    if (typeof window === "undefined") return {};
    try {
      return JSON.parse(localStorage.getItem(`kode-tutorial:${tutorialId}`) || "{}");
    } catch {
      return {};
    }
  };
  const writeProgress = next => {
    if (typeof window === "undefined") return;
    localStorage.setItem(`kode-tutorial:${tutorialId}`, JSON.stringify(next));
    window.dispatchEvent(new CustomEvent(progressEvent, {
      detail: {
        tutorialId,
        progress: next
      }
    }));
  };
  const computeComplete = current => {
    const subs = parseIds(subStepIds);
    if (subs.length > 0) {
      return subs.every(stepId => current[stepId] === true);
    }
    return current[id] === true;
  };
  const playConfetti = () => {
    if (typeof window === "undefined" || typeof document === "undefined") return;
    const canvas = document.createElement("canvas");
    canvas.className = "tutorial-confetti-canvas";
    canvas.width = window.innerWidth;
    canvas.height = window.innerHeight;
    document.body.appendChild(canvas);
    const ctx = canvas.getContext("2d");
    const pieces = Array.from({
      length: 80
    }, () => ({
      x: Math.random() * canvas.width,
      y: -20 - Math.random() * canvas.height * 0.35,
      w: 4 + Math.random() * 5,
      h: 6 + Math.random() * 8,
      color: brandBlues[Math.floor(Math.random() * brandBlues.length)],
      vy: 2.2 + Math.random() * 3.2,
      vx: -1.5 + Math.random() * 3,
      rot: Math.random() * Math.PI,
      vr: -0.12 + Math.random() * 0.24,
      alpha: 0.55 + Math.random() * 0.35
    }));
    let frame = 0;
    const maxFrames = 110;
    const draw = () => {
      frame += 1;
      ctx.clearRect(0, 0, canvas.width, canvas.height);
      pieces.forEach(piece => {
        piece.x += piece.vx;
        piece.y += piece.vy;
        piece.rot += piece.vr;
        piece.vy += 0.045;
        const fade = Math.max(0, 1 - frame / maxFrames);
        ctx.save();
        ctx.translate(piece.x, piece.y);
        ctx.rotate(piece.rot);
        ctx.globalAlpha = piece.alpha * fade;
        ctx.fillStyle = piece.color;
        ctx.fillRect(-piece.w / 2, -piece.h / 2, piece.w, piece.h);
        ctx.restore();
      });
      if (frame < maxFrames) {
        requestAnimationFrame(draw);
      } else {
        canvas.remove();
      }
    };
    requestAnimationFrame(draw);
  };
  useEffect(() => {
    const current = readProgress();
    setComplete(computeComplete(current));
    const onProgress = event => {
      if (event.detail?.tutorialId === tutorialId) {
        setComplete(computeComplete(event.detail.progress || ({})));
      }
    };
    window.addEventListener(progressEvent, onProgress);
    return () => window.removeEventListener(progressEvent, onProgress);
  }, [tutorialId, id, subStepIds]);
  const toggleComplete = () => {
    if (hasSubsteps) return;
    const current = readProgress();
    const nextValue = !current[id];
    const next = {
      ...current,
      [id]: nextValue
    };
    writeProgress(next);
    setComplete(nextValue);
    const topLevel = parseIds(stepIds);
    if (nextValue && topLevel.length > 0 && topLevel.every(stepId => next[stepId] === true)) {
      playConfetti();
    }
  };
  return <section id={`tutorial-step-${id}`} className={`tutorial-step-card${complete ? " is-complete" : ""}`}>
      <div className="tutorial-step-header">
        <div>
          <p className="tutorial-step-number">Step {number}</p>
          <h3 className="tutorial-step-title">{title}</h3>
        </div>
        <button type="button" className={`tutorial-complete-btn${complete ? " is-complete" : ""}${hasSubsteps ? " is-locked" : ""}`} onClick={toggleComplete} aria-pressed={complete} aria-label={hasSubsteps ? complete ? "All substeps complete" : "Complete all substeps first" : complete ? "Mark step incomplete" : "Mark step complete"} title={hasSubsteps ? complete ? "All substeps complete" : "Complete all substeps first" : complete ? "Mark incomplete" : "Mark complete"} disabled={hasSubsteps}>
          {complete ? "✓" : ""}
        </button>
      </div>

      {mediaLabel ? <div className="tutorial-media-slot" aria-label="Media placeholder">
          <div className="tutorial-media-placeholder">
            <span>{mediaLabel}</span>
            <span className="tutorial-media-hint">
              Replace this block with a Frame, image, or video when ready
            </span>
          </div>
        </div> : null}

      <div className="tutorial-step-body">{children}</div>

      <div className="tutorial-step-footer">
        {hasSubsteps ? <p className="tutorial-step-gate">
            {complete ? "All substeps complete" : "Mark each substep complete to finish this step"}
          </p> : <button type="button" className={`tutorial-mark-complete${complete ? " is-complete" : ""}`} onClick={toggleComplete}>
            {complete ? "Completed" : "Mark step complete"}
          </button>}
      </div>
    </section>;
};

export const TutorialProgress = ({tutorialId, steps = []}) => {
  const progressEvent = "kode-tutorial-progress";
  const [progress, setProgress] = useState({});
  const [activeId, setActiveId] = useState(steps[0]?.id || "");
  const parseSubsteps = step => {
    if (!step?.substeps) return [];
    if (Array.isArray(step.substeps)) return step.substeps;
    if (typeof step.substeps !== "string") return [];
    return step.substeps.split(";").map(entry => entry.trim()).filter(Boolean).map(entry => {
      const sep = entry.indexOf(":");
      if (sep === -1) return {
        id: entry,
        title: entry
      };
      return {
        id: entry.slice(0, sep).trim(),
        title: entry.slice(sep + 1).trim()
      };
    });
  };
  const isStepComplete = (step, current) => {
    const subs = parseSubsteps(step);
    if (subs.length > 0) {
      return subs.every(sub => current[sub.id] === true);
    }
    return current[step.id] === true;
  };
  const readProgress = () => {
    if (typeof window === "undefined") return {};
    try {
      return JSON.parse(localStorage.getItem(`kode-tutorial:${tutorialId}`) || "{}");
    } catch {
      return {};
    }
  };
  const resetProgress = () => {
    if (typeof window === "undefined") return;
    localStorage.removeItem(`kode-tutorial:${tutorialId}`);
    window.dispatchEvent(new CustomEvent(progressEvent, {
      detail: {
        tutorialId,
        progress: {}
      }
    }));
  };
  useEffect(() => {
    setProgress(readProgress());
    const onProgress = event => {
      if (event.detail?.tutorialId === tutorialId) {
        setProgress(event.detail.progress || ({}));
      }
    };
    window.addEventListener(progressEvent, onProgress);
    return () => window.removeEventListener(progressEvent, onProgress);
  }, [tutorialId]);
  useEffect(() => {
    const ids = [];
    steps.forEach(step => {
      ids.push(step.id);
      parseSubsteps(step).forEach(sub => ids.push(sub.id));
    });
    const nodes = ids.map(stepId => {
      return document.getElementById(`tutorial-step-${stepId}`) || document.getElementById(`tutorial-substep-${stepId}`);
    }).filter(Boolean);
    if (nodes.length === 0) return undefined;
    const observer = new IntersectionObserver(entries => {
      const visible = entries.filter(entry => entry.isIntersecting).sort((a, b) => b.intersectionRatio - a.intersectionRatio);
      if (visible[0]?.target?.id) {
        const raw = visible[0].target.id.replace("tutorial-substep-", "").replace("tutorial-step-", "");
        setActiveId(raw);
      }
    }, {
      rootMargin: "-20% 0px -55% 0px",
      threshold: [0.15, 0.4, 0.7]
    });
    nodes.forEach(node => observer.observe(node));
    return () => observer.disconnect();
  }, [tutorialId, steps.length]);
  const completedCount = steps.filter(step => isStepComplete(step, progress)).length;
  const done = steps.length > 0 && steps.every(step => isStepComplete(step, progress));
  const pct = steps.length ? Math.round(completedCount / steps.length * 100) : 0;
  return <aside className="tutorial-progress" aria-label="Tutorial progress">
      <div className="tutorial-progress-header">
        <p className="tutorial-progress-label">Progress</p>
        <p className="tutorial-progress-count">
          {completedCount} / {steps.length}
        </p>
      </div>

      <div className="tutorial-progress-bar" role="progressbar" aria-valuemin={0} aria-valuemax={steps.length} aria-valuenow={completedCount}>
        <div className="tutorial-progress-bar-fill" style={{
    width: `${pct}%`
  }} />
      </div>

      <ol className="tutorial-progress-list">
        {steps.map((step, index) => {
    const subs = parseSubsteps(step);
    const isComplete = isStepComplete(step, progress);
    const isActive = activeId === step.id || subs.some(sub => sub.id === activeId);
    return <li key={step.id} className="tutorial-progress-group">
              <a href={`#tutorial-step-${step.id}`} className={["tutorial-progress-item", isActive ? "is-active" : "", isComplete ? "is-complete" : ""].filter(Boolean).join(" ")}>
                <span className="tutorial-progress-marker" aria-hidden="true">
                  {isComplete ? "✓" : index}
                </span>
                <span className="tutorial-progress-title">{step.title}</span>
              </a>

              {subs.length > 0 ? <ol className="tutorial-progress-sublist">
                  {subs.map((sub, subIndex) => {
      const subComplete = progress[sub.id] === true;
      const subActive = activeId === sub.id;
      return <li key={sub.id}>
                        <a href={`#tutorial-substep-${sub.id}`} className={["tutorial-progress-item tutorial-progress-item--sub", subActive ? "is-active" : "", subComplete ? "is-complete" : ""].filter(Boolean).join(" ")}>
                              <span className="tutorial-progress-marker tutorial-progress-marker--sub" aria-hidden="true">
                            {subComplete ? "✓" : ""}
                          </span>
                          <span className="tutorial-progress-title">
                            {sub.title}
                          </span>
                        </a>
                      </li>;
    })}
                </ol> : null}
            </li>;
  })}
      </ol>

      <div className="tutorial-progress-footer">
        {done ? <p className="tutorial-progress-done">Tutorial complete</p> : null}

        {completedCount > 0 || Object.keys(progress).length > 0 ? <button type="button" className="tutorial-reset" onClick={resetProgress}>
            Reset progress
          </button> : null}
      </div>
    </aside>;
};

export const TutorialHero = ({eyebrow = "Tutorial", title, summary, time, level, imageSrc, imageAlt = "Tutorial preview"}) => {
  useEffect(() => {
    if (typeof document === "undefined") return undefined;
    document.documentElement.setAttribute("data-tutorial-layout", "true");
    const page = document.querySelector(".tutorial-page");
    if (page) {
      let sibling = page.previousElementSibling;
      while (sibling) {
        sibling.setAttribute("data-tutorial-hidden-header", "true");
        sibling = sibling.previousElementSibling;
      }
    }
    document.querySelectorAll("h1").forEach(heading => {
      if (heading.textContent.trim() !== title) return;
      heading.setAttribute("data-tutorial-hidden-header", "true");
      const prev = heading.previousElementSibling;
      if (prev) prev.setAttribute("data-tutorial-hidden-header", "true");
      const next = heading.nextElementSibling;
      if (next && next.tagName === "P") {
        next.setAttribute("data-tutorial-hidden-header", "true");
      }
    });
    return () => {
      document.documentElement.removeAttribute("data-tutorial-layout");
      document.querySelectorAll("[data-tutorial-hidden-header]").forEach(node => node.removeAttribute("data-tutorial-hidden-header"));
    };
  }, [title]);
  return <header className="tutorial-hero">
      <div className="tutorial-hero-glow" aria-hidden="true" />
      <div className="tutorial-hero-copy">
        <p className="tutorial-hero-eyebrow">{eyebrow}</p>
        <h2 className="tutorial-hero-title">{title}</h2>
        {summary ? <p className="tutorial-hero-summary">{summary}</p> : null}
        <div className="tutorial-hero-meta">
          {level ? <span className="tutorial-pill">{level}</span> : null}
          {time ? <span className="tutorial-pill">{time}</span> : null}
        </div>
      </div>
      <div className="tutorial-hero-media">
        {imageSrc ? <img src={imageSrc} alt={imageAlt} className="tutorial-hero-image" /> : <div className="tutorial-media-placeholder tutorial-media-placeholder--hero">
            <span>Add hero image</span>
            <span className="tutorial-media-hint">
              Drop a screenshot or still here
            </span>
          </div>}
      </div>
    </header>;
};

<div className="tutorial-page">
  <TutorialHero eyebrow="EnerG" title="Build an annual energy budget for a building" summary="Choose a budget model and target method, set the period, configure accruals, then track predicted spend against actual." level="Intermediate" time="About 20 min" />

  <div className="tutorial-body">
    <TutorialProgress
      tutorialId="energ-build-budget"
      steps={[
    { id: "prerequisites", title: "Before you begin" },
    { id: "open-planning", title: "Open budget planning" },
    { id: "choose-model", title: "Choose a budget model" },
    { id: "set-targets", title: "Set the period and targets" },
    { id: "configure-accruals", title: "Configure accruals" },
    { id: "review-variance", title: "Review predicted vs actual" },
  ]}
    />

    <div className="tutorial-steps">
      <TutorialStep tutorialId="energ-build-budget" id="prerequisites" number="0" title="Before you begin" stepIds="prerequisites,open-planning,choose-model,set-targets,configure-accruals,review-variance">
        This walkthrough builds an annual energy budget for **one building** and tracks it through the year. You set the plan, then EnerG compares actual spend against it.

        If you have not set up a building yet, complete [Set up your first building](/products/energ/tutorials/set-up-your-first-building) first. To understand how budgets, accruals, and variance fit together, read [Budgets and capital planning](/products/energ/concepts/budgets-and-capital-planning).

        Make sure you have the following:

        * **A configured building** — meters mapped and cost data flowing from bills
        * **A baseline year** — at least 12 months of complete billed data to model the plan against
        * **Rate assumptions** — any expected changes in utility rates for the budget year

        <Info>
          A budget compares **predicted** spend against **actual** spend. **Accruals** fill months that lack final bills so the comparison stays complete. See [Predicted, actual, and variance](/products/energ/concepts/budgets-and-capital-planning#predicted-actual-and-variance).
        </Info>
      </TutorialStep>

      <TutorialStep tutorialId="energ-build-budget" id="open-planning" number="1" title="Open budget planning" stepIds="prerequisites,open-planning,choose-model,set-targets,configure-accruals,review-variance" mediaLabel="Screenshot: Budget section in the building sidebar with the Budget Planning page open">
        Work in the building view. In the left sidebar, expand `Budget` and open `Budget Planning`.

        The planning workspace is where you define the model, targets, and accrual behavior for the budget year. For the full field reference, see [Budget planning](/products/energ/finance/budget-planning).
      </TutorialStep>

      <TutorialStep tutorialId="energ-build-budget" id="choose-model" number="2" title="Choose a budget model" stepIds="prerequisites,open-planning,choose-model,set-targets,configure-accruals,review-variance" mediaLabel="Screenshot: budget model selection showing baseline-driven and target-driven options">
        The <Tooltip tip="A budget model is the method EnerG uses to project a building's expected energy spend for the year.">budget model</Tooltip> sets how EnerG projects expected spend.

        <Steps>
          <Step title="Select a model type">
            Choose a baseline-driven model to carry last year's usage forward, or a target-driven model to build the budget around a reduction goal.
          </Step>

          <Step title="Confirm the source data">
            Confirm the model reads the correct baseline year and cost data for this building.
          </Step>
        </Steps>

        For how each model behaves, see [Budget models](/products/energ/concepts/budgets-and-capital-planning#budget-models).
      </TutorialStep>

      <TutorialStep tutorialId="energ-build-budget" id="set-targets" number="3" title="Set the period and targets" stepIds="prerequisites,open-planning,choose-model,set-targets,configure-accruals,review-variance" mediaLabel="Screenshot: budget period selector with monthly target values">
        Set the budget year, then set the targets EnerG measures spend against.

        <Steps>
          <Step title="Set the budget period">
            Select the fiscal or calendar year the budget covers.
          </Step>

          <Step title="Choose a target method">
            Pick how EnerG calculates targets, such as a percent reduction from baseline or a fixed value. See [Target calculation methods](/products/energ/reference/calculations-and-parameters#target-methods).
          </Step>

          <Step title="Review the monthly spread">
            Confirm the monthly targets. EnerG shapes them from historical patterns, so winter and summer months differ.
          </Step>
        </Steps>
      </TutorialStep>

      <TutorialStep tutorialId="energ-build-budget" id="configure-accruals" number="4" title="Configure accruals" stepIds="prerequisites,open-planning,choose-model,set-targets,configure-accruals,review-variance" mediaLabel="Screenshot: accrual settings with prior-year shape and calendarization options">
        <Tooltip tip="Accruals estimate usage for months that lack final bills, so month-end reporting stays complete.">Accruals</Tooltip> keep the budget comparison whole when final bills lag.

        Enable accruals so EnerG estimates usage for open months from prior-year shapes and calendarized reads. EnerG trues up each accrual when the utility posts the final bill.

        For how accruals behave, see [Accruals](/products/energ/reference/calculations-and-parameters#accruals).
      </TutorialStep>

      <TutorialStep tutorialId="energ-build-budget" id="review-variance" number="5" title="Review predicted versus actual" stepIds="prerequisites,open-planning,choose-model,set-targets,configure-accruals,review-variance" mediaLabel="Screenshot: budget overview with predicted, actual, and variance columns">
        With the plan saved, open the budget overview to track performance through the year.

        The view compares **predicted** spend against **actual** spend and shows the **variance** between them. A positive variance means you are under budget; a negative variance means you are over.

        For every field on this view, see [Budget overview](/products/energ/finance/budget-overview). To analyze what drives the variance, open [Cost analysis](/products/energ/finance/cost-analysis).
      </TutorialStep>
    </div>
  </div>
</div>

## What's next

<CardGroup cols={2}>
  <Card title="Budgets and capital planning" icon="dollar-sign" href="/products/energ/concepts/budgets-and-capital-planning" arrow={true} cta="Read">
    Understand budget models, accruals, variance, and capital scenarios.
  </Card>

  <Card title="Budget planning" icon="sliders-horizontal" href="/products/energ/finance/budget-planning" arrow={true} cta="Open">
    Configure every budget model and target field in detail.
  </Card>

  <Card title="Cost analysis" icon="chart-column" href="/products/energ/finance/cost-analysis" arrow={true} cta="Open">
    Break down spend by service type, rate, and period.
  </Card>

  <Card title="Capital planning" icon="trending-up" href="/products/energ/sustainability/capital-planning" arrow={true} cta="Open">
    Model measures and scenarios that reshape next year's budget.
  </Card>
</CardGroup>
