Skip to content

packages/engine/scram/targets/scram-node/src/ScramNodeModel.cpp

Classes

Name
structBasicEventInfo

Functions

Name
voidParseFaultTreeElements(const Napi::Object & node, std::vector< ParsedGate > & parsedGates, std::vector< ParsedBasicEvent > & parsedBasicEvents, std::set< std::string > & seenBasicEvents, const std::string & basePath)
ParsedBasicEventParseBasicEvent(const Napi::Object & nodeEvent)
ParsedGateParseGate(const Napi::Object & nodeGate)
ParsedParameterParseParameter(const Napi::Object & nodeParam)
ParsedCCFGroupParseCCFGroup(const Napi::Object & nodeCCF)
ParsedFaultTreeParseFaultTree(const Napi::Object & nodeFaultTree)
ParsedEventTreeParseEventTree(const Napi::Object & nodeEventTree)
ParsedInitiatingEventParseInitiatingEvent(const Napi::Object & nodeIE)
std::unique_ptr< scram::mef::BasicEvent >BuildBasicEvent(const ParsedBasicEvent & parsed, scram::mef::Model * model, const ElementRegistry & registry)
std::unique_ptr< scram::mef::HouseEvent >BuildHouseEvent(const ParsedBasicEvent & parsed, scram::mef::Model * model, const ElementRegistry & registry)
std::unique_ptr< scram::mef::Parameter >BuildParameter(const ParsedParameter & parsed, scram::mef::Model * model, const ElementRegistry & registry)
std::unique_ptr< scram::mef::CcfGroup >BuildCCFGroup(const ParsedCCFGroup & parsed, scram::mef::Model * model, const ElementRegistry & registry)
std::unique_ptr< scram::mef::Gate >BuildGate(const ParsedGate & parsed, scram::mef::Model * model, const ElementRegistry & registry)
std::unique_ptr< scram::mef::Gate >BuildGateWithFormula(const ParsedGate & parsed, scram::mef::Model * model, const ElementRegistry & registry)
scram::mef::Expression *BuildExpression(const Napi::Value & nodeValue, scram::mef::Model * model, const ElementRegistry & registry, const std::string & basePath)
std::stringNextFTGateName(const scram::mef::FaultTree * ft, int & counter)
voidCollectBasicEventsFromFaultTree(const Napi::Object & ftObj, std::unordered_map< std::string, BasicEventInfo > & allBasicEvents)
scram::mef::Gate *BuildGateFromLogicExprTS(Napi::Env env, const Napi::Value & jsExpr, scram::mef::Model * model, scram::mef::FaultTree * ft, std::unordered_map< std::string, scram::mef::BasicEvent * > & beMap, std::unordered_map< std::string, scram::mef::HouseEvent * > & heMap, int & gateCounter)
std::unique_ptr< scram::mef::Model >ScramNodeModel(const Napi::Object & nodeModel)
Napi::ValueBuildModelOnly(const Napi::CallbackInfo & info)
std::unique_ptr< scram::mef::FaultTree >ScramNodeFaultTree(const ParsedFaultTree & parsed, scram::mef::Model * model, const ElementRegistry & registry)
std::unique_ptr< scram::mef::EventTree >ScramNodeEventTree(const ParsedEventTree & parsed, scram::mef::Model * model, const ElementRegistry & registry)
std::unique_ptr< scram::mef::InitiatingEvent >ScramNodeInitiatingEvent(const ParsedInitiatingEvent & parsed, scram::mef::Model * model)
ParsedParameterParseParameterValue(const Napi::Object & nodeParam)
ParsedBuiltInFunctionParseBuiltInFunction(const Napi::Object & nodeFunction)
ParsedRandomDeviateParseRandomDeviate(const Napi::Object & nodeDeviate)
ParsedNumericalOperationParseNumericalOperation(const Napi::Object & nodeOperation)
scram::mef::Expression *BuildParameterExpression(const ParsedParameter & parsed, scram::mef::Model * model, const ElementRegistry & registry)
scram::mef::Expression *BuildBuiltInFunctionExpression(const ParsedBuiltInFunction & parsed, scram::mef::Model * model, const ElementRegistry & registry)
scram::mef::Expression *BuildRandomDeviateExpression(const ParsedRandomDeviate & parsed, scram::mef::Model * model, const ElementRegistry & registry)
scram::mef::Expression *BuildNumericalOperationExpression(const ParsedNumericalOperation & parsed, scram::mef::Model * model, const ElementRegistry & registry)

Functions Documentation

function ParseFaultTreeElements

cpp
void ParseFaultTreeElements(
    const Napi::Object & node,
    std::vector< ParsedGate > & parsedGates,
    std::vector< ParsedBasicEvent > & parsedBasicEvents,
    std::set< std::string > & seenBasicEvents,
    const std::string & basePath
)

function ParseBasicEvent

cpp
ParsedBasicEvent ParseBasicEvent(
    const Napi::Object & nodeEvent
)

function ParseGate

cpp
ParsedGate ParseGate(
    const Napi::Object & nodeGate
)

function ParseParameter

cpp
ParsedParameter ParseParameter(
    const Napi::Object & nodeParam
)

function ParseCCFGroup

cpp
ParsedCCFGroup ParseCCFGroup(
    const Napi::Object & nodeCCF
)

function ParseFaultTree

cpp
ParsedFaultTree ParseFaultTree(
    const Napi::Object & nodeFaultTree
)

function ParseEventTree

cpp
ParsedEventTree ParseEventTree(
    const Napi::Object & nodeEventTree
)

function ParseInitiatingEvent

cpp
ParsedInitiatingEvent ParseInitiatingEvent(
    const Napi::Object & nodeIE
)

function BuildBasicEvent

cpp
std::unique_ptr< scram::mef::BasicEvent > BuildBasicEvent(
    const ParsedBasicEvent & parsed,
    scram::mef::Model * model,
    const ElementRegistry & registry
)

function BuildHouseEvent

cpp
std::unique_ptr< scram::mef::HouseEvent > BuildHouseEvent(
    const ParsedBasicEvent & parsed,
    scram::mef::Model * model,
    const ElementRegistry & registry
)

function BuildParameter

cpp
std::unique_ptr< scram::mef::Parameter > BuildParameter(
    const ParsedParameter & parsed,
    scram::mef::Model * model,
    const ElementRegistry & registry
)

function BuildCCFGroup

cpp
std::unique_ptr< scram::mef::CcfGroup > BuildCCFGroup(
    const ParsedCCFGroup & parsed,
    scram::mef::Model * model,
    const ElementRegistry & registry
)

function BuildGate

cpp
std::unique_ptr< scram::mef::Gate > BuildGate(
    const ParsedGate & parsed,
    scram::mef::Model * model,
    const ElementRegistry & registry
)

function BuildGateWithFormula

cpp
std::unique_ptr< scram::mef::Gate > BuildGateWithFormula(
    const ParsedGate & parsed,
    scram::mef::Model * model,
    const ElementRegistry & registry
)

function BuildExpression

cpp
scram::mef::Expression * BuildExpression(
    const Napi::Value & nodeValue,
    scram::mef::Model * model,
    const ElementRegistry & registry,
    const std::string & basePath
)

function NextFTGateName

cpp
static std::string NextFTGateName(
    const scram::mef::FaultTree * ft,
    int & counter
)

function CollectBasicEventsFromFaultTree

cpp
static void CollectBasicEventsFromFaultTree(
    const Napi::Object & ftObj,
    std::unordered_map< std::string, BasicEventInfo > & allBasicEvents
)

function BuildGateFromLogicExprTS

cpp
static scram::mef::Gate * BuildGateFromLogicExprTS(
    Napi::Env env,
    const Napi::Value & jsExpr,
    scram::mef::Model * model,
    scram::mef::FaultTree * ft,
    std::unordered_map< std::string, scram::mef::BasicEvent * > & beMap,
    std::unordered_map< std::string, scram::mef::HouseEvent * > & heMap,
    int & gateCounter
)

function ScramNodeModel

cpp
std::unique_ptr< scram::mef::Model > ScramNodeModel(
    const Napi::Object & nodeModel
)

function BuildModelOnly

cpp
Napi::Value BuildModelOnly(
    const Napi::CallbackInfo & info
)

function ScramNodeFaultTree

cpp
std::unique_ptr< scram::mef::FaultTree > ScramNodeFaultTree(
    const ParsedFaultTree & parsed,
    scram::mef::Model * model,
    const ElementRegistry & registry
)

function ScramNodeEventTree

cpp
std::unique_ptr< scram::mef::EventTree > ScramNodeEventTree(
    const ParsedEventTree & parsed,
    scram::mef::Model * model,
    const ElementRegistry & registry
)

function ScramNodeInitiatingEvent

cpp
std::unique_ptr< scram::mef::InitiatingEvent > ScramNodeInitiatingEvent(
    const ParsedInitiatingEvent & parsed,
    scram::mef::Model * model
)

function ParseParameterValue

cpp
ParsedParameter ParseParameterValue(
    const Napi::Object & nodeParam
)

function ParseBuiltInFunction

cpp
ParsedBuiltInFunction ParseBuiltInFunction(
    const Napi::Object & nodeFunction
)

function ParseRandomDeviate

cpp
ParsedRandomDeviate ParseRandomDeviate(
    const Napi::Object & nodeDeviate
)

function ParseNumericalOperation

cpp
ParsedNumericalOperation ParseNumericalOperation(
    const Napi::Object & nodeOperation
)

function BuildParameterExpression

cpp
scram::mef::Expression * BuildParameterExpression(
    const ParsedParameter & parsed,
    scram::mef::Model * model,
    const ElementRegistry & registry
)

function BuildBuiltInFunctionExpression

