"use strict";
var __defProp = Object.defineProperty;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __hasOwnProp = Object.prototype.hasOwnProperty;
var __export = (target, all) => {
  for (var name in all)
    __defProp(target, name, { get: all[name], enumerable: true });
};
var __copyProps = (to, from, except, desc) => {
  if (from && typeof from === "object" || typeof from === "function") {
    for (let key of __getOwnPropNames(from))
      if (!__hasOwnProp.call(to, key) && key !== except)
        __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
  }
  return to;
};
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);

// src/cost-monitoring/index.ts
var cost_monitoring_exports = {};
__export(cost_monitoring_exports, {
  AnomalyDetector: () => AnomalyDetector,
  ApiCallTracker: () => ApiCallTracker,
  BatchTracker: () => BatchTracker,
  CacheHitTracker: () => CacheHitTracker,
  CostCalculator: () => CostCalculator,
  DEFAULT_OCR_COST_PER_PAGE: () => DEFAULT_OCR_COST_PER_PAGE,
  DEFAULT_PRICING: () => DEFAULT_PRICING,
  DEFAULT_QUALITY_MODEL_MAP: () => DEFAULT_QUALITY_MODEL_MAP,
  DefaultSpendTracker: () => DefaultSpendTracker,
  InMemoryBatchAttributionStore: () => InMemoryBatchAttributionStore,
  InMemoryCustomerTierStore: () => InMemoryCustomerTierStore,
  InMemoryOcrUsageStore: () => InMemoryOcrUsageStore,
  InMemorySpendStore: () => InMemorySpendStore,
  Ledger: () => Ledger,
  MarginMonitor: () => MarginMonitor,
  MarginMonitorAnalyzer: () => MarginMonitorAnalyzer,
  ModelRecommender: () => ModelRecommender,
  OPUS_MODEL_PATTERN: () => OPUS_MODEL_PATTERN,
  OcrTracker: () => OcrTracker,
  PerAgentSpendAnalyzer: () => PerAgentSpendAnalyzer,
  PerCustomerSpendAnalyzer: () => PerCustomerSpendAnalyzer,
  PerModelSpendAnalyzer: () => PerModelSpendAnalyzer,
  PricingChangeAdapter: () => PricingChangeAdapter,
  Tally: () => Tally
});
module.exports = __toCommonJS(cost_monitoring_exports);

// src/cost-monitoring/cost-calculator.ts
var DEFAULT_PRICING = {
  "claude-opus-4-7": {
    modelId: "claude-opus-4-7",
    displayName: "Claude Opus 4.7",
    provider: "anthropic",
    inputPerMillion: 15,
    outputPerMillion: 75,
    cacheWritePerMillion: 18.75,
    cacheReadPerMillion: 1.5,
    effectiveFrom: "2025-01-01"
  },
  "claude-sonnet-4-6": {
    modelId: "claude-sonnet-4-6",
    displayName: "Claude Sonnet 4.6",
    provider: "anthropic",
    inputPerMillion: 3,
    outputPerMillion: 15,
    cacheWritePerMillion: 3.75,
    cacheReadPerMillion: 0.3,
    effectiveFrom: "2025-01-01"
  },
  "claude-haiku-4-5-20251001": {
    modelId: "claude-haiku-4-5-20251001",
    displayName: "Claude Haiku 4.5",
    provider: "anthropic",
    inputPerMillion: 0.8,
    outputPerMillion: 4,
    cacheWritePerMillion: 1,
    cacheReadPerMillion: 0.08,
    effectiveFrom: "2025-10-01"
  }
};
var CostCalculator = class {
  pricing;
  constructor(config = {}) {
    this.pricing = { ...DEFAULT_PRICING };
    for (const [modelId, override] of Object.entries(config.pricingOverrides ?? {})) {
      const base = this.pricing[modelId] ?? {
        modelId,
        displayName: modelId,
        provider: "unknown",
        inputPerMillion: 0,
        outputPerMillion: 0,
        cacheWritePerMillion: 0,
        cacheReadPerMillion: 0,
        effectiveFrom: (/* @__PURE__ */ new Date()).toISOString().slice(0, 10)
      };
      this.pricing[modelId] = { ...base, ...override };
    }
  }
  getPricingTable() {
    return { ...this.pricing };
  }
  getPricing(modelId) {
    return this.pricing[modelId] ?? null;
  }
  /**
   * Computes USD cost for one API call.
   *
   * Returns 0 for unknown models — cost tracking must never block the call
   * path. Log unknown modelIds at the call site.
   */
  computeCost(modelId, inputTokens, outputTokens, cacheWriteTokens = 0, cacheReadTokens = 0) {
    const p = this.pricing[modelId];
    if (!p) return 0;
    return (inputTokens * p.inputPerMillion + outputTokens * p.outputPerMillion + cacheWriteTokens * p.cacheWritePerMillion + cacheReadTokens * p.cacheReadPerMillion) / 1e6;
  }
  /** Fills costUsd on a SpendRecord and returns the completed record. */
  computeRecordCost(record) {
    const costUsd = this.computeCost(
      record.modelId,
      record.inputTokens,
      record.outputTokens,
      record.cacheWriteTokens,
      record.cacheReadTokens
    );
    return { ...record, costUsd };
  }
  /**
   * Recomputes cost for an existing SpendRecord using the current pricing table.
   * Used by PricingChangeAdapter to project impact of price changes on historical spend.
   */
  recomputeCost(record) {
    return this.computeCost(
      record.modelId,
      record.inputTokens,
      record.outputTokens,
      record.cacheWriteTokens,
      record.cacheReadTokens
    );
  }
};

// src/cost-monitoring/spend-tracker.ts
var DefaultSpendTracker = class {
  constructor(store, calculator) {
    this.store = store;
    this.calculator = calculator;
  }
  store;
  calculator;
  async record(params) {
    const raw = {
      id: params.id,
      modelId: params.modelId,
      customerId: params.customerId,
      agentId: params.agentId,
      timestamp: params.timestamp ?? /* @__PURE__ */ new Date(),
      inputTokens: params.inputTokens,
      outputTokens: params.outputTokens,
      cacheWriteTokens: params.cacheWriteTokens ?? 0,
      cacheReadTokens: params.cacheReadTokens ?? 0,
      taskIntent: params.taskIntent
    };
    const record = this.calculator.computeRecordCost(raw);
    return this.store.record(record);
  }
  async getCustomerSpend(customerId, opts) {
    return this.store.listByCustomer(customerId, opts);
  }
  async getAgentSpend(agentId, opts) {
    return this.store.listByAgent(agentId, opts);
  }
  async getMonthlySpend(customerId, year, month) {
    return this.store.monthlySpendByCustomer(customerId, year, month);
  }
  async getAllSpend(opts) {
    return this.store.listAll(opts);
  }
};

