"""원본 가시설·측량설계 산근에서 발췌한 단위와 설명 열의 계산 시험.""" from test_est_rea_parity import run def test_replace_cost_words_preserves_markers_and_checks_lengths_before_changes(): result = run( "math", r"""(()=>{ const rows=[{index:3,row:{LineData:'\x0e모래\x0e + 모래 = \x121\x12'}},{index:5,row:{LineData:'유지'}}]; const before=JSON.stringify(rows),changed=EstText.replaceWords(rows,'모래','자갈');let rejected=0; for(const [a,b] of [['','x'],['모래','\x12'],['모래','x'.repeat(200001)]]){ try{EstText.replaceWords(rows,a,b)}catch{rejected++} } const markers=EstText.replaceWords([{index:7,row:{LineData:'Q=1 = \x121\x12; cost \x031\x03'}}],'1','2'); const calcRows=[{Index:1,Code:'H',LineData:'head'},{LineData:'Q=1 = \x121\x12'}]; const update=EstText.replaceWords([{row:calcRows[1],index:1}],'1','2')[0];calcRows[1].LineData=update.value; const recalculated=EstText.calculate(calcRows); return {changed,rejected,markers,unchanged:JSON.stringify(rows)===before, crossing:EstText.replaceWords(rows,'모래 +','x').length,recalculated:recalculated.rows[1].LineData,errors:recalculated.errors}; })()""", ) assert result["changed"] == [{"index": 3, "value": "\x0e자갈\x0e + 자갈 = \x121\x12"}] assert result["rejected"] == 3 assert result["markers"] == [{"index": 7, "value": "Q=2 = \x121\x12; cost \x031\x03"}] assert "\x122\x12" in result["recalculated"] and not result["errors"] assert result["unchanged"] and result["crossing"] == 0 def test_text_units_and_description_columns_preserve_numeric_assignments(): result = run( "math", r"""EstText.calculate([ {Index:1,Code:'units',LineData:'units'}, {LineData:' G2 = 3.14 / 4 ×0.105^2 ×1.50M × 2배 = \x120.026\x12 ㎥',Precision:3}, {LineData:' N = 2 set'}, {LineData:' 1. 재료비 : D=16mm, 1본당 4.0m'}, {LineData:' Q = D × N = \x1232\x12'}, {LineData:' V = G2 × 1000 = \x1226\x12'} ])""", ) assert result["errors"] == [] assert result["rows"][1]["TC"] == 0.026 assert result["rows"][4]["TC"] == 32 assert result["rows"][5]["TC"] == 26 def test_text_unit_changes_propagate_without_reusing_stored_results(): result = run( "math", r"""EstText.calculate([ {Index:1,Code:'changed',LineData:'changed'}, {LineData:' N = 3 set'}, {LineData:' 1. 재료비 : D=25mm, 1본당 4.0m'}, {LineData:' Q = D × N = \x1232\x12'}, {LineData:' V = 1.5M × 3배 = \x123\x12',Precision:2} ])""", ) assert result["errors"] == [] assert result["rows"][3]["TC"] == 75 assert result["rows"][4]["TC"] == 4.5 def test_per_set_unicode_millimetres_and_starred_weight_declarations(): result = run( "math", r"""EstText.calculate([ {Index:1,Code:'source_units',LineData:'source_units'}, {LineData:' * 본당 중량 W = 0.553톤/본*'}, {LineData:' T = 20㎜ A = 0.95 × 0.95 = \x120.9\x12㎡',Precision:1}, {LineData:' 재료비 : \x031,690\x03 × 0.005kg/set = \x128.4\x12',SCheck:-2}, {LineData:' Q = W × T = \x1211.06\x12',Precision:2} ])""", ) assert result["errors"] == [] assert result["rows"][2]["TC"] == 0.9 assert result["rows"][3]["TC"] == 8.4 assert result["rows"][4]["TC"] == 11.06 def test_parenthesized_volume_and_count_units_recalculate_from_variables(): result = run( "math", r"""EstText.calculate([ {Index:1,Code:'volume',LineData:'volume'}, {LineData:' A = 80㎡ B = 16㎡'}, {LineData:' 새끼 : L =0.05m × 4배 × 4번 × 1줄 = \x120.8\x12m',Precision:1}, {LineData:' 노무비 : \x0358,122\x03 × 0.1 × (A+B)㎥ = 58,122 × 0.1 × (80+16) = \x12557,971.2\x12',SCheck:-1}, {LineData:' Q = L × (A+B)㎡ = \x1276.8\x12',Precision:1} ])""", ) assert result["errors"] == [] assert result["rows"][2]["TC"] == 0.8 assert result["rows"][3]["TC"] == 557971.2 assert result["rows"][4]["TC"] == 76.8 def test_quoted_source_conversion_declaration_propagates_changed_value(): result = run( "math", r"""[2,3].map(quantity=>EstText.calculate([ {Index:1,Code:'quote',LineData:'quote'}, {LineData:` * 중량환산 : 사석기준 'Q1 = ${quantity}.0 톤/㎥'`}, {LineData:' 노무비 : \x03235,204\x03 ×1.04인×Q1 = 235,204 ×1.04×2 = \x12489,224.3\x12',SCheck:-1} ]))""", ) assert all(item["errors"] == [] for item in result) assert [item["rows"][2]["TC"] for item in result] == [489224.3, 733836.4] def test_group_sum_accepts_assigned_price_and_refreshes_from_source(): result = run( "math", r"""[550,600].map(price=>{ const p=EstNative.empty();p.options.SourceRefresh=true; p.tables.StuffcDat=[{Code:'M',ValueIndex:4,InputTC:price}]; p.tables.CostMainDat=[ {Index:1,Code:'C',LineData:'C'}, {LinkIndex:5,Code:'M',LineData:' \x0ebrick\x0e \x06A\x06\x03550\x03 : 별산'}, {LinkIndex:62,SCheck:-1,LineData:' \x07A\x07\x1a550\x1a × 5% = \x1227.5\x12'}, {SCheck:-1,LineData:' \x1a노무비:27\x1a'}, {SCheck:-5,LineData:' \x1a계:\x1a \x1227\x12'}]; p.tables.EstMainDat=[{Code:'C',LinkIndex:104,GN:10},{LinkIndex:54}]; return EstNative.calculate(p); })""", ) assert all(item["calculation"]["errors"] == [] for item in result) assert [item["tables"]["CostMainDat"][2]["TC"] for item in result] == [27.5, 30] assert [item["tables"]["EstMainDat"][0]["TC"] for item in result] == [270, 300] def test_subtotal_includes_price_only_rows_and_updates_truncated_final_amount(): result = run( "math", r"""[1094,2000].map(price=>{ const p=EstNative.empty(); p.tables.StuffcDat=[{Code:'T',ValueIndex:4,InputTC:110754},{Code:'H',ValueIndex:4,InputTC:price}]; p.tables.CostMainDat=[ {Index:1,Code:'C',LineData:'transport'}, {Level:2,LineData:'1. 운반 및 하역'}, {LinkIndex:5,Code:'T',LineData:' \x0etruck\x0e (\x03110,754\x03 ÷12) ×1.5 = \x1213,844.2\x12'}, {LinkIndex:5,Code:'H',LineData:' \x0eunloading\x0e \x031,094\x03'}, {LinkIndex:61,SCheck:-3,TC:14938.2,LineData:' 경비소계 : \x0c14,938.2\x0c'}, {Level:2,LineData:'2. 합계'}, {Level:-2,SCheck:-3,LineData:' \x1a경비:14,938\x1a'}, {Level:-2,SCheck:-5,LineData:' \x1a계:\x1a \x1214,938\x12'}]; p.tables.EstMainDat=[{Code:'C',LinkIndex:104,GN:10},{LinkIndex:54}]; const first=EstNative.calculate(p),again=EstNative.calculate(first); return {first,again}; })""", ) assert all(item["first"]["calculation"]["errors"] == [] for item in result) assert [item["first"]["tables"]["CostMainDat"][4]["TC"] for item in result] == [ 14938.2, 15844.2, ] for key in ["first", "again"]: assert [item[key]["tables"]["EstMainDat"][0]["TC"] for item in result] == [ 149380, 158440, ] def test_operator_followed_by_spaces_does_not_start_a_new_assignment(): result = run( "math", r"""[1700,2000].map(weight=>EstText.calculate([ {Index:1,Code:'forest',LineData:'forest'}, {LineData:` N = 54.93 q = 250 W = ${weight}`}, {LineData:' Q = N × q ÷ W = 54.93 × 250 ÷ 1,700 = \x128.08\x12 ㎥/hr'}, {LineData:' V = Q × 2 = \x1216.16\x12'} ]))""", ) assert all(item["errors"] == [] for item in result) assert [item["rows"][2]["TC"] for item in result] == [8.08, 6.87] assert [item["rows"][3]["TC"] for item in result] == [16.16, 13.74] def test_curly_wrapped_results_and_explanatory_calculations_keep_their_text(): result = run( "math", r"""EstText.calculate([ {Index:1,Code:'forest',LineData:'forest'}, {LineData:' {0.91m×0.91m×0.6m = \x120.49686\x12㎥}'}, {LineData:' {0.49686 ×2.6Ton = \x121.29\x12}'}, {LineData:' ※ ㎡당 중량 1.6개 ×1,325÷1,000 = \x122.12\x12Ton/㎡'} ])""", ) assert result["errors"] == [] assert [row["TC"] for row in result["rows"][1:]] == [0.49686, 1.29, 2.12] assert result["rows"][1]["LineData"].strip().startswith("{") assert result["rows"][1]["LineData"].endswith("}") def test_won_unit_glyphs_are_units_instead_of_operators(): result = run( "math", r"""EstText.calculate([ {Index:1,Code:'money',LineData:'money'}, {LineData:' 0.0347㎥ / 2.5 × 0 \\/㎥ = \x120\x12'}, {LineData:' 0.0347㎥ / 2.5 × 2500 \\/㎥ = \x1234.7\x12'}, {LineData:' 2㎥ × 2500₩/㎥ = \x125000\x12'} ])""", ) assert result["errors"] == [] assert [row["TC"] for row in result["rows"][1:]] == [0, 34.7, 5000] def test_range_conditions_are_not_assigned_arbitrary_values(): result = run( "math", r"""EstText.calculate([ {Index:1,Code:'range',LineData:'range'}, {LineData:' 앙카핀 (D=13, L=450~600mm) 1.0㎡ : 별도계상'}, {LineData:' H = 0~4m : (기계80%, 인력20%)'}, {LineData:' Q = 2 × 3 = \x126\x12'}, {LineData:' V = H × 2 = \x128\x12'} ])""", ) assert len(result["errors"]) == 1 assert "참조 항목 없음: H" in result["errors"][0] assert result["rows"][3]["TC"] == 6 def test_quantity_subtotal_keeps_six_digits_before_pricing(): result = run( "math", r"""[4.4,5].map(length=>EstText.calculate([ {Index:1,Code:'sign',LineData:'sign'}, {Level:2,LineData:'각재 수량'}, {Diget:7,DigetIndex:1,LineData:` 0.09 ×0.09 ×${length}m ×2개 = \x120.07128\x12`}, {Diget:7,DigetIndex:1,LineData:' 0.06 ×0.06 ×2.28m ×5개 = \x120.04104\x12'}, {Diget:7,DigetIndex:1,LineData:' 0.06 ×0.06 ×2.28m ×7개 = \x120.057456\x12'}, {Diget:7,DigetIndex:1,LinkIndex:61,LineData:' 소 계 : Q1 = \x0c0.169776\x0c ㎥/개소'}, {SCheck:-2,LinkIndex:5,LineData:' 재료비 : \x03577,842\x03 × Q1 = 577,842 × 0.169776 = \x1298,103.7\x12'} ]))""", ) assert all(item["errors"] == [] for item in result) assert [item["rows"][5]["TC"] for item in result] == [0.169776, 0.179496] assert [item["rows"][6]["TC"] for item in result] == [98103.7, 103720.3] def test_declaration_can_follow_an_hour_unit_with_a_single_space(): result = run( "math", r"""EstText.calculate([ {Index:1,Code:'transport',LineData:'transport'}, {LineData:' L = 1.0Km{?} V1 = 5.5Km/hr V2 = 9.3Km/hr t=35분'}, {LineData:' Q = t × 2 = \x1270\x12'} ])""", ) assert result["errors"] == [] assert result["rows"][2]["TC"] == 70 def test_fullwidth_unit_slash_and_ground_level_annotations(): result = run( "math", r"""EstText.calculate([ {Index:1,Code:'annotations',LineData:'annotations'}, {LineData:' Q = 23.32 ㎥/hr'}, {LineData:' D1 = {-}6.6m [평균지반고]'}, {LineData:' D2 = {-}18.6m [기초터파기 평균 저면 EL.]'}, {LineData:' V = Q + D1 - D2 = \x1235.32\x12',Precision:2} ])""", ) assert result["errors"] == [] assert result["rows"][4]["TC"] == 35.32 def test_marine_units_and_bullet_are_treated_as_labels(): result = run( "math", r"""EstText.calculate([ {Index:1,Code:'marine'}, {LineData:' q = 2인 ÷12구멍 = \x120.17\x12 인/구멍'}, {LineData:' ⊙ 480분 ÷ 88.42 = \x125.43\x12 본/일'}, {LineData:' 노무비 : \x03156,379\x03 ÷ \x11122.66\x11 × 1척 = \x121,274.8\x12', SCheck:-1}, {LineData:' ◎ 목재길이 L = 5.2m/EA × 3열 × 2단 × 3구역 = \x1293.6\x12 m'} ])""", ) assert result["errors"] == [] assert [row["TC"] for row in result["rows"][1:]] == [0.17, 5.43, 1274.8, 93.6] def test_arrow_to_sketch_is_not_a_fraction(): result = run( "math", r"""EstText.calculate([ {Index:1,Code:'sketch'}, {LineData:' L = 5.2m ⇒ ───────'}, {LineData:' Q = L × 2 = \x1210.4\x12'} ])""", ) assert result["errors"] == [] assert result["rows"][2]["TC"] == 10.4 def test_mixed_brackets_in_annotation(): result = run( "math", r"""EstText.calculate([ {Index:1,Code:'datum'}, {LineData:' t1 = 5분 〔토운선을 준설선에 접선 소요시간]'}, {LineData:' Q = t1 × 2 = \x1210\x12'} ])""", ) assert result["errors"] == [] assert result["rows"][2]["TC"] == 10 def test_greek_variables_and_implicit_multiplication_in_marine_formula(): result = run( "math", r"""EstText.calculate([ {Index:1,Code:'greek'}, {LineData:' α = 1.0'}, {LineData:' β = 0.6'}, {LineData:' ι = 14.0 N = 7'}, {LineData:' Tb = 0.08×α×β×ι (N + 2)'}, {LineData:' Tc = Tb × 2 = \x1212.10\x12'} ])""", ) assert result["errors"] == [] assert result["rows"][5]["TC"] == 12.1 def test_parenthesized_variable_label_assigns_explicit_value(): result = run( "math", r"""EstText.calculate([ {Index:1,Code:'label'}, {LineData:' 기타시간 (Te) : 10분'}, {LineData:' Tc = Te × 2 = \x1220\x12'} ])""", ) assert result["errors"] == [] assert result["rows"][2]["TC"] == 20 def test_variable_assignment_with_approximate_annotation(): result = run( "math", r"""EstText.calculate([ {Index:1,Code:'approx'}, {LineData:' Q1 = 3.2hr ≒ 0.5일'}, {LineData:' Q = Q1 × 2 = \x126.4\x12'} ])""", ) assert result["errors"] == [] assert result["rows"][2]["TC"] == 6.4 def test_braced_arithmetic_and_survey_point_units(): result = run( "math", r"""EstText.calculate([ {Index:1,Code:'brace'}, {LineData:' Q1 = { 6.0M×0.0002EA/M + 2.3M×0.0005EA/M + 1M×0.0005EA/M }'}, {LineData:' Q = Q1 × 10000 = \x1228.5\x12'}, {LineData:' 노무비 : \x03295,138\x03×3지점÷3지점×3인×4회 = \x123,541,656\x12',SCheck:-1}, {LineData:' B{적재량} = 1500/11.5{5㎥×2.3} = \x12130.43\x12'} ])""", ) assert result["errors"] == [] assert result["rows"][2]["TC"] == 28.5 assert result["rows"][3]["TC"] == 3541656 assert result["rows"][4]["TC"] == 130.43 def test_marine_compound_units_and_therefore_symbol(): result = run( "math", r"""EstText.calculate([ {Index:1,Code:'units'}, {LineData:' 시험 : 2지점×2개항목×4회×11,000= \x12176,000\x12'}, {LineData:' 보고서 : \x0350\x03×70×4분기×5권 = \x1270,000\x12', SCheck:-2}, {LineData:' Q = 4hr/일 × 15일/월 × 65월 = \x123,900\x12'}, {LineData:' ∴ 0.22 ÷ 6㎥ = \x120.04\x12 hr'}, {LineData:' C = ((1,294.95 + 1,174.6) ÷ 1.1) ÷ 5DM/Ton = \x12449\x12원/DM', Precision:0}, {LineData:' 행거 11조당 중량 4.69 × 2 = \x129.38\x12 ㎏'} ])""", ) assert result["errors"] == [] assert [row["TC"] for row in result["rows"][1:]] == [176000, 70000, 3900, 0.04, 449, 9.38] def test_spaced_metre_unit_is_not_a_second_variable_declaration(): result = run( "math", r"""EstText.calculate([ {Index:1,Code:'spaced_unit',LineData:'spaced_unit'}, {LineData:' L7 = 4.5m'}, {LineData:' Q2 = L7 / 0.8 M = 4.5 / 0.8 = \x125.63\x12 개'}, {SCheck:-2,LineData:' 재료비 : \x033,000\x03 × Q2 = 3,000 × 5.63 = \x1216,890\x12'} ])""", ) assert result["errors"] == [] assert result["rows"][2]["TC"] == 5.63 assert result["rows"][3]["TC"] == 16890