cpp
scram::mef::Expression * BuildBuiltInFunctionExpression(
    const ParsedBuiltInFunction & parsed,
    scram::mef::Model * model,
    const ElementRegistry & registry
)

function BuildRandomDeviateExpression

cpp
scram::mef::Expression * BuildRandomDeviateExpression(
    const ParsedRandomDeviate & parsed,
    scram::mef::Model * model,
    const ElementRegistry & registry
)

function BuildNumericalOperationExpression

cpp
scram::mef::Expression * BuildNumericalOperationExpression(
    const ParsedNumericalOperation & parsed,
    scram::mef::Model * model,
    const ElementRegistry & registry
)

Source code

cpp
#include "ScramNodeModel.h"
#include <array>
#include <functional>

// Structure to hold basic event information during collection phase
struct BasicEventInfo {
  std::string name;
  std::string description;
  double probability;
};

// ============ Helpers for parsing legacy/old JSON/XML-style ============

// Helper function to recursively parse gates and events from fault tree structure
void ParseFaultTreeElements(const Napi::Object& node,
 std::vector<ParsedGate>& parsedGates,
 std::vector<ParsedBasicEvent>& parsedBasicEvents, std::set<std::string>& seenBasicEvents,
 const std::string& basePath) {
 
 std::string nodeName = node.Get("name").ToString().Utf8Value();
 std::string nodeType = node.Has("type") ? node.Get("type").ToString().Utf8Value() : "unknown";
 
 // If this is a gate
 if (node.Has("type") && node.Get("type").ToString().Utf8Value() != "basic") {
  ParsedGate gate = ParseGate(node);
  gate.base_path = basePath;
  parsedGates.push_back(gate);

  // Recursively parse child gates
  if (node.Has("gates")) {
    Napi::Array gatesArr = node.Get("gates").As<Napi::Array>();
    for (uint32_t i = 0; i < gatesArr.Length(); ++i) {
      Napi::Object childGateObj = gatesArr.Get(i).As<Napi::Object>();
      ParseFaultTreeElements(childGateObj, parsedGates, parsedBasicEvents, seenBasicEvents, basePath);
    }
  }

  // Parse child events
  if (node.Has("events")) {
    Napi::Array eventsArr = node.Get("events").As<Napi::Array>();
    for (uint32_t i = 0; i < eventsArr.Length(); ++i) {
      Napi::Object eventObj = eventsArr.Get(i).As<Napi::Object>();
      ParseFaultTreeElements(eventObj, parsedGates, parsedBasicEvents, seenBasicEvents, basePath);
    }
  }
 }
 // If this is a basic event
 else if (node.Has("type") && node.Get("type").ToString().Utf8Value() == "basic") {
  // Only add if we haven't seen this basic event before
  if (seenBasicEvents.find(nodeName) == seenBasicEvents.end()) {
    ParsedBasicEvent event = ParseBasicEvent(node);
    event.base_path = basePath;
    parsedBasicEvents.push_back(event);
    seenBasicEvents.insert(nodeName);
  }
 }
}

// Step 1: Parse JSON into intermediate structures
ParsedBasicEvent ParseBasicEvent(const Napi::Object& nodeEvent) {
 ParsedBasicEvent parsed;
 parsed.name = nodeEvent.Get("name").ToString().Utf8Value();
 
 if (nodeEvent.Has("description")) {
  parsed.description = nodeEvent.Get("description").ToString().Utf8Value();
 }
 
 if (nodeEvent.Has("type")) {
  parsed.type = nodeEvent.Get("type").ToString().Utf8Value();
 } else {
  parsed.type = "basic";
 }
 
 if (nodeEvent.Has("value")) {
  parsed.value = nodeEvent.Get("value");
 }
 
 if (nodeEvent.Has("systemMissionTime")) {
  parsed.systemMissionTime = nodeEvent.Get("systemMissionTime").ToNumber().DoubleValue();
 }
 
 if (nodeEvent.Has("basePath")) {
  parsed.base_path = nodeEvent.Get("basePath").ToString().Utf8Value();
 }
 
 return parsed;
}

ParsedGate ParseGate(const Napi::Object& nodeGate) {
 ParsedGate parsed;
 parsed.name = nodeGate.Get("name").ToString().Utf8Value();
 
 if (nodeGate.Has("description")) {
  parsed.description = nodeGate.Get("description").ToString().Utf8Value();
 }
 
 if (nodeGate.Has("type")) {
  parsed.type = nodeGate.Get("type").ToString().Utf8Value();
 } else {
  parsed.type = "and";
 }
 
 // Parse child gates
 if (nodeGate.Has("gates")) {
  Napi::Array gatesArr = nodeGate.Get("gates").As<Napi::Array>();
  for (uint32_t i = 0; i < gatesArr.Length(); ++i) {
   Napi::Object childGateObj = gatesArr.Get(i).As<Napi::Object>();
   std::string childName = childGateObj.Get("name").ToString().Utf8Value();
   parsed.gate_refs.push_back(childName);
  }
 }
 
 // Parse child events
 if (nodeGate.Has("events")) {
  Napi::Array eventsArr = nodeGate.Get("events").As<Napi::Array>();
  for (uint32_t i = 0; i < eventsArr.Length(); ++i) {
   Napi::Object eventObj = eventsArr.Get(i).As<Napi::Object>();
   std::string eventName = eventObj.Get("name").ToString().Utf8Value();
   parsed.event_refs.push_back(eventName);
  }
 }
 
 // Parse min/max numbers for atleast/cardinality gates
 if (parsed.type == "atleast" || parsed.type == "cardinality") {
  if (nodeGate.Has("minNumber")) {
   parsed.min_number = nodeGate.Get("minNumber").ToNumber().Int32Value();
  }
  if (parsed.type == "cardinality" && nodeGate.Has("maxNumber")) {
   parsed.max_number = nodeGate.Get("maxNumber").ToNumber().Int32Value();
  }
 }
 
 if (nodeGate.Has("basePath")) {
  parsed.base_path = nodeGate.Get("basePath").ToString().Utf8Value();
 }
 
 return parsed; 
}

ParsedParameter ParseParameter(const Napi::Object& nodeParam) {
 ParsedParameter parsed;
 parsed.name = nodeParam.Get("name").ToString().Utf8Value();
 
 if (nodeParam.Has("description")) {
  parsed.description = nodeParam.Get("description").ToString().Utf8Value();
 }
 
 if (nodeParam.Has("value")) {
  parsed.value = nodeParam.Get("value");
 }
 
 if (nodeParam.Has("unit")) {
  parsed.unit = nodeParam.Get("unit").ToString().Utf8Value();
 }
 
 if (nodeParam.Has("basePath")) {
  parsed.base_path = nodeParam.Get("basePath").ToString().Utf8Value();
 }
 
 return parsed; 
}

ParsedCCFGroup ParseCCFGroup(const Napi::Object& nodeCCF) {
 ParsedCCFGroup parsed;
 parsed.name = nodeCCF.Get("name").ToString().Utf8Value();
 parsed.model_type = nodeCCF.Get("model").ToString().Utf8Value();
 
 if (nodeCCF.Has("description")) {
  parsed.description = nodeCCF.Get("description").ToString().Utf8Value();
 }
 
 // Parse members
 if (nodeCCF.Has("members")) {
  Napi::Array membersArr = nodeCCF.Get("members").As<Napi::Array>();
  for (uint32_t i = 0; i < membersArr.Length(); ++i) {
   std::string memberName = membersArr.Get(i).ToString().Utf8Value();
   parsed.member_refs.push_back(memberName);
  }
 }
 
 // Parse distribution
 if (nodeCCF.Has("distribution")) {
  parsed.distribution = nodeCCF.Get("distribution").ToNumber().DoubleValue();
 }
 
 // Parse factors
 if (nodeCCF.Has("factors")) {
  Napi::Array factorsArr = nodeCCF.Get("factors").As<Napi::Array>();
  for (uint32_t i = 0; i < factorsArr.Length(); ++i) {
   Napi::Object factorObj = factorsArr.Get(i).As<Napi::Object>();
   int level = factorObj.Has("level") ? factorObj.Get("level").ToNumber().Int32Value() : 0;
   double factorValue = factorObj.Has("value") ? factorObj.Get("value").ToNumber().DoubleValue() : 0.0;
   parsed.factors.emplace_back(level, factorValue);
  }
 }
 
 if (nodeCCF.Has("basePath")) {
  parsed.base_path = nodeCCF.Get("basePath").ToString().Utf8Value();
 }
 
 return parsed;
}

ParsedFaultTree ParseFaultTree(const Napi::Object& nodeFaultTree) {
 ParsedFaultTree parsed;
 parsed.name = nodeFaultTree.Get("name").ToString().Utf8Value();
 
 if (nodeFaultTree.Has("description")) {
  parsed.description = nodeFaultTree.Get("description").ToString().Utf8Value();
 }
 
 if (nodeFaultTree.Has("topEvent")) {
  Napi::Object topEventObj = nodeFaultTree.Get("topEvent").As<Napi::Object>();
  parsed.top_event_ref = topEventObj.Get("name").ToString().Utf8Value();
 }
 
 // Parse CCF groups
 if (nodeFaultTree.Has("ccfGroups")) {
  Napi::Array ccfArr = nodeFaultTree.Get("ccfGroups").As<Napi::Array>();
  for (uint32_t i = 0; i < ccfArr.Length(); ++i) {
   Napi::Object ccfObj = ccfArr.Get(i).As<Napi::Object>();
   std::string ccfName = ccfObj.Get("name").ToString().Utf8Value();
   parsed.ccf_group_refs.push_back(ccfName);
  }
 }
 
 return parsed;
}

