September 6, 2025

cNode SDK (Preview): Forecasts & scenarios right in your code with a mock demo to try

von

Leonardo Bornhäußer

shallow focus photo of brown cardboard box
shallow focus photo of brown cardboard box
shallow focus photo of brown cardboard box

Why an SDK — and why now?

APIs are powerful, but developer speed wins the day. The cNode SDK cuts boilerplate down to a single function call: cluster, goal, horizon, inputs — done.
During the preview you can already test realistically: either fully offline with synthetic data or against a mock endpoint, until the production SDK ships as @cnode/sdk.

Mental model: clusters × goals

Like the API, the SDK is domain- and goal-based.

Clusters (preview): finance, product, hr, marketing, compliance, supply_chain

Typical goals:

  • Finance: revenue_forecast, cashflow_forecast, break_even, profitability, roi_forecast, valuation_dcf, price_elasticity, anomaly_detection

  • Product: demand_forecast, inventory_coverage, return_rate_forecast

  • HR: headcount_forecast, attrition_forecast

  • Marketing: mql_forecast, cac_ltv_projection

  • Compliance: esg_risk_score, anomaly_detection

This lets you wire KPIs and context deliberately — without the black box.

Result format: forecasts, intervals, explainability, diagnostics

A single SDK call returns structured results:

  • Forecast: time series (date/value)

  • Intervals: confidence bands (lower/upper + confidence)

  • Explanations: top drivers (global & optional local)

  • Diagnostics: metrics (e.g., WAPE/MAE/RMSE), warnings, retrain hints

Forecasts stay auditable and explainable — ideal for Finance, ESG, Planning & Compliance.

Full SDK preview snippet (copy–paste)

Note: This snippet includes a mock SDK shim so you can demo today on your docs/website. Replace it later with @cnode/sdk.

/**
 * CNode SDK — Preview / Coming Soon (copy–paste)
 * -----------------------------------------------------------------------------
 * STATUS: The official SDK package is under active development.
 *         This snippet shows the intended params & responses and includes a
 *         small mock SDK so you can demo today on your docs/homepage.
 *
 * WHEN LIVE, REPLACE with:
 *   import { CNode } from "@cnode/sdk";
 *   const cnode = new CNode({ apiKey: process.env.CNODE_API_KEY });
 *
 * CLUSTERS (preview):
 *   "finance", "product", "hr", "marketing", "compliance", "supply_chain"
 *
 * COMMON GOALS by cluster (preview examples):
 *   finance:      "revenue_forecast", "cashflow_forecast", "break_even",
 *                 "profitability", "roi_forecast", "valuation_dcf",
 *                 "price_elasticity", "anomaly_detection"
 *   product:      "demand_forecast", "inventory_coverage", "return_rate_forecast"
 *   hr:           "headcount_forecast", "attrition_forecast"
 *   marketing:    "mql_forecast", "cac_ltv_projection"
 *   compliance:   "esg_risk_score", "anomaly_detection"
 *
 * RUN PARAMS (preview):
 *   {
 *     projectId: string,                         // required
 *     cluster: string,                           // required (see CLUSTERS)
 *     goal: string,                              // required (see GOALS)
 *     horizon: "3M"|"6M"|"12M"|string,           // required
 *     inputs: object,                            // required, domain-specific
 *     explainability?: boolean,                  // default: true
 *     options?: {
 *       confidence?: number,                     // 0.5..0.99; default: 0.8
 *       includeIntervals?: boolean,              // return prediction bands
 *       sensitivity?: { topK?: number },         // top drivers count
 *       backtest?: { from: string, to: string, metric?: "WAPE"|"MAE"|"RMSE" },
 *       retrain?: "auto"|"never",
 *       tags?: string[],
 *       metadata?: Record<string, unknown>
 *     }
 *   }
 *
 * RUN RESULT (preview):
 *   {
 *     run_id: string,
 *     forecast: Array<{ date: "YYYY-MM-DD", value: number }>,
 *     intervals?: Array<{ date: string, lower: number, upper: number, confidence: number }>,
 *     explanations?: {
 *       global_top_features: Array<{ feature: string, weight: number }>,
 *       local?: Array<{ date: string, contributions: Array<{ feature: string, weight: number }> }>
 *     },
 *     diagnostics?: {
 *       metrics?: { WAPE?: number, MAE?: number, RMSE?: number },
 *       retrain?: boolean,
 *       warnings?: string[]
 *     }
 *   }
 *
 * SECURITY NOTE:
 *   Keep real API keys server-side. For homepage demos, use mocked data or a
 *   short-lived preAuth token from a server route.
 */

