Files
Aislo/resources/tester/test_est_rea_parity.py
T

268 lines
10 KiB
Python

"""REA 저장 샘플과 웹앱 엔진의 대조. 원본 프로그램 실행 시험이 아님."""
import json
import subprocess
from pathlib import Path
ROOT = Path(__file__).resolve().parents[2]
HELPER = Path(__file__).with_name("helper_est_native.cjs")
EXPECTED = json.loads(
(ROOT / "resources/tester/fixtures/est_rea_expected.json").read_text(encoding="utf-8")
)
SAMPLE = json.loads((ROOT / "tools/est-test/sample.json").read_text(encoding="utf-8"))
def run(command, *args):
return json.loads(
subprocess.check_output(["node", str(HELPER), command, *args], text=True, encoding="utf-8")
)
def test_machine_stored_components_heads_and_titles():
result = run("machine")
assert result["errors"] == []
count = 0
for expected in EXPECTED["machine"]["rows"]:
if "stored" not in expected:
continue
row = result["rows"][expected["slot"]]
if isinstance(expected["stored"], dict):
for key, value in expected["stored"].items():
assert abs((row.get(key) or 0) - (value or 0)) < 1e-6, (
expected["slot"],
key,
row.get(key),
value,
)
else:
assert abs(row[expected["field"]] - expected["stored"]) < 1e-6, expected
count += 1
by_code = {row["Code"]: row for row in result["titles"]}
for expected in SAMPLE["tables"]["MachTitleDat"]:
actual = by_code[expected["Code"]]
for key in ["NC", "GC", "JC", "TC"]:
assert actual[key] == expected[key]
count += 1
assert count == 48
def test_numeric_helpers_distinguish_rounding_modes():
assert run(
"math",
"[EstMath.even(2.5),EstMath.even(3.5),EstMath.floating(-1.25,1,1),EstMath.fixed(1.23999,2)]",
) == [2, 4, -1.3, 1.24]
def test_head_41_stored_checks():
result = run("head")
assert result["errors"] == []
rows = result["rows"]
for expected in EXPECTED["head_est"]["head"]:
actual = rows[expected["source_slot"]]
for key, value in expected["stored"].items():
assert abs((actual.get(key) or 0) - value) < 1e-6, (expected, key, actual.get(key))
assert len(EXPECTED["head_est"]["head"]) == 41
def test_estimate_37_stored_checks():
result = run("estimate")
assert result["errors"] == []
for expected in EXPECTED["head_est"]["est"]:
row = result["rows"][expected["slot"]]
for key, value in expected["stored"].items():
assert abs((row.get(key) or 0) - value) < 1e-6, (
expected["slot"],
key,
row.get(key),
value,
)
assert len(EXPECTED["head_est"]["est"]) == 37
def test_linedata_118_results_and_8_titles():
result = run("text")
assert result["errors"] == []
assert len(result["checks"]) == 118
for item in result["checks"]:
assert abs(item["stored"] - item["calculated"]) < 1e-6, item
for expected in EXPECTED["linedata"]["rows"]:
if expected["kind"] == "title":
actual = result["rows"][expected["slot"]]
for key, value in expected["stored"].items():
assert actual[key] == value
def test_wonga_all_23_formulas_and_total_fix():
result = run("wonga")
assert result["errors"] == []
for expected in EXPECTED["wonga"]["results"]:
row = result["rows"][expected["row_position"]]
assert row["Cost"] == float(expected["stored"]), (
row["Gubun"],
row["Cost"],
expected["stored"],
)
assert len(EXPECTED["wonga"]["results"]) == 23
assert result["total"] == 79603000
assert [step["difference"] for step in result["adjustments"]] == [324, -32, 3, 0]
def test_entire_refresh_pipeline_preserves_stored_outputs():
result = run("refresh")
assert result["calculation"]["errors"] == []
for table in ["MachMainDat", "HeadMainDat", "EstMainDat"]:
for index, (before, after) in enumerate(
zip(SAMPLE["tables"][table], result["tables"][table])
):
for key in ["NC", "GC", "JC", "SC", "TC"]:
assert abs((before.get(key) or 0) - (after.get(key) or 0)) < 1e-6, (
table,
index,
key,
)
for table in ["CostTitleDat", "HeadTitleDat", "MachTitleDat"]:
lookup = {row["Code"]: row for row in result["tables"][table]}
for before in SAMPLE["tables"][table]:
for key in ["NC", "GC", "JC", "SC", "TC"]:
assert abs((before.get(key) or 0) - (lookup[before["Code"]].get(key) or 0)) < 1e-6
def test_recalculation_after_price_edit_is_idempotent():
first, second = run("repeat")
assert first["calculation"]["errors"] == second["calculation"]["errors"] == []
assert first["tables"]["CostTitleDat"] == second["tables"]["CostTitleDat"]
assert first["tables"]["EstMainDat"] == second["tables"]["EstMainDat"]
def test_progress_accepted_rounds_annual_allocation_and_resources():
result = run("business")
first, second = result["first"], result["second"]
assert first["errors"] == second["errors"] == []
assert first["rows"][0]["GumGN"] == 2.5
assert first["rows"][0]["TotalGN"] == 1.5
assert first["total"] == 330
assert second["rows"][0]["JunGN"] == 1.5
assert second["rows"][0]["GumGN"] == 2.5
assert second["rows"][0]["TotalGN"] == 4
assert second["rows"][0]["TotalTC"] == 800
assert first["rows"][-1]["GumTC"] == 550
assert first["rows"][-1]["SaTC"] == first["rows"][-1]["TotalTC"] == 330
assert second["rows"][-1]["JunTC"] == 330
assert second["rows"][-1]["TotalTC"] == 880
assert result["years"]["errors"] == []
assert [r["project"]["calculation"]["total"] for r in result["years"]["results"]] == [880, 1320]
assert result["resources"]["errors"] == []
assert result["resources"]["rows"][0]["quantity"] == 20
escalation = result["escalation"]
assert escalation["errors"] == []
assert escalation["rows"][0]["EscExcluded"] == 3
assert escalation["rows"][0]["EscApplied"] == 7
assert escalation["contractTotal"] == 2200
assert escalation["adjustedTotal"] == 2640
assert escalation["excludedTotal"] == 660
assert escalation["applied"] == 1540
assert escalation["rate"] == 20
assert escalation["adjustment"] == 308
assert [
next(row["TC"] for row in item["tables"]["EstMainDat"] if row.get("LinkIndex") == 54)
for item in result["originals"]
] == [2120, 2520, 2520]
assert result["bid"]["calculation"]["direct"]["TC"] == 1800
assert result["bidAgain"]["tables"]["EstMainDat"] == result["bid"]["tables"]["EstMainDat"]
def test_estx_import_reads_original_tables_options_and_templates(tmp_path):
import zipfile
bundle = next((ROOT / "resources/knowledge/original").glob("*/EST Plus 2/*/05_*/sample_bundle"))
archive = tmp_path / "sample.estx"
with zipfile.ZipFile(archive, "w", zipfile.ZIP_DEFLATED) as output:
for file in bundle.iterdir():
if file.is_file():
output.write(file, file.name)
result = run("import", str(archive))
assert result["schema"] == "est-web-2"
assert len(result["tables"]["EstMainDat"]) == len(SAMPLE["tables"]["EstMainDat"])
assert len(result["originalOptions"]) == 17
assert len(result["wongaTemplates"]) == 4
assert result["options"]["TotalFix"] is True
assert result["options"]["GCToWonga"] is False
assert result["originalArchive"]
def test_source_price_edit_changes_downstream_costs():
first, _ = run("repeat")
original = run("refresh")
assert first["calculation"]["direct"]["TC"] != original["calculation"]["direct"]["TC"]
assert first["tables"]["Wonga"] != original["tables"]["Wonga"]
def test_extended_original_rows_cover_conversion_fractions_and_fuel():
fixture = json.loads(
(ROOT / "resources/tester/fixtures/est_native_extended.json").read_text(encoding="utf-8")
)
result = run("extended")
for name, table in [("head", "HeadMainDat"), ("machine", "MachMainDat")]:
assert result[name]["errors"] == []
for index, (before, after) in enumerate(
zip(fixture["tables"][table], result[name]["rows"])
):
for key in ["NC", "GC", "JC", "SC", "TC"]:
assert abs((before.get(key) or 0) - (after.get(key) or 0)) < 1e-6, (
table,
index,
key,
)
assert result["text"]["errors"] == []
for check in result["text"]["checks"]:
assert abs(check["stored"] - check["calculated"]) < 1e-6, check
def test_installed_main_archive_all_stored_values_when_available():
import os
import pytest
archive = Path(
os.environ.get("EST_REA_MAIN", "C:/Dasansoft/Est Plus 2/Data/ProjectData/main.estx")
)
if not archive.is_file():
pytest.skip("Original EST main.estx is not installed; portable fixtures still run")
output = subprocess.check_output(
["node", str(HELPER.with_name("helper_est_archive.cjs")), str(archive)],
text=True,
encoding="utf-8",
timeout=120,
)
result = json.loads(output)
assert result["errors"] == []
assert result["differences"] == []
assert all(item["checked"] == item["matched"] for item in result["counts"].values())
assert sum(item["checked"] for item in result["counts"].values()) > 100000
def test_original_quantity_sheets_and_three_saved_formula_disagreements():
sheets = run("quantity")
assert len(sheets) == 75
# Original editor formulas disagree with MulList saved numbers in three
# places. Keep both visible instead of changing the formulas to replay them.
expected = {
("N0000000083", "r000004"): (286, 0.35 * 1.3 * 220),
("N0000000086", "r000004"): (0, 1 * 1),
("N0000000106", "r000000"): (138.72, 1.25 * 2 * 68 * 1.02),
}
differences = set()
checked = 0
for sheet in sheets:
assert sheet["errors"] == []
for before, after in zip(sheet["original"], sheet["items"]):
checked += 1
key = (sheet["Code"], before["Code"])
if key in expected:
assert before["GN"] == expected[key][0]
assert abs(after["GN"] - expected[key][1]) < 1e-6
differences.add(key)
else:
assert abs((before.get("GN") or 0) - (after.get("GN") or 0)) < 1e-6, key
assert checked == 449
assert differences == set(expected)