ParsedEventTree ParseEventTree(const Napi::Object& nodeEventTree) {
  ParsedEventTree parsed;
  parsed.name = nodeEventTree.Get("name").ToString().Utf8Value();
 
  if (nodeEventTree.Has("description")) {
   parsed.description = nodeEventTree.Get("description").ToString().Utf8Value();
  }
 
  if (nodeEventTree.Has("initiatingEvent")) {
   Napi::Object ieObj = nodeEventTree.Get("initiatingEvent").As<Napi::Object>();
   parsed.initiating_event_ref = ieObj.Get("name").ToString().Utf8Value();
  }
 
  // Parse sequences (new schema) to extract functional states and infer references
  if (nodeEventTree.Has("sequences")) {
   Napi::Array seqArr = nodeEventTree.Get("sequences").As<Napi::Array>();
   std::set<std::string> namesFromSequences;
 
   for (uint32_t i = 0; i < seqArr.Length(); ++i) {
    Napi::Object seqObj = seqArr.Get(i).As<Napi::Object>();
    ParsedEventSequence sequence;
    sequence.end_state = seqObj.Get("endState").ToString().Utf8Value();
 
    if (seqObj.Has("functionalStates")) {
      Napi::Array fsArr = seqObj.Get("functionalStates").As<Napi::Array>();
      for (uint32_t j = 0; j < fsArr.Length(); ++j) {
        Napi::Object fsObj = fsArr.Get(j).As<Napi::Object>();
        ParsedFunctionalEvent fe;
        fe.name  = fsObj.Get("name").ToString().Utf8Value();
        fe.state = fsObj.Get("state").ToString().Utf8Value();
        // Allow explicit refGate; otherwise treat the name as a gate/FT alias
        if (fsObj.Has("refGate")) {
          Napi::Object refGateObj = fsObj.Get("refGate").As<Napi::Object>();
          fe.ref_gate_ref = refGateObj.Get("name").ToString().Utf8Value();
        } else {
          fe.ref_gate_ref = fe.name;
        }
        sequence.functional_events.push_back(fe);
        namesFromSequences.insert(fe.name);
      }
    }
 
    parsed.sequences.push_back(sequence);
   }
 
   // Determine functional event ordering: prefer top-level definition if it matches; else derive from sequences
   std::vector<std::string> order;
   if (nodeEventTree.Has("functionalEvents")) {
     Napi::Array feDefs = nodeEventTree.Get("functionalEvents").As<Napi::Array>();
     std::vector<std::string> defs;
     defs.reserve(feDefs.Length());
     for (uint32_t i = 0; i < feDefs.Length(); ++i) {
       Napi::Object feDef = feDefs.Get(i).As<Napi::Object>();
       std::string n = feDef.Get("name").ToString().Utf8Value();
       defs.push_back(n);
     }
     bool allPresent = !defs.empty();
     for (const auto& n : defs) {
       if (namesFromSequences.find(n) == namesFromSequences.end()) { allPresent = false; break; }
     }
     if (allPresent) order = std::move(defs);
   }
   if (order.empty()) {
     order.assign(namesFromSequences.begin(), namesFromSequences.end());
   }
   parsed.functional_event_refs = std::move(order);
  }
 
  return parsed;
}

ParsedInitiatingEvent ParseInitiatingEvent(const Napi::Object& nodeIE) {
 ParsedInitiatingEvent parsed;
 parsed.name = nodeIE.Get("name").ToString().Utf8Value();
 
 if (nodeIE.Has("description")) {
  parsed.description = nodeIE.Get("description").ToString().Utf8Value();
 }
 
 if (nodeIE.Has("frequency")) {
  parsed.frequency = nodeIE.Get("frequency").ToNumber().DoubleValue();
 } else {
  parsed.frequency = 1.0; // default frequency
 }
 
 if (nodeIE.Has("unit")) {
  parsed.unit = nodeIE.Get("unit").ToString().Utf8Value();
 } else {
  parsed.unit = "year-1"; // default unit
 }
 
 return parsed;
}

// ============ Builders for legacy paths ============

std::unique_ptr<scram::mef::BasicEvent> BuildBasicEvent(const ParsedBasicEvent& parsed, scram::mef::Model* model, const ElementRegistry& 
registry) {
 auto be = std::make_unique<scram::mef::BasicEvent>(parsed.name);
 
 if (!parsed.description.empty()) {
  be->label(parsed.description);
 }
 
 if (!parsed.value.IsEmpty() && !parsed.value.IsUndefined() && !parsed.value.IsNull()) {
  scram::mef::Expression* expr = BuildExpression(parsed.value, model, registry, parsed.base_path);
  be->expression(expr);
 }
 
 // Handle systemMissionTime if present
 if (parsed.systemMissionTime.has_value()) {
  be->SetAttribute(scram::mef::Attribute("systemMissionTime", std::to_string(parsed.systemMissionTime.value())));
 }
 
 return be;
}

std::unique_ptr<scram::mef::HouseEvent> BuildHouseEvent(const ParsedBasicEvent& parsed, scram::mef::Model* model, const 
ElementRegistry& registry) {
 auto he = std::make_unique<scram::mef::HouseEvent>(parsed.name);
 
 if (!parsed.description.empty()) {
  he->label(parsed.description);
 }
 
 // Value (state)
 if (!parsed.value.IsEmpty() && !parsed.value.IsUndefined() && !parsed.value.IsNull()) {
  bool state = parsed.value.ToBoolean().Value();
  he->state(state);
 }
 
 return he;
}

std::unique_ptr<scram::mef::Parameter> BuildParameter(const ParsedParameter& parsed, scram::mef::Model* model, const ElementRegistry& 
registry) {
 auto param = std::make_unique<scram::mef::Parameter>(parsed.name);
 
 if (!parsed.description.empty()) {
  param->label(parsed.description);
 }
 
 if (!parsed.value.IsEmpty() && !parsed.value.IsUndefined() && !parsed.value.IsNull()) {
  scram::mef::Expression* expr = BuildExpression(parsed.value, model, registry, parsed.base_path);
  param->expression(expr);
 }
 
 if (!parsed.unit.empty()) {
  param->unit(ScramNodeUnit(parsed.unit));
 }
 
 return param;
}

std::unique_ptr<scram::mef::CcfGroup> BuildCCFGroup(const ParsedCCFGroup& parsed, scram::mef::Model* model, const ElementRegistry& 
registry) {
 std::string modelType = ScramNodeCCFModelType(parsed.model_type);
 std::unique_ptr<scram::mef::CcfGroup> ccf;
 
 if (modelType == "beta-factor") {
  ccf = std::make_unique<scram::mef::BetaFactorModel>(parsed.name);
 } else if (modelType == "MGL") {
  ccf = std::make_unique<scram::mef::MglModel>(parsed.name);
 } else if (modelType == "alpha-factor") {
  ccf = std::make_unique<scram::mef::AlphaFactorModel>(parsed.name);
 } else if (modelType == "phi-factor") {
  ccf = std::make_unique<scram::mef::PhiFactorModel>(parsed.name);
 } else {
  throw std::runtime_error("Unknown CCF model type: " + modelType);
 }
 
 if (!parsed.description.empty()) {
  ccf->label(parsed.description);
 }
 
 // Add distribution
 if (parsed.distribution.has_value()) {
  auto ce = std::make_unique<scram::mef::ConstantExpression>(parsed.distribution.value());
  scram::mef::Expression* cePtr = ce.get();
  model->Add(std::move(ce));
  ccf->AddDistribution(cePtr);
 }
 
 // Add factors
 for (const auto& [level, value] : parsed.factors) {
  auto fe = std::make_unique<scram::mef::ConstantExpression>(value);
  scram::mef::Expression* fePtr = fe.get();
  model->Add(std::move(fe));
  ccf->AddFactor(fePtr, level ? std::optional<int>(level) : std::nullopt);
 }
 
 return ccf;
}

std::unique_ptr<scram::mef::Gate> BuildGate(const ParsedGate& parsed, scram::mef::Model* model, const ElementRegistry& registry) {
 auto gate = std::make_unique<scram::mef::Gate>(parsed.name);
 
 if (!parsed.description.empty()) {
  gate->label(parsed.description);
 }
 
 scram::mef::Connective type = ScramNodeGateType(parsed.type);
 scram::mef::Formula::ArgSet argSet;
 
 // Build formula with proper min/max numbers
 std::optional<int> min_number = parsed.min_number;
 std::optional<int> max_number = parsed.max_number;
 
 auto formula = std::make_unique<scram::mef::Formula>(type, std::move(argSet), min_number, max_number);
 gate->formula(std::move(formula));
 
 return gate;
}

std::unique_ptr<scram::mef::Gate> BuildGateWithFormula(const ParsedGate& parsed, scram::mef::Model* model, const ElementRegistry& 
registry) {
 auto gate = std::make_unique<scram::mef::Gate>(parsed.name);
 
 if (!parsed.description.empty()) {
  gate->label(parsed.description);
 }
 
 // Build complete formula with all arguments resolved
 scram::mef::Connective type = ScramNodeGateType(parsed.type);
 scram::mef::Formula::ArgSet argSet;
 
 // Add child gates
 for (const auto& gateRef : parsed.gate_refs) {
  auto childGate = registry.FindElement<scram::mef::Gate>(gateRef);
  if (childGate) {
   argSet.Add(childGate);
  } else {
   throw std::runtime_error("Child gate not found: " + gateRef);
  }
 }
 
 // Add child events
 for (const auto& eventRef : parsed.event_refs) {
  auto event = registry.FindElement<scram::mef::BasicEvent>(eventRef);
  if (event) {
   argSet.Add(event);
  } else {
   throw std::runtime_error("Child event not found: " + eventRef);
  }
 }
 
 // Validate that we have at least 2 arguments (SCRAM requirement)
 if (argSet.size() < 2) {
  throw std::runtime_error("Gate " + parsed.name + " must have at least 2 arguments, but has " + std::to_string(argSet.size()));
 }
 
 // Build formula with proper min/max numbers and resolved arguments
 std::optional<int> min_number = parsed.min_number;
 std::optional<int> max_number = parsed.max_number;
 
 auto formula = std::make_unique<scram::mef::Formula>(type, std::move(argSet), min_number, max_number);
 gate->formula(std::move(formula));
 
 return gate;
}