// ---------- Types (preview) ----------
type Cluster =
  | "finance"
  | "product"
  | "hr"
  | "marketing"
  | "compliance"
  | "supply_chain"
  | (string & {}); // allow custom

type Goal =
  | "revenue_forecast"
  | "cashflow_forecast"
  | "break_even"
  | "profitability"
  | "roi_forecast"
  | "valuation_dcf"
  | "price_elasticity"
  | "anomaly_detection"
  | "demand_forecast"
  | "inventory_coverage"
  | "return_rate_forecast"
  | "headcount_forecast"
  | "attrition_forecast"
  | "mql_forecast"
  | "cac_ltv_projection"
  | "esg_risk_score"
  | (string & {}); // allow custom

type BacktestMetric = "WAPE" | "MAE" | "RMSE";

interface RunOptions {
  confidence?: number; // default: 0.8
  includeIntervals?: boolean;
  sensitivity?: { topK?: number }; // default: 5
  backtest?: { from: string; to: string; metric?: BacktestMetric };
  retrain?: "auto" | "never";
  tags?: string[];
  metadata?: Record<string, unknown>;
}

interface RunParams {
  projectId: string;
  cluster: Cluster;
  goal: Goal;
  horizon: "3M" | "6M" | "12M" | (string & {});
  inputs: Record<string, unknown>;
  explainability?: boolean;
  options?: RunOptions;
}

interface RunResult {
  run_id: string;
  forecast: Array<{ date: string; value: number }>;
  intervals?: Array<{ date: string; lower: number; upper: number; confidence: number }>;
  explanations?: {
    global_top_features: Array<{ feature: string; weight: number }>;
    local?: Array<{ date: string; contributions: Array<{ feature: string; weight: number }> }>;
  };
  diagnostics?: {
    metrics?: Partial<Record<BacktestMetric, number>>;
    retrain?: boolean;
    warnings?: string[];
  };
}

// ---------- Temporary SDK shim for demos (remove once SDK is live) ----------
class MockCNode {
  constructor(
    private cfg: {
      /**
       * If you supply a baseUrl (e.g. "/api/mock/cnode"), we'll POST to:
       *   `${baseUrl}/scenarios:run`
       * Otherwise we synthesize a deterministic forecast locally.
       */
      baseUrl?: string;
      /**
       * Do NOT put real API keys in client-side code. This is a placeholder to
       * illustrate the SDK signature only.
       */
      apiKey?: string;
      apiVersion?: string; // e.g. "2025-09-01"
    } = {}
  ) {
    this.cfg.apiVersion ??= "2025-09-01";
  }

