private String transformCategoryIndexesToValues(LightGBMNode node) {
        int featureIndex = node.getSplit_feature();
        boolean isIntegerFeature = categoricalIntegerFeatures.getOrDefault(featureIndex, false);

        return Arrays.stream(node.getThreshold().split("\\|\\|"))
                .map(index -> {
                    String value = transformCategoryIndexToValue(featureIndex, index);
                    return isIntegerFeature ? value : "\"" + value + "\"";
                })
                .collect(Collectors.joining(","));
    }
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