// src/cost-monitoring/margin-monitor.ts
var MarginMonitor = class {
  constructor(spendStore, tierStore, config = {}) {
    this.spendStore = spendStore;
    this.tierStore = tierStore;
    this.approachingCap = config.approachingCapThreshold ?? 0.7;
    this.overCap = config.overCapThreshold ?? 1;
  }
  spendStore;
  tierStore;
  approachingCap;
  overCap;
  /**
   * Computes the current month's margin for one customer.
   *
   * Returns null if no tier is configured for the customer.
   */
  async computeMargin(customerId, year, month, now = /* @__PURE__ */ new Date()) {
    const tier = await this.tierStore.getByCustomer(customerId);
    if (!tier) return null;
    const totalSpendUsd = await this.spendStore.monthlySpendByCustomer(
      customerId,
      year,
      month
    );
    const marginUsd = tier.monthlyRevenueUsd - totalSpendUsd;
    const marginRatio = tier.monthlyRevenueUsd > 0 ? marginUsd / tier.monthlyRevenueUsd : 0;
    const capUtilisation = tier.monthlySpendCapUsd > 0 ? totalSpendUsd / tier.monthlySpendCapUsd : 0;
    return {
      customerId,
      tierId: tier.tierId,
      periodYear: year,
      periodMonth: month,
      totalSpendUsd,
      monthlyRevenueUsd: tier.monthlyRevenueUsd,
      monthlySpendCapUsd: tier.monthlySpendCapUsd,
      targetMarginRatio: tier.targetMarginRatio,
      marginUsd,
      marginRatio,
      capUtilisation,
      computedAt: now
    };
  }
  /**
   * Checks all known customers for threshold breaches.
   * Intended for scheduled daily / weekly runs.
   */
  async checkAll(year, month, now = /* @__PURE__ */ new Date()) {
    const customers = await this.tierStore.listCustomers();
    const alerts = [];
    for (const { customerId } of customers) {
      const margin = await this.computeMargin(customerId, year, month, now);
      if (!margin) continue;
      alerts.push(...this.evaluateAlerts(margin, now));
    }
    return alerts;
  }
  /**
   * Evaluates threshold rules against a pre-computed margin snapshot.
   * Pure — no I/O. Safe to call from tests or pipeline logic.
   */
  evaluateAlerts(margin, now = /* @__PURE__ */ new Date()) {
    const alerts = [];
    let capAlertFired = false;
    if (margin.capUtilisation >= this.overCap) {
      capAlertFired = true;
      alerts.push({
        customerId: margin.customerId,
        tierId: margin.tierId,
        alertType: "over_cap",
        capUtilisation: margin.capUtilisation,
        marginRatio: margin.marginRatio,
        message: `Customer ${margin.customerId} has exceeded their monthly cap (${(margin.capUtilisation * 100).toFixed(1)}% utilised). Spend: $${margin.totalSpendUsd.toFixed(4)} / cap $${margin.monthlySpendCapUsd.toFixed(2)}.`,
        firedAt: now
      });
    } else if (margin.capUtilisation >= this.approachingCap) {
      alerts.push({
        customerId: margin.customerId,
        tierId: margin.tierId,
        alertType: "approaching_cap",
        capUtilisation: margin.capUtilisation,
        marginRatio: margin.marginRatio,
        message: `Customer ${margin.customerId} is at ${(margin.capUtilisation * 100).toFixed(1)}% of their monthly cap. Monitor closely.`,
        firedAt: now
      });
    }
    if (!capAlertFired && margin.marginRatio < margin.targetMarginRatio) {
      alerts.push({
        customerId: margin.customerId,
        tierId: margin.tierId,
        alertType: "margin_below_target",
        capUtilisation: margin.capUtilisation,
        marginRatio: margin.marginRatio,
        message: `Customer ${margin.customerId} margin ${(margin.marginRatio * 100).toFixed(1)}% is below target ${(margin.targetMarginRatio * 100).toFixed(1)}%.`,
        firedAt: now
      });
    }
    return alerts;
  }
};

// src/cost-monitoring/anomaly-detector.ts
var OPUS_MODEL_PATTERN = /opus/i;
function mkId() {
  return `anomaly_${Date.now()}_${Math.random().toString(36).slice(2, 8)}`;
}
var AnomalyDetector = class {
  constructor(spendStore, thresholds = {}) {
    this.spendStore = spendStore;
    this.opusSpendFraction = thresholds.opusSpendFraction ?? 0.3;
    this.cacheHitRatioMin = thresholds.cacheHitRatioMin ?? 0.2;
    this.cacheTokensWarmupMin = thresholds.cacheTokensWarmupMin ?? 1e4;
    this.velocitySpikeMultiplier = thresholds.velocitySpikeMultiplier ?? 3;
  }
  spendStore;
  opusSpendFraction;
  cacheHitRatioMin;
  cacheTokensWarmupMin;
  velocitySpikeMultiplier;
  /**
   * Runs all anomaly checks on a pre-fetched record set.
   * Pure — no I/O.
   */
  detectFromRecords(records, now = /* @__PURE__ */ new Date()) {
    return [
      ...this.checkOpusSpike(records, now),
      ...this.checkCacheHitRatio(records, now),
      ...this.checkSpendVelocitySpike(records, now)
    ];
  }
  /** Fetches a customer's monthly records and runs all checks. */
  async detectForCustomer(customerId, year, month, now = /* @__PURE__ */ new Date()) {
    const since = new Date(year, month - 1, 1);
    const until = new Date(year, month, 0, 23, 59, 59, 999);
    const records = await this.spendStore.listByCustomer(customerId, { since, until });
    return this.detectFromRecords(records, now);
  }
  /** Fetches all records in the date range and runs all checks. */
  async detectAll(opts, now = /* @__PURE__ */ new Date()) {
    const records = await this.spendStore.listAll(opts);
    return this.detectFromRecords(records, now);
  }
  // ── Private checkers ────────────────────────────────────────────────────────
  checkOpusSpike(records, now) {
    if (records.length === 0) return [];
    const totalSpend = records.reduce((s, r) => s + r.costUsd, 0);
    if (totalSpend === 0) return [];
    const opusSpend = records.filter((r) => OPUS_MODEL_PATTERN.test(r.modelId)).reduce((s, r) => s + r.costUsd, 0);
    const fraction = opusSpend / totalSpend;
    if (fraction < this.opusSpendFraction) return [];
    return [
      {
        id: mkId(),
        type: "opus_spike",
        severity: fraction >= 0.5 ? "critical" : "warning",
        description: `Opus-class models account for ${(fraction * 100).toFixed(1)}% of spend (threshold ${(this.opusSpendFraction * 100).toFixed(0)}%). Total: $${totalSpend.toFixed(6)}, Opus: $${opusSpend.toFixed(6)}.`,
        observedValue: fraction,
        threshold: this.opusSpendFraction,
        detectedAt: now
      }
    ];
  }
  checkCacheHitRatio(records, now) {
    const totalWrite = records.reduce((s, r) => s + r.cacheWriteTokens, 0);
    const totalRead = records.reduce((s, r) => s + r.cacheReadTokens, 0);
    const totalCache = totalWrite + totalRead;
    if (totalCache < this.cacheTokensWarmupMin) return [];
    const hitRatio = totalRead / totalCache;
    if (hitRatio >= this.cacheHitRatioMin) return [];
    return [
      {
        id: mkId(),
        type: "cache_hit_ratio_drop",
        severity: "warning",
        description: `Cache hit ratio ${(hitRatio * 100).toFixed(1)}% is below threshold ${(this.cacheHitRatioMin * 100).toFixed(0)}%. Read: ${totalRead.toLocaleString()} tokens, Write: ${totalWrite.toLocaleString()} tokens.`,
        observedValue: hitRatio,
        threshold: this.cacheHitRatioMin,
        detectedAt: now
      }
    ];
  }
  checkSpendVelocitySpike(records, now) {
    if (records.length < 4) return [];
    const sorted = [...records].sort(
      (a, b) => a.timestamp.getTime() - b.timestamp.getTime()
    );
    const half = Math.floor(sorted.length / 2);
    const firstSpend = sorted.slice(0, half).reduce((s, r) => s + r.costUsd, 0);
    const secondSpend = sorted.slice(half).reduce((s, r) => s + r.costUsd, 0);
    if (firstSpend === 0) return [];
    const multiplier = secondSpend / firstSpend;
    if (multiplier < this.velocitySpikeMultiplier) return [];
    return [
      {
        id: mkId(),
        type: "spend_velocity_spike",
        severity: multiplier >= this.velocitySpikeMultiplier * 2 ? "critical" : "warning",
        description: `Spend velocity increased ${multiplier.toFixed(1)}\xD7 (threshold ${this.velocitySpikeMultiplier}\xD7). Recent half: $${secondSpend.toFixed(6)}, Prior half: $${firstSpend.toFixed(6)}.`,
        observedValue: multiplier,
        threshold: this.velocitySpikeMultiplier,
        detectedAt: now
      }
    ];
  }
};

