889 lines
40 KiB
PHP
889 lines
40 KiB
PHP
<?php
|
|
|
|
namespace App\Services\Activity;
|
|
|
|
use App\Models\KegiatanMitigasi;
|
|
use App\Models\EmissionFactorReference;
|
|
use App\Repositories\ActivityDataRepository;
|
|
use App\Repositories\EmissionFactorRepository;
|
|
use App\Repositories\EmissionReductionRepository;
|
|
use App\Helpers\FormulaEvaluator;
|
|
use Illuminate\Support\Facades\DB;
|
|
|
|
class FormMitigasiService
|
|
{
|
|
protected array $unitCategoryMap = [
|
|
'liter' => 'volume',
|
|
'kiloliter' => 'volume',
|
|
'm3' => 'volume',
|
|
'mmbtu' => 'energy',
|
|
'mwh' => 'energy',
|
|
'boe' => 'energy',
|
|
'ton' => 'mass',
|
|
'kilogram' => 'mass',
|
|
'giga gram' => 'mass',
|
|
];
|
|
|
|
protected array $unitConversionFactor = [
|
|
'liter' => 0.000000001,
|
|
'kiloliter' => 0.001,
|
|
'm3' => 0.001,
|
|
'mmbtu' => 0.00105587,
|
|
'mwh' => 0.0036,
|
|
'boe' => 0.0058,
|
|
'ton' => 0.001,
|
|
'kilogram' => 0.000001,
|
|
'giga gram' => 1.0,
|
|
];
|
|
|
|
public function __construct(
|
|
protected ActivityDataRepository $adRepo,
|
|
protected EmissionFactorRepository $efRepo,
|
|
protected EmissionReductionRepository $erRepo,
|
|
protected FormulaEvaluator $evaluator
|
|
) {}
|
|
|
|
protected function cast($v)
|
|
{
|
|
if (is_null($v)) return 0;
|
|
if (is_numeric($v)) return $v + 0;
|
|
if (is_string($v) && is_numeric(trim($v))) return trim($v) + 0;
|
|
return $v;
|
|
}
|
|
|
|
protected function selectReduction($reductions, $adValues, $typeEmissionReduction = null)
|
|
{
|
|
if (!$reductions) return null;
|
|
if (is_array($reductions)) {
|
|
$array = $reductions;
|
|
} elseif (method_exists($reductions, 'all')) {
|
|
$array = $reductions->all();
|
|
} else {
|
|
$array = (array)$reductions;
|
|
}
|
|
|
|
if (count($array) === 0) return null;
|
|
if (count($array) === 1) return $array[0];
|
|
|
|
if (!empty($adValues['AD1'])) {
|
|
$match = strtolower(trim($adValues['AD1']));
|
|
foreach ($array as $reduction) {
|
|
if($reduction['type_emission_reduction'] == 1 && $match == strtolower("PJU sebelumnya terkoneksi dengan lgrid")) {
|
|
return $reduction;
|
|
} else if ($reduction['type_emission_reduction'] == 2 && $match == strtolower("PJU sebelumnya terkoneksi dengan PLTD")) {
|
|
return $reduction;
|
|
}
|
|
}
|
|
}
|
|
|
|
if ($typeEmissionReduction !== null) {
|
|
foreach ($array as $reduction) {
|
|
$typeEr = is_array($reduction)
|
|
? ($reduction['type_emission_reduction'] ?? null)
|
|
: ($reduction->type_emission_reduction ?? null);
|
|
if ((int)$typeEr === (int)$typeEmissionReduction) {
|
|
return $reduction;
|
|
}
|
|
}
|
|
}
|
|
|
|
return $array[0];
|
|
}
|
|
|
|
public function calculateAndPersist(string $mitigationId, KegiatanMitigasi $km): void
|
|
{
|
|
DB::transaction(function() use ($mitigationId, $km) {
|
|
logger()->info('--- calculateAndPersist start ---');
|
|
$adForms = $this->adRepo->getByMitigation($km->id);
|
|
|
|
$adValues = [];
|
|
$adArrays = [];
|
|
$adUnits = [];
|
|
$labelMap = [];
|
|
$typeEmissionReduction = null;
|
|
foreach ($adForms as $item) {
|
|
$key = 'AD' . $item->sequence;
|
|
|
|
$rawArray = $item->values_array ?: [];
|
|
if (!is_array($rawArray)) $rawArray = [];
|
|
if (empty($rawArray) || (count($rawArray) == 1 && $rawArray[0] === null)) {
|
|
$rawArray = [$item->value];
|
|
}
|
|
|
|
$values = array_map(fn($x) => $this->cast($x), $rawArray);
|
|
if (empty($values)) $values = [0];
|
|
|
|
$adArrays[$key] = $values;
|
|
$adValues[$key] = (is_array($values) && count($values) > 0) ? end($values) : 0;
|
|
$adUnits[$key] = strtolower($item->unit ?? '');
|
|
|
|
if (property_exists($item, 'label') && $item->label) {
|
|
$labelMap[$item->label] = $key;
|
|
}
|
|
if (!$typeEmissionReduction && !empty($item->type_emission_reduction)) {
|
|
$typeEmissionReduction = $item->type_emission_reduction;
|
|
}
|
|
}
|
|
logger()->info('ADValues', $adValues);
|
|
logger()->info('ADArrays', $adArrays);
|
|
logger()->info('ADUnits', $adUnits);
|
|
logger()->info('LabelMap', $labelMap);
|
|
logger()->info('TypeEmissionReduction', ['value' => $typeEmissionReduction]);
|
|
|
|
// Build REF_xxx mapping
|
|
$contextMaps = DB::table('activity.emission_reduction_context_map')
|
|
->where('mitigation_id', $mitigationId)
|
|
->get();
|
|
|
|
$refParams = [];
|
|
$refContext = [];
|
|
foreach ($contextMaps as $map) {
|
|
$adKey = 'AD'.$map->ad_sequence;
|
|
$val = $adValues[$adKey] ?? null;
|
|
|
|
$refParams[$map->ref_key] = [
|
|
'param' => $map->param,
|
|
'unit' => $map->unit,
|
|
'context' => [ $map->context_field => $adKey ],
|
|
];
|
|
if (!isset($refContext[$map->ref_key][$map->context_field]) || $refContext[$map->ref_key][$map->context_field] === null) {
|
|
if ($val !== null) {
|
|
$refContext[$map->ref_key][$map->context_field] = $val;
|
|
}
|
|
}
|
|
logger()->info('refContext build', [
|
|
'ref_key' => $map->ref_key,
|
|
'context_field' => $map->context_field,
|
|
'ad_seq' => $map->ad_sequence,
|
|
'value' => $val
|
|
]);
|
|
}
|
|
logger()->info('refParams', $refParams);
|
|
logger()->info('refContext', $refContext);
|
|
|
|
$reductions = $this->erRepo->findAllByMitigation($mitigationId);
|
|
if (is_object($reductions) && method_exists($reductions, 'toArray')) {
|
|
logger()->info('DEBUG: REDUCTIONS', $reductions->toArray());
|
|
} else {
|
|
logger()->info('DEBUG: REDUCTIONS', (array) $reductions);
|
|
}
|
|
logger()->info('REDUCTIONS RAW TYPE', ['type' => gettype($reductions), 'class' => is_object($reductions) ? get_class($reductions) : null]);
|
|
|
|
// --- PEMILIHAN REDUCTION PAKE LOGIC BARU ---
|
|
$chosenReduction = $this->selectReduction($reductions, $adValues, $typeEmissionReduction);
|
|
if (!$chosenReduction) {
|
|
logger()->error('REDUCTIONS EMPTY', [
|
|
'reductions' => is_object($reductions) ? $reductions->toArray() : $reductions
|
|
]);
|
|
throw new \Exception('Emission reduction formula not found.');
|
|
}
|
|
|
|
$formula = ltrim(is_array($chosenReduction) ? $chosenReduction['emission_reduction'] : $chosenReduction->emission_reduction, '=');
|
|
logger()->info('FORMULA RAW', ['formula' => $formula]);
|
|
logger()->info('FORMULA AFTER REF_CF', ['formula' => $formula]);
|
|
|
|
$result = $this->evaluateEmissionReductionFormula(
|
|
$formula,
|
|
$adValues,
|
|
$refParams,
|
|
$refContext,
|
|
$adArrays,
|
|
$adUnits,
|
|
$labelMap
|
|
);
|
|
logger()->info('HASIL PERHITUNGAN', ['emission_factor' => $result]);
|
|
$km->update(['emission_factor' => $result]);
|
|
logger()->info('--- calculateAndPersist DONE ---');
|
|
});
|
|
}
|
|
|
|
protected function getRefArray($param, $unit, $context, $adArrays, $adKeySigma)
|
|
{
|
|
$arr = [];
|
|
$adSource = $adArrays[$adKeySigma] ?? [];
|
|
foreach ($adSource as $i => $v) {
|
|
$q = \App\Models\EmissionFactorReference::where('param', $param)
|
|
->where('unit', $unit);
|
|
foreach ($context as $field => $adKey) {
|
|
$q->where('keterangan', $v);
|
|
}
|
|
$arr[] = $q->value('value') ?? 0;
|
|
}
|
|
return $arr;
|
|
}
|
|
|
|
/**
|
|
* Parse Σi=ADx*ADy menjadi sum produk per baris.
|
|
* Contoh: Σi=AD1*AD2 → (AD1[0]*AD2[0]) + (AD1[1]*AD2[1]) + ...
|
|
* @param string $formula
|
|
* @param array $adArrays
|
|
* @param array $adValues
|
|
* @param string $debugTag
|
|
* @return string
|
|
*/
|
|
public function parseSigmaSumProduct($formula, $adArrays, $adValues, $refParams, $refContext, $debugTag = '')
|
|
{
|
|
logger()->info("Masuk $debugTag", ['formula' => $formula]);
|
|
|
|
return preg_replace_callback(
|
|
'/Σi=(([A-Za-z0-9_\.]+|\Σi=[A-Za-z0-9_\.]+)(\s*[\*\/]\s*([A-Za-z0-9_\.]+|\Σi=[A-Za-z0-9_\.]+))+)/u',
|
|
function ($m) use ($adArrays, $adValues, $refParams, $refContext) {
|
|
$raw = $m[1];
|
|
$parts = preg_split('/\s*[\*\/]\s*/', $raw);
|
|
preg_match_all('/[\*\/]/', $raw, $opsRaw);
|
|
$ops = $opsRaw[0];
|
|
|
|
$arrays = [];
|
|
$maxRows = 1;
|
|
|
|
foreach ($parts as $p) {
|
|
$p = preg_replace('/^Σi=/', '', $p);
|
|
|
|
// AD array
|
|
if (isset($adArrays[$p]) && is_array($adArrays[$p]) && count($adArrays[$p]) > 1) {
|
|
$arr = array_map('floatval', $adArrays[$p]);
|
|
}
|
|
// REF array (array dari replaceREF as_array true)
|
|
else if (preg_match('/^REF_([A-Z0-9_]+)$/i', $p, $mm)) {
|
|
$refKey = $mm[1];
|
|
$arr = [0];
|
|
if (isset($refParams[$refKey])) {
|
|
$rawRef = $this->replaceREF("REF_$refKey", $refParams, $refContext, $adArrays, $adValues, [], '', true);
|
|
if (is_string($rawRef)) $rawRef = explode(',', $rawRef);
|
|
if (!is_array($rawRef)) $rawRef = [$rawRef];
|
|
$arr = array_map('floatval', $rawRef);
|
|
if (!$arr || count($arr) === 0) $arr = [0];
|
|
}
|
|
}
|
|
// Scalar (AD/REF satuan, angka, konstanta)
|
|
else if (isset($adArrays[$p]) && count($adArrays[$p]) === 1) {
|
|
$arr = [floatval($adArrays[$p][0])];
|
|
} else if (isset($adValues[$p])) {
|
|
$arr = [floatval($adValues[$p])];
|
|
} else if (is_numeric($p)) {
|
|
$arr = [floatval($p)];
|
|
} else {
|
|
$arr = [0];
|
|
}
|
|
|
|
$arrays[] = $arr;
|
|
$maxRows = max($maxRows, count($arr));
|
|
}
|
|
|
|
// Expand scalar ke semua baris jika perlu
|
|
foreach ($arrays as $i => $arr) {
|
|
if (count($arr) < $maxRows) {
|
|
$scalar = isset($arr[0]) ? $arr[0] : 0;
|
|
$arrays[$i] = array_fill(0, $maxRows, $scalar);
|
|
}
|
|
}
|
|
|
|
// SUMPRODUCT manual per baris
|
|
$sum = 0;
|
|
for ($i = 0; $i < $maxRows; $i++) {
|
|
$val = $arrays[0][$i];
|
|
for ($j = 1; $j < count($arrays); $j++) {
|
|
$op = $ops[$j - 1] ?? '*';
|
|
$v = $arrays[$j][$i];
|
|
if ($op == '*') $val *= $v;
|
|
else $val /= ($v != 0 ? $v : 1);
|
|
}
|
|
$sum += $val;
|
|
}
|
|
return (string)$sum;
|
|
},
|
|
$formula
|
|
);
|
|
}
|
|
|
|
|
|
/**
|
|
* Ganti semua blok SUMPRODUCT( … ) dengan hasil angka (Excel-like)
|
|
* - Support inner expression rumit, nested, pow, persen, kurung
|
|
* - Support AD/REF array, scalar, mixed, otomatis looping max row
|
|
* - Support nested SUMPRODUCT di dalam inner (rekursif)
|
|
* - Tidak support Σi=, Σj= (sigma): Khusus SUMPRODUCT saja
|
|
* @param string $formula
|
|
* @param array $adArrays
|
|
* @param array $adValues
|
|
* @param array $refParams
|
|
* @param array $refContext
|
|
* @return string
|
|
*/
|
|
public function parseSumproductExcel($formula, $adArrays, $adValues, $refParams, $refContext)
|
|
{
|
|
// Selama masih ada SUMPRODUCT(
|
|
while (preg_match('/SUMPRODUCT\s*\(/i', $formula)) {
|
|
$formula = preg_replace_callback(
|
|
'/SUMPRODUCT\s*\(([^()]*(?:\((?:[^()]++|(?1))*\)[^()]*)*)\)/i',
|
|
function ($m) use ($adArrays, $adValues, $refParams, $refContext) {
|
|
$inner = $m[1];
|
|
|
|
// 1. Cari semua variabel array
|
|
preg_match_all('/\b(AD[0-9]+|REF_[A-Z0-9_]+)\b/', $inner, $matches);
|
|
$vars = array_unique($matches[1]);
|
|
|
|
// 2. Dapatkan max rows (array terpanjang)
|
|
$maxRows = 1;
|
|
foreach ($vars as $v) {
|
|
if (isset($adArrays[$v]) && is_array($adArrays[$v]))
|
|
$maxRows = max($maxRows, count($adArrays[$v]));
|
|
}
|
|
|
|
$sum = 0;
|
|
for ($i = 0; $i < $maxRows; $i++) {
|
|
// Ganti AD
|
|
$expr = preg_replace_callback('/AD([0-9]+)/', function($mm) use ($adArrays, $adValues, $i) {
|
|
$adKey = "AD".$mm[1];
|
|
return isset($adArrays[$adKey][$i]) ? $adArrays[$adKey][$i] :
|
|
(isset($adArrays[$adKey][0]) ? $adArrays[$adKey][0] : (isset($adValues[$adKey]) ? $adValues[$adKey] : 0));
|
|
}, $inner);
|
|
|
|
// Ganti REF
|
|
$expr = preg_replace_callback('/REF_([A-Z0-9_]+)/', function($mm) use ($refParams, $refContext, $adArrays, $adValues, $i) {
|
|
$key = $mm[1];
|
|
if (!isset($refParams[$key])) return 0;
|
|
$map = $refParams[$key];
|
|
$contextKey = null;
|
|
foreach ($map['context'] as $field => $adKeyC) {
|
|
$contextKey = $adKeyC;
|
|
}
|
|
$adVal = $contextKey && isset($adArrays[$contextKey][$i]) ? $adArrays[$contextKey][$i] : ($adValues[$contextKey] ?? null);
|
|
$q = \App\Models\EmissionFactorReference::where('param', $map['param'])
|
|
->where('unit', $map['unit'])
|
|
->where('keterangan', $adVal);
|
|
$val = $q->value('value');
|
|
return $val ?? 0;
|
|
}, $expr);
|
|
|
|
// Ganti persen & pow
|
|
$expr = preg_replace_callback('/(\d+(?:\.\d+)?)%/', fn($mm) => $mm[1]/100, $expr);
|
|
$expr = preg_replace('/([0-9.Ee+\-]+)\s*\^\s*([0-9.Ee+\-]+)/', 'pow($1,$2)', $expr);
|
|
|
|
// Eval tiap baris, tambahkan ke sum
|
|
$expr = str_replace('CONST_', '', $expr);
|
|
|
|
// Special case
|
|
if (strpos($expr, 'SUM(') !== false) {
|
|
foreach (['AD3', 'AD4'] as $adKey) {
|
|
if (strpos($expr, "SUM($adKey)") !== false && isset($adArrays[$adKey])) {
|
|
$expr = str_replace("SUM($adKey)", array_sum($adArrays[$adKey]), $expr);
|
|
}
|
|
}
|
|
// Tambah di sini:
|
|
$expr = preg_replace('/SUM\(\s*([0-9\.]+)\s*\)/', '$1', $expr);
|
|
|
|
// Opsional: hapus SUM lain
|
|
$expr = str_replace('SUM', '', $expr);
|
|
}
|
|
|
|
try {
|
|
$sum += eval('return ' . $expr . ';');
|
|
} catch (\Throwable $e) {
|
|
logger()->error("[SUMPRODUCT] Eval row $i error: $expr | " . $e->getMessage());
|
|
}
|
|
}
|
|
return (string)$sum;
|
|
},
|
|
$formula
|
|
);
|
|
}
|
|
return $formula;
|
|
}
|
|
|
|
public function isSigmaArrayFormula($formula, $adArrays, $refParams) {
|
|
if (strpos($formula, 'Σi=') === false) return false;
|
|
|
|
// Ambil semua yang dalam Σi=(....)
|
|
if (preg_match('/Σi=\(([^)]+)\)/', $formula, $sumproductPart)) {
|
|
// Semua variabel AD/REF/CONST
|
|
preg_match_all('/\b(AD[0-9]+|REF_[A-Z0-9_]+)\b/', $sumproductPart[1], $vars);
|
|
foreach ($vars[1] as $var) {
|
|
if (isset($adArrays[$var]) && is_array($adArrays[$var]) && count($adArrays[$var]) > 1) return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* Ganti Σi=ADxx dengan hasil sum seluruh array ADxx.
|
|
* Contoh: Σi=AD1 → 19 (jika [10,5,4])
|
|
*/
|
|
public function parseSigmaSumOnly($formula, $adArrays, $adValues, $refParams = [], $refContext = [], $debugTag = '')
|
|
{
|
|
logger()->info("Masuk $debugTag", ['formula' => $formula]);
|
|
|
|
$self = $this; // Closure binding
|
|
|
|
// Σi=ADxx atau Σi=REF_xxx
|
|
$formula = preg_replace_callback('/[Σ∑]i\s*=?\s*([A-Za-z0-9_]+)/u', function ($m) use ($adArrays, $adValues, $refParams, $refContext, $self) {
|
|
$key = $m[1];
|
|
// Handle AD array
|
|
if (isset($adArrays[$key])) {
|
|
$arr = $adArrays[$key];
|
|
if (count($arr) === 0 && isset($adValues[$key])) $arr = [$adValues[$key]];
|
|
$sum = array_sum(array_filter($arr, 'is_numeric'));
|
|
logger()->info("parseSigmaSumOnly: SUM for $key", ['array' => $arr, 'sum' => $sum]);
|
|
return $sum;
|
|
}
|
|
// Handle REF_xxx (ambil array REF, lalu sum)
|
|
elseif (preg_match('/^REF_([A-Z0-9_]+)$/i', $key, $mm)) {
|
|
$refKey = $mm[1];
|
|
// Pakai replaceREF tapi as_array=true!
|
|
$refArr = $self->replaceREF(
|
|
"REF_$refKey",
|
|
$refParams,
|
|
$refContext,
|
|
$adArrays,
|
|
$adValues,
|
|
[], // adUnits
|
|
'parseSigmaSumOnly_REF',
|
|
true // AS ARRAY
|
|
);
|
|
if (is_string($refArr)) {
|
|
$refArr = explode(',', $refArr);
|
|
}
|
|
if (!is_array($refArr)) $refArr = [$refArr];
|
|
$arr = array_map('floatval', $refArr);
|
|
if (!$arr || count($arr) === 0) $arr = [0];
|
|
$sum = array_sum(array_filter($refArr, 'is_numeric'));
|
|
logger()->info("parseSigmaSumOnly: SUM for REF $key", ['array' => $refArr, 'sum' => $sum]);
|
|
return $sum;
|
|
}
|
|
// Scalar fallback
|
|
elseif (isset($adValues[$key]) && is_numeric($adValues[$key])) {
|
|
return $adValues[$key];
|
|
}
|
|
return 0;
|
|
}, $formula);
|
|
|
|
return $formula;
|
|
}
|
|
|
|
/**
|
|
* Evaluate the emission reduction formula with given parameters.
|
|
*/
|
|
// public function evaluateEmissionReductionFormula($formula, $adValues, $refParams, $refContext, $adArrays, $adUnits = [], $labelMap = []) {
|
|
// logger()->info("evaluateEmissionReductionFormula: START", ['formula' => $formula]);
|
|
|
|
// if ($this->isSigmaArrayFormula($formula, $adArrays, $refParams)) {
|
|
// // Hitung maxRows DARI variabel dalam Σi
|
|
// preg_match('/Σi=\(([^)]+)\)/', $formula, $m);
|
|
// preg_match_all('/\b(AD[0-9]+|REF_[A-Z0-9_]+)\b/', $m[1] ?? '', $vars);
|
|
// $maxRows = 1;
|
|
// foreach ($vars[1] as $var) {
|
|
// if (isset($adArrays[$var]) && is_array($adArrays[$var]))
|
|
// $maxRows = max($maxRows, count($adArrays[$var]));
|
|
// }
|
|
|
|
// $resultRows = [];
|
|
// for ($i = 0; $i < $maxRows; $i++) {
|
|
// // Build per baris
|
|
// $adValuesRow = [];
|
|
// $adArraysRow = [];
|
|
// foreach ($adArrays as $key => $arr) {
|
|
// $adArraysRow[$key] = [isset($arr[$i]) ? $arr[$i] : (isset($arr[0]) ? $arr[0] : 0)];
|
|
// $adValuesRow[$key] = isset($arr[$i]) ? $arr[$i] : (isset($arr[0]) ? $arr[0] : 0);
|
|
// }
|
|
// $f = $this->parseSigmaSumProduct($formula, $adArraysRow, $adValuesRow, $refParams, $refContext, "row-$i-parseSigmaSumProduct");
|
|
// $f = $this->parseSigmaSumOnly($f, $adArraysRow, $adValuesRow, $refParams, $refContext, "row-$i-parseSigmaSumOnly");
|
|
// $f = $this->replaceADDefault($f, $adArraysRow, $adValuesRow, "row-$i-replaceADDefault");
|
|
// $f = $this->replaceREF($f, $refParams, $refContext, $adArraysRow, $adValuesRow, $adUnits, "row-$i-replaceREF", false);
|
|
|
|
// $f = preg_replace_callback('/(\d+(?:\.\d+)?)%/', fn($mm) => $mm[1] / 100, $f);
|
|
// $f = preg_replace('/([A-Za-z0-9_\.]+)\^([0-9]+)/', 'pow($1,$2)', $f);
|
|
|
|
// // Clean up kurung, biar ga unmatched
|
|
// $f = preg_replace('/\)\)+$/', ')', $f);
|
|
// $open = substr_count($f, '('); $close = substr_count($f, ')');
|
|
// if ($open > $close) $f .= str_repeat(')', $open - $close);
|
|
// if ($close > $open) $f = str_repeat('(', $close - $open) . $f;
|
|
|
|
// // Remove sigma
|
|
// $f = preg_replace('/^(b")?([Σ∑Î]i=)/u', '', $f);
|
|
// logger()->info("BARIS $i - Formula Final Eval", ['formula' => $f]);
|
|
// try {
|
|
// $res = eval('return ' . $f . ';');
|
|
// $resultRows[] = $res;
|
|
// } catch (\Throwable $e) {
|
|
// logger()->error("Eval error BARIS $i: $f | " . $e->getMessage());
|
|
// $resultRows[] = 0;
|
|
// }
|
|
// }
|
|
// $result = array_sum($resultRows);
|
|
// logger()->info("evaluateEmissionReductionFormula: RESULT TOTAL", ['result' => $result, 'rows' => $resultRows]);
|
|
// return $result;
|
|
// }
|
|
|
|
// // --- Pipeline normal ---
|
|
// $formula = $this->parseSigmaSumProduct($formula, $adArrays, $adValues, $refParams, $refContext, 'parseSigmaSumProduct');
|
|
// $formula = $this->parseSigmaSumOnly($formula, $adArrays, $adValues, $refParams, $refContext, 'parseSigmaSumOnly');
|
|
// $formula = $this->replaceADDefault($formula, $adArrays, $adValues, 'replaceADDefault');
|
|
// $formula = $this->replaceREF($formula, $refParams, $refContext, $adArrays, $adValues, $adUnits, 'replaceREF', false);
|
|
// $formula = preg_replace_callback('/(\d+(?:\.\d+)?)%/', fn($mm) => $mm[1] / 100, $formula);
|
|
// $formula = preg_replace('/([A-Za-z0-9_\.]+)\^([0-9]+)/', 'pow($1,$2)', $formula);
|
|
// $open = substr_count($formula, '('); $close = substr_count($formula, ')');
|
|
// if ($open > $close) $formula .= str_repeat(')', $open - $close);
|
|
// if ($close > $open) $formula = str_repeat('(', $close - $open) . $formula;
|
|
// logger()->info("evaluateEmissionReductionFormula: FINAL FORMULA", ['formula' => $formula]);
|
|
// try {
|
|
// $result = eval('return ' . $formula . ';');
|
|
// logger()->info("evaluateEmissionReductionFormula: RESULT", ['result' => $result]);
|
|
// return $result;
|
|
// } catch (\Throwable $e) {
|
|
// logger()->error("Eval error FINAL: $formula | " . $e->getMessage());
|
|
// return 0;
|
|
// }
|
|
// }
|
|
public function evaluateEmissionReductionFormula($formula, $adValues, $refParams, $refContext, $adArrays, $adUnits = [], $labelMap = [])
|
|
{
|
|
logger()->info("evaluateEmissionReductionFormula: START", ['formula' => $formula]);
|
|
|
|
// $formula = $this->parseSumproductExcelLikeV3($formula, $adArrays, $adValues, $refParams, $refContext);
|
|
|
|
// 1. PARSE SEMUA BLOK SUMPRODUCT(…) (Excel style)
|
|
$formula = $this->parseSumproductExcel($formula, $adArrays, $adValues, $refParams, $refContext);
|
|
|
|
// 2. Sigma / Σi= pipeline (legacy, kalau ada)
|
|
if ($this->isSigmaArrayFormula($formula, $adArrays, $refParams)) {
|
|
preg_match('/Σi=\(([^)]+)\)/', $formula, $m);
|
|
preg_match_all('/\b(AD[0-9]+|REF_[A-Z0-9_]+)\b/', $m[1] ?? '', $vars);
|
|
$maxRows = 1;
|
|
foreach ($vars[1] as $var) {
|
|
if (isset($adArrays[$var]) && is_array($adArrays[$var]))
|
|
$maxRows = max($maxRows, count($adArrays[$var]));
|
|
}
|
|
|
|
$resultRows = [];
|
|
for ($i = 0; $i < $maxRows; $i++) {
|
|
// Build per baris
|
|
$adValuesRow = [];
|
|
$adArraysRow = [];
|
|
foreach ($adArrays as $key => $arr) {
|
|
$adArraysRow[$key] = [isset($arr[$i]) ? $arr[$i] : (isset($arr[0]) ? $arr[0] : 0)];
|
|
$adValuesRow[$key] = isset($arr[$i]) ? $arr[$i] : (isset($arr[0]) ? $arr[0] : 0);
|
|
}
|
|
$f = $this->parseSigmaSumProduct($formula, $adArraysRow, $adValuesRow, $refParams, $refContext, "row-$i-parseSigmaSumProduct");
|
|
$f = $this->parseSigmaSumOnly($f, $adArraysRow, $adValuesRow, $refParams, $refContext, "row-$i-parseSigmaSumOnly");
|
|
$f = $this->replaceADDefault($f, $adArraysRow, $adValuesRow, "row-$i-replaceADDefault");
|
|
$f = $this->replaceREF($f, $refParams, $refContext, $adArraysRow, $adValuesRow, $adUnits, "row-$i-replaceREF", false);
|
|
|
|
$f = preg_replace_callback('/(\d+(?:\.\d+)?)%/', fn($mm) => $mm[1] / 100, $f);
|
|
$f = preg_replace('/([A-Za-z0-9_\.]+)\^([0-9]+)/', 'pow($1,$2)', $f);
|
|
|
|
// Clean up kurung
|
|
$f = preg_replace('/\)\)+$/', ')', $f);
|
|
$open = substr_count($f, '('); $close = substr_count($f, ')');
|
|
if ($open > $close) $f .= str_repeat(')', $open - $close);
|
|
if ($close > $open) $f = str_repeat('(', $close - $open) . $f;
|
|
$f = preg_replace('/^(b")?([Σ∑Î]i=)/u', '', $f);
|
|
|
|
$formula = str_replace('CONST_', '', $formula);
|
|
$formula = preg_replace('/^(b")?([Σ∑Î][ijk]=)/u', '', $formula);
|
|
|
|
logger()->info("BARIS $i - Formula Final Eval", ['formula' => $f]);
|
|
try {
|
|
$res = eval('return ' . $f . ';');
|
|
$resultRows[] = $res;
|
|
} catch (\Throwable $e) {
|
|
logger()->error("Eval error BARIS $i: $f | " . $e->getMessage());
|
|
$resultRows[] = 0;
|
|
}
|
|
}
|
|
$result = array_sum($resultRows);
|
|
logger()->info("evaluateEmissionReductionFormula: RESULT TOTAL", ['result' => $result, 'rows' => $resultRows]);
|
|
return $result;
|
|
}
|
|
|
|
// 3. Pipeline normal non-sigma
|
|
$formula = $this->parseSigmaSumProduct($formula, $adArrays, $adValues, $refParams, $refContext, 'parseSigmaSumProduct');
|
|
$formula = $this->parseSigmaSumOnly($formula, $adArrays, $adValues, $refParams, $refContext, 'parseSigmaSumOnly');
|
|
$formula = $this->replaceADDefault($formula, $adArrays, $adValues, 'replaceADDefault');
|
|
$formula = $this->replaceREF($formula, $refParams, $refContext, $adArrays, $adValues, $adUnits, 'replaceREF', false);
|
|
$formula = preg_replace_callback('/(\d+(?:\.\d+)?)%/', fn($mm) => $mm[1] / 100, $formula);
|
|
$formula = preg_replace('/([A-Za-z0-9_\.]+)\^([0-9]+)/', 'pow($1,$2)', $formula);
|
|
|
|
// Balance kurung
|
|
$open = substr_count($formula, '('); $close = substr_count($formula, ')');
|
|
if ($open > $close) $formula .= str_repeat(')', $open - $close);
|
|
if ($close > $open) $formula = str_repeat('(', $close - $open) . $formula;
|
|
|
|
// Special case
|
|
if (strpos($formula, 'SUM(') !== false) {
|
|
foreach (['AD3', 'AD4'] as $adKey) {
|
|
if (strpos($formula, "SUM($adKey)") !== false && isset($adArrays[$adKey])) {
|
|
$formula = str_replace("SUM($adKey)", array_sum($adArrays[$adKey]), $formula);
|
|
}
|
|
}
|
|
// Tambah di sini:
|
|
$formula = preg_replace('/SUM\(\s*([0-9\.]+)\s*\)/', '$1', $formula);
|
|
|
|
// Opsional: hapus SUM lain
|
|
$formula = str_replace('SUM', '', $formula);
|
|
}
|
|
|
|
logger()->info("evaluateEmissionReductionFormula: FINAL FORMULA", ['formula' => $formula]);
|
|
try {
|
|
$result = eval('return ' . $formula . ';');
|
|
logger()->info("evaluateEmissionReductionFormula: RESULT", ['result' => $result]);
|
|
return $result;
|
|
} catch (\Throwable $e) {
|
|
logger()->error("Eval error FINAL: $formula | " . $e->getMessage());
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
function replaceADDefault($formula, $adArrays, $adValues, $debugTag = 'replaceADDefault') {
|
|
logger()->info("Masuk $debugTag", [
|
|
'formula' => $formula,
|
|
'adArrays' => $adArrays,
|
|
'adValues' => $adValues
|
|
]);
|
|
return preg_replace_callback('/AD([0-9]+)/', function($ad) use ($adArrays, $adValues) {
|
|
$adFull = "AD".$ad[1];
|
|
$val = $adArrays[$adFull][0] ?? $adValues[$adFull] ?? 0;
|
|
if (!is_numeric($val)) {
|
|
logger()->warning("AD non-numeric (default), replaced with 1", [
|
|
'ad' => $adFull, 'value' => $val
|
|
]);
|
|
return 1;
|
|
}
|
|
logger()->info("replaceADDefault: replace", [
|
|
'ad' => $adFull, 'value' => $val
|
|
]);
|
|
return $val;
|
|
}, $formula);
|
|
}
|
|
|
|
public function replaceREF(
|
|
$formula,
|
|
$refParams,
|
|
$refContext,
|
|
$adArrays,
|
|
$adValues,
|
|
$adUnits = [],
|
|
$debugTag = '',
|
|
$as_array = false
|
|
) {
|
|
logger()->info("Masuk $debugTag", ['formula' => $formula]);
|
|
|
|
// Hitung jumlah CF di formula
|
|
preg_match_all('/REF_CF[0-9]+/i', $formula, $cfMatches);
|
|
$cfCount = count(array_unique($cfMatches[0]));
|
|
|
|
if ($cfCount > 2) {
|
|
logger()->error("Formula mengandung lebih dari 2 Conversion Factor (CF): $cfCount", [
|
|
'formula' => $formula,
|
|
'cf_found' => $cfMatches[0]
|
|
]);
|
|
throw new \Exception("Formula hanya boleh mengandung maksimal 2 Conversion Factor (CF), ditemukan: $cfCount");
|
|
}
|
|
|
|
// FIX: Use $as_array in closure
|
|
return preg_replace_callback('/REF_([A-Z0-9_]+)/', function($mm) use ($refParams, $refContext, $adArrays, $adValues, $adUnits, $as_array) {
|
|
$key = $mm[1];
|
|
if (!isset($refParams[$key])) {
|
|
logger()->error("Mapping untuk REF_$key tidak ditemukan.", [
|
|
'key' => $key, 'refParams' => $refParams
|
|
]);
|
|
throw new \Exception("Mapping untuk REF_$key tidak ditemukan.");
|
|
}
|
|
$map = $refParams[$key];
|
|
|
|
// Special handling: Conversion Factor
|
|
if (
|
|
strtoupper(substr($map['param'], 0, 2)) === 'CF'
|
|
|| strtoupper($map['param']) === 'CONVERSION FACTOR'
|
|
) {
|
|
$adKeyUnit = null;
|
|
foreach ($map['context'] as $field => $adKeyC) {
|
|
$adKeyUnit = $adKeyC;
|
|
break;
|
|
}
|
|
$unit = $adUnits[$adKeyUnit] ?? null;
|
|
if (!$unit) {
|
|
logger()->error("Unit untuk Conversion Factor tidak ditemukan di AD.", [
|
|
'param' => $map['param'],
|
|
'context' => $map['context'],
|
|
'adKeyUnit' => $adKeyUnit,
|
|
'adUnits' => $adUnits,
|
|
'adValues' => $adValues,
|
|
'adArrays' => $adArrays,
|
|
]);
|
|
throw new \Exception("Unit untuk Conversion Factor tidak ditemukan di AD $adKeyUnit.");
|
|
}
|
|
|
|
// Cek kategori unit
|
|
$category = $this->unitCategoryMap[strtolower($unit)] ?? null;
|
|
logger()->info("CF Detected", [
|
|
'unit' => $unit,
|
|
'category' => $category,
|
|
]);
|
|
if (!$category) throw new \Exception("Kategori unit tidak diketahui: $unit");
|
|
|
|
// Ambil conversion factor value
|
|
$q = \App\Models\EmissionFactorReference::where('param', $map['param'])
|
|
->whereRaw('LOWER(unit) = ?', [strtolower($unit)]);
|
|
$cfValue = $q->value('value');
|
|
logger()->info("REF CF Value", [
|
|
'param' => $map['param'],
|
|
'unit' => $unit,
|
|
'cfValue' => $cfValue
|
|
]);
|
|
if ($cfValue === null) throw new \Exception("CF reference tidak ditemukan (param=$map[param], unit=$unit)");
|
|
|
|
// Bangun rumus berdasarkan kategori
|
|
$formulaCF = '';
|
|
if ($category == 'volume') {
|
|
$formulaCF = "($cfValue*REF_BERAT_JENIS*REF_NCV)";
|
|
} else if ($category == 'mass') {
|
|
$formulaCF = "($cfValue*REF_NCV)";
|
|
} else if ($category == 'energy') {
|
|
$formulaCF = "($cfValue)";
|
|
} else {
|
|
throw new \Exception("Kategori unit tidak didukung: $category");
|
|
}
|
|
// Recursively replace reference di rumus baru!
|
|
$finalVal = $this->replaceREF($formulaCF, $refParams, $refContext, $adArrays, $adValues, $adUnits, 'CF_RECURSE');
|
|
$result = null;
|
|
eval("\$result = $finalVal;");
|
|
logger()->info("Eval CF Result", [
|
|
'formula' => $finalVal,
|
|
'result' => $result
|
|
]);
|
|
return $result;
|
|
}
|
|
|
|
// --- Query reference biasa, ambil dari keterangan/value AD ---
|
|
$contextKey = null;
|
|
foreach ($map['context'] as $field => $adKeyC) {
|
|
$contextKey = $adKeyC;
|
|
}
|
|
$contextIsArray = $contextKey && isset($adArrays[$contextKey]) && is_array($adArrays[$contextKey]) && count($adArrays[$contextKey]) > 1;
|
|
|
|
if ($contextIsArray) {
|
|
$adVals = $adArrays[$contextKey];
|
|
$refVals = [];
|
|
foreach ($adVals as $v) {
|
|
$q = \App\Models\EmissionFactorReference::where('param', $map['param'])
|
|
->where('unit', $map['unit'])
|
|
->where('keterangan', $v);
|
|
$val = $q->value('value');
|
|
if ($val !== null) $refVals[] = $val;
|
|
}
|
|
if ($as_array) {
|
|
logger()->info("REF Array Result for $key", [
|
|
'adVals' => $adVals,
|
|
'refVals' => $refVals
|
|
]);
|
|
return implode(',', $refVals);
|
|
// return (string)$refVals;
|
|
}
|
|
$sum = array_sum($refVals);
|
|
logger()->info("REF Array SUM for $key", [
|
|
'adVals' => $adVals,
|
|
'refVals' => $refVals,
|
|
'sum' => $sum
|
|
]);
|
|
// dd(strval($sum == 0 ? 1 : $sum));
|
|
return strval($sum == 0 ? 1 : $sum); // <-- Ini kunci!
|
|
} else {
|
|
// default: ambil single value (seperti sebelumnya)
|
|
$where = [];
|
|
foreach ($map['context'] as $field => $adKeyC) {
|
|
$cv = $adArrays[$adKeyC][0] ?? $adValues[$adKeyC] ?? null;
|
|
if ($cv !== null) $where[] = ['keterangan', '=', $cv];
|
|
}
|
|
logger()->info("QUERY REF Single", [
|
|
'param' => $map['param'],
|
|
'unit' => $map['unit'],
|
|
'where' => $where
|
|
]);
|
|
$q = \App\Models\EmissionFactorReference::where('param', $map['param'])
|
|
->where('unit', $map['unit']);
|
|
foreach ($where as $w) $q->where($w[0], $w[2]);
|
|
$rv = $q->value('value');
|
|
logger()->info("REF Value Result Single", [
|
|
'param' => $map['param'],
|
|
'unit' => $map['unit'],
|
|
'context' => $map['context'],
|
|
'where' => $where,
|
|
'value' => $rv
|
|
]);
|
|
return $rv ?? 1;
|
|
}
|
|
}, $formula);
|
|
}
|
|
|
|
/**
|
|
* Parse dan evaluasi semua fungsi SUMPRODUCT pada formula, mendukung konstanta CONST_ADn, ADn, REF_xxx, angka, power, persen, bracket.
|
|
* SUMPRODUCT(AD8*AD9*0.8/1000*(1/(1-20%)))
|
|
* SUMPRODUCT(CONST_AD2*AD5*...)+SUMPRODUCT(...)
|
|
* dst.
|
|
* @param string $formula
|
|
* @param array $adArrays
|
|
* @param array $adValues
|
|
* @param array $refParams
|
|
* @param array $refContext
|
|
* @return string
|
|
*/
|
|
public function parseSumproductExcelLikeV3($formula, $adArrays, $adValues, $refParams, $refContext)
|
|
{
|
|
// Jika terdapat lebih dari satu SUMPRODUCT di formula, gunakan versi multi
|
|
if (substr_count(strtoupper($formula), 'SUMPRODUCT(') > 1) {
|
|
return $this->parseSumproductMultiple($formula, $adArrays, $adValues, $refParams, $refContext);
|
|
}
|
|
|
|
// Regex pattern handle nested bracket dalam SUMPRODUCT (recursively, non-greedy)
|
|
$pattern = '/SUMPRODUCT\s*\(((?:[^()]++|(?R))*?)\)/i';
|
|
while (preg_match($pattern, $formula)) {
|
|
$formula = preg_replace_callback($pattern, function($m) use ($adArrays, $adValues, $refParams, $refContext) {
|
|
$inner = $m[1];
|
|
$sum = 0;
|
|
|
|
// Ganti AD dan REF inline
|
|
$expr = preg_replace_callback('/AD([0-9]+)/', function($mm) use ($adArrays, $adValues) {
|
|
$adKey = 'AD' . $mm[1];
|
|
return $adValues[$adKey] ?? $adArrays[$adKey][0] ?? 0;
|
|
}, $inner);
|
|
|
|
$expr = preg_replace_callback('/REF_([A-Z0-9_]+)/', function($mm) use ($refParams, $refContext, $adArrays, $adValues) {
|
|
$key = $mm[1];
|
|
if (!isset($refParams[$key])) return 0;
|
|
$map = $refParams[$key];
|
|
$contextKey = null;
|
|
foreach ($map['context'] as $field => $adKeyC) {
|
|
$contextKey = $adKeyC;
|
|
}
|
|
$adVal = $contextKey && isset($adValues[$contextKey]) ? $adValues[$contextKey] : ($adArrays[$contextKey][0] ?? null);
|
|
$q = \App\Models\EmissionFactorReference::where('param', $map['param'])
|
|
->where('unit', $map['unit'])
|
|
->where('keterangan', $adVal);
|
|
return $q->value('value') ?? 0;
|
|
}, $expr);
|
|
|
|
$expr = preg_replace_callback('/(\d+(?:\.\d+)?)%/', fn($mm) => $mm[1]/100, $expr);
|
|
$expr = preg_replace('/([0-9.Ee+\-]+)\s*\^\s*([0-9.Ee+\-]+)/', 'pow($1,$2)', $expr);
|
|
|
|
try {
|
|
$sum = eval('return ' . $expr . ';');
|
|
} catch (\Throwable $e) {
|
|
logger()->error("[SUMPRODUCT] Eval error: $expr | " . $e->getMessage());
|
|
$sum = 0;
|
|
}
|
|
|
|
return (string)$sum;
|
|
}, $formula, 1); // replace 1x per loop
|
|
}
|
|
return $formula;
|
|
}
|
|
|
|
public function parseSumproductMultiple($formula, $adArrays, $adValues, $refParams, $refContext)
|
|
{
|
|
$pattern = '/SUMPRODUCT\s*\(((?:[^()]++|(?R))*?)\)/i';
|
|
return preg_replace_callback($pattern, function ($m) use ($adArrays, $adValues, $refParams, $refContext) {
|
|
$sub = 'SUMPRODUCT(' . $m[1] . ')';
|
|
return $this->parseSumproductExcelLikeV3($sub, $adArrays, $adValues, $refParams, $refContext);
|
|
}, $formula);
|
|
}
|
|
} |