// Expression builder for complex Value types (legacy)
scram::mef::Expression* BuildExpression(const Napi::Value& nodeValue, scram::mef::Model* model, const ElementRegistry& registry, const 
std::string& basePath) {
 if (nodeValue.IsBoolean()) {
  bool val = nodeValue.ToBoolean().Value();
  return val ? &scram::mef::ConstantExpression::kOne : &scram::mef::ConstantExpression::kZero;
 }
 
 if (nodeValue.IsNumber()) {
  double val = nodeValue.ToNumber().DoubleValue();
  auto expr = std::make_unique<scram::mef::ConstantExpression>(val);
  scram::mef::Expression* ptr = expr.get();
  model->Add(std::move(expr));
  return ptr;
 }
 
 if (nodeValue.IsString()) {
  std::string paramName = nodeValue.ToString().Utf8Value();
  // Try to find parameter in registry first
  if (auto param = registry.FindElement<scram::mef::Parameter>(paramName)) {
   param->usage(true);
   return param;
  }
  // Fall back to model lookup
  const auto& params = model->table<scram::mef::Parameter>();
  auto it = std::find_if(params.begin(), params.end(), 
   [&](const scram::mef::Parameter& p) { return p.name() == paramName; });
  if (it != params.end()) {
   it->usage(true);
   return const_cast<scram::mef::Parameter*>(&(*it));
  }
  throw std::runtime_error("Unknown parameter reference: " + paramName);
 }
 
 if (nodeValue.IsObject()) {
  Napi::Object obj = nodeValue.As<Napi::Object>();
  
  // Check for Parameter object
  if (obj.Has("name") && obj.Has("value")) {
   ParsedParameter parsedParam = ParseParameterValue(obj);
   return BuildParameterExpression(parsedParam, model, registry);
  }
  
  // Check for BuiltInFunction
  if (obj.Has("exponential") || obj.Has("GLM") || obj.Has("Weibull") || obj.Has("periodicTest")) {
   ParsedBuiltInFunction parsedFunc = ParseBuiltInFunction(obj);
   return BuildBuiltInFunctionExpression(parsedFunc, model, registry);
  }
  
  // Check for RandomDeviate
  if (obj.Has("uniformDeviate") || obj.Has("normalDeviate") || obj.Has("lognormalDeviate") || 
      obj.Has("gammaDeviate") || obj.Has("betaDeviate") || obj.Has("histogram")) {
   ParsedRandomDeviate parsedDeviate = ParseRandomDeviate(obj);
   return BuildRandomDeviateExpression(parsedDeviate, model, registry);
  }
  
  // Check for NumericalOperation
  if (obj.Has("neg") || obj.Has("add") || obj.Has("sub") || obj.Has("mul") || obj.Has("div") ||
      obj.Has("pow") || obj.Has("sin") || obj.Has("cos") || obj.Has("tan") || obj.Has("log") ||
      obj.Has("exp") || obj.Has("sqrt") || obj.Has("abs") || obj.Has("min") || obj.Has("max")) {
   ParsedNumericalOperation parsedOp = ParseNumericalOperation(obj);
   return BuildNumericalOperationExpression(parsedOp, model, registry);
  }
 }
 
 throw std::runtime_error("Unsupported value type in BuildExpression");
}

// ============ NEW: Helpers for TS MVP FaultTree builder (LogicExpr) ============

// Build a unique gate name inside this FT
static std::string NextFTGateName(const scram::mef::FaultTree* ft, int& counter) {
  return ft->name() + "_g" + std::to_string(++counter);
}

// Helper function to collect basic events from a fault tree into a map
static void CollectBasicEventsFromFaultTree(
  const Napi::Object& ftObj, 
  std::unordered_map<std::string, BasicEventInfo>& allBasicEvents) {
  
  if (ftObj.Has("basicEvents")) {
    Napi::Array beArr = ftObj.Get("basicEvents").As<Napi::Array>();
    for (uint32_t j = 0; j < beArr.Length(); ++j) {
      Napi::Object beO = beArr.Get(j).As<Napi::Object>();
      std::string name = beO.Get("name").ToString().Utf8Value();
      double p = beO.Get("p").ToNumber().DoubleValue();
      
      // If event doesn't exist yet, add it
      if (allBasicEvents.find(name) == allBasicEvents.end()) {
        BasicEventInfo bei;
        bei.name = name;
        bei.probability = p;
        if (beO.Has("description")) {
          bei.description = beO.Get("description").ToString().Utf8Value();
        }
        allBasicEvents[name] = bei;
      }
    }
  }
}

// Recursively build a Gate from TS LogicExpr (supports {event}, {op:"and"/"or"/"not"})
// Returns a Gate* owned by the model (and added to the FT). May return nullptr for single-leaf pass-through.
static scram::mef::Gate* BuildGateFromLogicExprTS(
  Napi::Env env,
  const Napi::Value& jsExpr,
  scram::mef::Model* model,
  scram::mef::FaultTree* ft,
  std::unordered_map<std::string, scram::mef::BasicEvent*>& beMap,
  std::unordered_map<std::string, scram::mef::HouseEvent*>& heMap,
  int& gateCounter)
{
  using namespace scram::mef;
  if (!jsExpr.IsObject()) {
    Napi::TypeError::New(env, "LogicExpr must be an object").ThrowAsJavaScriptException();
    return nullptr;
  }
  Napi::Object obj = jsExpr.As<Napi::Object>();

  // Leaf: { event: "NAME" }
  if (obj.Has("event")) {
    // Caller should add this as an Arg directly; we return nullptr to signal "just a leaf"
    return nullptr;
  }

  // NOT
  if (obj.Has("op") && obj.Get("op").ToString().Utf8Value() == "not") {
    if (!obj.Has("arg") || !obj.Get("arg").IsObject()) {
      Napi::TypeError::New(env, "not.arg must be present").ThrowAsJavaScriptException();
      return nullptr;
    }
    Napi::Object argObj = obj.Get("arg").As<Napi::Object>();
    Formula::ArgSet as;
    if (argObj.Has("event")) {
      std::string ev = argObj.Get("event").ToString().Utf8Value();
      if (auto it = beMap.find(ev); it != beMap.end()) as.Add(it->second);
      else if (auto ih = heMap.find(ev); ih != heMap.end()) as.Add(ih->second);
      else {
        Napi::Error::New(env, "Unknown event in NOT: " + ev).ThrowAsJavaScriptException();
        return nullptr;
      }
      auto g = std::make_unique<Gate>(NextFTGateName(ft, gateCounter));
      auto f = std::make_unique<Formula>(kNot, std::move(as));
      Gate* gp = g.get();
      model->Add(std::move(g)); // model owns
      ft->Add(gp);              // FT references
      gp->formula(std::move(f));
      return gp;
    } else {
      // NOT of composite: build sub-gate first
      Gate* child = BuildGateFromLogicExprTS(env, argObj, model, ft, beMap, heMap, gateCounter);
      if (!child) {
        Napi::Error::New(env, "Invalid NOT arg").ThrowAsJavaScriptException();
        return nullptr;
      }
      as.Add(child);
      auto g = std::make_unique<Gate>(NextFTGateName(ft, gateCounter));
      auto f = std::make_unique<Formula>(kNot, std::move(as));
      Gate* gp = g.get();
      model->Add(std::move(g));
      ft->Add(gp);
      gp->formula(std::move(f));
      return gp;
    }
  }

  // Gates with args
  if (obj.Has("op")) {
    std::string op = obj.Get("op").ToString().Utf8Value();
    if (op != "and" && op != "or" && op != "xor" && op != "nand" && op != "nor" && op != "atleast") {
      Napi::TypeError::New(env, "Only and/or/not/xor/nand/nor/atleast are supported in top").ThrowAsJavaScriptException();
      return nullptr;
    }
    if (!obj.Has("args") || !obj.Get("args").IsArray()) {
      Napi::TypeError::New(env, "gate requires args[]").ThrowAsJavaScriptException();
      return nullptr;
    }
    Napi::Array arr = obj.Get("args").As<Napi::Array>();
    if (arr.Length() == 0) return nullptr;

    Formula::ArgSet as;
    for (uint32_t i = 0; i < arr.Length(); ++i) {
      auto av = arr.Get(i);
      if (!av.IsObject()) {
        Napi::TypeError::New(env, "args[" + std::to_string(i) + "] must be object").ThrowAsJavaScriptException();
        return nullptr;
      }
      Napi::Object aobj = av.As<Napi::Object>();
      if (aobj.Has("event")) {
        std::string ev = aobj.Get("event").ToString().Utf8Value();
        if (auto it = beMap.find(ev); it != beMap.end()) as.Add(it->second);
        else if (auto ih = heMap.find(ev); ih != heMap.end()) as.Add(ih->second);
        else {
          Napi::Error::New(env, "Unknown event: " + ev).ThrowAsJavaScriptException();
          return nullptr;
        }
      } else {
        Gate* sub = BuildGateFromLogicExprTS(env, av, model, ft, beMap, heMap, gateCounter);
        if (!sub) {
          Napi::Error::New(env, "Invalid composite arg").ThrowAsJavaScriptException();
          return nullptr;
        }
        as.Add(sub);
      }
    }

    // Determine connective and optional cardinality
    scram::mef::Connective k = scram::mef::kAnd;
    std::optional<int> min_number;
    std::optional<int> max_number;
    if (op == "and") k = scram::mef::kAnd;
    else if (op == "or") k = scram::mef::kOr;
    else if (op == "xor") k = scram::mef::kXor;
    else if (op == "nand") k = scram::mef::kNand;
    else if (op == "nor") k = scram::mef::kNor;
    else if (op == "atleast") {
      k = scram::mef::kAtleast;
      if (!obj.Has("k") || !obj.Get("k").IsNumber()) {
        Napi::TypeError::New(env, "atleast.k (number) is required").ThrowAsJavaScriptException();
        return nullptr;
      }
      int kval = obj.Get("k").ToNumber().Int32Value();
      if (kval <= 0 || static_cast<uint32_t>(kval) > arr.Length()) {
        Napi::TypeError::New(env, "atleast.k must be in 1..args.length").ThrowAsJavaScriptException();
        return nullptr;
      }
      min_number = kval;
    }

    // Single argument pass-through for simple gates
    if ((op == "and" || op == "or") && arr.Length() == 1) {
      k = scram::mef::kNull;
    }

    auto g = std::make_unique<Gate>(NextFTGateName(ft, gateCounter));
    auto f = std::make_unique<Formula>(k, std::move(as), min_number, max_number);
    Gate* gp = g.get();
    model->Add(std::move(g));
    ft->Add(gp);
    gp->formula(std::move(f));
    return gp;
  }

  Napi::TypeError::New(env, "Unknown LogicExpr shape").ThrowAsJavaScriptException();
  return nullptr;
}