// src/cost-monitoring/model-recommender.ts
var DEFAULT_QUALITY_MODEL_MAP = {
  highest: ["claude-opus-4-7"],
  high: ["claude-sonnet-4-6", "claude-opus-4-7"],
  balanced: ["claude-sonnet-4-6", "claude-opus-4-7"],
  efficient: [
    "claude-haiku-4-5-20251001",
    "claude-sonnet-4-6",
    "claude-opus-4-7"
  ]
};
var ModelRecommender = class {
  constructor(calculator, config = {}) {
    this.calculator = calculator;
    this.qualityModelMap = config.qualityModelMap ?? DEFAULT_QUALITY_MODEL_MAP;
    this.monthlyCallVolume = config.monthlyCallVolume ?? 0;
  }
  calculator;
  qualityModelMap;
  monthlyCallVolume;
  /**
   * Recommends the optimal model for the task signature.
   *
   * Returns null when the current model is already the cheapest viable
   * option, or when no pricing data exists for candidate models.
   */
  recommend(task, currentModelId, now = /* @__PURE__ */ new Date()) {
    const candidateIds = this.qualityModelMap[task.qualityRequirement] ?? [];
    const pricingTable = this.calculator.getPricingTable();
    const viable = candidateIds.filter((id) => pricingTable[id] != null);
    if (viable.length === 0) return null;
    const recommended = viable.reduce((cheapest, id) => {
      return this.estimateCost(pricingTable[cheapest], task) <= this.estimateCost(pricingTable[id], task) ? cheapest : id;
    });
    if (recommended === currentModelId) return null;
    const currentPricing = pricingTable[currentModelId];
    const recommendedPricing = pricingTable[recommended];
    if (!currentPricing || !recommendedPricing) return null;
    const currentCostUsd = this.estimateCost(currentPricing, task);
    const estimatedCostUsd = this.estimateCost(recommendedPricing, task);
    if (estimatedCostUsd >= currentCostUsd) return null;
    const savingPerCall = currentCostUsd - estimatedCostUsd;
    return {
      recommendedModelId: recommended,
      currentModelId,
      estimatedCostUsd,
      currentCostUsd,
      projectedMonthlySavingUsd: savingPerCall * this.monthlyCallVolume,
      rationale: this.buildRationale(
        task,
        recommended,
        currentModelId,
        currentCostUsd,
        estimatedCostUsd,
        recommendedPricing
      ),
      qualityPreserved: true,
      requiresApproval: true,
      generatedAt: now
    };
  }
  estimateCost(pricing, task) {
    const { estimatedInputTokens, estimatedOutputTokens, cacheableContext } = task;
    const inputTokens = cacheableContext ? Math.floor(estimatedInputTokens * 0.2) : estimatedInputTokens;
    const cacheReadTokens = cacheableContext ? Math.floor(estimatedInputTokens * 0.8) : 0;
    return (inputTokens * pricing.inputPerMillion + estimatedOutputTokens * pricing.outputPerMillion + cacheReadTokens * pricing.cacheReadPerMillion) / 1e6;
  }
  buildRationale(task, recommended, current, currentCost, estimatedCost, recommendedPricing) {
    const pct = ((1 - estimatedCost / currentCost) * 100).toFixed(1);
    return `For "${task.intent}" at quality="${task.qualityRequirement}", ${recommendedPricing.displayName} (${recommended}) delivers equivalent quality at ${pct}% lower cost per call ($${estimatedCost.toFixed(6)} vs $${currentCost.toFixed(6)} for ${current}). Quality requirement satisfied.`;
  }
};

// src/cost-monitoring/pricing-change-adapter.ts
var PricingChangeAdapter = class {
  constructor(spendStore, tierStore, previousCalculator, newCalculator, config = {}) {
    this.spendStore = spendStore;
    this.tierStore = tierStore;
    this.previousCalculator = previousCalculator;
    this.newCalculator = newCalculator;
    this.projectionMonths = config.projectionMonths ?? 1;
  }
  spendStore;
  tierStore;
  previousCalculator;
  newCalculator;
  projectionMonths;
  /**
   * Recomputes projected monthly spend for every known customer.
   *
   * Uses the prior `projectionMonths` of actual SpendRecords as the baseline,
   * applying both pricing tables to derive spend deltas. Results are sorted
   * by `deltaUsd` descending (most-impacted customers first).
   *
   * @param referenceDate  Month to project forward from (defaults to today).
   */
  async recalculate(referenceDate = /* @__PURE__ */ new Date(), now = /* @__PURE__ */ new Date()) {
    const customers = await this.tierStore.listCustomers();
    const refYear = referenceDate.getFullYear();
    const refMonth = referenceDate.getMonth();
    const until = new Date(refYear, refMonth, 0, 23, 59, 59, 999);
    const since = new Date(refYear, refMonth - this.projectionMonths, 1);
    const impacts = [];
    for (const { customerId } of customers) {
      const tier = await this.tierStore.getByCustomer(customerId);
      if (!tier) continue;
      const records = await this.spendStore.listByCustomer(customerId, {
        since,
        until
      });
      if (records.length === 0) continue;
      const previousTotal = records.reduce(
        (sum, r) => sum + this.previousCalculator.recomputeCost(r),
        0
      );
      const newTotal = records.reduce(
        (sum, r) => sum + this.newCalculator.recomputeCost(r),
        0
      );
      const previousMonthly = previousTotal / this.projectionMonths;
      const newMonthly = newTotal / this.projectionMonths;
      const previousMarginRatio = tier.monthlyRevenueUsd > 0 ? (tier.monthlyRevenueUsd - previousMonthly) / tier.monthlyRevenueUsd : 0;
      const newMarginRatio = tier.monthlyRevenueUsd > 0 ? (tier.monthlyRevenueUsd - newMonthly) / tier.monthlyRevenueUsd : 0;
      const overCap = newMonthly > tier.monthlySpendCapUsd;
      const belowTargetMargin = newMarginRatio < tier.targetMarginRatio;
      const atRisk = overCap || belowTargetMargin;
      let atRiskReason;
      if (overCap) {
        atRiskReason = `Projected spend $${newMonthly.toFixed(2)}/mo exceeds cap $${tier.monthlySpendCapUsd.toFixed(2)}/mo.`;
      } else if (belowTargetMargin) {
        atRiskReason = `Projected margin ${(newMarginRatio * 100).toFixed(1)}% is below target ${(tier.targetMarginRatio * 100).toFixed(1)}%.`;
      }
      impacts.push({
        customerId,
        tierId: tier.tierId,
        previousProjectedSpendUsd: previousMonthly,
        newProjectedSpendUsd: newMonthly,
        deltaUsd: newMonthly - previousMonthly,
        previousMarginRatio,
        newMarginRatio,
        atRisk,
        atRiskReason,
        computedAt: now
      });
    }
    return impacts.sort((a, b) => b.deltaUsd - a.deltaUsd);
  }
  /** Convenience — returns only at-risk customers from recalculate(). */
  async atRiskCustomers(referenceDate, now) {
    const all = await this.recalculate(referenceDate, now);
    return all.filter((i) => i.atRisk);
  }
};

