<commit_before>package org.openlca.io.openepd;

import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.Objects;
import java.util.OptionalDouble;
import java.util.function.BiPredicate;
import java.util.function.Function;

import org.openlca.core.model.Actor;
import org.openlca.core.model.Epd;
import org.openlca.core.model.EpdProduct;
import org.openlca.core.model.FlowProperty;
import org.openlca.core.model.RefEntity;
import org.openlca.core.model.Source;
import org.openlca.core.model.Unit;
import org.openlca.util.Categories;
import org.openlca.util.Pair;
import org.openlca.util.Strings;

/**
 * A utility class for converting openLCA EPDs into the openEPD format.
 */
public final class EpdConverter {

	private EpdConverter() {
	}

	/**
	 * Checks if the given EPD model can be converted into on openEPD document.
	 */
	public static Validation validate(Epd epd) {
		if (epd == null)
			return Validation.error("The EPD is empty.");
		if (epd.product == null || epd.product.flow == null)
			return Validation.error("The EPD has no product.");
		if (epd.product.unit == null)
			return Validation.error("The EPD has no declared unit");
		if (epd.product.amount == 0)
			return Validation.error("The product amount is 0.");

		for (var module : epd.modules) {
			var result = module.result;
			if (result == null)
				return Validation.error("The EPD contains modules without results.");
			if (result.impactMethod == null)
				return Validation.error(
					"The EPD contains module results without links to LCIA methods.");
			if (Strings.nullOrEmpty(result.impactMethod.code))
				return Validation.error(
					"The EPD contains links to LCIA methods without mapping codes.");
			for (var impact : result.impactResults) {
				if (impact.indicator == null
					|| Strings.nullOrEmpty(impact.indicator.code))
					return Validation.error(
						"The EPD contains links to LCIA categories without mapping codes.");
			}
		}
		return Validation.ok();
	}

	public static EpdDoc toEpdDoc(Epd epd) {
		var doc = new EpdDoc();
		doc.isPrivate = true;
		doc.version = 1;
		doc.productName = epd.name;
		doc.lcaDiscussion = epd.description;

		// declared unit
		if (epd.product != null && epd.product.unit != null) {
			doc.declaredUnit = new EpdQuantity(
				epd.product.amount, epd.product.unit.name);
		}
		var mass = massInKgOf(epd.product);
		if (mass.isPresent()) {
			doc.kgPerDeclaredUnit = new EpdQuantity(
				mass.getAsDouble(), "kg");
		}

		// category
		if (epd.category != null) {
			var path = Categories.path(epd.category);
			if (!path.isEmpty()) {
				doc.productClasses.add(
					Pair.of("io.cqd.ec3", String.join(" >> ", path)));
			}
		}

		doc.manufacturer = toOrg(epd.manufacturer);
		doc.verifier = toOrg(epd.verifier);
		doc.programOperator = toOrg(epd.programOperator);
		doc.pcr = toPcr(epd.pcr);

		doc.impactResults.addAll(resultsOf(epd));
		return doc;
	}

	public static EpdOrg toOrg(Actor actor) {
		if (actor == null)
			return null;
		var org = new EpdOrg();
		org.name = actor.name;
		org.webDomain = actor.website;
		return org;
	}

	public static EpdPcr toPcr(Source source) {
		if (source == null)
			return null;
		var pcr = new EpdPcr();
		pcr.id = source.refId;
		pcr.name = source.name;
		return pcr;
	}

	public static Collection<EpdImpactResult> resultsOf(Epd epd) {
		var map = new HashMap<String, EpdImpactResult>();
		for (var mod : epd.modules) {
			var result = mod.result;
			if (result == null
				|| result.impactMethod == null
				|| Strings.nullOrEmpty(result.impactMethod.code))
				continue;
			var docResult = map.computeIfAbsent(
				result.impactMethod.code,
				code -> new EpdImpactResult(code, new ArrayList<>()));

			for (var impact : result.impactResults) {
				if (impact.indicator == null
					|| Strings.nullOrEmpty(impact.indicator.code))
					continue;
				var code = impact.indicator.code;
				EpdIndicatorResult docImpact = null;
				for (var i : docResult.indicatorResults()) {
					if (Objects.equals(code, i.indicator())) {
						docImpact = i;
						break;
					}
				}
				if (docImpact == null) {
					docImpact = new EpdIndicatorResult(code, new ArrayList<>());
					docResult.indicatorResults().add(docImpact);
				}
				var value = EpdMeasurement.of(
					impact.amount, impact.indicator.referenceUnit);
				docImpact.values().add(new EpdScopeValue(mod.name, value));
			}
		}
		return map.values();
	}

