All files / src/pages/flowsheet-page/economics/cost-curves/model driverSpecs.ts

83.67% Statements 41/49
55.1% Branches 27/49
83.33% Functions 5/6
92.68% Lines 38/41

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134                                                    5122x   177x 177x 177x 177x                                   2440x 2440x 42x 42x 42x 42x   42x 42x 42x 42x 42x         42x 42x 42x 42x 42x       42x   42x       55x             36x           219x       219x   438x           15x           15x           15x 15x 15x 15x 15x 15x   15x     15x              
import type { CostCurveDriverSpec, CostCurveRead } from "@/api/apiStore.gen";
import { RoleEnum, SourceOptionsEnum } from "@/api/apiStore.gen";
 
export type RequiredDriverSpec = {
  key: string;
  label: string;
  unit: string;
  role: RoleEnum;
  variableSymbol: string;
  primary: boolean;
  required: boolean;
  defaultManualValue: string;
  sourceOptions: SourceOptionsEnum[];
};
 
/**
 * Return the complete, display-ready driver inputs declared by a cost curve.
 *
 * The backend has already validated this payload through the Pydantic
 * `CostCurveDriverSpec` model. If a response is malformed, callers should
 * render the selected curve as invalid rather than falling back to legacy
 * sizing controls.
 */
export function requiredDriverSpecsFromCurve(
  curve?: CostCurveRead | null,
): RequiredDriverSpec[] {
  return (curve?.required_driver_specs ?? []).flatMap(
    (spec: CostCurveDriverSpec): RequiredDriverSpec[] => {
      const key = spec.key.trim();
      const unit = spec.unit.trim();
      Iif (!key || !unit) return [];
      return [
        {
          key,
          label: spec.label.trim() || key,
          unit,
          role: spec.role,
          variableSymbol: spec.variable_symbol?.trim() ?? "",
          primary: Boolean(spec.primary),
          required: Boolean(spec.required ?? true),
          defaultManualValue: spec.default_manual_value?.trim() ?? "",
          sourceOptions: normalizedSourceOptions(spec.source_options),
        },
      ];
    },
  );
}
 
export function hasInvalidDriverSpecPayload(curve?: CostCurveRead | null) {
  const specs = curve?.required_driver_specs;
  if (!Array.isArray(specs) || specs.length === 0) return Boolean(curve);
  let primaryFormulaInputs = 0;
  let formulaInputs = 0;
  const keys = new Set<string>();
  const symbols = new Set<string>();
  for (const spec of specs) {
    const key = spec.key?.trim();
    const unit = spec.unit?.trim();
    const role = spec.role;
    Iif (!key || !unit || keys.has(key)) return true;
    keys.add(key);
    if (role !== RoleEnum.FormulaInput && role !== RoleEnum.DiscreteSelector) {
      return true;
    }
    if (role === RoleEnum.FormulaInput) {
      const symbol = spec.variable_symbol?.trim();
      Iif (!symbol || symbols.has(symbol)) return true;
      symbols.add(symbol);
      formulaInputs += 1;
      primaryFormulaInputs += spec.primary ? 1 : 0;
    } else if (spec.variable_symbol?.trim() || spec.primary) {
      return true;
    }
    Iif (normalizedSourceOptions(spec.source_options).length === 0) return true;
  }
  return formulaInputs === 0 || primaryFormulaInputs !== 1;
}
 
export function driverSpecIsManualOnly(spec: RequiredDriverSpec) {
  return (
    spec.sourceOptions.length === 1 &&
    spec.sourceOptions[0] === SourceOptionsEnum.Manual
  );
}
 
export function defaultManualValueForSpec(spec: RequiredDriverSpec) {
  return driverSpecIsManualOnly(spec) ? spec.defaultManualValue : "";
}
 
function normalizedSourceOptions(
  sourceOptions: CostCurveDriverSpec["source_options"],
) {
  const allowed = new Set([
    SourceOptionsEnum.Property,
    SourceOptionsEnum.Manual,
  ]);
  return (
    sourceOptions ?? [SourceOptionsEnum.Property, SourceOptionsEnum.Manual]
  ).filter((option): option is SourceOptionsEnum => allowed.has(option));
}
 
export function formulaDriverSpecs(
  specs: readonly CostCurveDriverSpec[],
): CostCurveDriverSpec[] {
  return specs.filter((spec) => spec.role === RoleEnum.FormulaInput);
}
 
export function selectorDriverSpecs(
  specs: readonly CostCurveDriverSpec[],
): CostCurveDriverSpec[] {
  return specs.filter((spec) => spec.role === RoleEnum.DiscreteSelector);
}
 
export function driverSpecSummary(
  specs: readonly CostCurveDriverSpec[] | undefined,
) {
  const normalized = specs ?? [];
  Iif (normalized.length === 0) return "Inputs not declared";
  const formulaInputs = formulaDriverSpecs(normalized);
  const selectors = selectorDriverSpecs(normalized);
  const formulaText = formulaInputs
    .map((spec) => `${spec.label || spec.key} (${spec.unit})`)
    .join(", ");
  const selectorText = selectors
    .map((spec) => `${spec.label || spec.key} (${spec.unit})`)
    .join(", ");
  return [
    formulaText ? `Inputs: ${formulaText}` : "",
    selectorText ? `Sizing inputs: ${selectorText}` : "",
  ]
    .filter(Boolean)
    .join(". ");
}