// src/cost-monitoring/memory-stores.ts
var InMemorySpendStore = class {
  records = [];
  async record(spend) {
    this.records.push(spend);
    return spend;
  }
  async listByCustomer(customerId, opts) {
    return this.records.filter(
      (r) => r.customerId === customerId && (opts?.since == null || r.timestamp >= opts.since) && (opts?.until == null || r.timestamp <= opts.until)
    );
  }
  async listByAgent(agentId, opts) {
    return this.records.filter(
      (r) => r.agentId === agentId && (opts?.since == null || r.timestamp >= opts.since) && (opts?.until == null || r.timestamp <= opts.until)
    );
  }
  async monthlySpendByCustomer(customerId, year, month) {
    return this.records.filter(
      (r) => r.customerId === customerId && r.timestamp.getFullYear() === year && r.timestamp.getMonth() + 1 === month
    ).reduce((sum, r) => sum + r.costUsd, 0);
  }
  async listAll(opts) {
    return this.records.filter(
      (r) => (opts?.since == null || r.timestamp >= opts.since) && (opts?.until == null || r.timestamp <= opts.until)
    );
  }
  /** Test helper — remove all records. */
  clear() {
    this.records.length = 0;
  }
  /** Test helper — current record count. */
  size() {
    return this.records.length;
  }
};
var InMemoryCustomerTierStore = class {
  tiers = /* @__PURE__ */ new Map();
  assignments = /* @__PURE__ */ new Map();
  // customerId → tierId
  async get(tierId) {
    return this.tiers.get(tierId) ?? null;
  }
  async getByCustomer(customerId) {
    const tierId = this.assignments.get(customerId);
    if (!tierId) return null;
    return this.tiers.get(tierId) ?? null;
  }
  async upsert(tier) {
    this.tiers.set(tier.tierId, { ...tier });
    return tier;
  }
  async assignTier(customerId, tierId) {
    this.assignments.set(customerId, tierId);
  }
  async listCustomers(tierId) {
    const entries = [...this.assignments.entries()].map(([customerId, tid]) => ({
      customerId,
      tierId: tid
    }));
    if (tierId) return entries.filter((e) => e.tierId === tierId);
    return entries;
  }
  /** Test helper — remove all tiers and assignments. */
  clear() {
    this.tiers.clear();
    this.assignments.clear();
  }
};

// src/cost-monitoring/trackers/api-call-tracker.ts
var ApiCallTracker = class {
  constructor(tracker, logger) {
    this.tracker = tracker;
    this.logger = logger;
  }
  tracker;
  logger;
  /**
   * Records a single LLM API call as a SpendRecord via the injected tracker.
   * Emits a structured log line on success; warns on unknown models (costUsd === 0).
   */
  async trackApiCall(params) {
    const spend = await this.tracker.record({
      id: params.id,
      modelId: params.modelId,
      customerId: params.customerId,
      agentId: params.agentId,
      inputTokens: params.inputTokens,
      outputTokens: params.outputTokens,
      cacheWriteTokens: params.cacheWriteTokens,
      cacheReadTokens: params.cacheReadTokens,
      taskIntent: params.taskIntent,
      timestamp: params.timestamp
    });
    if (spend.costUsd === 0) {
      this.logger?.warn("ApiCallTracker: unknown modelId \u2014 cost recorded as $0", {
        id: params.id,
        modelId: params.modelId
      });
    } else {
      this.logger?.info("ApiCallTracker: recorded spend", {
        id: spend.id,
        modelId: spend.modelId,
        customerId: spend.customerId,
        agentId: spend.agentId,
        costUsd: spend.costUsd,
        taskIntent: spend.taskIntent
      });
    }
    return spend;
  }
  /**
   * Runs `call`, records the spend attribution, and returns both the call
   * result and the resulting SpendRecord.
   *
   * The spend is recorded even if `params` has an unknown modelId (costUsd
   * will be 0 and a warning is logged). The call's promise rejection is NOT
   * caught here — let it propagate naturally.
   */
  static async wrap(call, tracker, params) {
    const result = await call();
    const spend = await tracker.trackApiCall(params);
    return { result, spend };
  }
};

// src/cost-monitoring/trackers/cache-hit-tracker.ts
var CacheHitTracker = class {
  constructor(spendStore) {
    this.spendStore = spendStore;
  }
  spendStore;
  /**
   * Computes cache efficiency over a pre-fetched record set.
   * Pure — no I/O.
   */
  analyzeCacheHits(records) {
    let totalRead = 0;
    let totalWrite = 0;
    const agentMap = /* @__PURE__ */ new Map();
    const workflowMap = /* @__PURE__ */ new Map();
    let hasWorkflows = false;
    for (const r of records) {
      totalRead += r.cacheReadTokens;
      totalWrite += r.cacheWriteTokens;
      const ag = agentMap.get(r.agentId) ?? { read: 0, write: 0 };
      ag.read += r.cacheReadTokens;
      ag.write += r.cacheWriteTokens;
      agentMap.set(r.agentId, ag);
      if (r.taskIntent) {
        hasWorkflows = true;
        const wf = workflowMap.get(r.taskIntent) ?? { read: 0, write: 0 };
        wf.read += r.cacheReadTokens;
        wf.write += r.cacheWriteTokens;
        workflowMap.set(r.taskIntent, wf);
      }
    }
    const totalCacheTokens = totalRead + totalWrite;
    const hitRatio = totalCacheTokens > 0 ? totalRead / totalCacheTokens : 0;
    const perAgent = {};
    for (const [agentId, stats] of agentMap.entries()) {
      const total = stats.read + stats.write;
      perAgent[agentId] = {
        readTokens: stats.read,
        writeTokens: stats.write,
        hitRatio: total > 0 ? stats.read / total : 0
      };
    }
    let perWorkflow;
    if (hasWorkflows) {
      perWorkflow = {};
      for (const [intent, stats] of workflowMap.entries()) {
        const total = stats.read + stats.write;
        perWorkflow[intent] = {
          readTokens: stats.read,
          writeTokens: stats.write,
          hitRatio: total > 0 ? stats.read / total : 0
        };
      }
    }
    return {
      totalCacheReadTokens: totalRead,
      totalCacheWriteTokens: totalWrite,
      totalCacheTokens,
      hitRatio,
      perAgent,
      ...perWorkflow ? { perWorkflow } : {}
    };
  }
  /**
   * Fetches all records for a customer within a calendar month and returns
   * the cache hit report.
   */
  async loadAndAnalyzeForCustomer(customerId, year, month) {
    const since = new Date(year, month - 1, 1);
    const until = new Date(year, month, 0, 23, 59, 59, 999);
    const records = await this.spendStore.listByCustomer(customerId, { since, until });
    return this.analyzeCacheHits(records);
  }
};