// ============ Model builder (integrated legacy + TS MVP) ============

// Step 3: Main model building function
std::unique_ptr<scram::mef::Model> ScramNodeModel(const Napi::Object& nodeModel) {
 // Create the top-level Model
 std::string modelName = nodeModel.Has("name") ? nodeModel.Get("name").ToString().Utf8Value() : "model";
 auto model = std::make_unique<scram::mef::Model>(modelName);
 
 // Description (optional)
 if (nodeModel.Has("description")) {
  model->label(nodeModel.Get("description").ToString().Utf8Value());
 }
 
 // Set global mission time if present
 if (nodeModel.Has("missionTime")) {
  double mt = nodeModel.Get("missionTime").ToNumber().DoubleValue();
  model->mission_time().value(mt);
 }
 
 // Create element registry
 ElementRegistry registry;
 
 // Phase 1: Parse all elements into intermediate structures (legacy inputs)
 std::vector<ParsedBasicEvent> parsedBasicEvents;
 std::vector<ParsedGate> parsedGates;
 std::vector<ParsedParameter> parsedParameters;
 std::vector<ParsedCCFGroup> parsedCCFGroups;
 std::vector<ParsedFaultTree> parsedFaultTrees;
 std::vector<ParsedEventTree> parsedEventTrees;
 std::vector<ParsedInitiatingEvent> parsedInitiatingEvents;
 std::set<std::string> seenInitiatingEventNames;
 
 // Parse basic events (legacy global)
 if (nodeModel.Has("basicEvents")) {
  Napi::Array beArr = nodeModel.Get("basicEvents").As<Napi::Array>();
  for (uint32_t i = 0; i < beArr.Length(); ++i) {
   Napi::Object beObj = beArr.Get(i).As<Napi::Object>();
   parsedBasicEvents.push_back(ParseBasicEvent(beObj));
  }
 }
 
 // Parse modelData (alternative format for basic events) - legacy
 if (nodeModel.Has("modelData")) {
  Napi::Array mdArr = nodeModel.Get("modelData").As<Napi::Array>();
  for (uint32_t i = 0; i < mdArr.Length(); ++i) {
   Napi::Object mdObj = mdArr.Get(i).As<Napi::Object>();
   ParsedBasicEvent be;
   be.name = mdObj.Get("name").ToString().Utf8Value();
   if (mdObj.Has("description")) {
    be.description = mdObj.Get("description").ToString().Utf8Value();
   }
   be.type = "basic";
   if (mdObj.Has("value")) {
    be.value = mdObj.Get("value");
   }
   if (mdObj.Has("systemMissionTime")) {
    be.systemMissionTime = mdObj.Get("systemMissionTime").ToNumber().DoubleValue();
   }
   parsedBasicEvents.push_back(be);
  }
 }
 
 // Parse parameters (legacy)
 if (nodeModel.Has("parameters")) {
  Napi::Array paramArr = nodeModel.Get("parameters").As<Napi::Array>();
  for (uint32_t i = 0; i < paramArr.Length(); ++i) {
   Napi::Object paramObj = paramArr.Get(i).As<Napi::Object>();
   parsedParameters.push_back(ParseParameter(paramObj));
  }
 }
 
 // Parse CCF groups (legacy)
 if (nodeModel.Has("ccfGroups")) {
  Napi::Array ccfArr = nodeModel.Get("ccfGroups").As<Napi::Array>();
  for (uint32_t i = 0; i < ccfArr.Length(); ++i) {
   Napi::Object ccfObj = ccfArr.Get(i).As<Napi::Object>();
   parsedCCFGroups.push_back(ParseCCFGroup(ccfObj));
  }
 }
 
 // Parse gates (legacy)
 if (nodeModel.Has("gates")) {
  Napi::Array gateArr = nodeModel.Get("gates").As<Napi::Array>();
  for (uint32_t i = 0; i < gateArr.Length(); ++i) {
   Napi::Object gateObj = gateArr.Get(i).As<Napi::Object>();
   parsedGates.push_back(ParseGate(gateObj));
  }
 }
 
 // FIRST PASS: Collect all basic events from all fault trees
 std::unordered_map<std::string, BasicEventInfo> allBasicEvents;
 std::unordered_map<std::string, std::vector<std::string>> ftHouseEvents; // Map of FT name to house event names

 // Parse fault trees and first collect all basic events
 if (nodeModel.Has("faultTrees")) {
  Napi::Array ftArr = nodeModel.Get("faultTrees").As<Napi::Array>();
  
  // FIRST PASS: Collect all basic events
  for (uint32_t i = 0; i < ftArr.Length(); ++i) {
    Napi::Object ftObj = ftArr.Get(i).As<Napi::Object>();
    
    // === TS MVP format path ===
    if (ftObj.Has("top") && ftObj.Has("basicEvents")) {
      // Collect basic events from this fault tree
      CollectBasicEventsFromFaultTree(ftObj, allBasicEvents);
      
      // Store house events for later processing
      std::string ftName = ftObj.Get("name").ToString().Utf8Value();
      if (ftObj.Has("houseEvents")) {
        Napi::Array heArr = ftObj.Get("houseEvents").As<Napi::Array>();
        std::vector<std::string> heNames;
        for (uint32_t j = 0; j < heArr.Length(); ++j) {
          Napi::Object heO = heArr.Get(j).As<Napi::Object>();
          std::string name = heO.Get("name").ToString().Utf8Value();
          heNames.push_back(name);
        }
        ftHouseEvents[ftName] = heNames;
      }
    } else if (ftObj.Has("topEvent")) {
      // Legacy path - extract gates and events for later processing
      parsedFaultTrees.push_back(ParseFaultTree(ftObj));
    }
  }
  
  // SECOND PASS: Register all unique basic events to the model once
  std::unordered_map<std::string, scram::mef::BasicEvent*> globalBeMap;
  for (const auto& [name, info] : allBasicEvents) {
    auto be = std::make_unique<scram::mef::BasicEvent>(name);
    if (!info.description.empty()) {
      be->label(info.description);
    }
    auto ce = std::make_unique<scram::mef::ConstantExpression>(info.probability);
    scram::mef::Expression* cePtr = ce.get();
    be->expression(cePtr);
    scram::mef::BasicEvent* bePtr = be.get();
    model->Add(std::move(ce));
    model->Add(std::move(be));
    globalBeMap[name] = bePtr;
  }
  
  // THIRD PASS: Build fault trees using the registered basic events
  for (uint32_t i = 0; i < ftArr.Length(); ++i) {
    Napi::Object ftObj = ftArr.Get(i).As<Napi::Object>();
    
    // === TS MVP format path ===
    if (ftObj.Has("top") && ftObj.Has("basicEvents")) {
      std::string ftName = ftObj.Get("name").ToString().Utf8Value();
      auto ft = std::make_unique<scram::mef::FaultTree>(ftName);
      
      // Build maps of events for this FT using the global registered events
      std::unordered_map<std::string, scram::mef::BasicEvent*> beMap;
      std::unordered_map<std::string, scram::mef::HouseEvent*> heMap;
      
      // Add the already registered basic events to this fault tree
      Napi::Array beArr = ftObj.Get("basicEvents").As<Napi::Array>();
      for (uint32_t j = 0; j < beArr.Length(); ++j) {
        Napi::Object beO = beArr.Get(j).As<Napi::Object>();
        std::string name = beO.Get("name").ToString().Utf8Value();
        
        // Find the pre-registered basic event and add it to this fault tree
        auto beIt = globalBeMap.find(name);
        if (beIt != globalBeMap.end()) {
          ft->Add(beIt->second);
          beMap[name] = beIt->second;
        } else {
          throw std::runtime_error("Basic event not found in global map: " + name);
        }
      }
      
      // Process house events
      if (ftObj.Has("houseEvents")) {
        Napi::Array heArr = ftObj.Get("houseEvents").As<Napi::Array>();
        for (uint32_t j = 0; j < heArr.Length(); ++j) {
          Napi::Object heO = heArr.Get(j).As<Napi::Object>();
          std::string name = heO.Get("name").ToString().Utf8Value();
          bool state = heO.Get("state").ToBoolean().Value();
          auto he = std::make_unique<scram::mef::HouseEvent>(name);
          he->state(state);
          heMap.emplace(name, he.get());
          ft->Add(he.get());
          model->Add(std::move(he));
        }
      }
      
      // Build top gate from LogicExpr
      int gateCounter = 0;
      scram::mef::Gate* topGate = BuildGateFromLogicExprTS(
        nodeModel.Env(),
        ftObj.Get("top"),
        model.get(),
        ft.get(),
        beMap,
        heMap,
        gateCounter);
        
      if (!topGate) {
        throw std::runtime_error("Failed to build top gate for FaultTree '" + ftName + "'");
      }
      
      // Create an alias gate under the FaultTree name that references the built top gate.
      // This allows EventTrees to reference the FT directly by name in functionalStates.
      {
        scram::mef::Formula::ArgSet as;
        as.Add(topGate);
        auto alias = std::make_unique<scram::mef::Gate>(ftName);
        auto f = std::make_unique<scram::mef::Formula>(scram::mef::kNull, std::move(as));
        alias->formula(std::move(f));
        registry.RegisterElement(ftName, std::move(alias));
      }
      
      // Now ensure FT top event is discoverable
      ft->CollectTopEvents();
      model->Add(std::move(ft));
    } else if (ftObj.Has("topEvent")) {
      // Legacy path - Extract gates and events from the fault tree structure (legacy hierarchical)
      Napi::Object topEventObj = ftObj.Get("topEvent").As<Napi::Object>();
      std::set<std::string> seenBasicEvents;
      ParseFaultTreeElements(topEventObj, parsedGates, parsedBasicEvents, seenBasicEvents, "");
    }
  }
 }

 // Parse event trees (legacy-style input JSON for ET)
 if (nodeModel.Has("eventTrees")) {
  Napi::Array etArr = nodeModel.Get("eventTrees").As<Napi::Array>();
  for (uint32_t i = 0; i < etArr.Length(); ++i) {
   Napi::Object etObj = etArr.Get(i).As<Napi::Object>();
   parsedEventTrees.push_back(ParseEventTree(etObj));
   // Also parse initiating event from within ET to create/ensure IE
   if (etObj.Has("initiatingEvent")) {
     Napi::Object ieObj = etObj.Get("initiatingEvent").As<Napi::Object>();
     std::string ieName = ieObj.Get("name").ToString().Utf8Value();
     if (seenInitiatingEventNames.insert(ieName).second) {
       parsedInitiatingEvents.push_back(ParseInitiatingEvent(ieObj));
     }
   }
  }
 }
 
 // Parse initiating events (top-level optional; avoid duplicates)
 if (nodeModel.Has("initiatingEvents")) {
  Napi::Array ieArr = nodeModel.Get("initiatingEvents").As<Napi::Array>();
  for (uint32_t i = 0; i < ieArr.Length(); ++i) {
   Napi::Object ieObj = ieArr.Get(i).As<Napi::Object>();
   std::string ieName = ieObj.Get("name").ToString().Utf8Value();
   if (seenInitiatingEventNames.insert(ieName).second) {
     parsedInitiatingEvents.push_back(ParseInitiatingEvent(ieObj));
   }
  }
 }
 
// Summary of parsing phase
// Phase 2: Build SCRAM elements in dependency order
// First, build basic events and parameters (legacy global) — TS path already added to model
 for (const auto& parsed : parsedBasicEvents) {
  auto be = BuildBasicEvent(parsed, model.get(), registry);
  registry.RegisterElement(parsed.name, std::move(be));
 }
 if (!parsedParameters.empty()) {
  for (const auto& parsed : parsedParameters) {
   auto param = BuildParameter(parsed, model.get(), registry);
   registry.RegisterElement(parsed.name, std::move(param));
  }
 }
 
 // Then build CCF groups (legacy)
 if (!parsedCCFGroups.empty()) {
  for (const auto& parsed : parsedCCFGroups) {
   auto ccf = BuildCCFGroup(parsed, model.get(), registry);
   registry.RegisterElement(parsed.name, std::move(ccf));
  }
 }
 
 // Phase 3: Build gates with complete formulas in dependency order (legacy)
 // Show what's available in the registry
 // Print dependency information (summary)
 
 // Build gates in dependency order using topological sort
 std::vector<ParsedGate> remainingGates = parsedGates;
 std::set<std::string> builtGateNames;
 int iteration = 0;
 
 while (!remainingGates.empty()) {
  iteration++;
 
  bool progressMade = false;
  std::vector<ParsedGate> stillRemaining;
 
  for (const auto& parsed : remainingGates) {
   // Check if all child gates and events are available
   bool allDependenciesAvailable = true;
 
   // Check child gates
   for (const auto& gateRef : parsed.gate_refs) {
    if (builtGateNames.find(gateRef) == builtGateNames.end()) {
     allDependenciesAvailable = false;
     break;
    }
   }
 
   // Check child events
   for (const auto& eventRef : parsed.event_refs) {
    if (!registry.FindElement<scram::mef::BasicEvent>(eventRef)) {
     allDependenciesAvailable = false;
     break;
    }
   }
 
   if (allDependenciesAvailable) {
    // Build gate with complete formula
    auto gate = BuildGateWithFormula(parsed, model.get(), registry);
    registry.RegisterElement(parsed.name, std::move(gate));
    builtGateNames.insert(parsed.name);
    progressMade = true;
   } else {
    // Keep for next iteration
    stillRemaining.push_back(parsed);
   }
  }
 
  // If no progress was made in this iteration, we have a circular dependency
  if (!progressMade && !stillRemaining.empty()) {
   std::string errorMsg = "Circular dependency detected after " + std::to_string(iteration) + " iterations. Gates with unresolved dependencies: ";
   for (const auto& gate : stillRemaining) {
    errorMsg += gate.name + " ";
   }
   throw std::runtime_error(errorMsg);
  }
 
  remainingGates = std::move(stillRemaining);
 }
 
 // Phase 4: Resolve CCF group members (legacy)
 if (!parsedCCFGroups.empty()) {
  for (const auto& parsed : parsedCCFGroups) {
   auto ccf = registry.FindElement<scram::mef::CcfGroup>(parsed.name);
   if (ccf) {
    for (const auto& memberRef : parsed.member_refs) {
     auto member = registry.FindElement<scram::mef::BasicEvent>(memberRef);
     if (!member) {
      // Fallback to model lookup (TS path may have added directly to model)
      const auto& bes = model->basic_events();
      auto it = std::find_if(bes.begin(), bes.end(), [&](const scram::mef::BasicEvent& e){ return e.name() == memberRef; });
      if (it != bes.end()) {
       member = const_cast<scram::mef::BasicEvent*>(&(*it));
      }
     }
     if (member) {
      ccf->AddMember(member);
     } else {
      throw std::runtime_error("CCF group member not found: " + memberRef);
     }
    }
   }
  }
 }
 
 // Phase 5: Build fault trees (legacy parsedFaultTrees)
 if (!parsedFaultTrees.empty()) {
  for (const auto& parsed : parsedFaultTrees) {
   auto ft = ScramNodeFaultTree(parsed, model.get(), registry);
   ft->CollectTopEvents();
   model->Add(std::move(ft));
  }
 }
 
 // Phase 5b: Build event trees (using our new ET mapping that constructs forks/paths and CollectFormula)
 if (!parsedEventTrees.empty()) {
  for (const auto& parsed : parsedEventTrees) {
   auto et = ScramNodeEventTree(parsed, model.get(), registry);
   model->Add(std::move(et));
  }
 }
 
 // Phase 6: Build initiating events (from ETs and/or top-level defs) and link to ETs
 if (!parsedInitiatingEvents.empty()) {
  for (const auto& parsed : parsedInitiatingEvents) {
   auto ie = ScramNodeInitiatingEvent(parsed, model.get());
   model->Add(std::move(ie));
  }
 }
 
// Transfer all elements from registry to model (legacy constructed)
 registry.ExtractAllToModel(model.get());

 // Link InitiatingEvent -> EventTree by name reference captured in parsedEventTrees
 {
   std::unordered_map<std::string, scram::mef::EventTree*> etByName;
   for (auto& et : model->event_trees()) {
     etByName.emplace(et.name(), const_cast<scram::mef::EventTree*>(&et));
   }
   std::unordered_map<std::string, scram::mef::InitiatingEvent*> ieByName;
   for (auto& ie : model->initiating_events()) {
     ieByName.emplace(ie.name(), const_cast<scram::mef::InitiatingEvent*>(&ie));
   }
   for (const auto& pet : parsedEventTrees) {
     if (pet.initiating_event_ref.empty()) continue;
     auto itET = etByName.find(pet.name);
     auto itIE = ieByName.find(pet.initiating_event_ref);
     if (itET == etByName.end()) {
       throw std::runtime_error("EventTree not found for linking: " + pet.name);
     }
     if (itIE == ieByName.end()) {
       throw std::runtime_error("InitiatingEvent '" + pet.initiating_event_ref + "' not found to link ET '" + pet.name + "'");
     }
     itIE->second->event_tree(itET->second);
   }
 }
 
 return model;
}