	/**
	 * Tries to infer the mass in kilograms per declared unit.
	 */
	public static OptionalDouble massInKgOf(EpdProduct product) {
		if (product == null
			|| product.flow == null
			|| product.property == null
			|| product.unit == null)
			return OptionalDouble.empty();

		BiPredicate<String, RefEntity> matches = (name, entity) -> {
			if (entity == null || entity.name == null)
				return false;
			var eName = entity.name.trim();
			return eName.equalsIgnoreCase(name);
		};

		Function<FlowProperty, Unit> getKg = prop -> {
			if (prop.unitGroup == null)
				return null;<commit_msg>apply the module multiplier in the EPD export<commit_after>package org.openlca.io.openepd;

import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.Objects;
import java.util.OptionalDouble;
import java.util.function.BiPredicate;
import java.util.function.Function;

import org.openlca.core.model.Actor;
import org.openlca.core.model.Epd;
import org.openlca.core.model.EpdProduct;
import org.openlca.core.model.FlowProperty;
import org.openlca.core.model.RefEntity;
import org.openlca.core.model.Source;
import org.openlca.core.model.Unit;
import org.openlca.util.Categories;
import org.openlca.util.Pair;
import org.openlca.util.Strings;

/**
 * A utility class for converting openLCA EPDs into the openEPD format.
 */
public final class EpdConverter {

	private EpdConverter() {
	}

	/**
	 * Checks if the given EPD model can be converted into on openEPD document.
	 */
	public static Validation validate(Epd epd) {
		if (epd == null)
			return Validation.error("The EPD is empty.");
		if (epd.product == null || epd.product.flow == null)
			return Validation.error("The EPD has no product.");
		if (epd.product.unit == null)
			return Validation.error("The EPD has no declared unit");
		if (epd.product.amount == 0)
			return Validation.error("The product amount is 0.");

		for (var module : epd.modules) {
			var result = module.result;
			if (result == null)
				return Validation.error("The EPD contains modules without results.");
			if (result.impactMethod == null)
				return Validation.error(
					"The EPD contains module results without links to LCIA methods.");
			if (Strings.nullOrEmpty(result.impactMethod.code))
				return Validation.error(
					"The EPD contains links to LCIA methods without mapping codes.");
			for (var impact : result.impactResults) {
				if (impact.indicator == null
					|| Strings.nullOrEmpty(impact.indicator.code))
					return Validation.error(
						"The EPD contains links to LCIA categories without mapping codes.");
			}
		}
		return Validation.ok();
	}

	public static EpdDoc toEpdDoc(Epd epd) {
		var doc = new EpdDoc();
		doc.isPrivate = true;
		doc.version = 1;
		doc.productName = epd.name;
		doc.lcaDiscussion = epd.description;

		// declared unit
		if (epd.product != null && epd.product.unit != null) {
			doc.declaredUnit = new EpdQuantity(
				epd.product.amount, epd.product.unit.name);
		}
		var mass = massInKgOf(epd.product);
		if (mass.isPresent()) {
			doc.kgPerDeclaredUnit = new EpdQuantity(
				mass.getAsDouble(), "kg");
		}

		// category
		if (epd.category != null) {
			var path = Categories.path(epd.category);
			if (!path.isEmpty()) {
				doc.productClasses.add(
					Pair.of("io.cqd.ec3", String.join(" >> ", path)));
			}
		}

		doc.manufacturer = toOrg(epd.manufacturer);
		doc.verifier = toOrg(epd.verifier);
		doc.programOperator = toOrg(epd.programOperator);
		doc.pcr = toPcr(epd.pcr);

		doc.impactResults.addAll(resultsOf(epd));
		return doc;
	}

	public static EpdOrg toOrg(Actor actor) {
		if (actor == null)
			return null;
		var org = new EpdOrg();
		org.name = actor.name;
		org.webDomain = actor.website;
		return org;
	}

	public static EpdPcr toPcr(Source source) {
		if (source == null)
			return null;
		var pcr = new EpdPcr();
		pcr.id = source.refId;
		pcr.name = source.name;
		return pcr;
	}

	public static Collection<EpdImpactResult> resultsOf(Epd epd) {
		var map = new HashMap<String, EpdImpactResult>();
		for (var mod : epd.modules) {
			var result = mod.result;
			if (result == null
				|| result.impactMethod == null
				|| Strings.nullOrEmpty(result.impactMethod.code))
				continue;
			var docResult = map.computeIfAbsent(
				result.impactMethod.code,
				code -> new EpdImpactResult(code, new ArrayList<>()));

			for (var impact : result.impactResults) {
				if (impact.indicator == null
					|| Strings.nullOrEmpty(impact.indicator.code))
					continue;
				var code = impact.indicator.code;
				EpdIndicatorResult docImpact = null;
				for (var i : docResult.indicatorResults()) {
					if (Objects.equals(code, i.indicator())) {
						docImpact = i;
						break;
					}
				}
				if (docImpact == null) {
					docImpact = new EpdIndicatorResult(code, new ArrayList<>());
					docResult.indicatorResults().add(docImpact);
				}
				var value = EpdMeasurement.of(
					mod.multiplier * impact.amount,
					impact.indicator.referenceUnit);
				docImpact.values().add(new EpdScopeValue(mod.name, value));
			}
		}
		return map.values();
	}

	/**
	 * Tries to infer the mass in kilograms per declared unit.
	 */
	public static OptionalDouble massInKgOf(EpdProduct product) {
		if (product == null
			|| product.flow == null
			|| product.property == null
			|| product.unit == null)
			return OptionalDouble.empty();

		BiPredicate<String, RefEntity> matches = (name, entity) -> {
			if (entity == null || entity.name == null)
				return false;
			var eName = entity.name.trim();
			return eName.equalsIgnoreCase(name);
		};

		Function<FlowProperty, Unit> getKg = prop -> {
			if (prop.unitGroup == null)
				return null;
{'fraction_non_alphanumeric': 0.12836767036450078, 'fraction_numerical': 0.000792393026941363, 'mean_word_length': 3.4875555555555557, 'pattern_counts': {'":': 0, '<': 15, '<?xml version=': 0, '>': 25, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '7058428', 'n_tokens_mistral': 3909, 'n_tokens_neox': 3309, 'n_words': 825}