// src/cost-monitoring/trackers/ocr-tracker.ts
var DEFAULT_OCR_COST_PER_PAGE = {
  tesseract: 0,
  aws_textract: 15e-4,
  google_vision: 15e-4,
  claude_vision: 3e-3
};
var InMemoryOcrUsageStore = class {
  records = [];
  async record(usage) {
    this.records.push(usage);
    return usage;
  }
  async listByCustomer(customerId, opts) {
    return this.records.filter(
      (r) => r.customerId === customerId && (opts?.since == null || r.timestamp >= opts.since) && (opts?.until == null || r.timestamp <= opts.until)
    );
  }
  async listAll(opts) {
    return this.records.filter(
      (r) => (opts?.since == null || r.timestamp >= opts.since) && (opts?.until == null || r.timestamp <= opts.until)
    );
  }
  clear() {
    this.records.length = 0;
  }
  size() {
    return this.records.length;
  }
};
var OcrTracker = class {
  constructor(store, config = {}) {
    this.store = store;
    this.costPerPage = {
      ...DEFAULT_OCR_COST_PER_PAGE,
      ...config.costPerPage ?? {}
    };
  }
  store;
  costPerPage;
  /** Records an OCR usage event. Computes cost automatically unless overridden. */
  async trackOcrUsage(params) {
    const costUsd = params.costUsd ?? this.costPerPage[params.provider] * params.pagesProcessed;
    const record = {
      id: params.id,
      customerId: params.customerId,
      agentId: params.agentId,
      provider: params.provider,
      pagesProcessed: params.pagesProcessed,
      costUsd,
      durationMs: params.durationMs,
      timestamp: params.timestamp ?? /* @__PURE__ */ new Date()
    };
    return this.store.record(record);
  }
  /**
   * Compares actual spend across providers vs the cheapest available provider.
   * Pure — no I/O.
   */
  costDifferential(records) {
    const byProvider = /* @__PURE__ */ new Map();
    for (const r of records) {
      const existing = byProvider.get(r.provider) ?? { pages: 0, cost: 0 };
      existing.pages += r.pagesProcessed;
      existing.cost += r.costUsd;
      byProvider.set(r.provider, existing);
    }
    const perProvider = [];
    for (const [provider, stats] of byProvider.entries()) {
      perProvider.push({
        provider,
        totalPages: stats.pages,
        totalCostUsd: stats.cost,
        avgCostPerPage: stats.pages > 0 ? stats.cost / stats.pages : 0
      });
    }
    const allProviders = [
      "tesseract",
      "aws_textract",
      "google_vision",
      "claude_vision"
    ];
    const cheapestProvider = allProviders.reduce(
      (min, p) => this.costPerPage[p] <= this.costPerPage[min] ? p : min
    );
    const cheapestRate = this.costPerPage[cheapestProvider];
    const totalPages = records.reduce((s, r) => s + r.pagesProcessed, 0);
    const totalActualCost = records.reduce((s, r) => s + r.costUsd, 0);
    const hypotheticalCost = totalPages * cheapestRate;
    const potentialSavingUsd = Math.max(0, totalActualCost - hypotheticalCost);
    return { perProvider, cheapestProvider, potentialSavingUsd };
  }
};

// src/cost-monitoring/trackers/batch-tracker.ts
var InMemoryBatchAttributionStore = class {
  records = [];
  async record(attribution) {
    this.records.push(attribution);
    return attribution;
  }
  async listBySpendRecordId(spendRecordId) {
    return this.records.find((r) => r.spendRecordId === spendRecordId) ?? null;
  }
  async listAll() {
    return [...this.records];
  }
  clear() {
    this.records.length = 0;
  }
  size() {
    return this.records.length;
  }
};
var BATCH_DISCOUNT = 0.5;
var BatchTracker = class {
  constructor(tracker, calculator, attributionStore) {
    this.tracker = tracker;
    this.calculator = calculator;
    this.attributionStore = attributionStore;
  }
  tracker;
  calculator;
  attributionStore;
  /**
   * Records a batch or realtime API call.
   *
   * When viaBatchApi is true:
   *   1. Computes full realtime cost via CostCalculator.
   *   2. Applies 50% discount to costUsd before persisting via SpendTracker.
   *   3. Stores a BatchAttribution linking the SpendRecord to its realtime equivalent.
   *
   * The resulting SpendRecord.costUsd reflects the actual (discounted) spend.
   */
  async trackBatchUsage(params) {
    if (!params.viaBatchApi) {
      const spend2 = await this.tracker.record({
        id: params.id,
        modelId: params.modelId,
        customerId: params.customerId,
        agentId: params.agentId,
        inputTokens: params.inputTokens,
        outputTokens: params.outputTokens,
        cacheWriteTokens: params.cacheWriteTokens,
        cacheReadTokens: params.cacheReadTokens,
        taskIntent: params.taskIntent,
        timestamp: params.timestamp
      });
      return spend2;
    }
    const realtimeCostUsd = this.calculator.computeCost(
      params.modelId,
      params.inputTokens,
      params.outputTokens,
      params.cacheWriteTokens ?? 0,
      params.cacheReadTokens ?? 0
    );
    const batchCostUsd = realtimeCostUsd * BATCH_DISCOUNT;
    const raw = {
      id: params.id,
      modelId: params.modelId,
      customerId: params.customerId,
      agentId: params.agentId,
      timestamp: params.timestamp ?? /* @__PURE__ */ new Date(),
      inputTokens: params.inputTokens,
      outputTokens: params.outputTokens,
      cacheWriteTokens: params.cacheWriteTokens ?? 0,
      cacheReadTokens: params.cacheReadTokens ?? 0,
      taskIntent: params.taskIntent
    };
    const fullRecord = this.calculator.computeRecordCost(raw);
    const discountedRecord = { ...fullRecord, costUsd: batchCostUsd };
    const spend = await this.tracker.record({
      id: params.id,
      modelId: params.modelId,
      customerId: params.customerId,
      agentId: params.agentId,
      inputTokens: params.inputTokens,
      outputTokens: params.outputTokens,
      cacheWriteTokens: params.cacheWriteTokens,
      cacheReadTokens: params.cacheReadTokens,
      taskIntent: params.taskIntent,
      timestamp: params.timestamp
    });
    const recordedAt = params.timestamp ?? /* @__PURE__ */ new Date();
    await this.attributionStore.record({
      spendRecordId: params.id,
      viaBatchApi: true,
      realtimeCostUsd,
      batchCostUsd,
      recordedAt
    });
    return { ...spend, costUsd: batchCostUsd };
  }
  /**
   * Computes savings from Batch API usage.
   *
   * Uses attribution records (not SpendRecords) for accurate cost comparison,
   * since the SpendRecord.costUsd for batch calls reflects full token cost.
   */
  batchSavingsReport(_records, attributions) {
    const batchAttributions = attributions.filter((a) => a.viaBatchApi);
    const totalBatchSpendUsd = batchAttributions.reduce(
      (s, a) => s + a.batchCostUsd,
      0
    );
    const equivalentRealtimeSpendUsd = batchAttributions.reduce(
      (s, a) => s + a.realtimeCostUsd,
      0
    );
    const savingsUsd = equivalentRealtimeSpendUsd - totalBatchSpendUsd;
    const savingsPercent = equivalentRealtimeSpendUsd > 0 ? savingsUsd / equivalentRealtimeSpendUsd * 100 : 0;
    return {
      totalBatchSpendUsd,
      equivalentRealtimeSpendUsd,
      savingsUsd,
      savingsPercent
    };
  }
};

