private String nodeToRankingExpression(LightGBMNode node) {
        if (node.isLeaf()) {
            return Double.toString(node.getLeaf_value());
        } else {
            String condition;
            String feature = featureNames.get(node.getSplit_feature());
            if (node.getDecision_type().equals("==")) {
                String values = transformCategoryIndexesToValues(node);
                if (node.isDefault_left()) {  // means go left (true) when isNan
                    condition = "isNan(" + feature + ") || (" + feature + " in [ " + values + "])";
                } else {
                    condition = feature + " in [" + values + "]";
                }
            } else {  // assumption: all other decision types are <=
                double value = Double.parseDouble(node.getThreshold());
                if (node.isDefault_left()) {
                    condition = "!(" + feature + " >= " + value + ")";
                } else {
                    condition = feature + " < " + value;
                }
            }
            String left = nodeToRankingExpression(node.getLeft_child());
            String right = nodeToRankingExpression(node.getRight_child());
            return "if (" + condition + ", " + left + ", " + right + ")";
        }
    }
As primary method, we try to determine which are categorical features from the "features_infos" structure
 and then later use the "pandas_categorical" structure to determine the categoryValues.
 If we can't find the categorical features in the "features_infos" structure (models exported without
 "categorical_feature" parameter, or older versions of lightgbm), we fall back to traversing the
 decision tree looking for categorical decisions ('==') and use that to determine which features are categorical.
 Due to lack of documentation, we can't _know_ that the "feature_infos" and "pandas_categorical" will always
 be of the same length and in same order, but we have tried to verify this and not managed to find a case where they are not.
 And it is at least a lot better than the fallback, where we know some features can be
 treated incorrectly.
model-integration/src/main/java/ai/vespa/rankingexpression/importer/lightgbm/LightGBMParser.java