// New function: Build model and return summary info without running analysis
Napi::Value BuildModelOnly(const Napi::CallbackInfo& info) {
 Napi::Env env = info.Env();
 if (info.Length() < 1) {
  Napi::TypeError::New(env, "Model object is required").ThrowAsJavaScriptException();
  return env.Null();
 }
 if (!info[0].IsObject()) {
  Napi::TypeError::New(env, "Model object required").ThrowAsJavaScriptException();
  return env.Null();
 }
 Napi::Object nodeModel = info[0].As<Napi::Object>();
 
 try {
  // Build the model (this will show the diagnostic output)
  auto model = ScramNodeModel(nodeModel);
 
  // Create a summary object to return
  Napi::Object summary = Napi::Object::New(env);
 
  // Get model statistics
  summary.Set("totalBasicEvents", Napi::Number::New(env, model->basic_events().size()));
  summary.Set("totalGates",       Napi::Number::New(env, model->gates().size()));
  summary.Set("totalFaultTrees",  Napi::Number::New(env, model->fault_trees().size()));
  summary.Set("totalParameters",  Napi::Number::New(env, model->parameters().size()));
  summary.Set("totalCCFGroups",   Napi::Number::New(env, model->ccf_groups().size()));
 
  // Get basic event names
  Napi::Array basicEventNames = Napi::Array::New(env);
  int i = 0;
  for (const auto& event : model->basic_events()) {
   basicEventNames.Set(i++, event.name());
  }
  summary.Set("basicEventNames", basicEventNames);
 
  // Get gate names
  Napi::Array gateNames = Napi::Array::New(env);
  i = 0;
  for (const auto& gate : model->gates()) {
   gateNames.Set(i++, gate.name());
  }
  summary.Set("gateNames", gateNames);
 
  // Get fault tree names
  Napi::Array faultTreeNames = Napi::Array::New(env);
  i = 0;
  for (const auto& ft : model->fault_trees()) {
   faultTreeNames.Set(i++, ft.name());
  }
  summary.Set("faultTreeNames", faultTreeNames);
 
  summary.Set("message", "Model built successfully! Use QuantifyModel() to run analysis.");
  return summary;
 
 } catch (const std::exception& e) {
  Napi::Error::New(env, std::string("Failed to build model: ") + e.what()).ThrowAsJavaScriptException();
  return env.Null();
 }
}