// src/cost-monitoring/analyzers/per-customer-spend.ts
function toYMD(d) {
  const y = d.getFullYear();
  const m = String(d.getMonth() + 1).padStart(2, "0");
  const day = String(d.getDate()).padStart(2, "0");
  return `${y}-${m}-${day}`;
}
function toISOWeek(d) {
  const date = new Date(Date.UTC(d.getFullYear(), d.getMonth(), d.getDate()));
  const dayNum = date.getUTCDay() || 7;
  date.setUTCDate(date.getUTCDate() + 4 - dayNum);
  const yearStart = new Date(Date.UTC(date.getUTCFullYear(), 0, 1));
  const weekNo = Math.ceil(
    ((date.getTime() - yearStart.getTime()) / 864e5 + 1) / 7
  );
  return `${date.getUTCFullYear()}-W${String(weekNo).padStart(2, "0")}`;
}
function toYM(d) {
  const y = d.getFullYear();
  const m = String(d.getMonth() + 1).padStart(2, "0");
  return `${y}-${m}`;
}
function addRecord(bucket, key, r) {
  const existing = bucket.get(key) ?? {
    customerId: r.customerId,
    totalSpendUsd: 0,
    callCount: 0,
    perAgent: {},
    perModel: {}
  };
  existing.totalSpendUsd += r.costUsd;
  existing.callCount += 1;
  existing.perAgent[r.agentId] = (existing.perAgent[r.agentId] ?? 0) + r.costUsd;
  existing.perModel[r.modelId] = (existing.perModel[r.modelId] ?? 0) + r.costUsd;
  bucket.set(key, existing);
}
var PerCustomerSpendAnalyzer = class {
  constructor(spendStore) {
    this.spendStore = spendStore;
  }
  spendStore;
  /** Daily aggregation over a pre-fetched set of records. Pure. */
  aggregateDaily(records) {
    const bucket = /* @__PURE__ */ new Map();
    for (const r of records) {
      const key = `${r.customerId}|${toYMD(r.timestamp)}`;
      addRecord(bucket, key, r);
    }
    return [...bucket.entries()].map(([key, v]) => ({
      date: key.split("|")[1],
      customerId: v.customerId,
      totalSpendUsd: v.totalSpendUsd,
      callCount: v.callCount,
      perAgent: v.perAgent,
      perModel: v.perModel
    }));
  }
  /** Weekly aggregation (ISO 8601 weeks). Pure. */
  aggregateWeekly(records) {
    const bucket = /* @__PURE__ */ new Map();
    for (const r of records) {
      const key = `${r.customerId}|${toISOWeek(r.timestamp)}`;
      addRecord(bucket, key, r);
    }
    return [...bucket.entries()].map(([key, v]) => ({
      week: key.split("|")[1],
      customerId: v.customerId,
      totalSpendUsd: v.totalSpendUsd,
      callCount: v.callCount,
      perAgent: v.perAgent,
      perModel: v.perModel
    }));
  }
  /** Monthly aggregation. Pure. */
  aggregateMonthly(records) {
    const bucket = /* @__PURE__ */ new Map();
    for (const r of records) {
      const key = `${r.customerId}|${toYM(r.timestamp)}`;
      addRecord(bucket, key, r);
    }
    return [...bucket.entries()].map(([key, v]) => ({
      month: key.split("|")[1],
      customerId: v.customerId,
      totalSpendUsd: v.totalSpendUsd,
      callCount: v.callCount,
      perAgent: v.perAgent,
      perModel: v.perModel
    }));
  }
  /** Fetches records from the store and aggregates at the requested granularity. */
  async loadAndAggregate(customerId, since, until, granularity) {
    const records = await this.spendStore.listByCustomer(customerId, { since, until });
    if (granularity === "daily") return this.aggregateDaily(records);
    if (granularity === "weekly") return this.aggregateWeekly(records);
    return this.aggregateMonthly(records);
  }
};

// src/cost-monitoring/analyzers/per-agent-spend.ts
function toYMD2(d) {
  const y = d.getFullYear();
  const m = String(d.getMonth() + 1).padStart(2, "0");
  const day = String(d.getDate()).padStart(2, "0");
  return `${y}-${m}-${day}`;
}
function toYM2(d) {
  const y = d.getFullYear();
  const m = String(d.getMonth() + 1).padStart(2, "0");
  return `${y}-${m}`;
}
function addRecord2(bucket, key, r) {
  const existing = bucket.get(key) ?? {
    agentId: r.agentId,
    totalSpendUsd: 0,
    callCount: 0,
    perCustomer: {},
    perModel: {}
  };
  existing.totalSpendUsd += r.costUsd;
  existing.callCount += 1;
  existing.perCustomer[r.customerId] = (existing.perCustomer[r.customerId] ?? 0) + r.costUsd;
  existing.perModel[r.modelId] = (existing.perModel[r.modelId] ?? 0) + r.costUsd;
  bucket.set(key, existing);
}
var PerAgentSpendAnalyzer = class {
  constructor(spendStore) {
    this.spendStore = spendStore;
  }
  spendStore;
  /** Daily aggregation grouped by agent. Pure. */
  aggregateDailyByAgent(records) {
    const bucket = /* @__PURE__ */ new Map();
    for (const r of records) {
      const key = `${r.agentId}|${toYMD2(r.timestamp)}`;
      addRecord2(bucket, key, r);
    }
    return [...bucket.entries()].map(([key, v]) => ({
      date: key.split("|")[1],
      agentId: v.agentId,
      totalSpendUsd: v.totalSpendUsd,
      callCount: v.callCount,
      perCustomer: v.perCustomer,
      perModel: v.perModel
    }));
  }
  /** Monthly aggregation grouped by agent. Pure. */
  aggregateMonthlyByAgent(records) {
    const bucket = /* @__PURE__ */ new Map();
    for (const r of records) {
      const key = `${r.agentId}|${toYM2(r.timestamp)}`;
      addRecord2(bucket, key, r);
    }
    return [...bucket.entries()].map(([key, v]) => ({
      month: key.split("|")[1],
      agentId: v.agentId,
      totalSpendUsd: v.totalSpendUsd,
      callCount: v.callCount,
      perCustomer: v.perCustomer,
      perModel: v.perModel
    }));
  }
  /**
   * Fetches all records from the store for a given agent and time range,
   * then aggregates monthly.
   */
  async loadAndAggregateByAgent(agentId, since, until) {
    const records = await this.spendStore.listByAgent(agentId, { since, until });
    return this.aggregateMonthlyByAgent(records);
  }
  /**
   * Fetches all records (all agents, all customers) within a window
   * and aggregates by agent monthly.
   */
  async loadAllAndAggregateMonthly(since, until) {
    const records = await this.spendStore.listAll({ since, until });
    return this.aggregateMonthlyByAgent(records);
  }
};

// src/cost-monitoring/analyzers/per-model-spend.ts
var SONNET_PATTERN = /sonnet/i;
var HAIKU_PATTERN = /haiku/i;
function classifyModel(modelId) {
  if (OPUS_MODEL_PATTERN.test(modelId)) return "opus";
  if (SONNET_PATTERN.test(modelId)) return "sonnet";
  if (HAIKU_PATTERN.test(modelId)) return "haiku";
  return "other";
}
var PerModelSpendAnalyzer = class {
  /**
   * Breaks down spend by model across a record set.
   * Pure — no I/O.
   */
  breakdown(records) {
    const perModel = /* @__PURE__ */ new Map();
    let totalSpendUsd = 0;
    const classTotals = {
      opus: 0,
      sonnet: 0,
      haiku: 0,
      other: 0
    };
    for (const r of records) {
      totalSpendUsd += r.costUsd;
      const cls = classifyModel(r.modelId);
      classTotals[cls] += r.costUsd;
      const existing = perModel.get(r.modelId) ?? {
        totalSpendUsd: 0,
        callCount: 0,
        totalInputTokens: 0,
        totalOutputTokens: 0
      };
      existing.totalSpendUsd += r.costUsd;
      existing.callCount += 1;
      existing.totalInputTokens += r.inputTokens;
      existing.totalOutputTokens += r.outputTokens;
      perModel.set(r.modelId, existing);
    }
    const perModelOut = {};
    for (const [modelId, stats] of perModel.entries()) {
      perModelOut[modelId] = {
        totalSpendUsd: stats.totalSpendUsd,
        callCount: stats.callCount,
        totalInputTokens: stats.totalInputTokens,
        totalOutputTokens: stats.totalOutputTokens,
        averageCostPerCall: stats.callCount > 0 ? stats.totalSpendUsd / stats.callCount : 0
      };
    }
    const opusFraction = totalSpendUsd > 0 ? classTotals.opus / totalSpendUsd : 0;
    const sonnetFraction = totalSpendUsd > 0 ? classTotals.sonnet / totalSpendUsd : 0;
    const haikuFraction = totalSpendUsd > 0 ? classTotals.haiku / totalSpendUsd : 0;
    const otherFraction = totalSpendUsd > 0 ? classTotals.other / totalSpendUsd : 0;
    return {
      perModel: perModelOut,
      totalSpendUsd,
      opusFraction,
      sonnetFraction,
      haikuFraction,
      otherFraction
    };
  }
};