  scenarios = {
    run: async (p: RunParams): Promise<RunResult> => {
      if (this.cfg.baseUrl) {
        // Proxy to your mock endpoint while backend is under development
        const res = await fetch(`${this.cfg.baseUrl}/scenarios:run`, {
          method: "POST",
          headers: {
            "Content-Type": "application/json",
            "X-API-Version": this.cfg.apiVersion!,
            "X-Demo-Mode": "true",
            // Example only; never ship real keys in browser
            Authorization: this.cfg.apiKey ? `Bearer ${this.cfg.apiKey}` : undefined
          } as Record<string, string>,
          body: JSON.stringify({
            project_id: p.projectId,
            cluster: p.cluster,
            goal: p.goal,
            horizon: p.horizon,
            inputs: p.inputs,
            explainability: p.explainability ?? true,
            options: p.options,
            _note: "SDK-preview: replace mock route with production once live"
          })
        });

        if (!res.ok) {
          const err = await res.json().catch(() => ({}));
          throw new Error(`Mock SDK ${res.status}: ${err.message ?? res.statusText}`);
        }
        return res.json();
      }

      // Fallback: synthesize a deterministic, upward-trending demo series
      const months = parseHorizonToMonths(p.horizon);
      const start = new Date(Date.UTC(2025, 0, 1));
      const base = 42000;
      const step = 3500;

      const forecast = Array.from({ length: months }, (_, i) => {
        const d = new Date(start);
        d.setUTCMonth(d.getUTCMonth() + i);
        const trend = base + step * i;
        const season = 1 + 0.1 * Math.sin((2 * Math.PI * i) / 12); // ±10% seasonality
        return { date: d.toISOString().slice(0, 10), value: Math.round(trend * season) };
      });

      const intervals = forecast.map((pnt) => ({
        date: pnt.date,
        lower: Math.round(pnt.value * 0.9),
        upper: Math.round(pnt.value * 1.1),
        confidence: (p.options?.confidence ?? 0.8)
      }));

      const explanations: RunResult["explanations"] = {
        global_top_features: [
          { feature: "trend", weight: 0.45 },
          { feature: "seasonality", weight: 0.32 },
          { feature: "promo_index", weight: 0.11 }
        ]
      };

      const diagnostics: RunResult["diagnostics"] = {
        metrics: p.options?.backtest ? { WAPE: 0.14, MAE: 5200, RMSE: 7300 } : undefined,
        retrain: p.options?.retrain === "auto",
        warnings: []
      };

      return {
        run_id: "mock-sdk-run-001",
        forecast,
        intervals: p.options?.includeIntervals !== false ? intervals : undefined,
        explanations: p.explainability === false ? undefined : explanations,
        diagnostics
      };
    }
  };
}

// ---------- Helper ----------
function parseHorizonToMonths(h: RunParams["horizon"]): number {
  if (typeof h !== "string") return 12;
  const m = h.match(/^(\d+)\s*(M|m|months?)$/);
  if (m) return Math.max(1, parseInt(m[1], 10));
  const y = h.match(/^(\d+)\s*(Y|y|years?)$/);
  if (y) return Math.max(1, parseInt(y[1], 10) * 12);
  // common presets
  if (h === "3M") return 3;
  if (h === "6M") return 6;
  if (h === "12M") return 12;
  return 12;
}

// ---------- Example usage for docs/homepage ----------
(async () => {
  // OPTION A (no network): synth data only
  const cnode = new MockCNode();

  // OPTION B (with mock API): POST to your server route until prod is live
  // const cnode = new MockCNode({ baseUrl: "/api/mock/cnode" });

  const run = await cnode.scenarios.run({
    projectId: "demo-finance",
    cluster: "finance",
    goal: "revenue_forecast", // try: "cashflow_forecast", "valuation_dcf"
    horizon: "12M",
    inputs: {
      currency: "EUR",
      revenue_history: [
        { date: "2024-09-01", value: 42000 },
        { date: "2025-08-01", value: 86500 }
      ],
      cost_history: [
        { date: "2024-09-01", value: -28000 },
        { date: "2025-08-01", value: -52500 }
      ],
      promo_index: [{ date: "2025-06-01", value: 0.6 }]
    },
    explainability: true,
    options: {
      confidence: 0.8,
      includeIntervals: true,
      sensitivity: { topK: 5 },
      backtest: { from: "2025-01-01", to: "2025-08-01", metric: "WAPE" },
      retrain: "auto",
      tags: ["homepage-demo"],
      metadata: { source: "marketing-site" }
    }
  });

  console.log("Run:", run.run_id);
  console.table(run.forecast.slice(0, 6)); // preview first 6 points
  if (run.intervals) console.table(run.intervals.slice(0, 3));
  console.log("Top drivers:", run.explanations?.global_top_features?.slice(0, 5));
  console.log("Metrics:", run.diagnostics?.metrics);
})();

(Code continues exactly as in your preview; no changes needed.)

Security & governance

  • Never expose API keys in the browser — keep them server-side or use a short-lived preAuth token.

  • EU hosting, GDPR compliance, tenant isolation.

  • Versioning & audit trails via the governance layer.

Roadmap & GA note

The SDK signatures are set, but details may change before GA. Goal: a stable, versioned npm package (@cnode/sdk) with clear SemVer and developer guides.

Weitere Beiträge entdecken

Bleib auf dem Laufenden mit unseren neuesten Artikeln zu datenbasierter Planung, KI-gestützter Szenariosteuerung und Best Practices aus der Anwendung von cNode.