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                                                    4042x   70x 70x 70x 70x                                   1980x 1980x 17x 17x 17x 17x   17x 17x 17x 17x 17x         17x 17x 17x 17x 17x       17x   17x       38x             17x           87x       87x   174x           3x           3x           3x 3x 3x 3x 3x 3x   3x     3x              
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(". ");
}