// FaultTree building with resolved references (legacy)
std::unique_ptr<scram::mef::FaultTree> ScramNodeFaultTree(const ParsedFaultTree& parsed, scram::mef::Model* model, const ElementRegistry& 
registry) {
 auto ft = std::make_unique<scram::mef::FaultTree>(parsed.name);
 
 if (!parsed.description.empty()) {
  ft->label(parsed.description);
 }
 
 // Add CCF groups
 for (const auto& ccfRef : parsed.ccf_group_refs) {
  auto ccf = registry.FindElement<scram::mef::CcfGroup>(ccfRef);
  if (ccf) {
   ft->Add(ccf);
  } else {
   throw std::runtime_error("CCF group not found: " + ccfRef);
  }
 }
 
 // Set top event
 if (!parsed.top_event_ref.empty()) {
  auto topEvent = registry.FindElement<scram::mef::Gate>(parsed.top_event_ref);
  if (topEvent) {
   ft->Add(topEvent);
  } else {
   throw std::runtime_error("Top event not found: " + parsed.top_event_ref);
  }
 }
 
 return ft;
}

// EventTree building with resolved references (constructs forks and sequence CollectFormula)
std::unique_ptr<scram::mef::EventTree> ScramNodeEventTree(const ParsedEventTree& parsed, scram::mef::Model* model, const 
ElementRegistry& registry) {
 using namespace scram::mef;

 auto et = std::make_unique<EventTree>(parsed.name);
 if (!parsed.description.empty()) {
   et->label(parsed.description);
 }

 // 1) Functional events registry
 std::unordered_map<std::string, FunctionalEvent*> feByName;
 feByName.reserve(parsed.functional_event_refs.size());
 for (const auto& feName : parsed.functional_event_refs) {
   auto fe = std::make_unique<FunctionalEvent>(feName);
   FunctionalEvent* fePtr = fe.get();
   et->Add(std::move(fe));
   feByName.emplace(feName, fePtr);
 }

 // 2) Create sequences; attach CollectFormula instructions based on FE states
 std::vector<Sequence*> seqPtrs;
 seqPtrs.reserve(parsed.sequences.size());

 auto makeCollectFormula = [&](const scram::mef::Gate* gate, bool complement) -> scram::mef::Instruction* {
   Formula::ArgSet as;
   as.Add(const_cast<Gate*>(gate));
   std::unique_ptr<Formula> f;
   if (complement) f = std::make_unique<Formula>(kNot, std::move(as));
   else            f = std::make_unique<Formula>(kNull, std::move(as));
   auto instr = std::make_unique<CollectFormula>(std::move(f));
   Instruction* p = instr.get();
   model->Add(std::move(instr));
   return p;
 };

 for (const auto& seqParsed : parsed.sequences) {
   auto seq = std::make_unique<Sequence>(seqParsed.end_state);
   Sequence* seqPtr = seq.get();
   et->Add(seq.get());
   model->Add(std::move(seq));
   seqPtrs.push_back(seqPtr);

   std::vector<Instruction*> instrs;
   instrs.reserve(seqParsed.functional_events.size());
   for (const auto& fe : seqParsed.functional_events) {
     if (fe.ref_gate_ref.empty()) continue;
     auto g = registry.FindElement<Gate>(fe.ref_gate_ref);
     if (!g) {
       throw std::runtime_error("EventTree '" + parsed.name +
                                "': functional event '" + fe.name +
                                "' references unknown gate '" + fe.ref_gate_ref + "'");
     }
     if (fe.state == "failure") {
       instrs.push_back(makeCollectFormula(g, false));
     } else if (fe.state == "success") {
       instrs.push_back(makeCollectFormula(g, true));
     } else if (fe.state == "bypass") {
       // no op
     } else {
       throw std::runtime_error("EventTree '" + parsed.name +
                                "': functional event '" + fe.name +
                                "' has unknown state '" + fe.state + "'");
     }
   }
   if (!instrs.empty()) {
     seqPtr->instructions(std::move(instrs));
   }
 }

 // 3) Fork chain over functional_event_refs to dispatch sequences by FE states
 std::vector<std::unordered_map<std::string, std::string>> seqStateMap(parsed.sequences.size());
 for (size_t i = 0; i < parsed.sequences.size(); ++i) {
   for (const auto& fe : parsed.sequences[i].functional_events) {
     seqStateMap[i][fe.name] = fe.state;
   }
 }

 // recursive builder: returns Fork* added to ET
 std::function<Fork*(size_t, const std::vector<size_t>&)> buildFork =
   [&](size_t level, const std::vector<size_t>& indices) -> Fork* {
     if (level >= parsed.functional_event_refs.size()) {
       return nullptr;
     }
     const std::string& feName = parsed.functional_event_refs[level];
     auto itFE = feByName.find(feName);
     if (itFE == feByName.end()) {
       throw std::runtime_error("EventTree '" + parsed.name + "': unknown functional event '" + feName + "' in order.");
     }
     auto fork = std::make_unique<Fork>(*itFE->second, std::vector<Path>{});
     Fork* forkPtr = fork.get();

     std::array<const char*, 3> stateOrder{{"failure","success","bypass"}};
     for (const char* state : stateOrder) {
       std::vector<size_t> next;
       next.reserve(indices.size());
       for (size_t idx : indices) {
         auto sit = seqStateMap[idx].find(feName);
         std::string s = (sit != seqStateMap[idx].end() ? sit->second : std::string("bypass"));
         if (s == state) {
           next.push_back(idx);
         }
       }
       if (next.empty()) continue;

       Path path(state);
       if (level + 1 < parsed.functional_event_refs.size()) {
         Fork* child = buildFork(level + 1, next);
         if (!child) {
           if (next.size() != 1) {
             throw std::runtime_error("EventTree '" + parsed.name + "': non-unique leaf under functional chain.");
           }
           path.target(seqPtrs[next.front()]);
         } else {
           path.target(child);
         }
       } else {
         if (next.size() != 1) {
           throw std::runtime_error("EventTree '" + parsed.name + "': non-unique leaf for final level.");
         }
         path.target(seqPtrs[next.front()]);
       }
       forkPtr->paths().push_back(std::move(path));
     }

     if (forkPtr->paths().empty()) {
       return nullptr;
     }

     et->Add(std::move(fork));
     return forkPtr;
   };

 std::vector<size_t> rootIdx(parsed.sequences.size());
 for (size_t i = 0; i < rootIdx.size(); ++i) rootIdx[i] = i;

 scram::mef::Branch initial;
 if (!parsed.functional_event_refs.empty()) {
   Fork* rootFork = buildFork(0, rootIdx);
   if (!rootFork) {
     if (seqPtrs.size() == 1) {
       initial.target(seqPtrs[0]);
     } else {
       throw std::runtime_error("EventTree '" + parsed.name + "': no paths constructed and multiple sequences present.");
     }
   } else {
     initial.target(rootFork);
   }
 } else {
   if (seqPtrs.size() == 1) {
     initial.target(seqPtrs[0]);
   } else {
     throw std::runtime_error("EventTree '" + parsed.name + "': functionalEvents are empty but sequences > 1.");
   }
 }
 et->initial_state(std::move(initial));

 // The InitiatingEvent is linked later in ScramNodeModel()
 return et;
}

// InitiatingEvent building
std::unique_ptr<scram::mef::InitiatingEvent> ScramNodeInitiatingEvent(const ParsedInitiatingEvent& parsed, scram::mef::Model* model) {
 auto ie = std::make_unique<scram::mef::InitiatingEvent>(parsed.name);
 
 if (!parsed.description.empty()) {
  ie->label(parsed.description);
 }
 
 // Set frequency and unit as attributes
 ie->SetAttribute(scram::mef::Attribute("frequency", std::to_string(parsed.frequency)));
 ie->SetAttribute(scram::mef::Attribute("unit", parsed.unit));

  auto freq_expr = std::make_unique<scram::mef::ConstantExpression>(parsed.frequency);
  scram::mef::Expression* freq_ptr = freq_expr.get();
  model->Add(std::move(freq_expr));
  ie->frequency(freq_ptr);
 
 return ie;
}