// src/cost-monitoring/analyzers/margin-monitor-analyzer.ts
var MarginMonitorAnalyzer = class {
  constructor(marginMonitor, tierStore) {
    this.marginMonitor = marginMonitor;
    this.tierStore = tierStore;
  }
  marginMonitor;
  tierStore;
  /**
   * Computes margin for every customer assigned to a specific tier in a
   * given calendar month. Returns null margins filtered out.
   */
  async analyzeTier(tierId, year, month, now) {
    const customers = await this.tierStore.listCustomers(tierId);
    const reports = [];
    for (const { customerId } of customers) {
      const margin = await this.marginMonitor.computeMargin(
        customerId,
        year,
        month,
        now
      );
      if (margin) reports.push(margin);
    }
    return reports;
  }
  /**
   * Produces a tier-level summary for every known tier in a given month.
   *
   * Runs analyzeTier for each unique tier found in the customer list, then
   * aggregates customer-level margins into tier-level statistics.
   */
  async summaryByTier(year, month, now) {
    const allCustomers = await this.tierStore.listCustomers();
    const tierIds = [...new Set(allCustomers.map((c) => c.tierId))];
    const summaries = [];
    for (const tierId of tierIds) {
      const reports = await this.analyzeTier(tierId, year, month, now);
      if (reports.length === 0) {
        summaries.push({
          tierId,
          customerCount: 0,
          avgMarginRatio: 0,
          customersBelowTarget: 0,
          customersOverCap: 0
        });
        continue;
      }
      const avgMarginRatio = reports.reduce((s, r) => s + r.marginRatio, 0) / reports.length;
      const customersBelowTarget = reports.filter(
        (r) => r.marginRatio < r.targetMarginRatio
      ).length;
      const customersOverCap = reports.filter((r) => r.capUtilisation > 1).length;
      summaries.push({
        tierId,
        customerCount: reports.length,
        avgMarginRatio,
        customersBelowTarget,
        customersOverCap
      });
    }
    return summaries;
  }
};

// src/cost-monitoring/sub-agents/ledger.ts
var Ledger = class {
  spendStore;
  tierStore;
  calculator;
  marginMonitor;
  anomalyDetector;
  perCustomer;
  perAgent;
  perModel;
  cacheHitTracker;
  modelRecommender;
  constructor(deps) {
    this.spendStore = deps.spendStore;
    this.tierStore = deps.tierStore;
    this.calculator = deps.calculator;
    this.marginMonitor = deps.marginMonitor ?? new MarginMonitor(deps.spendStore, deps.tierStore);
    this.anomalyDetector = deps.anomalyDetector ?? new AnomalyDetector(deps.spendStore);
    this.perCustomer = deps.perCustomer ?? new PerCustomerSpendAnalyzer(deps.spendStore);
    this.perAgent = deps.perAgent ?? new PerAgentSpendAnalyzer(deps.spendStore);
    this.perModel = deps.perModel ?? new PerModelSpendAnalyzer();
    this.cacheHitTracker = new CacheHitTracker(deps.spendStore);
    this.modelRecommender = deps.modelRecommender;
  }
  /**
   * Produces a full spend report for a customer in a given month.
   *
   * Includes: monthly total, per-agent breakdown, per-model breakdown,
   * daily series, cache efficiency, and current margin snapshot.
   */
  async spendReport(customerId, period, now) {
    const { year, month } = period;
    const since = new Date(year, month - 1, 1);
    const until = new Date(year, month, 0, 23, 59, 59, 999);
    const records = await this.spendStore.listByCustomer(customerId, { since, until });
    const monthlyTotalUsd = records.reduce((s, r) => s + r.costUsd, 0);
    const agentMap = {};
    for (const r of records) {
      agentMap[r.agentId] = (agentMap[r.agentId] ?? 0) + r.costUsd;
    }
    const perModel = this.perModel.breakdown(records);
    const dailySeries = this.perCustomer.aggregateDaily(records);
    const cacheHitReport = this.cacheHitTracker.analyzeCacheHits(records);
    const margin = await this.marginMonitor.computeMargin(customerId, year, month, now);
    return {
      customerId,
      period,
      monthlyTotalUsd,
      perAgent: agentMap,
      perModel,
      dailySeries,
      cacheHitReport,
      margin
    };
  }
  /**
   * Generates cost optimisation recommendations for a customer.
   *
   * Checks:
   *   1. model_swap — uses ModelRecommender on the customer's heaviest taskIntent
   *   2. caching_opportunity — cache hit ratio < 0.2 and write tokens > 50k
   *   3. opus_overuse — opusFraction > 0.3 for a customer on a tier with cap < $200
   */
  async recommendOptimisations(customerId, period, now) {
    const { year, month } = period;
    const since = new Date(year, month - 1, 1);
    const until = new Date(year, month, 0, 23, 59, 59, 999);
    const records = await this.spendStore.listByCustomer(customerId, { since, until });
    const recommendations = [];
    const modelBreakdown = this.perModel.breakdown(records);
    const cacheReport = this.cacheHitTracker.analyzeCacheHits(records);
    const tier = await this.tierStore.getByCustomer(customerId);
    if (this.modelRecommender && records.length > 0) {
      const intentModelSpend = /* @__PURE__ */ new Map();
      for (const r of records) {
        if (!r.taskIntent) continue;
        const key = `${r.taskIntent}|${r.modelId}`;
        intentModelSpend.set(key, (intentModelSpend.get(key) ?? 0) + r.costUsd);
      }
      let bestKey = null;
      let bestSpend = 0;
      for (const [key, spend] of intentModelSpend.entries()) {
        if (spend > bestSpend) {
          bestSpend = spend;
          bestKey = key;
        }
      }
      if (bestKey) {
        const [intent, modelId] = bestKey.split("|");
        const callCount = records.filter(
          (r) => r.taskIntent === intent && r.modelId === modelId
        ).length;
        const rec = this.modelRecommender.recommend(
          {
            intent,
            estimatedInputTokens: 1e3,
            estimatedOutputTokens: 300,
            qualityRequirement: "balanced",
            cacheableContext: false
          },
          modelId,
          now
        );
        if (rec) {
          recommendations.push({
            kind: "model_swap",
            description: `Switch ${intent} calls from ${modelId} to ${rec.recommendedModelId} \u2014 estimated ${((1 - rec.estimatedCostUsd / rec.currentCostUsd) * 100).toFixed(1)}% cost reduction.`,
            estimatedMonthlySavingUsd: (rec.currentCostUsd - rec.estimatedCostUsd) * callCount,
            requiresApproval: true,
            detail: {
              intent,
              currentModelId: modelId,
              recommendedModelId: rec.recommendedModelId,
              callCount,
              recommendation: rec
            }
          });
        }
      }
    }
    if (cacheReport.hitRatio < 0.2 && cacheReport.totalCacheWriteTokens > 5e4) {
      recommendations.push({
        kind: "caching_opportunity",
        description: `Cache hit ratio is ${(cacheReport.hitRatio * 100).toFixed(1)}% with ${cacheReport.totalCacheWriteTokens.toLocaleString()} write tokens. Increasing system prompt stability or adding explicit cache breakpoints could materially reduce input costs.`,
        estimatedMonthlySavingUsd: 0,
        requiresApproval: true,
        detail: {
          hitRatio: cacheReport.hitRatio,
          totalCacheWriteTokens: cacheReport.totalCacheWriteTokens,
          totalCacheReadTokens: cacheReport.totalCacheReadTokens
        }
      });
    }
    if (modelBreakdown.opusFraction > 0.3 && tier && tier.monthlySpendCapUsd < 200) {
      recommendations.push({
        kind: "opus_overuse",
        description: `${(modelBreakdown.opusFraction * 100).toFixed(1)}% of spend is on Opus-class models, but this customer's tier cap is $${tier.monthlySpendCapUsd.toFixed(0)}/mo. Downgrading to claude-sonnet-4-6 for most tasks could save ~${(modelBreakdown.opusFraction * 0.8 * 100).toFixed(0)}% of Opus spend.`,
        estimatedMonthlySavingUsd: modelBreakdown.totalSpendUsd * modelBreakdown.opusFraction * 0.8 * 0.75,
        requiresApproval: true,
        detail: {
          opusFraction: modelBreakdown.opusFraction,
          tierCapUsd: tier.monthlySpendCapUsd,
          totalSpendUsd: modelBreakdown.totalSpendUsd
        }
      });
    }
    return recommendations;
  }
  /**
   * Returns the current margin snapshot and threshold alerts for a customer.
   * Delegates to Phase A MarginMonitor.
   */
  async checkMargin(customerId, period, now) {
    const { year, month } = period;
    const margin = await this.marginMonitor.computeMargin(customerId, year, month, now);
    const alerts = margin ? this.marginMonitor.evaluateAlerts(margin, now) : [];
    return { margin, alerts };
  }
  /**
   * Runs anomaly detection across all records in the given time window.
   * Delegates to Phase A AnomalyDetector.
   */
  async anomalyScan(period, now) {
    return this.anomalyDetector.detectAll(period, now);
  }
};

