268 lines
10 KiB
Python
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)
|