> ## 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.

# Verify energy savings with an M&V model

> Train a measurement and verification model, check its accuracy, and read verified savings against an adjusted baseline.

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="Verify energy savings with an M&V model" summary="Pick an IPMVP option, train a model on a baseline period, confirm its accuracy, then read verified savings for one building." level="Intermediate" time="About 25 min" />

  <div className="tutorial-body">
    <TutorialProgress
      tutorialId="energ-verify-mv"
      steps={[
    { id: "prerequisites", title: "Before you begin" },
    { id: "choose-option", title: "Choose an IPMVP option" },
    { id: "create-model", title: "Create the model" },
    { id: "select-meters", title: "Select meters and baseline" },
    { id: "add-drivers", title: "Add drivers and train" },
    { id: "check-fit", title: "Check model fit" },
    { id: "read-savings", title: "Read verified savings" },
  ]}
    />

    <div className="tutorial-steps">
      <TutorialStep tutorialId="energ-verify-mv" id="prerequisites" number="0" title="Before you begin" stepIds="prerequisites,choose-option,create-model,select-meters,add-drivers,check-fit,read-savings">
        This walkthrough verifies savings for **one building** that already has energy data flowing into EnerG. You train a model, confirm it is accurate, then read the savings it reports.

        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 the method before you click, read [Energy modeling and M\&V](/products/energ/concepts/energy-modeling).

        Make sure you have the following:

        * **A configured building** — meters mapped and a qualifying [baseline](/products/energ/concepts/baselines-and-weather-normalization) in place
        * **Enough history** — at least 12 consecutive months of complete billed data, or interval data for meter-level work
        * **A known measure** — the retrofit or operational change whose savings you want to verify

        <Info>
          <Tooltip tip="Measurement and Verification confirms that savings from energy projects are real, using agreed methods and data.">M\&V</Tooltip> compares later usage against an **adjusted baseline**: what the building would have used without the measure, under the same conditions.
        </Info>
      </TutorialStep>

      <TutorialStep tutorialId="energ-verify-mv" id="choose-option" number="1" title="Choose an IPMVP option" stepIds="prerequisites,choose-option,create-model,select-meters,add-drivers,check-fit,read-savings" mediaLabel="Diagram: Option B isolates a system with dedicated metering; Option C reads the whole building">
        <Tooltip tip="The International Performance Measurement and Verification Protocol defines standard approaches for quantifying energy savings.">IPMVP</Tooltip> defines how you draw the measurement boundary. EnerG supports two options.

        * **Option B** — model savings at the meter or system level when a measure targets specific equipment with dedicated metering.
        * **Option C** — model savings at the whole building when a retrofit or program is best measured at billing boundaries.

        Choose Option B when metering isolation is strong. Choose Option C when boundary-level bills represent performance. For the trade-offs, see [IPMVP options](/products/energ/concepts/energy-modeling#ipmvp-options).
      </TutorialStep>

      <TutorialStep tutorialId="energ-verify-mv" id="create-model" number="2" title="Create the model" stepIds="prerequisites,choose-option,create-model,select-meters,add-drivers,check-fit,read-savings" mediaLabel="Screenshot: Energy Modeling page with the Create Model button">
        Open the building view, then start a new model from the modeling workspace.

        <Steps>
          <Step title="Open Energy Modeling">
            In the building-level left sidebar, expand `Energy Modeling` and open the modeling workspace.
          </Step>

          <Step title="Start a new model">
            Click `+ Create Model`, then give the model a clear name that identifies the building and the measure.
          </Step>

          <Step title="Set the option">
            Select the IPMVP option you chose in the previous step.
          </Step>
        </Steps>

        For the full field-by-field walkthrough, see [Create an M\&V model](/products/energ/energy-modeling/mv-models).
      </TutorialStep>

      <TutorialStep tutorialId="energ-verify-mv" id="select-meters" number="3" title="Select meters and baseline" stepIds="prerequisites,choose-option,create-model,select-meters,add-drivers,check-fit,read-savings">
        <Frame caption="Baseline configuration with Energy, Water, and Waste categories">
          <img src="https://mintcdn.com/kodelabs/euAGYgyin2AxlZ4b/images/energ/energ-quickstart-baseline-config.png?fit=max&auto=format&n=euAGYgyin2AxlZ4b&q=85&s=68e164615928aad7f3795f43edb0bc62" alt="Baselines page with Energy, Water, and Waste sections each showing the auto-configure option, and an Edit Configuration button in the top-right corner" width="1268" height="549" data-path="images/energ/energ-quickstart-baseline-config.png" />
        </Frame>

        Choose the meters that carry the measure's signal, then set the baseline period the model trains on.

        <Steps>
          <Step title="Assign meters">
            For Option B, select the isolated system meters. For Option C, select the whole-building meters. Confirm each meter has `Include in Metrics` enabled.
          </Step>

          <Step title="Set the baseline period">
            Pick a baseline window that reflects normal operation before the measure. A <Tooltip tip="A baseline is a historical reference window you measure future usage against.">baseline</Tooltip> of 12 months captures a full weather cycle.
          </Step>
        </Steps>

        If a window will not qualify, close gaps first. See [Data completeness](/products/energ/data-quality/data-completeness).
      </TutorialStep>

      <TutorialStep tutorialId="energ-verify-mv" id="add-drivers" number="4" title="Add drivers and train the model" stepIds="prerequisites,choose-option,create-model,select-meters,add-drivers,check-fit,read-savings" mediaLabel="Screenshot: driver selection with weather degree days and the Train button">
        Drivers are the variables the model regresses energy against, so it separates real savings from noise.

        Add weather as <Tooltip tip="Degree days measure how much outdoor weather departs from configured base temperatures each day.">degree days</Tooltip>, then add any operational drivers you track, such as occupancy or production. Start with weather alone, then add drivers only if they improve fit.

        Click `Train` to fit the model over the baseline period. Training runs in the background and returns accuracy metrics when it finishes.
      </TutorialStep>

      <TutorialStep tutorialId="energ-verify-mv" id="check-fit" number="5" title="Check model fit" stepIds="prerequisites,choose-option,create-model,select-meters,add-drivers,check-fit,read-savings" mediaLabel="Screenshot: model accuracy panel showing R-squared, CV-RMSE, and MAPE">
        A model is only useful if it fits the baseline well. EnerG reports three accuracy metrics.

        * **R-squared** — the share of variation the model explains. Higher is better.
        * **CV-RMSE** — the scatter of predictions around actuals. Lower is better.
        * **MAPE** — the average percent error. Lower is better.

        Compare each value against your thresholds in [Calculations and parameters](/products/energ/reference/calculations-and-parameters#model-accuracy). If the model falls short, widen the baseline, adjust drivers, or improve meter completeness, then train again.

        <Warning>
          Do not report savings from a model that fails your accuracy thresholds. A poor fit produces an unreliable adjusted baseline.
        </Warning>
      </TutorialStep>

      <TutorialStep tutorialId="energ-verify-mv" id="read-savings" number="6" title="Read verified savings" stepIds="prerequisites,choose-option,create-model,select-meters,add-drivers,check-fit,read-savings" mediaLabel="Screenshot: M&V analysis with adjusted baseline versus actual usage and cumulative savings">
        With an accurate model, open the analysis view to read savings against the adjusted baseline.

        The chart plots the **adjusted baseline** against **actual usage** for the reporting period. The gap between them is your verified savings, expressed in energy, cost, and avoided emissions.

        For every field on this view, see [M\&V analysis](/products/energ/energy-modeling/mv-analysis). To project the trained model forward, continue to [Forecasting](/products/energ/energy-modeling/forecasting).
      </TutorialStep>
    </div>
  </div>
</div>

## What's next

<CardGroup cols={2}>
  <Card title="Energy modeling and M&V" icon="activity" href="/products/energ/concepts/energy-modeling" arrow={true} cta="Read">
    Review IPMVP options, adjusted baselines, and model fit in depth.
  </Card>

  <Card title="M&V analysis" icon="cog" href="/products/energ/energy-modeling/mv-analysis" arrow={true} cta="Open">
    Read every field on the savings and adjusted baseline view.
  </Card>

  <Card title="Forecasting" icon="chart-line" href="/products/energ/energy-modeling/forecasting" arrow={true} cta="Open">
    Project one to 12 months of consumption from your trained model.
  </Card>

  <Card title="Capital planning" icon="dollar-sign" href="/products/energ/sustainability/capital-planning" arrow={true} cta="Open">
    Link verified savings to measures and scenarios that shape budgets.
  </Card>
</CardGroup>
