from test_est_rea_parity import run def test_mean_sea_level_labels_do_not_break_variable_declarations(): result = run( "math", """(() => { const rows = [ {Index:1, Code:'D1'}, {Index:0, LineData:'D3 = {+}1.9m [ M.S.L ]'}, {Index:0, LineData:'1개소당 50㎥ q = 1.53 M.S.L h1 = 3.841'}, {Index:0, LineData:'Q = D3 + q + h1 = \\x127.27\\x12'} ]; return EstText.calculate(rows); })()""", ) assert result["errors"] == [] assert result["checks"][0]["calculated"] == 7.27 def test_redundant_price_caption_keeps_quantity_and_source_updates(): result = run( "math", """(() => { const rows = [ {Index:1, Code:'D1'}, {Index:0, LineData:'Q = 7.35'}, {Index:0, SCheck:-2, LinkIndex:5, LineData:'\\x0e황동주물\\x0e \\x0324,200\\x03C × Q = 24,200 × 7.35 = \\x12177,870\\x12'} ]; const initial=EstText.calculate(rows); rows[1].LineData='Q = 4'; const changed=EstText.calculate(rows,{},()=>({GC:30000,TC:30000,Code:'M1',SourceKind:5})); const repeat=EstText.calculate(changed.rows,{},()=>({GC:30000,TC:30000,Code:'M1',SourceKind:5})); const conflict=structuredClone(rows);conflict.splice(1,0,{Index:0,LineData:'C = 2'}); const declared=EstText.calculate(conflict); const mismatch=structuredClone(rows);mismatch[2].LineData=mismatch[2].LineData.replace('= 24,200','= 24,201'); return {initial,changed,repeat,declared,mismatch:EstText.calculate(mismatch)}; })()""", ) assert result["initial"]["errors"] == [] assert result["initial"]["rows"][2]["TC"] == 177870 assert result["changed"]["errors"] == [] assert result["changed"]["rows"][2]["TC"] == 120000 assert result["changed"]["rows"][2]["ResourceFactor"] == 4 assert result["repeat"]["errors"] == [] assert result["repeat"]["rows"][2]["TC"] == 120000 assert result["declared"]["errors"] assert result["mismatch"]["errors"] def test_component_total_ignores_following_numeric_substitution(): result = run( "math", """(() => { const rows = [ {Index:1, Code:'D1'}, {Index:0, LineData:'LL = 5'}, {Index:0, SCheck:-3, LineData:'100 = \\x12100\\x12'}, {Index:0, SCheck:-3, LineData:'\\x1a경비 : 100\\x1a ÷ LL = 100 ÷ 5 = \\x1220\\x12'} ]; const first=EstText.calculate(rows);rows[1].LineData='LL = 4';rows[2].LineData='120 = \\x12100\\x12'; return {first,changed:EstText.calculate(rows)}; })()""", ) assert result["first"]["errors"] == [] assert result["first"]["rows"][3]["TC"] == 20 assert result["changed"]["errors"] == [] assert result["changed"]["rows"][3]["TC"] == 30 def test_equals_in_description_is_not_a_formula_separator(): result = run( "math", """(() => { const rows = [ {Index:1, Code:'D1'}, {Index:0, LineData:'강판{T=22} 67.659kg × 317.40 = \\x1221474.97\\x12'} ]; return EstText.calculate(rows); })()""", ) assert result["errors"] == [] assert result["checks"][0]["calculated"] == 21474.97 def test_korean_length_unit_ja_is_not_a_variable(): result = run( "math", """(() => { const rows = [ {Index:1, Code:'D1'}, {Index:0, SCheck:-1, LineData:'\\x0e목형원(제조)\\x0e : \\x03115706\\x03 × 0.16인 ×80자 = \\x061\\x06\\x121481036.8\\x12'} ]; return EstText.calculate(rows); })()""", ) assert result["errors"] == [] assert result["checks"][0]["calculated"] == 1481036.8 def test_selected_group_can_be_assigned_to_a_variable(): result = run( "math", """(() => { const rows = [ {Index:1, Code:'D1'}, {Index:0, LineData:'0.15 = \\x06A\\x06\\x120.15\\x12'}, {Index:0, LineData:'0.87 = \\x06A\\x06\\x120.87\\x12'}, {Index:0, LinkIndex:62, LineData:'Q = (\\x07A\\x07\\x1a0.15 + 0.87\\x1a) = \\x121\\x12'}, {Index:0, LineData:'Q × 2 = \\x122\\x12'} ]; return EstText.calculate(rows); })()""", ) assert result["errors"] == [] assert result["checks"][-1]["calculated"] == 2.04 def test_independent_results_on_one_line_do_not_replace_the_declared_variable(): result = run( "math", """(() => { const rows = [ {Index:1, Code:'D1'}, {Index:0, LineData:'Q = 3.43'}, {Index:0, LineData:'L = 2인×2조÷(Q×8hr) = \\x120.15\\x12 2×2÷(1×8) = \\x120.5\\x12'}, {Index:0, SCheck:-1, LineData:'노무비 : 226122 × L = \\x1233918.3\\x12'} ]; const initial = EstText.calculate(rows); rows[1].LineData = 'Q = 4'; const changed = EstText.calculate(rows); return {initial, changed}; })()""", ) assert result["initial"]["errors"] == [] assert [check["calculated"] for check in result["initial"]["checks"][:2]] == [0.5, 0.15] assert result["initial"]["rows"][3]["TC"] == 33918.3 assert [check["calculated"] for check in result["changed"]["checks"][:2]] == [0.5, 0.13] assert result["changed"]["rows"][3]["TC"] == 29395.8 def test_unmarked_cost_component_declaration_is_available_to_later_formulas(): result = run( "math", """(() => { const rows = [ {Index:1, Code:'D1'}, {Index:0, SCheck:-3, LineData:'D = 1,466원'}, {Index:0, SCheck:-3, LineData:'E = D ×0.075 = 1,466 ×0.075 = \\x12109.9\\x12'} ]; const first = EstText.calculate(rows); rows[1].LineData = 'D = 2,000원'; const second = EstText.calculate(rows); return {first, second}; })()""", ) assert result["first"]["errors"] == [] assert result["first"]["rows"][2]["TC"] == 109.9 assert result["second"]["rows"][2]["TC"] == 150 def test_repeated_saved_subtotal_tracks_the_preceding_subtotal(): result = run( "math", """(() => { const rows = [ {Index:1, Code:'D1'}, {Index:0, SCheck:-1, LineData:'100 = \\x12100\\x12'}, {Index:0, SCheck:-3, LineData:'50 = \\x1250\\x12'}, {Index:0, SCheck:-5, LineData:'소계 : \\x12150\\x12'}, {Index:0, SCheck:-5, LineData:'소계 : \\x12150\\x12'} ]; const first=EstText.calculate(rows); rows[1].LineData='110 = \\x12100\\x12'; const second=EstText.calculate(rows); return {first, second}; })()""", ) assert [row["TC"] for row in result["first"]["rows"][-2:]] == [150, 150] assert [row["TC"] for row in result["second"]["rows"][-2:]] == [160, 160] def test_comment_only_declaration_waits_for_a_later_numeric_value(): result = run( "math", """(() => { const rows = [ {Index:1, Code:'D1'}, {Index:0, LineData:'L = {공사구간 1/4}'}, {Index:0, LineData:'L = 1.213 km'}, {Index:0, LineData:'Q = L × 2 = \\x122.43\\x12'} ]; const normal=EstText.calculate(rows); rows[1].LineData='L ='; const invalid=EstText.calculate(rows); return {normal, invalid}; })()""", ) assert result["normal"]["errors"] == [] assert result["normal"]["rows"][3]["TC"] == 2.43 assert result["invalid"]["errors"] def test_unmarked_linked_price_declaration_excludes_name_and_specification_tags(): result = run( "math", """(() => { const rows = [ {Index:1, Code:'D1'}, {Index:0, SCheck:-3, LinkIndex:5, LineData:'C = \\x0e구역화물\\x0e\\x13(10.5톤,230km)\\x13 \\x03293,690\\x03'}, {Index:0, LineData:'Q = C × 0.1 = \\x1229369\\x12'} ]; const original = EstText.calculate(rows); const refreshed = EstText.calculate(rows, {}, () => ({JC: 400000})); return {original, refreshed}; })()""", ) assert result["original"]["errors"] == [] assert result["original"]["rows"][2]["TC"] == 29369 assert result["refreshed"]["rows"][2]["TC"] == 40000 def test_tonnes_per_area_is_a_unit_in_a_numeric_formula(): result = run( "math", """(() => { const rows = [ {Index:1, Code:'D1'}, {Index:0, LineData:'0.1 ㎥/㎡ × 2.10 t/㎡ = \\x120.21\\x12'} ]; const first=EstText.calculate(rows); rows[1].LineData='0.1 ㎥/㎡ × 2.50 t/㎡ = \\x120.21\\x12'; const second=EstText.calculate(rows); return {first, second}; })()""", ) assert result["first"]["errors"] == [] assert result["first"]["rows"][1]["TC"] == 0.21 assert result["second"]["rows"][1]["TC"] == 0.25