// src/cost-monitoring/sub-agents/tally.ts
function mkId2() {
  return `tally_${Date.now()}_${Math.random().toString(36).slice(2, 8)}`;
}
function currentPeriod(now) {
  return { year: now.getFullYear(), month: now.getMonth() + 1 };
}
var Tally = class {
  marginMonitor;
  anomalyDetector;
  ledger;
  surfacingSource;
  constructor(deps) {
    this.marginMonitor = deps.marginMonitor;
    this.anomalyDetector = deps.anomalyDetector;
    this.ledger = deps.ledger;
    this.surfacingSource = deps.surfacingSource;
  }
  /**
   * Scans for margin threshold breaches and cache hit ratio anomalies.
   *
   * 1. Runs MarginMonitor.checkAll → converts MarginAlert → TallyAlert
   * 2. Runs AnomalyDetector.detectAll → filters to cache_hit_ratio_drop → TallyAlert
   */
  async scan(period, now = /* @__PURE__ */ new Date()) {
    const { year, month } = period;
    const alerts = [];
    const marginAlerts = await this.marginMonitor.checkAll(year, month, now);
    for (const ma of marginAlerts) {
      alerts.push(this.marginAlertToTally(ma, now));
    }
    const since = new Date(year, month - 1, 1);
    const until = new Date(year, month, 0, 23, 59, 59, 999);
    const anomalies = await this.anomalyDetector.detectAll({ since, until }, now);
    for (const a of anomalies) {
      if (a.type === "cache_hit_ratio_drop") {
        alerts.push(this.anomalyToTally(a, now));
      }
    }
    return alerts;
  }
  /**
   * Pure mapping — converts PricingChangeImpact[] at-risk entries to TallyAlerts.
   */
  notifyExternalPricingChange(report) {
    return report.map((impact) => ({
      id: mkId2(),
      kind: "external_pricing_change",
      severity: impact.atRisk ? "critical" : "warning",
      customerId: impact.customerId,
      description: `Pricing change impact for customer ${impact.customerId}: projected spend +$${impact.deltaUsd.toFixed(4)}/mo (was $${impact.previousProjectedSpendUsd.toFixed(4)}, now $${impact.newProjectedSpendUsd.toFixed(4)}). ` + (impact.atRiskReason ?? ""),
      observedValue: impact.newProjectedSpendUsd,
      threshold: impact.previousProjectedSpendUsd,
      detectedAt: impact.computedAt,
      payload: { impact }
    }));
  }
  /**
   * F0.10 adapter. Returns a TriggerSource implementation that calls scan()
   * and maps each TallyAlert to a CandidateEvent.
   *
   * Runtime: does NOT import proactive-surfacing at runtime — only uses the
   * type-only import for interface conformance. The returned object satisfies
   * the TriggerSource interface structurally.
   */
  asTriggerSource(name) {
    const sourceName = name ?? this.surfacingSource?.sourceName ?? "finance";
    const tally = this;
    return {
      name: sourceName,
      async fetchCandidates(_operatorContext, since) {
        const now = /* @__PURE__ */ new Date();
        const period = currentPeriod(now);
        const tallyAlerts = await tally.scan(period, now);
        const filtered = tallyAlerts.filter((a) => a.detectedAt >= since);
        return filtered.map((alert) => ({
          id: alert.id,
          sourceName,
          occurredAt: alert.detectedAt,
          payload: {
            alert,
            baseRelevance: alert.severity === "critical" ? 0.95 : 0.7,
            suggestedUrgency: alert.severity === "critical" ? "now" : "morning_brief"
          }
        }));
      }
    };
  }
  // ── Private converters ──────────────────────────────────────────────────────
  marginAlertToTally(ma, now) {
    const kind = ma.alertType === "over_cap" ? "monthly_cap_crossed" : "margin_below_floor";
    const severity = ma.alertType === "over_cap" ? "critical" : "warning";
    return {
      id: mkId2(),
      kind,
      severity,
      customerId: ma.customerId,
      description: ma.message,
      observedValue: ma.capUtilisation,
      threshold: kind === "monthly_cap_crossed" ? 1 : ma.marginRatio,
      detectedAt: now,
      payload: { marginAlert: ma }
    };
  }
  anomalyToTally(a, now) {
    return {
      id: mkId2(),
      kind: "cache_hit_ratio_dropped",
      severity: a.severity,
      customerId: a.customerId,
      description: a.description,
      observedValue: a.observedValue,
      threshold: a.threshold,
      detectedAt: now,
      payload: { anomaly: a }
    };
  }
};
// Annotate the CommonJS export names for ESM import in node:
0 && (module.exports = {
  AnomalyDetector,
  ApiCallTracker,
  BatchTracker,
  CacheHitTracker,
  CostCalculator,
  DEFAULT_OCR_COST_PER_PAGE,
  DEFAULT_PRICING,
  DEFAULT_QUALITY_MODEL_MAP,
  DefaultSpendTracker,
  InMemoryBatchAttributionStore,
  InMemoryCustomerTierStore,
  InMemoryOcrUsageStore,
  InMemorySpendStore,
  Ledger,
  MarginMonitor,
  MarginMonitorAnalyzer,
  ModelRecommender,
  OPUS_MODEL_PATTERN,
  OcrTracker,
  PerAgentSpendAnalyzer,
  PerCustomerSpendAnalyzer,
  PerModelSpendAnalyzer,
  PricingChangeAdapter,
  Tally
});
//# sourceMappingURL=index.js.map