// Complex Value type parsing functions (legacy)
ParsedParameter ParseParameterValue(const Napi::Object& nodeParam) {
 ParsedParameter parsed;
 parsed.name = nodeParam.Get("name").ToString().Utf8Value();
 
 if (nodeParam.Has("description")) {
  parsed.description = nodeParam.Get("description").ToString().Utf8Value();
 }
 
 if (nodeParam.Has("value")) {
  parsed.value = nodeParam.Get("value");
 }
 
 if (nodeParam.Has("unit")) {
  parsed.unit = nodeParam.Get("unit").ToString().Utf8Value();
 }
 
 return parsed;
}

ParsedBuiltInFunction ParseBuiltInFunction(const Napi::Object& nodeFunction) {
 ParsedBuiltInFunction parsed;
 
 // Determine function type and extract arguments
 if (nodeFunction.Has("exponential")) {
  parsed.function_type = "exponential";
  Napi::Array args = nodeFunction.Get("exponential").As<Napi::Array>();
  for (uint32_t i = 0; i < args.Length(); ++i) {
   parsed.arguments.push_back(args.Get(i));
  }
 } else if (nodeFunction.Has("GLM")) {
  parsed.function_type = "GLM";
  Napi::Array args = nodeFunction.Get("GLM").As<Napi::Array>();
  for (uint32_t i = 0; i < args.Length(); ++i) {
   parsed.arguments.push_back(args.Get(i));
  }
 } else if (nodeFunction.Has("Weibull")) {
  parsed.function_type = "Weibull";
  Napi::Array args = nodeFunction.Get("Weibull").As<Napi::Array>();
  for (uint32_t i = 0; i < args.Length(); ++i) {
   parsed.arguments.push_back(args.Get(i));
  }
 } else if (nodeFunction.Has("periodicTest")) {
  parsed.function_type = "periodicTest";
  Napi::Array args = nodeFunction.Get("periodicTest").As<Napi::Array>();
  for (uint32_t i = 0; i < args.Length(); ++i) {
   parsed.arguments.push_back(args.Get(i));
  }
 } else {
  throw std::runtime_error("Unknown built-in function type");
 }
 
 return parsed; 
}

ParsedRandomDeviate ParseRandomDeviate(const Napi::Object& nodeDeviate) {
 ParsedRandomDeviate parsed;
 
 // Determine deviate type and extract arguments
 if (nodeDeviate.Has("uniformDeviate")) {
  parsed.deviate_type = "uniformDeviate";
  Napi::Array args = nodeDeviate.Get("uniformDeviate").As<Napi::Array>();
  for (uint32_t i = 0; i < args.Length(); ++i) {
   parsed.arguments.push_back(args.Get(i));
  }
 } else if (nodeDeviate.Has("normalDeviate")) {
  parsed.deviate_type = "normalDeviate";
  Napi::Array args = nodeDeviate.Get("normalDeviate").As<Napi::Array>();
  for (uint32_t i = 0; i < args.Length(); ++i) {
   parsed.arguments.push_back(args.Get(i));
  }
 } else if (nodeDeviate.Has("lognormalDeviate")) {
  parsed.deviate_type = "lognormalDeviate";
  Napi::Array args = nodeDeviate.Get("lognormalDeviate").As<Napi::Array>();
  for (uint32_t i = 0; i < args.Length(); ++i) {
   parsed.arguments.push_back(args.Get(i));
  }
 } else if (nodeDeviate.Has("gammaDeviate")) {
  parsed.deviate_type = "gammaDeviate";
  Napi::Array args = nodeDeviate.Get("gammaDeviate").As<Napi::Array>();
  for (uint32_t i = 0; i < args.Length(); ++i) {
   parsed.arguments.push_back(args.Get(i));
  }
 } else if (nodeDeviate.Has("betaDeviate")) {
  parsed.deviate_type = "betaDeviate";
  Napi::Array args = nodeDeviate.Get("betaDeviate").As<Napi::Array>();
  for (uint32_t i = 0; i < args.Length(); ++i) {
   parsed.arguments.push_back(args.Get(i));
  }
 } else if (nodeDeviate.Has("histogram")) {
  parsed.deviate_type = "histogram";
  Napi::Object histObj = nodeDeviate.Get("histogram").As<Napi::Object>();
  if (histObj.Has("base")) {
   parsed.arguments.push_back(histObj.Get("base"));
  }
  if (histObj.Has("bins")) {
   Napi::Array bins = histObj.Get("bins").As<Napi::Array>();
   for (uint32_t i = 0; i < bins.Length(); ++i) {
    parsed.arguments.push_back(bins.Get(i));
   }
  }
 } else {
  throw std::runtime_error("Unknown random deviate type");
 }
 
 return parsed;
}

ParsedNumericalOperation ParseNumericalOperation(const Napi::Object& nodeOperation) {
 ParsedNumericalOperation parsed;
 
 // Determine operation type and extract arguments
 std::vector<std::string> operations = {
  "neg", "add", "sub", "mul", "div", "pow", "sin", "cos", "tan", "log", "exp", "sqrt", "abs",
  "min", "max", "mean", "pi", "acos", "asin", "atan", "cosh", "sinh", "tanh", "log10", "mod",
  "ceil", "foor"
 };
 
 for (const auto& op : operations) {
  if (nodeOperation.Has(op.c_str())) {
   parsed.operation = op;
   Napi::Value opValue = nodeOperation.Get(op.c_str());
 
   if (opValue.IsArray()) {
    Napi::Array args = opValue.As<Napi::Array>();
    for (uint32_t i = 0; i < args.Length(); ++i) {
     parsed.arguments.push_back(args.Get(i));
    }
   } else {
    parsed.arguments.push_back(opValue);
   }
   break;
  }
 }
 
 if (parsed.operation.empty()) {
  throw std::runtime_error("Unknown numerical operation type");
 }
 
 return parsed;
}

// Complex Value type builder functions (legacy)
scram::mef::Expression* BuildParameterExpression(const ParsedParameter& parsed, scram::mef::Model* model, const ElementRegistry& 
registry) {
 auto param = std::make_unique<scram::mef::Parameter>(parsed.name);
 
 if (!parsed.description.empty()) {
  param->label(parsed.description);
 }
 
 if (!parsed.value.IsEmpty() && !parsed.value.IsUndefined() && !parsed.value.IsNull()) {
  scram::mef::Expression* expr = BuildExpression(parsed.value, model, registry);
  param->expression(expr);
 }
 
 if (!parsed.unit.empty()) {
  param->unit(ScramNodeUnit(parsed.unit));
 }
 
 scram::mef::Expression* ptr = param.get();
 model->Add(std::move(param));
 return ptr;
}

scram::mef::Expression* BuildBuiltInFunctionExpression(const ParsedBuiltInFunction& parsed, scram::mef::Model* model, const 
ElementRegistry& registry) {
 if (parsed.function_type == "exponential" && parsed.arguments.size() >= 2) {
  scram::mef::Expression* lambda = BuildExpression(parsed.arguments[0], model, registry);
  scram::mef::Expression* time   = BuildExpression(parsed.arguments[1], model, registry);
  (void)lambda; (void)time;
  auto expr = std::make_unique<scram::mef::ConstantExpression>(1.0);
  scram::mef::Expression* ptr = expr.get();
  model->Add(std::move(expr));
  return ptr;
 }
 auto expr = std::make_unique<scram::mef::ConstantExpression>(1.0);
 scram::mef::Expression* ptr = expr.get();
 model->Add(std::move(expr));
 return ptr;
}

scram::mef::Expression* BuildRandomDeviateExpression(const ParsedRandomDeviate& /*parsed*/, scram::mef::Model* model, const 
ElementRegistry& /*registry*/) {
 auto expr = std::make_unique<scram::mef::ConstantExpression>(1.0);
 scram::mef::Expression* ptr = expr.get();
 model->Add(std::move(expr));
 return ptr;
}

scram::mef::Expression* BuildNumericalOperationExpression(const ParsedNumericalOperation& parsed, scram::mef::Model* model, const 
ElementRegistry& registry) {
 if (parsed.operation == "add" && parsed.arguments.size() >= 2) {
  scram::mef::Expression* left  = BuildExpression(parsed.arguments[0], model, registry);
  scram::mef::Expression* right = BuildExpression(parsed.arguments[1], model, registry);
  (void)left; (void)right;
  auto expr = std::make_unique<scram::mef::ConstantExpression>(1.0);
  scram::mef::Expression* ptr = expr.get();
  model->Add(std::move(expr));
  return ptr;
 } else if (parsed.operation == "mul" && parsed.arguments.size() >= 2) {
  scram::mef::Expression* left  = BuildExpression(parsed.arguments[0], model, registry);
  scram::mef::Expression* right = BuildExpression(parsed.arguments[1], model, registry);
  (void)left; (void)right;
  auto expr = std::make_unique<scram::mef::ConstantExpression>(1.0);
  scram::mef::Expression* ptr = expr.get();
  model->Add(std::move(expr));
  return ptr;
 }
 auto expr = std::make_unique<scram::mef::ConstantExpression>(1.0);
 scram::mef::Expression* ptr = expr.get();
 model->Add(std::move(expr));
 return ptr;
}

Updated on 2026-01-09 at 21:59:13 +0000