From 8472fc9f40141a7eda96e86e2364d65bb0935500 Mon Sep 17 00:00:00 2001 From: umsangdon Date: Tue, 22 Sep 2026 12:27:45 +0900 Subject: [PATCH] =?UTF-8?q?refactor(B08,B09):=20=ED=8F=B4=EB=8D=94?= =?UTF-8?q?=EC=A7=B8=20old=5Fcode=20=EB=A1=9C=20=EC=98=AE=EA=B8=B0?= =?UTF-8?q?=EA=B3=A0=20=EB=B9=88=20=ED=99=94=EB=A9=B4=20=EB=91=98=EB=A7=8C?= =?UTF-8?q?=20=EB=82=A8=EA=B9=80=20(PLAN=207-3)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit B08_Quantity 86 · B09_Estimation 111 파일을 old_code/ 로 옮김(지우지 않음). 화면은 메뉴·주소·단계 막대만 남은 빈 틀 둘 — main.py 라우터 13 개는 끊음. B07 이 빌려 쓰던 비탈 길이·면적은 필요한 함수만 B07_DesignDetail_Engine_SlopeGeometry 로 옮겨 적음(면적 적분·노면 면적·측점 묶음은 안 옮김) · 시험 하나를 새로 둠. B07 구조물도 조립(Cad_StandardSheet)은 2026-09-13 에 이미 도면 목록에서 빠져 부르는 곳이 없어 old_code 로 같이 보냄 — 구조물 그림은 되살리지 않음. B06 구조물 몫 조회는 빈 값으로 두어 화면이 그대로 서게 함. B08·B09 를 부르던 시험 25 개도 old_code/resources/tester 로 옮김. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01PAdA5ThqmtVSsbzjusk1cJ --- .../B06_Section_Server_Calc_Prebuild.py | 14 +- .../B07_DesignDetail_Engine_Cross_Quantity.py | 21 +- .../B07_DesignDetail_Engine_SlopeGeometry.py | 284 +++++ B08_Quantity/B08_Quantity_UI_Page.ts | 1103 +--------------- B09_Estimation/B09_Estimation_UI_Shell.ts | 97 +- main.py | 29 +- ...7_DesignDetail_Engine_Cad_StandardSheet.py | 0 .../B08_Quantity}/0_old_260726_codex.zip | Bin .../B08_Quantity_Build_WorkItemMaster.py | 0 ...B08_Quantity_Build_WorkItemMaster_Const.py | 0 ...B08_Quantity_Build_WorkItemMaster_Forms.py | 0 ...uantity_Build_WorkItemMaster_FormsConst.py | 0 .../B08_Quantity_Build_WorkItemMaster_Keys.py | 0 ...8_Quantity_Build_WorkItemMaster_Parents.py | 0 ...B08_Quantity_Build_WorkItemMaster_Table.py | 0 ..._Quantity_Build_WorkItemMaster_Variants.py | 0 .../B08_Quantity_Engine_BasisUnit.py | 0 .../B08_Quantity_Engine_EarthworkSummary.py | 0 .../B08_Quantity_Engine_EarthworkTable.py | 0 .../B08_Quantity_Engine_Formula.py | 0 .../B08_Quantity_Engine_Formwork.py | 0 .../B08_Quantity_Engine_GravelSurfacing.py | 0 .../B08_Quantity_Engine_Handoff.py | 0 .../B08_Quantity_Engine_Handoff_Boundaries.py | 0 .../B08_Quantity_Engine_Handoff_Mapping.py | 0 .../B08_Quantity_Engine_Handoff_Rows.py | 0 .../B08_Quantity_Engine_Handoff_Rows_Prep.py | 0 .../B08_Quantity_Engine_Handoff_Spoil.py | 0 .../B08_Quantity_Engine_Handoff_Trench.py | 0 .../B08_Quantity_Engine_HaulInputs.py | 0 .../B08_Quantity_Engine_HaulSummary.py | 0 .../B08_Quantity_Engine_MaterialSummary.py | 0 .../B08_Quantity_Engine_ObservedUnit.py | 0 .../B08_Quantity}/B08_Quantity_Engine_Pipe.py | 0 .../B08_Quantity_Engine_Preparation.py | 0 ...8_Quantity_Engine_Preparation_Ancillary.py | 0 ...antity_Engine_Preparation_FrameMaterial.py | 0 .../B08_Quantity_Engine_Preparation_Status.py | 0 ...8_Quantity_Engine_Preparation_TreeWaste.py | 0 .../B08_Quantity_Engine_RockSplit.py | 0 .../B08_Quantity_Engine_SlopeArea.py | 0 .../B08_Quantity_Engine_SlopeLength.py | 0 .../B08_Quantity_Engine_StmateRecipe.py | 0 .../B08_Quantity_Engine_StructureFigure.py | 0 ...8_Quantity_Engine_StructureFigure_Small.py | 0 .../B08_Quantity_Engine_StructureLibrary.py | 0 .../B08_Quantity_Engine_StructurePriceLink.py | 0 .../B08_Quantity_Engine_StructureSheet.py | 0 ...B08_Quantity_Engine_StructureSheet_Edit.py | 0 .../B08_Quantity_Engine_StructureSummary.py | 0 .../B08_Quantity_Engine_StructureTemplate.py | 0 .../B08_Quantity_Engine_StructureUnitPrice.py | 0 .../B08_Quantity_Engine_UnitQuantity.py | 0 .../B08_Quantity_Engine_UnitQuantity_Base.py | 0 .../B08_Quantity_Engine_UnitQuantity_Box.py | 0 ...08_Quantity_Engine_UnitQuantity_Masonry.py | 0 ...8_Quantity_Engine_UnitQuantity_Pavement.py | 0 ...antity_Engine_UnitQuantity_Revegetation.py | 0 ..._Quantity_Engine_UnitQuantity_Revetment.py | 0 ..._Quantity_Engine_UnitQuantity_StoneSpec.py | 0 .../B08_Quantity}/B08_Quantity_Formula.ts | 0 .../B08_Quantity_Formula_Node.ts | 0 .../B08_Quantity}/B08_Quantity_Provenance.py | 0 .../B08_Quantity}/B08_Quantity_Router.py | 0 .../B08_Quantity_Router_Earthwork.py | 0 .../B08_Quantity_Router_Earthwork_HaulPlan.py | 0 .../B08_Quantity_Router_Earthwork_Settings.py | 0 .../B08_Quantity_Router_Material.py | 0 .../B08_Quantity_Router_StmateLibrary.py | 0 .../B08_Quantity_Router_StructureSheet.py | 0 .../B08_Quantity_UI_ConversionFactors.ts | 0 .../B08_Quantity_UI_EarthworkGrid.ts | 0 .../B08_Quantity_UI_EarthworkGrid_Style.ts | 0 .../B08_Quantity_UI_MaterialGrid.ts | 0 old_code/B08_Quantity/B08_Quantity_UI_Page.ts | 1116 +++++++++++++++++ .../B08_Quantity_UI_SidePanel_Sections.ts | 0 .../B08_Quantity_UI_Side_BenchCut.ts | 0 .../B08_Quantity_UI_Side_FaceDressing.ts | 0 .../B08_Quantity_UI_Side_Gravel.ts | 0 .../B08_Quantity_UI_Side_RootRemoval.ts | 0 .../B08_Quantity_UI_Side_SeedSpray.ts | 0 .../B08_Quantity_UI_Side_TreeWaste.ts | 0 .../B08_Quantity_UI_StructureFigure.ts | 0 .../B08_Quantity_UI_StructureSheet.ts | 0 .../B08_Quantity_UI_StructureSheet_Formula.ts | 0 .../B08_Quantity_UI_StructureSheet_Library.ts | 0 .../B08_Quantity_UI_StructureSheet_Spec.ts | 0 .../B08_Quantity_UI_StructureSheet_Stmate.ts | 0 ...08_Quantity_UI_StructureSheet_UnitPrice.ts | 0 ...uantity_UI_StructureSheet_UnitPriceEdit.ts | 0 .../B08_Quantity_UI_StructureSummary.ts | 0 .../B08_Quantity_UI_SummaryGrid.ts | 0 .../B08_Quantity}/B08_Quantity_Wording.py | 0 .../B09_Estimation_BasisSheet.py | 0 .../B09_Estimation_BillOfQuantities.py | 0 .../B09_Estimation_BillOfQuantities_Input.py | 0 ...9_Estimation_BillOfQuantities_Materials.py | 0 .../B09_Estimation_BillOfQuantities_Rows.py | 0 .../B09_Estimation_BillRates.py | 0 .../B09_Estimation_Brushcutting.py | 0 .../B09_Estimation}/B09_Estimation_Chisel.py | 0 .../B09_Estimation_Consumables.py | 0 .../B09_Estimation_Contract.py | 0 .../B09_Estimation_CostSheet.py | 0 .../B09_Estimation_CostSheet_Forms.py | 0 .../B09_Estimation_CrewOutput.py | 0 .../B09_Estimation_DesignDocIndex.py | 0 .../B09_Estimation}/B09_Estimation_Edits.py | 0 .../B09_Estimation_Engine_Cost.py | 0 .../B09_Estimation_Engine_Cost_Input.py | 0 .../B09_Estimation_Engine_Cost_Options.py | 0 .../B09_Estimation_Euroform.py | 0 .../B09_Estimation_Execution.py | 0 .../B09_Estimation_Explosives.py | 0 .../B09_Estimation_FactorChoices.py | 0 .../B09_Estimation}/B09_Estimation_Guards.py | 0 .../B09_Estimation_KnownGaps.py | 0 .../B09_Estimation_LaborSurcharge.py | 0 .../B09_Estimation}/B09_Estimation_Lists.py | 0 .../B09_Estimation_Lists_Sources.py | 0 .../B09_Estimation_MachineCost.py | 0 .../B09_Estimation_MachineExpenseSheet.py | 0 .../B09_Estimation_MachineOperating.py | 0 .../B09_Estimation_MachineProductivity.py | 0 ...timation_MachineProductivity_Compaction.py | 0 ...B09_Estimation_MachineProductivity_Data.py | 0 ...09_Estimation_MachineProductivity_Dozer.py | 0 ...B09_Estimation_MachineProductivity_Dump.py | 0 ...stimation_MachineProductivity_Reference.py | 0 .../B09_Estimation_MaterialCatalog.py | 0 .../B09_Estimation_MaterialPrices.py | 0 .../B09_Estimation_MaterialSheet.py | 0 .../B09_Estimation_ParentSteps.py | 0 .../B09_Estimation_PriceBasis.py | 0 .../B09_Estimation_PriceBook.py | 0 .../B09_Estimation_Progress.py | 0 .../B09_Estimation_Provenance.py | 0 .../B09_Estimation_Provenance_Common.py | 0 .../B09_Estimation_Provenance_Sources.py | 0 .../B09_Estimation_QuantityDigits.py | 0 .../B09_Estimation_RateOverride.py | 0 .../B09_Estimation_RateTable.py | 0 .../B09_Estimation}/B09_Estimation_Rates.py | 0 .../B09_Estimation_ResourceAxis.py | 0 .../B09_Estimation_ResourceAxis_ChooseOne.py | 0 .../B09_Estimation_ResourceAxis_Join.py | 0 ...B09_Estimation_ResourceAxis_JudgedTable.py | 0 .../B09_Estimation_ResourceAxis_Labels.py | 0 .../B09_Estimation_ResourceAxis_ProcessSum.py | 0 .../B09_Estimation_ResourceAxis_Sources.py | 0 .../B09_Estimation_ResourceAxis_ThreeAxis.py | 0 .../B09_Estimation_ResourceAxis_Transposed.py | 0 ..._Estimation_ResourceAxis_TwoLevelHeader.py | 0 .../B09_Estimation_ResourceAxis_UnitBasis.py | 0 .../B09_Estimation_RockLoss.py | 0 .../B09_Estimation_Rounding.py | 0 .../B09_Estimation}/B09_Estimation_Router.py | 0 .../B09_Estimation_Router_Contract.py | 0 .../B09_Estimation_Router_CostSheet.py | 0 .../B09_Estimation_Router_Edits.py | 0 .../B09_Estimation_Router_Execution.py | 0 .../B09_Estimation_Router_Factors.py | 0 .../B09_Estimation_Router_MaterialPrices.py | 0 .../B09_Estimation_Router_Progress.py | 0 .../B09_Estimation_SeedSpray.py | 0 .../B09_Estimation_Statutory.py | 0 .../B09_Estimation}/B09_Estimation_Storage.py | 0 .../B09_Estimation_TopsoilRemoval.py | 0 .../B09_Estimation_Transport.py | 0 .../B09_Estimation_UI_BillRates.ts | 0 .../B09_Estimation_UI_Detail.ts | 0 .../B09_Estimation_UI_DetailEdit.ts | 0 .../B09_Estimation_UI_Factors.ts | 0 .../B09_Estimation_UI_MachineExpense.ts | 0 .../B09_Estimation_UI_Sheet.ts | 0 .../B09_Estimation/B09_Estimation_UI_Shell.ts | 106 ++ .../B09_Estimation_UI_Shell_Types.ts | 0 .../B09_Estimation_UI_Store.ts | 0 .../B09_Estimation_UI_Tab_BaseData.ts | 0 .../B09_Estimation_UI_Tab_BasisSheet.ts | 0 .../B09_Estimation_UI_Tab_Bill.ts | 0 .../B09_Estimation_UI_Tab_Completion.ts | 0 .../B09_Estimation_UI_Tab_Contract.ts | 0 .../B09_Estimation_UI_Tab_CostSheet.ts | 0 .../B09_Estimation_UI_Tab_DesignDoc.ts | 0 .../B09_Estimation_UI_Tab_Execution.ts | 0 .../B09_Estimation_UI_Tab_Lists.ts | 0 .../B09_Estimation_UI_Tab_Machine.ts | 0 .../B09_Estimation_UI_Tab_MaterialPrices.ts | 0 .../B09_Estimation_UI_Tab_PriceBasis.ts | 0 .../B09_Estimation_UI_Tab_PriceCompare.ts | 0 .../B09_Estimation_UI_Tab_Progress.ts | 0 .../B09_Estimation_UI_Tab_RateTable.ts | 0 .../B09_Estimation_UI_Tab_Summary.ts | 0 .../B09_Estimation_UI_Tab_Supply.ts | 0 .../B09_Estimation_UI_Tab_UnitPrice.ts | 0 .../B09_Estimation_UI_Table.ts | 0 .../B09_Estimation_UnitPrice.py | 0 .../B09_Estimation_UnitPriceSheet.py | 0 .../B09_Estimation_UnitPrice_View.py | 0 .../B09_Estimation_UnreadTables.py | 0 .../B09_Estimation_WoodChipping.py | 0 .../B09_Estimation_WorkItemUnit.py | 0 .../B09_Estimation_WorkItems_AX.py | 0 .../tester/test_b05_empty_means_select.py | 0 .../tester/test_b05_not_in_table_marks.py | 0 .../tester/test_b05_registry_fill_a2_a3.py | 0 .../test_b05_retaining_wall_height_default.py | 0 .../tester/test_b05_revetment_sabang_basis.py | 0 .../test_b06_b08_wall_thickness_match.py | 0 .../tester/test_b06_wall_lean_table.py | 0 .../tester/test_b07_sheet_key_place.py | 0 .../tester/test_b07_standard_figure_reason.py | 0 .../tester/test_b07_standard_sheet_rows.py | 0 .../test_b07_standard_spec_blinding_weep.py | 0 .../tester/test_b10a_rules_visible_b05_b09.py | 0 .../resources}/tester/test_common_aliases.py | 0 .../tester/test_double_count_boundaries.py | 0 .../resources}/tester/test_form_const.py | 0 .../tester/test_haul_limit_setting.py | 0 .../tester/test_haul_state_natural.py | 0 .../resources}/tester/test_pum_edition.py | 0 .../tester/test_revetment_stone_fields.py | 0 .../tester/test_work_item_key_gate.py | 0 .../tester/test_work_item_key_registry.py | 0 .../tester/test_work_item_link_choice.py | 0 .../tester/test_work_item_master_const.py | 0 .../tester/test_work_item_master_toc.py | 0 .../tester/test_work_item_master_variants.py | 0 resources/tester/test_b07_slope_geometry.py | 53 + ui_template/ui_template_locale_b3.ts | 10 + 231 files changed, 1617 insertions(+), 1216 deletions(-) create mode 100644 B07_DesignDetail/B07_DesignDetail_Engine_SlopeGeometry.py rename {B07_DesignDetail => old_code/B07_DesignDetail}/B07_DesignDetail_Engine_Cad_StandardSheet.py (100%) rename {B08_Quantity => old_code/B08_Quantity}/0_old_260726_codex.zip (100%) rename {B08_Quantity => old_code/B08_Quantity}/B08_Quantity_Build_WorkItemMaster.py (100%) rename {B08_Quantity => old_code/B08_Quantity}/B08_Quantity_Build_WorkItemMaster_Const.py (100%) rename {B08_Quantity => old_code/B08_Quantity}/B08_Quantity_Build_WorkItemMaster_Forms.py (100%) rename {B08_Quantity => old_code/B08_Quantity}/B08_Quantity_Build_WorkItemMaster_FormsConst.py (100%) rename {B08_Quantity => old_code/B08_Quantity}/B08_Quantity_Build_WorkItemMaster_Keys.py (100%) rename {B08_Quantity => old_code/B08_Quantity}/B08_Quantity_Build_WorkItemMaster_Parents.py (100%) rename {B08_Quantity => old_code/B08_Quantity}/B08_Quantity_Build_WorkItemMaster_Table.py (100%) rename {B08_Quantity => old_code/B08_Quantity}/B08_Quantity_Build_WorkItemMaster_Variants.py (100%) rename {B08_Quantity => old_code/B08_Quantity}/B08_Quantity_Engine_BasisUnit.py (100%) rename {B08_Quantity => old_code/B08_Quantity}/B08_Quantity_Engine_EarthworkSummary.py (100%) rename {B08_Quantity => old_code/B08_Quantity}/B08_Quantity_Engine_EarthworkTable.py (100%) rename {B08_Quantity => old_code/B08_Quantity}/B08_Quantity_Engine_Formula.py (100%) rename {B08_Quantity => old_code/B08_Quantity}/B08_Quantity_Engine_Formwork.py (100%) rename {B08_Quantity => old_code/B08_Quantity}/B08_Quantity_Engine_GravelSurfacing.py (100%) rename {B08_Quantity => old_code/B08_Quantity}/B08_Quantity_Engine_Handoff.py (100%) rename {B08_Quantity => old_code/B08_Quantity}/B08_Quantity_Engine_Handoff_Boundaries.py (100%) rename {B08_Quantity => old_code/B08_Quantity}/B08_Quantity_Engine_Handoff_Mapping.py (100%) rename {B08_Quantity => old_code/B08_Quantity}/B08_Quantity_Engine_Handoff_Rows.py (100%) rename {B08_Quantity => old_code/B08_Quantity}/B08_Quantity_Engine_Handoff_Rows_Prep.py (100%) rename {B08_Quantity => old_code/B08_Quantity}/B08_Quantity_Engine_Handoff_Spoil.py (100%) rename {B08_Quantity => old_code/B08_Quantity}/B08_Quantity_Engine_Handoff_Trench.py (100%) rename {B08_Quantity => old_code/B08_Quantity}/B08_Quantity_Engine_HaulInputs.py (100%) rename {B08_Quantity => old_code/B08_Quantity}/B08_Quantity_Engine_HaulSummary.py (100%) rename {B08_Quantity => old_code/B08_Quantity}/B08_Quantity_Engine_MaterialSummary.py (100%) rename {B08_Quantity => old_code/B08_Quantity}/B08_Quantity_Engine_ObservedUnit.py (100%) rename {B08_Quantity => old_code/B08_Quantity}/B08_Quantity_Engine_Pipe.py (100%) rename {B08_Quantity => old_code/B08_Quantity}/B08_Quantity_Engine_Preparation.py (100%) rename {B08_Quantity => old_code/B08_Quantity}/B08_Quantity_Engine_Preparation_Ancillary.py (100%) rename {B08_Quantity => old_code/B08_Quantity}/B08_Quantity_Engine_Preparation_FrameMaterial.py (100%) rename {B08_Quantity => old_code/B08_Quantity}/B08_Quantity_Engine_Preparation_Status.py (100%) rename {B08_Quantity => old_code/B08_Quantity}/B08_Quantity_Engine_Preparation_TreeWaste.py (100%) rename {B08_Quantity => old_code/B08_Quantity}/B08_Quantity_Engine_RockSplit.py (100%) rename {B08_Quantity => old_code/B08_Quantity}/B08_Quantity_Engine_SlopeArea.py (100%) rename {B08_Quantity => old_code/B08_Quantity}/B08_Quantity_Engine_SlopeLength.py (100%) rename {B08_Quantity => old_code/B08_Quantity}/B08_Quantity_Engine_StmateRecipe.py (100%) rename {B08_Quantity => old_code/B08_Quantity}/B08_Quantity_Engine_StructureFigure.py (100%) rename {B08_Quantity => old_code/B08_Quantity}/B08_Quantity_Engine_StructureFigure_Small.py (100%) rename {B08_Quantity => old_code/B08_Quantity}/B08_Quantity_Engine_StructureLibrary.py (100%) rename {B08_Quantity => old_code/B08_Quantity}/B08_Quantity_Engine_StructurePriceLink.py (100%) rename {B08_Quantity => old_code/B08_Quantity}/B08_Quantity_Engine_StructureSheet.py (100%) rename {B08_Quantity => old_code/B08_Quantity}/B08_Quantity_Engine_StructureSheet_Edit.py (100%) rename {B08_Quantity => old_code/B08_Quantity}/B08_Quantity_Engine_StructureSummary.py (100%) rename {B08_Quantity => old_code/B08_Quantity}/B08_Quantity_Engine_StructureTemplate.py (100%) rename {B08_Quantity => old_code/B08_Quantity}/B08_Quantity_Engine_StructureUnitPrice.py (100%) rename {B08_Quantity => old_code/B08_Quantity}/B08_Quantity_Engine_UnitQuantity.py (100%) rename {B08_Quantity => old_code/B08_Quantity}/B08_Quantity_Engine_UnitQuantity_Base.py (100%) rename {B08_Quantity => old_code/B08_Quantity}/B08_Quantity_Engine_UnitQuantity_Box.py (100%) rename {B08_Quantity => old_code/B08_Quantity}/B08_Quantity_Engine_UnitQuantity_Masonry.py (100%) rename {B08_Quantity => old_code/B08_Quantity}/B08_Quantity_Engine_UnitQuantity_Pavement.py (100%) rename {B08_Quantity => old_code/B08_Quantity}/B08_Quantity_Engine_UnitQuantity_Revegetation.py (100%) rename {B08_Quantity => old_code/B08_Quantity}/B08_Quantity_Engine_UnitQuantity_Revetment.py (100%) rename {B08_Quantity => old_code/B08_Quantity}/B08_Quantity_Engine_UnitQuantity_StoneSpec.py (100%) rename {B08_Quantity => old_code/B08_Quantity}/B08_Quantity_Formula.ts (100%) rename {B08_Quantity => old_code/B08_Quantity}/B08_Quantity_Formula_Node.ts (100%) rename {B08_Quantity => old_code/B08_Quantity}/B08_Quantity_Provenance.py (100%) rename {B08_Quantity => old_code/B08_Quantity}/B08_Quantity_Router.py (100%) rename {B08_Quantity => old_code/B08_Quantity}/B08_Quantity_Router_Earthwork.py (100%) rename {B08_Quantity => old_code/B08_Quantity}/B08_Quantity_Router_Earthwork_HaulPlan.py (100%) rename {B08_Quantity => old_code/B08_Quantity}/B08_Quantity_Router_Earthwork_Settings.py (100%) rename {B08_Quantity => old_code/B08_Quantity}/B08_Quantity_Router_Material.py (100%) rename {B08_Quantity => old_code/B08_Quantity}/B08_Quantity_Router_StmateLibrary.py (100%) rename {B08_Quantity => old_code/B08_Quantity}/B08_Quantity_Router_StructureSheet.py (100%) rename {B08_Quantity => old_code/B08_Quantity}/B08_Quantity_UI_ConversionFactors.ts (100%) rename {B08_Quantity => old_code/B08_Quantity}/B08_Quantity_UI_EarthworkGrid.ts (100%) rename {B08_Quantity => old_code/B08_Quantity}/B08_Quantity_UI_EarthworkGrid_Style.ts (100%) rename {B08_Quantity => old_code/B08_Quantity}/B08_Quantity_UI_MaterialGrid.ts (100%) create mode 100644 old_code/B08_Quantity/B08_Quantity_UI_Page.ts rename {B08_Quantity => old_code/B08_Quantity}/B08_Quantity_UI_SidePanel_Sections.ts (100%) rename {B08_Quantity => old_code/B08_Quantity}/B08_Quantity_UI_Side_BenchCut.ts (100%) rename {B08_Quantity => old_code/B08_Quantity}/B08_Quantity_UI_Side_FaceDressing.ts (100%) rename {B08_Quantity => old_code/B08_Quantity}/B08_Quantity_UI_Side_Gravel.ts (100%) rename {B08_Quantity => old_code/B08_Quantity}/B08_Quantity_UI_Side_RootRemoval.ts (100%) rename {B08_Quantity => old_code/B08_Quantity}/B08_Quantity_UI_Side_SeedSpray.ts (100%) rename {B08_Quantity => old_code/B08_Quantity}/B08_Quantity_UI_Side_TreeWaste.ts (100%) rename {B08_Quantity => old_code/B08_Quantity}/B08_Quantity_UI_StructureFigure.ts (100%) rename {B08_Quantity => old_code/B08_Quantity}/B08_Quantity_UI_StructureSheet.ts (100%) rename {B08_Quantity => old_code/B08_Quantity}/B08_Quantity_UI_StructureSheet_Formula.ts (100%) rename {B08_Quantity => old_code/B08_Quantity}/B08_Quantity_UI_StructureSheet_Library.ts (100%) rename {B08_Quantity => old_code/B08_Quantity}/B08_Quantity_UI_StructureSheet_Spec.ts (100%) rename {B08_Quantity => old_code/B08_Quantity}/B08_Quantity_UI_StructureSheet_Stmate.ts (100%) rename {B08_Quantity => old_code/B08_Quantity}/B08_Quantity_UI_StructureSheet_UnitPrice.ts (100%) rename {B08_Quantity => old_code/B08_Quantity}/B08_Quantity_UI_StructureSheet_UnitPriceEdit.ts (100%) rename {B08_Quantity => old_code/B08_Quantity}/B08_Quantity_UI_StructureSummary.ts (100%) rename {B08_Quantity => old_code/B08_Quantity}/B08_Quantity_UI_SummaryGrid.ts (100%) rename {B08_Quantity => old_code/B08_Quantity}/B08_Quantity_Wording.py (100%) rename {B09_Estimation => old_code/B09_Estimation}/B09_Estimation_BasisSheet.py (100%) rename {B09_Estimation => old_code/B09_Estimation}/B09_Estimation_BillOfQuantities.py (100%) rename {B09_Estimation => old_code/B09_Estimation}/B09_Estimation_BillOfQuantities_Input.py (100%) rename {B09_Estimation => old_code/B09_Estimation}/B09_Estimation_BillOfQuantities_Materials.py (100%) rename {B09_Estimation => old_code/B09_Estimation}/B09_Estimation_BillOfQuantities_Rows.py (100%) rename {B09_Estimation => old_code/B09_Estimation}/B09_Estimation_BillRates.py (100%) rename {B09_Estimation => old_code/B09_Estimation}/B09_Estimation_Brushcutting.py (100%) rename {B09_Estimation => old_code/B09_Estimation}/B09_Estimation_Chisel.py (100%) rename {B09_Estimation => old_code/B09_Estimation}/B09_Estimation_Consumables.py (100%) rename {B09_Estimation => old_code/B09_Estimation}/B09_Estimation_Contract.py (100%) rename {B09_Estimation => old_code/B09_Estimation}/B09_Estimation_CostSheet.py (100%) rename {B09_Estimation => old_code/B09_Estimation}/B09_Estimation_CostSheet_Forms.py (100%) rename {B09_Estimation => old_code/B09_Estimation}/B09_Estimation_CrewOutput.py (100%) rename {B09_Estimation => old_code/B09_Estimation}/B09_Estimation_DesignDocIndex.py (100%) rename {B09_Estimation => old_code/B09_Estimation}/B09_Estimation_Edits.py (100%) rename {B09_Estimation => old_code/B09_Estimation}/B09_Estimation_Engine_Cost.py (100%) rename {B09_Estimation => old_code/B09_Estimation}/B09_Estimation_Engine_Cost_Input.py (100%) rename {B09_Estimation => old_code/B09_Estimation}/B09_Estimation_Engine_Cost_Options.py (100%) rename {B09_Estimation => old_code/B09_Estimation}/B09_Estimation_Euroform.py (100%) rename {B09_Estimation => old_code/B09_Estimation}/B09_Estimation_Execution.py (100%) rename {B09_Estimation => old_code/B09_Estimation}/B09_Estimation_Explosives.py (100%) rename {B09_Estimation => old_code/B09_Estimation}/B09_Estimation_FactorChoices.py (100%) rename {B09_Estimation => old_code/B09_Estimation}/B09_Estimation_Guards.py (100%) rename {B09_Estimation => old_code/B09_Estimation}/B09_Estimation_KnownGaps.py (100%) rename {B09_Estimation => old_code/B09_Estimation}/B09_Estimation_LaborSurcharge.py (100%) rename {B09_Estimation => old_code/B09_Estimation}/B09_Estimation_Lists.py (100%) rename {B09_Estimation => old_code/B09_Estimation}/B09_Estimation_Lists_Sources.py (100%) rename {B09_Estimation => old_code/B09_Estimation}/B09_Estimation_MachineCost.py (100%) rename {B09_Estimation => old_code/B09_Estimation}/B09_Estimation_MachineExpenseSheet.py (100%) rename {B09_Estimation => old_code/B09_Estimation}/B09_Estimation_MachineOperating.py (100%) rename {B09_Estimation => old_code/B09_Estimation}/B09_Estimation_MachineProductivity.py (100%) rename {B09_Estimation => old_code/B09_Estimation}/B09_Estimation_MachineProductivity_Compaction.py (100%) rename {B09_Estimation => old_code/B09_Estimation}/B09_Estimation_MachineProductivity_Data.py (100%) rename {B09_Estimation => old_code/B09_Estimation}/B09_Estimation_MachineProductivity_Dozer.py (100%) rename {B09_Estimation => old_code/B09_Estimation}/B09_Estimation_MachineProductivity_Dump.py (100%) rename {B09_Estimation => old_code/B09_Estimation}/B09_Estimation_MachineProductivity_Reference.py (100%) rename {B09_Estimation => old_code/B09_Estimation}/B09_Estimation_MaterialCatalog.py (100%) rename {B09_Estimation => old_code/B09_Estimation}/B09_Estimation_MaterialPrices.py (100%) rename {B09_Estimation => old_code/B09_Estimation}/B09_Estimation_MaterialSheet.py (100%) rename {B09_Estimation => old_code/B09_Estimation}/B09_Estimation_ParentSteps.py (100%) rename {B09_Estimation => old_code/B09_Estimation}/B09_Estimation_PriceBasis.py (100%) rename {B09_Estimation => old_code/B09_Estimation}/B09_Estimation_PriceBook.py (100%) rename {B09_Estimation => old_code/B09_Estimation}/B09_Estimation_Progress.py (100%) rename {B09_Estimation => old_code/B09_Estimation}/B09_Estimation_Provenance.py (100%) rename {B09_Estimation => old_code/B09_Estimation}/B09_Estimation_Provenance_Common.py (100%) rename {B09_Estimation => old_code/B09_Estimation}/B09_Estimation_Provenance_Sources.py (100%) rename {B09_Estimation => old_code/B09_Estimation}/B09_Estimation_QuantityDigits.py (100%) rename {B09_Estimation => old_code/B09_Estimation}/B09_Estimation_RateOverride.py (100%) rename {B09_Estimation => old_code/B09_Estimation}/B09_Estimation_RateTable.py (100%) rename {B09_Estimation => old_code/B09_Estimation}/B09_Estimation_Rates.py (100%) rename {B09_Estimation => old_code/B09_Estimation}/B09_Estimation_ResourceAxis.py (100%) rename {B09_Estimation => old_code/B09_Estimation}/B09_Estimation_ResourceAxis_ChooseOne.py (100%) rename {B09_Estimation => old_code/B09_Estimation}/B09_Estimation_ResourceAxis_Join.py (100%) rename {B09_Estimation => old_code/B09_Estimation}/B09_Estimation_ResourceAxis_JudgedTable.py (100%) rename {B09_Estimation => old_code/B09_Estimation}/B09_Estimation_ResourceAxis_Labels.py (100%) rename {B09_Estimation => old_code/B09_Estimation}/B09_Estimation_ResourceAxis_ProcessSum.py (100%) rename {B09_Estimation => old_code/B09_Estimation}/B09_Estimation_ResourceAxis_Sources.py (100%) rename {B09_Estimation => old_code/B09_Estimation}/B09_Estimation_ResourceAxis_ThreeAxis.py (100%) rename {B09_Estimation => old_code/B09_Estimation}/B09_Estimation_ResourceAxis_Transposed.py (100%) rename {B09_Estimation => old_code/B09_Estimation}/B09_Estimation_ResourceAxis_TwoLevelHeader.py (100%) rename {B09_Estimation => old_code/B09_Estimation}/B09_Estimation_ResourceAxis_UnitBasis.py (100%) rename {B09_Estimation => old_code/B09_Estimation}/B09_Estimation_RockLoss.py (100%) rename {B09_Estimation => old_code/B09_Estimation}/B09_Estimation_Rounding.py (100%) rename {B09_Estimation => old_code/B09_Estimation}/B09_Estimation_Router.py (100%) rename {B09_Estimation => old_code/B09_Estimation}/B09_Estimation_Router_Contract.py (100%) rename {B09_Estimation => old_code/B09_Estimation}/B09_Estimation_Router_CostSheet.py (100%) rename {B09_Estimation => old_code/B09_Estimation}/B09_Estimation_Router_Edits.py (100%) rename {B09_Estimation => old_code/B09_Estimation}/B09_Estimation_Router_Execution.py (100%) rename {B09_Estimation => old_code/B09_Estimation}/B09_Estimation_Router_Factors.py (100%) rename {B09_Estimation => old_code/B09_Estimation}/B09_Estimation_Router_MaterialPrices.py (100%) rename {B09_Estimation => old_code/B09_Estimation}/B09_Estimation_Router_Progress.py (100%) rename {B09_Estimation => old_code/B09_Estimation}/B09_Estimation_SeedSpray.py (100%) rename {B09_Estimation => old_code/B09_Estimation}/B09_Estimation_Statutory.py (100%) rename {B09_Estimation => old_code/B09_Estimation}/B09_Estimation_Storage.py (100%) rename {B09_Estimation => old_code/B09_Estimation}/B09_Estimation_TopsoilRemoval.py (100%) rename {B09_Estimation => old_code/B09_Estimation}/B09_Estimation_Transport.py (100%) rename {B09_Estimation => old_code/B09_Estimation}/B09_Estimation_UI_BillRates.ts (100%) rename {B09_Estimation => old_code/B09_Estimation}/B09_Estimation_UI_Detail.ts (100%) rename {B09_Estimation => old_code/B09_Estimation}/B09_Estimation_UI_DetailEdit.ts (100%) rename {B09_Estimation => old_code/B09_Estimation}/B09_Estimation_UI_Factors.ts (100%) rename {B09_Estimation => old_code/B09_Estimation}/B09_Estimation_UI_MachineExpense.ts (100%) rename {B09_Estimation => old_code/B09_Estimation}/B09_Estimation_UI_Sheet.ts (100%) create mode 100644 old_code/B09_Estimation/B09_Estimation_UI_Shell.ts rename {B09_Estimation => old_code/B09_Estimation}/B09_Estimation_UI_Shell_Types.ts (100%) rename {B09_Estimation => old_code/B09_Estimation}/B09_Estimation_UI_Store.ts (100%) rename {B09_Estimation => old_code/B09_Estimation}/B09_Estimation_UI_Tab_BaseData.ts (100%) rename {B09_Estimation => old_code/B09_Estimation}/B09_Estimation_UI_Tab_BasisSheet.ts (100%) rename {B09_Estimation => old_code/B09_Estimation}/B09_Estimation_UI_Tab_Bill.ts (100%) rename {B09_Estimation => old_code/B09_Estimation}/B09_Estimation_UI_Tab_Completion.ts (100%) rename {B09_Estimation => old_code/B09_Estimation}/B09_Estimation_UI_Tab_Contract.ts (100%) rename {B09_Estimation => old_code/B09_Estimation}/B09_Estimation_UI_Tab_CostSheet.ts (100%) rename {B09_Estimation => old_code/B09_Estimation}/B09_Estimation_UI_Tab_DesignDoc.ts (100%) rename {B09_Estimation => old_code/B09_Estimation}/B09_Estimation_UI_Tab_Execution.ts (100%) rename {B09_Estimation => old_code/B09_Estimation}/B09_Estimation_UI_Tab_Lists.ts (100%) rename {B09_Estimation => old_code/B09_Estimation}/B09_Estimation_UI_Tab_Machine.ts (100%) rename {B09_Estimation => old_code/B09_Estimation}/B09_Estimation_UI_Tab_MaterialPrices.ts (100%) rename {B09_Estimation => old_code/B09_Estimation}/B09_Estimation_UI_Tab_PriceBasis.ts (100%) rename {B09_Estimation => old_code/B09_Estimation}/B09_Estimation_UI_Tab_PriceCompare.ts (100%) rename {B09_Estimation => old_code/B09_Estimation}/B09_Estimation_UI_Tab_Progress.ts (100%) rename {B09_Estimation => old_code/B09_Estimation}/B09_Estimation_UI_Tab_RateTable.ts (100%) rename {B09_Estimation => old_code/B09_Estimation}/B09_Estimation_UI_Tab_Summary.ts (100%) rename {B09_Estimation => old_code/B09_Estimation}/B09_Estimation_UI_Tab_Supply.ts (100%) rename {B09_Estimation => old_code/B09_Estimation}/B09_Estimation_UI_Tab_UnitPrice.ts (100%) rename {B09_Estimation => old_code/B09_Estimation}/B09_Estimation_UI_Table.ts (100%) rename {B09_Estimation => old_code/B09_Estimation}/B09_Estimation_UnitPrice.py (100%) rename {B09_Estimation => old_code/B09_Estimation}/B09_Estimation_UnitPriceSheet.py (100%) rename {B09_Estimation => old_code/B09_Estimation}/B09_Estimation_UnitPrice_View.py (100%) rename {B09_Estimation => old_code/B09_Estimation}/B09_Estimation_UnreadTables.py (100%) rename {B09_Estimation => old_code/B09_Estimation}/B09_Estimation_WoodChipping.py (100%) rename {B09_Estimation => old_code/B09_Estimation}/B09_Estimation_WorkItemUnit.py (100%) rename {B09_Estimation => old_code/B09_Estimation}/B09_Estimation_WorkItems_AX.py (100%) rename {resources => old_code/resources}/tester/test_b05_empty_means_select.py (100%) rename {resources => old_code/resources}/tester/test_b05_not_in_table_marks.py (100%) rename {resources => old_code/resources}/tester/test_b05_registry_fill_a2_a3.py (100%) rename {resources => old_code/resources}/tester/test_b05_retaining_wall_height_default.py (100%) rename {resources => old_code/resources}/tester/test_b05_revetment_sabang_basis.py (100%) rename {resources => old_code/resources}/tester/test_b06_b08_wall_thickness_match.py (100%) rename {resources => old_code/resources}/tester/test_b06_wall_lean_table.py (100%) rename {resources => old_code/resources}/tester/test_b07_sheet_key_place.py (100%) rename {resources => old_code/resources}/tester/test_b07_standard_figure_reason.py (100%) rename {resources => old_code/resources}/tester/test_b07_standard_sheet_rows.py (100%) rename {resources => old_code/resources}/tester/test_b07_standard_spec_blinding_weep.py (100%) rename {resources => old_code/resources}/tester/test_b10a_rules_visible_b05_b09.py (100%) rename {resources => old_code/resources}/tester/test_common_aliases.py (100%) rename {resources => old_code/resources}/tester/test_double_count_boundaries.py (100%) rename {resources => old_code/resources}/tester/test_form_const.py (100%) rename {resources => old_code/resources}/tester/test_haul_limit_setting.py (100%) rename {resources => old_code/resources}/tester/test_haul_state_natural.py (100%) rename {resources => old_code/resources}/tester/test_pum_edition.py (100%) rename {resources => old_code/resources}/tester/test_revetment_stone_fields.py (100%) rename {resources => old_code/resources}/tester/test_work_item_key_gate.py (100%) rename {resources => old_code/resources}/tester/test_work_item_key_registry.py (100%) rename {resources => old_code/resources}/tester/test_work_item_link_choice.py (100%) rename {resources => old_code/resources}/tester/test_work_item_master_const.py (100%) rename {resources => old_code/resources}/tester/test_work_item_master_toc.py (100%) rename {resources => old_code/resources}/tester/test_work_item_master_variants.py (100%) create mode 100644 resources/tester/test_b07_slope_geometry.py diff --git a/B06_Section/B06_Section_Server_Calc_Prebuild.py b/B06_Section/B06_Section_Server_Calc_Prebuild.py index 545dd448..f14a3e08 100644 --- a/B06_Section/B06_Section_Server_Calc_Prebuild.py +++ b/B06_Section/B06_Section_Server_Calc_Prebuild.py @@ -74,15 +74,13 @@ async def haul_inputs_for(project_id: Any) -> dict[str, Any]: ⚠ **여기서 다시 세지 않는다** — 두 값 다 B08 전개에서 나오는 것이라 이쪽이 세면 같은 계산이 두 벌이 된다(CLAUDE.md 5장). 못 읽으면 빈 값으로 두고 **0 으로 눅이지 않는다**. - """ - try: - from B08_Quantity.B08_Quantity_Router_Material import project_haul_inputs - data = await project_haul_inputs(project_id) - return data if isinstance(data, dict) else {} - except Exception: - logger.exception("구조물 몫(공제·잔토) 조회 실패 — 값 없이 진행: project_id=%s", project_id) - return {} + ⚠ **지금은 늘 빈 값이다**(PLAN 7-3) — B08 구조물 전개가 `old_code/B08_Quantity/` 로 가고 + 화면이 빈 틀이라 낼 값이 없다. 값을 지어내지 않고 빈 값으로 두어 화면은 그대로 선다. + B08 을 마스터 얼개로 다시 지을 때 그쪽이 낼 값을 여기로 이으면 된다. + """ + _ = project_id + return {} async def conversion_factors_for(project_id: Any) -> dict[str, dict[str, float]]: diff --git a/B07_DesignDetail/B07_DesignDetail_Engine_Cross_Quantity.py b/B07_DesignDetail/B07_DesignDetail_Engine_Cross_Quantity.py index 0604ec3f..20344059 100644 --- a/B07_DesignDetail/B07_DesignDetail_Engine_Cross_Quantity.py +++ b/B07_DesignDetail/B07_DesignDetail_Engine_Cross_Quantity.py @@ -9,12 +9,13 @@ 표가 `source["quantities"]` 를 보는데 **그 키가 원본 파일에 아예 없다**(실측: `cross_00960m.json` 에 `samples`·`center_z`·`frame` 뿐). 반면 **저장된 횡단 설계에는 단면적이 그대로 있고**(`cut_soil_area_m2` 등), **사면길이도 그 설계선에서 유도된다** - (`B08_Quantity_Engine_SlopeLength.station_slope`). 즉 **통로만 없었다.** + (`B07_DesignDetail_Engine_SlopeGeometry.station_slope`). 즉 **통로만 없었다.** ⚠ 계산을 새로 짜지 않는다 (CLAUDE.md 5장) - 단면적은 B06 이 낸 저장값을 **그대로 읽고**, 사면 계열은 **B08 이 쓰는 그 함수**를 부른다. - 계열 이름과 「어느 면을 쓰나」도 `B08_Quantity_Engine_SlopeArea` 의 정의를 빌려 쓴다 — - 거기서 밑수가 바뀌면 이 표도 같이 움직여야 하기 때문이다. + 단면적은 B06 이 낸 저장값을 **그대로 읽고**, 사면 계열은 `_SlopeGeometry` 의 함수를 부른다. + 계열 이름과 「어느 면을 쓰나」도 그 파일의 정의를 빌려 쓴다 — 거기서 밑수가 바뀌면 이 표도 + 같이 움직여야 하기 때문이다. (옛 자리 = `old_code/B08_Quantity/…_SlopeLength`·`_SlopeArea`, + PLAN 7-3 에서 B07 로 옮겨 적음.) ⚠ 사면 계열 칸의 **단위** B08 은 측점 사이를 평균단면적법으로 적분해 **면적(㎡)** 을 내지만, 그것은 두 측점이 있어야 @@ -34,8 +35,12 @@ from __future__ import annotations from typing import Any -from B08_Quantity.B08_Quantity_Engine_SlopeArea import PROTECTION_SOURCE, _key, _length_of -from B08_Quantity.B08_Quantity_Engine_SlopeLength import station_slope +from B07_DesignDetail.B07_DesignDetail_Engine_SlopeGeometry import ( + PROTECTION_SOURCE, + length_of, + series_key, + station_slope, +) #: 저장된 설계 단면적을 그대로 옮기는 칸 — `표 키 → 설계 키`. #: ⚠ 측구 둘은 B06 이 **절토 분리와 같은 근거**(지반 유형 + 암반 경계선)로 갈라 낸 값이다 @@ -115,9 +120,9 @@ def derived_cells(chainage_m: float, design: dict[str, Any] | None) -> dict[str, slope = station_slope(float(chainage_m), design) lengths = { - _key(series, face): _length_of(slope, series, face) + series_key(series, face): length_of(slope, series, face) for _table_key, series, face in SLOPE_KEYS } for table_key, series, face in SLOPE_KEYS: - cells[table_key] = lengths[_key(series, face)] + cells[table_key] = lengths[series_key(series, face)] return cells diff --git a/B07_DesignDetail/B07_DesignDetail_Engine_SlopeGeometry.py b/B07_DesignDetail/B07_DesignDetail_Engine_SlopeGeometry.py new file mode 100644 index 00000000..23a34305 --- /dev/null +++ b/B07_DesignDetail/B07_DesignDetail_Engine_SlopeGeometry.py @@ -0,0 +1,284 @@ +"""횡단 사면 기하 — 저장된 횡단 설계선에서 절토·성토 사면 구간을 가려낸다. + +어디서 왔나 (PLAN 7-3) + 옛 `B08_Quantity_Engine_SlopeLength` · `_SlopeArea` 에 있던 것을 **B07 이 쓰는 몫만** + 옮겨 적었다. 옛 코드는 `old_code/B08_Quantity/` 에 그대로 있다(지우지 않음). + 흐름이 B07 → B08 → B09 이므로 도면(B07)이 수량(B08)을 부르던 거꾸로를 여기서 끊는다. + + 옮겨 적지 않은 것 — 면적 적분(`build_rows`·`totals`·`build_table`) · 노면 면적 + (`road_surface_area`) · 측점 묶음(`station_slopes`). 수량 쪽 몫이라 B07 이 안 쓴다. + +왜 유도하나 (B06 무접촉) + 설계 엔진이 `cut_slope_segments` 를 내기는 하나 **정본에 저장되지 않는다**. 저장되는 것은 + 화면이 보낸 설계 지정이고 조회는 저장분을 그대로 싣는다. 그래서 그 값을 원천으로 쓰면 + 사면적이 조용히 0 이 된다. 대신 **`design_line`(설계선 폴리라인) + 저장된 경사비**로 + 유도한다. 필요한 입력이 전부 정본에 있어 B06 을 고치지 않아도 된다. + +가려내는 방법 + 노체 끝(`road_edges`)에서 바깥으로 나아가며, 구간 기울기가 **저장된 설계 경사비와 맞는 + 동안**이 사면이다. 원지반은 기울기가 안 맞아 저절로 끊긴다. 2단 사면(암/토사)도 경사비가 + 달라 그대로 갈린다. + +⚠ 두 가지를 조심한다 + · **끝 조각은 딱 안 떨어진다** — 샘플 격자에 걸려 잘리면 `n=1.025` 처럼 나온다. 허용오차를 둔다. + · **지형이 우연히 같은 경사면** 사면이 길게 잡힐 수 있다. 노체에서 바깥으로 **연속**인 + 구간만 세고 끊기면 멈추는 것으로 막는다. +""" + +from __future__ import annotations + +import math +from dataclasses import dataclass +from typing import Any + +# 경사비 일치 허용오차(비율). 끝 조각이 격자에 잘려 생기는 오차를 덮는 크기다. +_RATIO_TOLERANCE = 0.12 +# 평탄부로 볼 기울기 — 소단·측구 바닥은 오름이 거의 없다. +_FLAT_RISE_M = 1e-6 + +# 법면보호공이 참조하는 계열 — 기본은 면고르기다(같은 사면길이를 쓴다). +PROTECTION_SOURCE = "face_dressing" + + +@dataclass(slots=True) +class SlopeSegment: + """사면 한 조각. `side` 는 `left`/`right`, `role` 은 `cut`/`fill`.""" + + side: str + role: str + from_offset_m: float + to_offset_m: float + rise_m: float + length_m: float + ratio: float + material: str | None = None + + +@dataclass(slots=True) +class StationSlope: + """측점 하나의 사면길이 묶음.""" + + chainage_m: float + cut_length_m: float = 0.0 + fill_length_m: float = 0.0 + #: 절토 사면길이의 토사·암 몫 — 면고르기가 9-19-1(토사)·9-19-2·3(암)으로 갈림. + #: 둘의 합이 `cut_length_m` 보다 작으면 그 차이는 **못 가른 몫**(설계 지반 프리셋 없음). + cut_soil_length_m: float = 0.0 + cut_rock_length_m: float = 0.0 + #: 층따기 밑수 — **원지반 표면**의 경사길이(m). B06 설계가 측점마다 낸다 + #: (`design.bench_cut_length_m`). + #: ⚠ **성토 비탈면 길이와 다른 면이다** — 층따기는 성토부 **아래 원지반**을 계단으로 + #: 깎는 일이라(교본 6장 4절) 비탈면이 아니라 지표면을 따라간다. + bench_cut_length_m: float = 0.0 + berm_width_m: float = 0.0 + # 성토고(m) — 성토 사면 조각들의 **수직 낙차 합**. 노면 끝에서 원지반까지 내려간 높이다. + # ⚠ 좌우가 다르면 **큰 쪽**을 쓴다. 「중심점 성토고 5m 이상」(품셈 11-3 [주]①) 판정은 + # 가장 높은 쪽이 기준이고, 양쪽을 더하면 실제보다 두 배가 된다. + fill_height_m: float = 0.0 + #: 노면 폭(m) — 노체 끝(`road_edges`) 좌우 사이. + #: 노체 끝이 저장에 없으면 `None` — 0 으로 때우지 않는다. + roadbed_width_m: float | None = None + segments: tuple[SlopeSegment, ...] = () + # 사면이 샘플 범위 끝까지 원지반을 못 만나 **면적이 잘린** 측점. + # 설계 엔진이 `slope_unclosed` 로 이미 경고하는 값을 그대로 물고 온다. + unclosed: bool = False + + +def _num(value: Any) -> float | None: + return float(value) if isinstance(value, (int, float)) else None + + +def _ratios(design: dict[str, Any]) -> dict[str, list[float]]: + """역할별로 받아들일 경사비 목록. 2단 사면이면 암·토사 둘 다 절토로 본다.""" + cut = [ + value + for value in ( + _num(design.get("cut_slope_ratio")), + _num(design.get("soil_cut_slope_ratio")), + ) + if value is not None and value > 0 + ] + fill = [value for value in (_num(design.get("fill_slope_ratio")),) if value and value > 0] + return {"cut": cut, "fill": fill} + + +def _match(ratio: float, candidates: list[float]) -> float | None: + """구간 경사비가 후보 중 하나와 맞으면 그 후보를 돌려준다.""" + for candidate in candidates: + if abs(ratio - candidate) <= max(_RATIO_TOLERANCE * candidate, _RATIO_TOLERANCE): + return candidate + return None + + +def _outward( + line: list[dict[str, float]], edge_offset: float, side: str +) -> list[tuple[float, float, float, float]]: + """노체 끝에서 **바깥으로** 향하는 구간 목록 `(시작오프셋, 끝오프셋, run, rise)`. + + 좌측은 오프셋이 커지는 쪽, 우측은 작아지는 쪽이 바깥이다(설계선 좌표 관례). + """ + points = sorted( + ((float(p["offset_m"]), float(p["elevation_m"])) for p in line), key=lambda p: p[0] + ) + if side == "left": + outer = [p for p in points if p[0] >= edge_offset] + else: + outer = [p for p in points if p[0] <= edge_offset][::-1] + return [ + ( + outer[i - 1][0], + outer[i][0], + abs(outer[i][0] - outer[i - 1][0]), + outer[i][1] - outer[i - 1][1], + ) + for i in range(1, len(outer)) + ] + + +def slope_start_offset(design: dict[str, Any], side: str) -> float | None: + """사면이 시작하는 오프셋 — 노체 끝, 측구가 있으면 **측구 바깥 끝**. + + ⚠ 이것이 없으면 **측구 바깥 벽이 사면으로 잡힌다.** 측구 벽 경사가 토사 절토비와 같으면 + 그대로 걸린다. 측구는 노체 배수 시설이지 사면이 아니므로 그 바깥 끝에서부터 세어야 한다. + """ + edges = design.get("road_edges") or {} + edge = _num((edges.get(side) or {}).get("offset_m")) + if edge is None: + return None + if not design.get("ditch_enabled"): + return edge + ditch_side = design.get("ditch_side") + if ditch_side not in (side, "both", None): + return edge + width = _num((design.get("ditch") or {}).get("top_width_m")) or 0.0 + # 좌측은 오프셋이 커지는 쪽, 우측은 작아지는 쪽이 바깥이다. + return edge + width if side == "left" else edge - width + + +def _material(design: dict[str, Any], ratio: float) -> str | None: + """경사비로 재료를 가른다 — 그린 대로 적는다(B06 `cut_slope_segments` 와 같은 규칙).""" + if not design.get("two_stage_slope"): + return None + rock = _num(design.get("cut_slope_ratio")) + soil = _num(design.get("soil_cut_slope_ratio")) + if rock is None or soil is None or abs(rock - soil) < 1e-9: + return None + return "rock" if abs(ratio - rock) < abs(ratio - soil) else "soil" + + +def _side_segments( + design: dict[str, Any], side: str, ratios: dict[str, list[float]] +) -> list[SlopeSegment]: + """한쪽 사면 구간 목록. 경사비가 안 맞는 구간을 만나면 거기서 멈춘다.""" + line = design.get("design_line") or [] + edge = slope_start_offset(design, side) + if not line or edge is None: + return [] + + berm = design.get("berm") or {} + berm_width = _num(berm.get("width_m")) or 0.0 + # 소단 안쪽 기울기(°) 만큼은 평탄부로 봄 — 기울인 소단이 비탈로 읽혀 끊기지 않게. + berm_grade = math.tan(math.radians(_num(berm.get("slope_deg")) or 0.0)) + # 설계선 표고는 넷째 자리 반올림 — 기울인 소단 조각이 그만큼 더 가파르게 읽힘. + berm_slack = 2e-4 if berm_width > 0 else 0.0 + + segments: list[SlopeSegment] = [] + started = False + # ⚠ 설계선이 격자(0.5m)로 찍혀 소단 평탄부가 **여러 조각**으로 쪼개짐 — 이어진 평탄 조각을 모아 + # 합이 소단 폭일 때만 소단으로 넘김(한 조각만 보면 첫 소단에서 사면이 끊긴다). + flat_run = 0.0 + for start, end, run, rise in _outward(line, float(edge), side): + if run <= 1e-9: + continue + if abs(rise) <= _FLAT_RISE_M + run * berm_grade + berm_slack: + # 평탄부 — 측구 바닥·소단. 사면이 시작된 뒤라면 모아 두고 다음 조각에서 판정. + if started: + flat_run += run + continue + if flat_run > 0: + if not (berm_width > 0 and abs(flat_run - berm_width) < 0.05): + break # 사면이 끝나고 평지를 만난 것이다 + flat_run = 0.0 + ratio = run / abs(rise) + # 절토는 바깥으로 갈수록 오르고, 성토는 내려간다. + role = "cut" if rise > 0 else "fill" + matched = _match(ratio, ratios[role]) + if matched is None: + if started: + break # 원지반에 닿았다 + continue # 아직 노체·측구 구간이다 + started = True + segments.append( + SlopeSegment( + side=side, + role=role, + from_offset_m=start, + to_offset_m=end, + rise_m=rise, + length_m=math.hypot(run, rise), + ratio=matched, + material=_material(design, matched), + ) + ) + return segments + + +def _roadbed_width(design: dict[str, Any]) -> float | None: + """노면 폭 — 노체 끝 좌(+)·우(−) 오프셋 사이. 한쪽이라도 없으면 `None`.""" + edges = design.get("road_edges") or {} + left = _num((edges.get("left") or {}).get("offset_m")) + right = _num((edges.get("right") or {}).get("offset_m")) + return None if left is None or right is None else abs(left - right) + + +def station_slope(chainage_m: float, design: dict[str, Any]) -> StationSlope: + """측점 하나의 사면길이. 좌우를 합쳐 절토·성토 각각의 총 사면길이를 낸다.""" + ratios = _ratios(design) + segments: list[SlopeSegment] = [] + for side in ("left", "right"): + segments.extend(_side_segments(design, side, ratios)) + berm = design.get("berm") or {} + fill_by_side = { + side: sum(abs(s.rise_m) for s in segments if s.role == "fill" and s.side == side) + for side in ("left", "right") + } + # 2단 비탈은 경사비가 토사·암을 가름 · 1단은 그 측점 설계의 지반 프리셋(토사/암)이 비탈 전체 + preset = {"soil": "soil", "rock": "rock"}.get(str(design.get("geometry_preset") or "")) + cut_by = {"soil": 0.0, "rock": 0.0} + for segment in segments: + material = segment.material or preset + if segment.role == "cut" and material in cut_by: + cut_by[material] += segment.length_m + return StationSlope( + chainage_m=float(chainage_m), + cut_length_m=sum(s.length_m for s in segments if s.role == "cut"), + cut_soil_length_m=cut_by["soil"], + cut_rock_length_m=cut_by["rock"], + fill_length_m=sum(s.length_m for s in segments if s.role == "fill"), + # ⚠ 없는 측점은 0 이다 — 성토 사면길이로 **대신 채우지 않는다**(면이 다름). + bench_cut_length_m=_num(design.get("bench_cut_length_m")) or 0.0, + fill_height_m=max(fill_by_side.values(), default=0.0), + roadbed_width_m=_roadbed_width(design), + berm_width_m=_num(berm.get("width_m")) or 0.0, + segments=tuple(segments), + unclosed=bool(design.get("slope_unclosed")), + ) + + +def series_key(series: str, face: str) -> str: + """계열·면 묶음 키. 옛 `B08_Quantity_Engine_SlopeArea._key` 와 같은 글자를 낸다.""" + return f"{series}_{face}" + + +def length_of(slope: StationSlope, series: str, face: str) -> float: + """계열·면별 「거리」 = 그 측점의 사면길이. + + 법면보호공은 면고르기를 참조한다 — 같은 사면길이를 쓴다. 끊고 싶으면 이 함수만 고친다. + 층따기는 성토면만 대상이다. + """ + if series == "bench_cut": + # ⚠ 층따기는 **원지반 표면**을 깎는 일이라 밑수가 성토 비탈면이 아니다(교본 6장 4절). + # B06 설계가 측점마다 내는 값을 그대로 쓴다. 없으면 0 — 성토 사면길이로 대신 채우면 + # **다른 면을 세게 된다**. + return slope.bench_cut_length_m if face == "fill" else 0.0 + return slope.fill_length_m if face == "fill" else slope.cut_length_m diff --git a/B08_Quantity/B08_Quantity_UI_Page.ts b/B08_Quantity/B08_Quantity_UI_Page.ts index a68a30e7..fe35956c 100644 --- a/B08_Quantity/B08_Quantity_UI_Page.ts +++ b/B08_Quantity/B08_Quantity_UI_Page.ts @@ -1,1112 +1,35 @@ /* ============================================================================= * B08_Quantity_UI_Page.ts - * 로그인 후 08: 5차 워크플로우 (수량 산출) + * 로그인 후 08: 5차 워크플로우 (수량 산출) — **빈 화면** (PLAN 7-3) * - * 우측 = 실무 수량산출서의 시트를 탭으로 옮긴 것. 지금은 **토적표** 한 장이 서 있고 - * 나머지(토적집계·구조물위치·수량집계표·총괄집계·수리계산·운반거리)는 차례로 붙인다. - * 확정 = 워크플로 stage 5(QUANTITY) 완료 처리 후 B09 설계도서로 이동. + * - 옛 수량 산출 코드는 통째로 `old_code/B08_Quantity/` 에 있음(지우지 않음). + * - 다시 짤 때까지 메뉴와 주소만 남김 — 제목·단계 막대만 뜨고 표·계산은 없음. * ========================================================================== */ -import { ui_locales, currentLanguageIndex } from "@ui/ui_template_locale"; -import { createButton, showToast } from "@ui/ui_template_elements"; -import { API_BASE_URL, CURRENT_PROJECT_ID_KEY } from "@config/config_frontend"; import { createWorkflowLayout } from "@ui/ui_template_workflow_layout"; -import { attachCollapsible } from "@ui/ui_template_collapsible"; -import { groupPanelSections } from "./B08_Quantity_UI_SidePanel_Sections"; -import { createProvenanceToggle } from "@ui/ui_template_provenance"; +import { ui_locales, currentLanguageIndex } from "@ui/ui_template_locale"; +import { CURRENT_PROJECT_ID_KEY } from "@config/config_frontend"; import { workflowSteps } from "../A00_Common/b_page_scaffold"; -import { - fetchWorkflowState, - goToWorkflowStage, - WORKFLOW_STEP_ROUTES, -} from "../A00_Common/b_workflow_nav"; -import { renderEarthworkGrid, type EarthworkTable } from "./B08_Quantity_UI_EarthworkGrid"; -import { - conversionOverridePayload, - renderConversionFactorFields, - type FactorDraft, -} from "./B08_Quantity_UI_ConversionFactors"; -import { injectEarthworkGridStyles } from "./B08_Quantity_UI_EarthworkGrid_Style"; -import { - renderHaulGrid, - renderPreparationGrid, - renderSummaryGrid, - type PreparationTable, -} from "./B08_Quantity_UI_SummaryGrid"; -import { - renderMaterialGrid, - renderUnitQuantityGrid, - type MaterialResponse, -} from "./B08_Quantity_UI_MaterialGrid"; -import { renderStructureSheets } from "./B08_Quantity_UI_StructureSheet"; -import { renderStructureSummary } from "./B08_Quantity_UI_StructureSummary"; -import { appendTreeWasteFields } from "./B08_Quantity_UI_Side_TreeWaste"; -import { appendFaceDressingFields } from "./B08_Quantity_UI_Side_FaceDressing"; -import { appendRootRemovalFields } from "./B08_Quantity_UI_Side_RootRemoval"; -import { appendSeedSprayField } from "./B08_Quantity_UI_Side_SeedSpray"; -import type { GravelDraft } from "./B08_Quantity_UI_Side_Gravel"; -import { appendGravelFields, gravelDraftFrom, gravelPayload } from "./B08_Quantity_UI_Side_Gravel"; -import { appendBenchCutFields } from "./B08_Quantity_UI_Side_BenchCut"; +import { goToWorkflowStage, WORKFLOW_STEP_ROUTES } from "../A00_Common/b_workflow_nav"; -/** locale 헬퍼 */ function L(key: keyof typeof ui_locales): string { return ui_locales[key][currentLanguageIndex]; } -/** stage 5(QUANTITY) 완료 요청 — 본문 미구현 상태의 유일한 백엔드 연동. */ -async function confirmQuantityStage(projectId: string): Promise { - const response = await fetch( - `${API_BASE_URL}/projects/${encodeURIComponent(projectId)}/quantity/confirm`, - { method: "POST", credentials: "include" }, - ); - if (!response.ok) { - throw new Error(`quantity confirm failed: ${response.status}`); - } -} - -/** 토적표를 받아 온다. 노선을 안 주면 워크플로가 보고 있는 최신 노선으로 나온다. */ -async function fetchEarthworkTable(projectId: string): Promise { - const response = await fetch( - `${API_BASE_URL}/projects/${encodeURIComponent(projectId)}/quantity/earthwork-table`, - { credentials: "include" }, - ); - if (!response.ok) throw new Error(`earthwork table failed: ${response.status}`); - return (await response.json()) as EarthworkTable; -} - -/** 구조물 원단위·자재총괄을 받아 온다. 한 번에 받는 까닭은 자재총괄이 원단위의 부분집합이라서다. */ -async function fetchMaterialSummary(projectId: string): Promise { - const response = await fetch( - `${API_BASE_URL}/projects/${encodeURIComponent(projectId)}/quantity/material-summary`, - { credentials: "include" }, - ); - if (!response.ok) throw new Error(`material summary failed: ${response.status}`); - return (await response.json()) as MaterialResponse; -} - -/** [저장] — 산출 조건을 정본에 남긴다. `quantity` 구획만 간다(서버가 막고 있다). */ -async function saveQuantitySettings(projectId: string, draft: DraftSettings): Promise { - const response = await fetch( - `${API_BASE_URL}/projects/${encodeURIComponent(projectId)}/quantity/settings`, - { - method: "PUT", - credentials: "include", - headers: { "Content-Type": "application/json" }, - body: JSON.stringify({ - rock_class_set: draft.rock_class_set ?? null, - rock_ratios_pct: draft.rock_ratios_pct, - application_ratios_pct: draft.application_ratios_pct, - // ⚠ 「안 정함」으로 되돌린 갈래까지 **통째로** 보낸다. 정한 것만 보내면 서버가 - // 병합해 옛 값이 남아 되돌릴 길이 없다(화면에서 걸린 자리). 빈 값은 서버가 버린다. - rock_methods: draft.rock_methods, - material_supply: draft.material_supply, - material_surcharge: draft.material_surcharge, - concrete_placing_method: draft.concrete_placing_method, - // ⚠ `null` 도 그대로 보낸다 — 「안 정함」으로 되돌릴 길이 있어야 한다(시공법과 같은 규칙). - topsoil_thickness_m: draft.topsoil_thickness_m, - // 확정 2차 ① · 3차 ②③④ — 값이 화면 칸에서 오고, 비면 그 줄이 막힌다. - bench_cut_depth_m: draft.bench_cut_depth_m, - rubble_base_thickness_m: draft.rubble_base_thickness_m, - spoil_site_distance_m: draft.spoil_site_distance_m, - structure_trench_water: draft.structure_trench_water, - topsoil_haul_distance_m: draft.topsoil_haul_distance_m, - // `""` 는 기본(노면 + 절토)으로 되돌림 — 서버가 None 으로 둔다. - topsoil_target: draft.topsoil_target, - stand_volume_class: draft.stand_volume_class, - root_removal_excavator_m3: draft.root_removal_excavator_m3, - seed_spray_ground: draft.seed_spray_ground, - // 면고르기 — 갈래 `""`·면적 `null` 도 그대로(「안 정함」·「파종 면적 그대로」로 되돌리는 길). - face_dressing_cut_class: draft.face_dressing_cut_class, - face_dressing_fill_class: draft.face_dressing_fill_class, - face_dressing_fill_area_m2: draft.face_dressing_fill_area_m2, - face_dressing_cut_area_m2: draft.face_dressing_cut_area_m2, - frame_material: draft.frame_material, - wood_chipping_enabled: draft.wood_chipping_enabled, - wood_chipping_volume_m3: draft.wood_chipping_volume_m3, - subgrade_compaction_enabled: draft.subgrade_compaction_enabled, - // 조사값은 **통째로** — 지운 칸까지 가야 되돌릴 길이 있다. - tree_waste: draft.tree_waste, - tree_waste_unit_price_krw_per_ton: draft.tree_waste_unit_price_krw_per_ton, - waste_separate_order: draft.waste_separate_order, - tree_waste_root_method: draft.tree_waste_root_method, - // 개소는 **통째로** 보낸다 — 지운 항목까지 그대로 가야 되돌릴 길이 있다. - ancillary_counts: draft.ancillary_counts, - // 토량환산계수 — 고른 갈래만 담긴다. 빈 dict 는 「전부 기본값으로 되돌림」이다. - conversion_factors_override: conversionOverridePayload(draft.conversion_factors), - // 도쟈 한계거리 — `null` 도 보낸다(기본값으로 되돌리는 길). - dozer_haul_limit_m: draft.dozer_haul_limit_m, - ...gravelPayload(draft), // 혼합석 부설 — 칸은 `_Side_Gravel` - }), - }, - ); - if (!response.ok) throw new Error(`quantity settings save failed: ${response.status}`); -} - -/** 좌측 패널의 한 줄 — 이름과 값. 산출 조건을 읽기 전용으로 보인다. */ -function field(label: string, value: string): HTMLElement { - const row = document.createElement("div"); - row.className = "b08-quantity__field"; - const name = document.createElement("span"); - name.textContent = label; - const amount = document.createElement("span"); - amount.className = "b08-quantity__field-value"; - amount.textContent = value; - row.append(name, amount); - return row; -} - -/** 규준틀 재료 칸 — 이름은 **서버 `FRAME_MATERIAL_SUGGESTED` 와 같은 낱말**이라야 한다. */ -const FRAME_MATERIAL_FIELDS: ReadonlyArray = [ - ["각재 50×50", "B08_Quantity_Frame_Square", "0.0001"], - ["판재 T12", "B08_Quantity_Frame_Board", "0.0001"], - ["못", "B08_Quantity_Frame_Nail", "0.01"], -]; -/** 채워 보이는 **제안값**(실무 관측). ⚠ 법정 기준이 아니라 「고치라고 보이는 값」이다. */ -const FRAME_MATERIAL_SUGGESTED: Record = { - "각재 50×50": 0.0044, - "판재 T12": 0.0029, - 못: 0.03, -}; - -/** 부대시설 항목 키 — 서버 `ANCILLARY_ITEMS` 와 **같은 차례·같은 낱말**이라야 한다. */ -const ANCILLARY_KEYS = [ - "national_point_sign", - "guide_sign", - "gate", - "site_container", - "flood_supplies", -] as const; - -/** 칸 밑에 붙는 **근거 한 줄** — 왜 그 값인지 화면에서 보이게 한다(2026-09-09 사용자 지시). */ -function hintRow(text: string): HTMLElement { - const row = document.createElement("p"); - row.className = "b08-quantity__hint"; - row.textContent = text; - return row; -} - -/** 반영률 키 → 사람이 읽는 이름. 서버 키를 그대로 보이면 설계자가 못 읽는다. */ -const RATIO_LABEL_KEYS: Record = { - fill_slope_compaction: "B08_Quantity_Ratio_FillCompaction", - seed_spray_fill: "B08_Quantity_Ratio_SeedFill", - seed_spray_cut: "B08_Quantity_Ratio_SeedCut", - obstacle_removal: "B08_Quantity_Ratio_TreeRemoval", -}; - -function ratioLabel(key: string): string { - const localeKey = RATIO_LABEL_KEYS[key]; - return localeKey ? L(localeKey) : key; -} - -/** 반영률·비율 입력 한 칸. 값은 **캐시에만** 쌓이고 [저장]에서 정본으로 간다(5장). */ -function numberField(label: string, value: number, onInput: (value: number) => void): HTMLElement { - const row = document.createElement("label"); - row.className = "b08-quantity__field"; - const name = document.createElement("span"); - name.textContent = label; - const input = document.createElement("input"); - input.type = "number"; - input.className = "b08-quantity__input"; - input.min = "0"; - input.step = "1"; - input.value = String(value); - // 자동저장은 만들지 않는다 — 입력은 캐시에만 남는다(CLAUDE.md 5장). - input.addEventListener("input", () => onInput(Number(input.value))); - row.append(name, input); - return row; -} - -/** 비워 둘 수 있는 숫자 칸 — **빈 값은 「안 정함」**이고 0 과 다르다. - * - * ⚠ `numberField` 로 두면 빈 칸이 0 으로 읽혀 「두께 0m」와 「안 정함」이 같아진다. - * 표토제거처럼 **안 정하면 줄이 아예 안 서는** 값은 그 둘을 갈라야 한다. - */ -function optionalNumberField( - label: string, - value: number | null, - step: string, - onInput: (value: number | null) => void, -): HTMLElement { - const row = document.createElement("label"); - row.className = "b08-quantity__field"; - const name = document.createElement("span"); - name.textContent = label; - const input = document.createElement("input"); - input.type = "number"; - input.className = "b08-quantity__input"; - input.min = "0"; - input.step = step; - input.placeholder = L("B08_Quantity_Unset_Placeholder"); - input.value = value === null || value === undefined ? "" : String(value); - // 자동저장은 만들지 않는다 — 입력은 캐시에만 남는다(CLAUDE.md 5장). - input.addEventListener("input", () => { - const text = input.value.trim(); - onInput(text === "" ? null : Number(text)); - }); - row.append(name, input); - return row; -} - -/** 타설 방식 표기 — 코드가 아니라 사람이 읽는 이름으로 보인다. */ -const PLACING_LABELS: Record = { - ready_mixed: "레디믹스트", - machine_mixed: "기계비빔", - hand_mixed: "인력비빔", -}; - -/** 고르는 칸. 첫 보기는 **「안 정함」**이고 그것이 기본이다 — 찍으면 값이 조용히 틀린다. */ -function selectField( - label: string, - value: string, - options: { value: string; label: string }[], - onChange: (value: string) => void, -): HTMLElement { - const row = document.createElement("label"); - row.className = "b08-quantity__field"; - const name = document.createElement("span"); - name.textContent = label; - const select = document.createElement("select"); - select.className = "b08-quantity__input"; - for (const option of options) { - const element = document.createElement("option"); - element.value = option.value; - element.textContent = option.label; - select.append(element); - } - select.value = value; - // 자동저장은 만들지 않는다 — 고른 값은 캐시에만 남는다(CLAUDE.md 5장). - select.addEventListener("change", () => onChange(select.value)); - row.append(name, select); - return row; -} - -/** 지반 종류 표기 — 서버 키가 화면에 새지 않게. 모르는 키는 그대로 보인다. */ -/** 자재 한 줄의 관급/사급. `install_by` 는 **관급 줄에만** 뜻이 있다. */ -export interface SupplyChoice { - supply: string; - install_by: string | null; -} - -/** 저장 전 변경분 — 조작은 여기 쌓이고 [저장]에서만 정본으로 간다. */ -interface DraftSettings extends GravelDraft { - rock_class_set?: string; - rock_ratios_pct: Record; - application_ratios_pct: Record; - // 갈래별 시공법 — `""` 는 「안 정함」이고 저장에서 빠진다. - rock_methods: Record; - // 콘크리트 타설 방식 — `""` 는 「안 정함」이고 저장에서 지워진다. - concrete_placing_method: string; - // 표토 두께(m) — `null` 은 「안 정함」. 정해야 표토제거 줄이 선다(품셈 9-15 [주]② 의 T). - topsoil_thickness_m: number | null; - // 층따기 길이(m) — 면적 × 이 값 = ㎥ (확정 2차 ①). `null` 이면 층따기 줄이 막힌다. - bench_cut_depth_m: number | null; - // 기초잡석 두께(m) — 확정 3차 ② 0.2. 폭은 버림 폭과 같다(KCS 34 50 05). - rubble_base_thickness_m: number | null; - // 사토장까지 거리(m) — 현장값. `null` 이면 사토 운반 줄이 막힌다(확정 3차 ③ 는 미정). - spoil_site_distance_m: number | null; - // 구조물터파기 용수 — "육상"·"용수". ⚠ 「육상」은 **통상값**이지 사용자 확정이 아니다. - structure_trench_water: string; - // 표토 운반거리(m) — 별표2 가 요구하는 운반·적치의 밑수. `null` 은 「안 정함」. - topsoil_haul_distance_m: number | null; - // 표토제거 대상 — `""` 기본(노면 + 절토, 별표2 문언) · "road_only" 노면만(설계자 선택). - topsoil_target: string; - // 부대시설 개소 — `{항목키: 개소}`. ⚠ 산식으로 만들지 않는다(확정 13). - ancillary_counts: Record; - // 임목축적 등급 — "소림"·"중림"·"밀림". ⚠ 본수가 아니라 축적이다(품셈 9-21 [주]①). - stand_volume_class: string; - // 제근 굴착기 크기 — "0.2"·"0.7"(서버 선택지) · `""` 는 「안 정함」. 칸은 `_Side_RootRemoval`. - root_removal_excavator_m3: string; - // 초류종자살포 비탈면 토질 — "일반"·"마사토"(5-24 잎) · `""` 는 「안 정함」. 칸은 `_Side_SeedSpray`. - seed_spray_ground: string; - // 면고르기 — 갈래 둘(서버 선택지) · 면적 덮어쓰기 둘(`null` = 파종 면적). 칸은 `_Side_FaceDressing`. - face_dressing_cut_class: string; - face_dressing_fill_class: string; - face_dressing_fill_area_m2: number | null; - face_dressing_cut_area_m2: number | null; - // 규준틀 개소당 재료 — 비우면 **제안값(실무 관측)**이 선다. 값이 아니라 「고칠 수 있음」이 요점. - frame_material: Record; - // 임목파쇄 — **기본 꺼짐**(확정 5차 5번). 켜야 줄이 선다. 근주이식은 칸 자체가 없다. - wood_chipping_enabled: boolean; - wood_chipping_volume_m3: number | null; - // 노체다짐 — **기본 꺼짐**(9-16-2 [주]⑤ 조건부). 켜면 토공집계에 별도 줄(수량 = 성토량). - subgrade_compaction_enabled: boolean; - // 임목폐기물 — 조사값 넷 · 수동 처리단가(미확정) · 분리발주(기본 아님). - tree_waste: Record; - tree_waste_unit_price_krw_per_ton: number | null; - waste_separate_order: boolean; - tree_waste_root_method: string; - // 자재별 관급/사급 — 표 안에서 줄마다 고른 값. - material_supply: Record; - // 자재별 할증률(%) — 표값 이내에서 고른 값만 · `null` 은 표값. - material_surcharge: Record; - // 갈래별 토량환산계수(다짐) — `compacted` 가 `null` 이면 「안 고름」이라 기본값이 선다. - conversion_factors: Record; - // 도쟈 한계거리(m) — `null` 은 기본값(서버 정본 60 m)이 선다. - dozer_haul_limit_m: number | null; - dirty: boolean; -} - -/** 개발 전용 「확정 없이 다음으로」 한 줄 — 단추 둘 + 지금 상태 안내. - * - * ⚠ **조용히 넘어가지 않는다.** 우회로 열린 상태면 「확정을 건너뛴 상태입니다」를 띄운다 — - * 안 그러면 다음 사람이 「왜 값이 없나」로 헤맨다. - * ⚠ **되돌리는 단추를 같은 줄에 둔다.** 되돌릴 길이 안 보이면 검증용 프로젝트가 굳는다. - */ -function devUnlockRow(projectId: string, reload: () => void): HTMLElement { - const row = document.createElement("div"); - row.className = "b08-quantity__dev"; - - const title = document.createElement("p"); - title.className = "b08-quantity__note"; - title.textContent = L("B08_Quantity_Dev_Title"); - row.append(title); - - const state = document.createElement("p"); - state.className = "b08-quantity__note"; - row.append(state); - - const paint = (): void => { - fetch(`/api/projects/${projectId}/dev/unlock`, { credentials: "include" }) - .then((response) => (response.ok ? response.json() : null)) - .then((body: { bypassed_stages?: number[] } | null) => { - const stages = body?.bypassed_stages ?? []; - state.textContent = stages.length - ? `${L("B08_Quantity_Dev_Bypassed")} (${stages.join(", ")})` - : L("B08_Quantity_Dev_Normal"); - }) - .catch(() => { - state.textContent = L("B08_Quantity_Dev_Normal"); - }); - }; - paint(); - - const call = (method: "POST" | "DELETE", button: HTMLButtonElement): void => { - button.disabled = true; - fetch(`/api/projects/${projectId}/dev/unlock`, { method, credentials: "include" }) - .then((response) => { - if (!response.ok) throw new Error(String(response.status)); - showToast(L("B08_Quantity_Dev_Done"), "success"); - paint(); - reload(); - }) - .catch(() => showToast(L("B08_Quantity_Dev_Failed"), "error")) - .finally(() => { - button.disabled = false; - }); - }; - - const unlock = createButton({ - label: L("B08_Quantity_Dev_Unlock"), - variant: "ghost", - onClick: () => call("POST", unlock), - }); - const relock = createButton({ - label: L("B08_Quantity_Dev_Relock"), - variant: "ghost", - onClick: () => call("DELETE", relock), - }); - const buttons = document.createElement("div"); - // ⚠ 공용 `ui-sidebar-actions` 를 쓰지 않는다 — 공용 코드가 **첫 번째** 그 클래스를 - // 패널 바닥 액션 줄로 집어(`ui_template_overlay.ts` splitSidebarActions), - // 스크롤 영역이 이 줄 안에 갇히고 아래 칸들이 통째로 잘린다(2026-09-12). - buttons.className = "b08-quantity__dev-actions"; - buttons.append(unlock, relock); - row.append(buttons); - return row; -} - -/** 좌측 패널: 산출 조건 + 하단 [저장]·[확정] 액션 행. - * `reload` 는 저장 뒤 표를 다시 그리는 손잡이다 — 조건이 바뀌면 집계·운반 값이 달라진다. */ -function buildQuantitySidePanel( - projectId: string | null, - table: EarthworkTable | null, - draft: DraftSettings, - reload: () => void, -): HTMLElement { - const panel = document.createElement("div"); - panel.className = "b08-quantity__panel"; - - // ── 개발 전용 「확정 없이 다음으로」 ──────────────────────────────────────── - // ⚠ **맨 위에 둔다.** 패널이 `overflow: hidden` 이라 아래에 붙이면 화면 밖으로 - // 밀려 **눌리지 않는다**(2026-09-08 화면에서 실제로 그랬다 — 단추 y=772, - // 패널 높이 740). 상태 경고이기도 하니 자리도 여기가 맞다. - // ⚠ **이것은 보조 문일 뿐이다.** 진짜 문은 서버가 `ENVIRONMENT` 로 막는다 - // (`common_util_dev_unlock`). 화면만 숨기면 API 는 그대로 뚫려 있다. - // ⚠ **계산을 대신 돌리지 않는다** — 워크플로 잠금만 푼다. 값이 비어 보이는 것은 - // 정상이고, 그것을 「미확보」로 보이는 것이 이 화면이 이미 하는 일이다. - if (import.meta.env.DEV && projectId) { - panel.append(devUnlockRow(projectId, reload)); - } - - panel.append(field(L("B08_Quantity_Side_Method"), L("B08_Quantity_Side_Method_Value"))); - // 토량환산계수 — **고를 수 있는 값**이다(오솔길 대조 06절 3번). 기본값 정의처는 서버 한 곳이고, - // 화면은 고른 값만 보낸다. 유토곡선·운반표·기초단가가 같이 읽는다는 안내도 그 칸이 낸다. - const factorFields = renderConversionFactorFields( - table?.conversion_factor_choices, - table?.conversion_factor_pumsem_ranges, - draft.conversion_factors, - () => { - draft.dirty = true; - }, - ); - if (factorFields) panel.append(factorFields); - - // ── 운반장비 거리 경계 — 도쟈 한계거리는 설계 조건이라 칸으로(2026-09-13 판정) ── - // ⚠ 기본값과 근거를 **서버가 준 그대로** 옆에 보인다 — 「이 값이 어디서 왔나」가 화면에 있어야 함. - const haul = table?.haul_limit_choice; - if (haul) { - panel.append(field(L("B08_Quantity_Side_HaulLimits"), "")); - const dozer = optionalNumberField( - L("B08_Quantity_Side_DozerLimit_Label"), - draft.dozer_haul_limit_m, - "5", - (value) => { - draft.dozer_haul_limit_m = value; - draft.dirty = true; - }, - ); - const input = dozer.querySelector("input"); - if (input) input.placeholder = String(haul.default); - panel.append(dozer); - panel.append(hintRow(`${L("B08_Quantity_Factor_Default")} ${haul.default} m — ${haul.basis}`)); - panel.append( - hintRow(`${L("B08_Quantity_Side_FreeHaul")} ${haul.free_haul_m} m — ${haul.free_haul_basis}`), - ); - panel.append(hintRow(L("B08_Quantity_Side_HaulLimits_Reach"))); - } - - // ── 지반 구성비 — 갈래 수는 프로젝트 세트가 정한다(코드에 안 박음, PLAN 8-13) ── - const classes = [...(table?.summary?.rock_classes ?? [])]; - // ⚠ 비율을 아직 안 넣었으면 집계가 **「암」 한 줄**로 나온다(갈래로 안 갈림). 그 줄에도 - // 시공법을 정할 수 있어야 공종이 선다 — 그때만 칸을 하나 더 낸다. - const hasRockFallback = (table?.summary?.rows ?? []).some((row) => row.item === "암"); - if (hasRockFallback && !classes.includes("암")) classes.push("암"); - if (classes.length) { - panel.append(field(L("B08_Quantity_Side_RockRatios"), "")); - for (const name of classes) { - // 「암」은 비율을 넣으면 사라지는 되메움 줄이라 비율 칸을 두지 않는다. - // 「토사」도 비율 칸을 두지 않는다 — 토사 물량은 토적표의 흙깎기 값이 그대로 서고, - // 비율은 **암 총량을 갈래로 나누는 데만** 쓰인다(집계 엔진 `_rock_split`). - // 칸을 두면 넣은 값이 조용히 버려져 「입력 합 60 %」 같은 안내가 뜬다(2026-09-08 통과). - if (name !== "암" && name !== "토사") { - panel.append( - numberField(name, draft.rock_ratios_pct[name] ?? 0, (value) => { - draft.rock_ratios_pct[name] = value; - draft.dirty = true; - }), - ); - } - // ⚠ 암 갈래는 **시공법까지 정해야** 공종이 갈린다 — 품셈이 긁어내기(암절취)와 - // 터뜨리기(발파암)를 다른 공종으로 두기 때문이다. 「토사」에는 안 붙인다. - if (name !== "토사") { - panel.append( - selectField( - ` ${name} ${L("B08_Quantity_Side_Method_Label")}`, - draft.rock_methods[name] ?? "", - [ - { value: "", label: L("B08_Quantity_Method_Unset") }, - { value: "ripping", label: L("B08_Quantity_Method_Ripping") }, - { value: "blasting", label: L("B08_Quantity_Method_Blasting") }, - ], - (value) => { - draft.rock_methods[name] = value; - draft.dirty = true; - }, - ), - ); - } - } - } - - // ── 반영률 — 기본 100 %. 실무 관측 80/50/80 은 기본값이 아니다(PLAN 8-11) ── - const ratios = table?.settings?.application_ratios_pct ?? {}; - if (Object.keys(ratios).length) { - panel.append(field(L("B08_Quantity_Side_Ratios"), "")); - for (const key of Object.keys(ratios)) { - panel.append( - numberField(ratioLabel(key), draft.application_ratios_pct[key] ?? 100, (value) => { - draft.application_ratios_pct[key] = value; - draft.dirty = true; - }), - ); - } - } - - // ── 초류종자살포 토질 · 면고르기 — 밑수가 파종 면적이라 반영률 바로 밑. 선택지는 서버 목록 그대로. - const sideHelpers = { field, optionalNumberField, selectField, hintRow }; - appendSeedSprayField(panel, draft, table?.seed_spray_choices, sideHelpers); - appendFaceDressingFields(panel, draft, table?.face_dressing_choices, sideHelpers); - - // ── 표토 두께 — 표토 운반 부피(제거 ㎡ × T)에 씀 · 제거 줄은 ㎡ 라 안 곱함(2026-09-13 「실무대로」) ── - // ⚠ 2026-09-08 ㉘ 자기 감사: 서버·엔진은 이 값을 받고 있었는데 **화면에 넣을 칸이 없었다.** - // 「죽은 칸」(넣어도 안 쓰임)의 반대 짝이다 — 쓰이는데 넣을 데가 없던 자리. - panel.append(field(L("B08_Quantity_Side_Topsoil"), "")); - // 대상 — 기본은 법 문언(노면 + 절토). 영월 한 건이 노면만이라 3배 차이 → 설계자가 고름(2026-09-14). - panel.append( - selectField( - L("B08_Quantity_Side_TopsoilTarget_Label"), - draft.topsoil_target, - [ - { value: "", label: L("B08_Quantity_TopsoilTarget_RoadAndCut") }, - { value: "road_only", label: L("B08_Quantity_TopsoilTarget_RoadOnly") }, - ], - (value) => { - draft.topsoil_target = value; - draft.dirty = true; - }, - ), - ); - panel.append(hintRow(L("B08_Quantity_Side_TopsoilTarget_Hint"))); - panel.append( - optionalNumberField( - L("B08_Quantity_Side_Topsoil_Label"), - draft.topsoil_thickness_m, - "0.01", - (value) => { - draft.topsoil_thickness_m = value; - draft.dirty = true; - }, - ), - ); - - // ── 구조물·사토 — 확정 2차 ① · 3차 ②③④ 의 값들 ────────────────────── - // ⚠ 사용자 지시(2026-09-09): **값을 코드에 박고 끝내지 말고 화면에 칸으로 세우고 - // 지금 값과 근거를 보이고 바꿀 수 있게 할 것.** 정한 값이 화면에 안 보이면 다음 사람이 - // 왜 그 값인지 모른다. - panel.append(field(L("B08_Quantity_Side_Structure"), "")); - // 층따기 칸 · KDS 근거 줄 · [제안값 넣기] — 누른 때만 채움(교본 6-4 「설계도서에 명시」). - appendBenchCutFields(panel, draft, { field, optionalNumberField, selectField, hintRow }); - panel.append( - optionalNumberField( - L("B08_Quantity_Side_Rubble_Label"), - draft.rubble_base_thickness_m, - "0.05", - (value) => { - draft.rubble_base_thickness_m = value; - draft.dirty = true; - }, - ), - ); - panel.append(hintRow(L("B08_Quantity_Side_Rubble_Hint"))); - panel.append( - optionalNumberField( - L("B08_Quantity_Side_SpoilDistance_Label"), - draft.spoil_site_distance_m, - "10", - (value) => { - draft.spoil_site_distance_m = value; - draft.dirty = true; - }, - ), - ); - panel.append(hintRow(L("B08_Quantity_Side_SpoilDistance_Hint"))); - panel.append( - selectField( - L("B08_Quantity_Side_Water_Label"), - draft.structure_trench_water, - [ - { value: "", label: L("B08_Quantity_Water_Unset") }, - { value: "육상", label: L("B08_Quantity_Water_Dry") }, - { value: "용수", label: L("B08_Quantity_Water_Wet") }, - ], - (value) => { - draft.structure_trench_water = value; - draft.dirty = true; - }, - ), - ); - panel.append(hintRow(L("B08_Quantity_Side_Water_Hint"))); - panel.append( - optionalNumberField( - L("B08_Quantity_Side_TopsoilHaul_Label"), - draft.topsoil_haul_distance_m, - "10", - (value) => { - draft.topsoil_haul_distance_m = value; - draft.dirty = true; - }, - ), - ); - panel.append(hintRow(L("B08_Quantity_Side_TopsoilHaul_Hint"))); - panel.append( - selectField( - L("B08_Quantity_Side_StandVolume_Label"), - draft.stand_volume_class, - [ - { value: "", label: L("B08_Quantity_StandVolume_Unset") }, - { value: "소림", label: L("B08_Quantity_StandVolume_Low") }, - { value: "중림", label: L("B08_Quantity_StandVolume_Mid") }, - { value: "밀림", label: L("B08_Quantity_StandVolume_High") }, - ], - (value) => { - draft.stand_volume_class = value; - draft.dirty = true; - }, - ), - ); - panel.append(hintRow(L("B08_Quantity_Side_StandVolume_Hint"))); - appendRootRemovalFields(panel, draft, table?.root_removal_excavator_choices, { - field, - optionalNumberField, - selectField, - hintRow, - }); - - // ── 부대시설 개소 — ⚠ **산식으로 만들지 않는다**(확정 13). 넣어야 줄이 선다 ── - panel.append(field(L("B08_Quantity_Side_Ancillary"), "")); - for (const key of ANCILLARY_KEYS) { - panel.append( - optionalNumberField( - L(`B08_Quantity_Ancillary_${key}` as keyof typeof ui_locales), - draft.ancillary_counts[key] ?? null, - "1", - (value) => { - draft.ancillary_counts[key] = value; - draft.dirty = true; - }, - ), - ); - } - panel.append(hintRow(L("B08_Quantity_Side_Ancillary_Hint"))); - - // ── 규준틀 재료 — ⚠ **세는 것은 확정이고 수량만 모르던 자리**(품셈 [주]④ 「설계수량에 따른다」). - // 그래서 **제안값을 채워 보이고 고칠 수 있게** 둔다(확정 ⑨·⑩ 과 같은 틀). - // ⚠ 값만 박고 근거를 안 보이면 사용자 지시(「대신 페이지에 남길 것」)를 어기는 것이라 - // 칸 밑에 **관측값임**과 **손율 원문값**을 함께 적는다. - panel.append(field(L("B08_Quantity_Side_Frame"), "")); - for (const [key, label, step] of FRAME_MATERIAL_FIELDS) { - panel.append( - optionalNumberField( - L(label as keyof typeof ui_locales), - draft.frame_material[key] ?? FRAME_MATERIAL_SUGGESTED[key], - step, - (value) => { - draft.frame_material[key] = value; - draft.dirty = true; - }, - ), - ); - } - panel.append(hintRow(L("B08_Quantity_Side_Frame_Hint"))); - - // ── 임목파쇄 — ⚠ **기본 꺼짐**(확정 5차 5번). 「셀지 말지가 설계 판단」이라 켜야 줄이 선다. - // ⚠ 근주이식은 **칸 자체를 안 만든다** — 켤 자리가 없으면 물을 일도 없다. - panel.append(field(L("B08_Quantity_Side_Chipping"), "")); - panel.append( - selectField( - L("B08_Quantity_Chipping_Label"), - draft.wood_chipping_enabled ? "on" : "", - [ - { value: "", label: L("B08_Quantity_Chipping_Off") }, - { value: "on", label: L("B08_Quantity_Chipping_On") }, - ], - (value) => { - draft.wood_chipping_enabled = value === "on"; - draft.dirty = true; - }, - ), - ); - panel.append( - optionalNumberField( - L("B08_Quantity_Chipping_Volume"), - draft.wood_chipping_volume_m3, - "1", - (value) => { - draft.wood_chipping_volume_m3 = value; - draft.dirty = true; - }, - ), - ); - panel.append(hintRow(L("B08_Quantity_Side_Chipping_Hint"))); - - // ── 노체다짐 — ⚠ **기본 꺼짐**(2026-09-13 판정). 9-16-2 [주]⑤ 조건부라 켜야 토공집계에 줄이 선다. - panel.append(field(L("B08_Quantity_Side_SubgradeCompaction"), "")); - panel.append( - selectField( - L("B08_Quantity_SubgradeCompaction_Label"), - draft.subgrade_compaction_enabled ? "on" : "", - [ - { value: "", label: L("B08_Quantity_Chipping_Off") }, - { value: "on", label: L("B08_Quantity_Chipping_On") }, - ], - (value) => { - draft.subgrade_compaction_enabled = value === "on"; - draft.dirty = true; - }, - ), - ); - panel.append(hintRow(L("B08_Quantity_Side_SubgradeCompaction_Hint"))); - // ── 혼합석 부설 — 법령 조건 자동 · 연약·습윤 칸 · 전 구간 고르개(2026-09-15 브레인 ⓑ). - appendGravelFields(panel, draft, table?.gravel, { - field, - optionalNumberField, - selectField, - hintRow, - }); - - // ── 임목폐기물 — 조사값 넷 · 수동 처리단가 · 분리발주(2026-09-14). 칸은 따로 뺀 파일에서. - appendTreeWasteFields(panel, draft, { field, optionalNumberField, selectField, hintRow }); - - // ── 콘크리트 타설 방식 — ⚠ **금액에 바로 걸리는 값**이라 잠정임을 조용히 두지 않는다 ── - panel.append(field(L("B08_Quantity_Side_Placing"), "")); - panel.append( - selectField( - L("B08_Quantity_Side_Placing_Label"), - draft.concrete_placing_method, - [ - { value: "", label: L("B08_Quantity_Placing_Unset") }, - { value: "ready_mixed", label: L("B08_Quantity_Placing_Ready") }, - { value: "machine_mixed", label: L("B08_Quantity_Placing_Machine") }, - { value: "hand_mixed", label: L("B08_Quantity_Placing_Hand") }, - ], - (value) => { - draft.concrete_placing_method = value; - draft.dirty = true; - }, - ), - ); - // ⚠ `?.` 이 빠지면 표를 못 받은 때(`table === null`) 여기서 터져 **페이지가 통째로 - // 백지**가 된다 — 정작 보여야 할 「표를 못 불렀다」 안내까지 같이 사라진다(2026-09-12 실측). - const placing = ( - table as unknown as { - concrete_placing?: { - method: string; - is_default: boolean; - price_hint?: { basis?: string; values?: Record }; - }; - } | null - )?.concrete_placing; - if (placing) { - // ⚠ 방식 이름을 **늘** 값 옆에 보인다 — 코드(`12-01-01`)만으로는 무엇을 쓰는지 모른다. - const label = PLACING_LABELS[placing.method] ?? placing.method; - panel.append( - field( - L("B08_Quantity_Placing_Current"), - placing.is_default ? `${label} (${L("B08_Quantity_Placing_Default_Tag")})` : label, - ), - ); - // 2026-09-08 ㉙: 인계가 **타설 공종 줄을 실제로 세운다.** 겹치지 않는 것이 확인됐다 — - // 품셈 12-1-1 표는 직종·품만 주고 재료를 안 줘서, 품은 이 줄 · 재료는 자재 쪽이다. - // ⚠ 돌쌓기 뒤채움(채움콘크리트)은 뺐다 — 그 공종 품에 이미 들어 있을 수 있다. - // ⚠ **기본값일 때는 싣지 않는다** — 바로 아래 「기본값으로 계산 중」 경고가 더 많은 것을 - // 말하는데, 둘을 겹쳐 실으면 긴 문구가 그 경고를 화면 밖으로 밀어낸다 - // (2026-09-08 ㉕ 화면 통과에서 실제로 잘려 있었다). - if (!placing.is_default) { - const applied = document.createElement("p"); - applied.className = "b08-quantity__note"; - applied.textContent = L("B08_Quantity_Placing_NotApplied"); - panel.append(applied); - } - } - // ⚠ 「정하면 얼마나 달라지는지」까지 보여야 사용자가 판단한다. 이 값은 **참고 표시 전용**이고 - // B08 의 어떤 계산에도 안 들어간다(금액은 B09 몫). - const hint = placing?.price_hint; - if (hint?.values) { - const line = document.createElement("p"); - line.className = "b08-quantity__notice"; - const parts = Object.entries(hint.values).map( - ([key, value]) => - `${PLACING_LABELS[key] ?? key} ${Math.round(value).toLocaleString("ko-KR")}원`, - ); - line.textContent = `${L("B08_Quantity_Placing_Hint")} ${hint.basis ?? ""} — ${parts.join(" · ")}`; - panel.append(line); - } - if (placing?.is_default) { - // 「확인 필요」만 있으면 무엇을 정해야 하는지 모른다 — **지금 무엇으로 돌고 있는지**를 함께 적는다. - const notice = document.createElement("p"); - notice.className = "b08-quantity__notice"; - notice.textContent = L("B08_Quantity_Placing_Default_Notice"); - panel.append(notice); - } - - const saveButton = createButton({ - label: L("B08_Quantity_Btn_Save"), - variant: "ghost", - onClick: () => { - if (!projectId) { - showToast(L("B08_Quantity_Save_Failed"), "error"); - return; - } - saveButton.disabled = true; - saveQuantitySettings(projectId, draft) - .then(() => { - draft.dirty = false; - showToast(L("B08_Quantity_Save_Success"), "success"); - // 조건이 바뀌면 집계·운반 값이 달라진다 — 표를 다시 받아 그린다. - reload(); - }) - .catch(() => { - showToast(L("B08_Quantity_Save_Failed"), "error"); - saveButton.disabled = false; - }); - }, - }); - - const confirmButton = createButton({ - label: L("B08_Quantity_Btn_Confirm"), - variant: "filled", - onClick: () => { - if (!projectId) { - showToast(L("B08_Quantity_Confirm_Failed"), "error"); - return; - } - confirmButton.disabled = true; - confirmQuantityStage(projectId) - .then(() => { - showToast(L("B08_Quantity_Confirm_Success"), "success"); - goToWorkflowStage(projectId, WORKFLOW_STEP_ROUTES[6]); - }) - .catch(() => { - showToast(L("B08_Quantity_Confirm_Failed"), "error"); - confirmButton.disabled = false; - }); - }, - }); - - const actions = document.createElement("div"); - actions.className = "b08-quantity__actions ui-sidebar-actions"; - // [초기화]는 이번에 달지 않는다 — 5장의 [초기화]는 초기값(`initial_snapshot/`)을 작업본에 - // 덮어쓰는 것인데 설정에는 대응하는 초기값이 아직 없다. 재계산 단추로 오해될 자리다. - // TODO(미결) — 설정의 초기값을 무엇으로 볼지 사용자 확인 뒤에 붙인다. - actions.append(saveButton, confirmButton); - panel.append(actions); - - // 조건 칸을 B03~B07 공통 상자로 묶고 제목 클릭으로 접히게 한다. - groupPanelSections(panel); - attachCollapsible(panel); - return panel; -} - -/** 우측 본문 — 시트 탭 + 고른 장의 표. 실무 산출서의 시트를 탭으로 옮긴 것이다. */ -function buildQuantityBody( - table: EarthworkTable | null, - failed: boolean, - material: MaterialResponse | null, - draft: DraftSettings, - projectId: string | null, -): HTMLElement { - const body = document.createElement("div"); - body.className = "b08-quantity__body"; - - const tabs = document.createElement("div"); - tabs.className = "b08-quantity__tabs"; - const pane = document.createElement("div"); - pane.className = "b08-quantity__pane"; - - const message = (text: string): HTMLElement => { - const element = document.createElement("p"); - element.className = "b08-quantity__message"; - element.textContent = text; - return element; - }; - - // 등급색 토글 — ⚠ **사전이 왔을 때만** 만든다(개발환경). 배포 빌드에서는 단추 자체가 없다. - // ⚠ 만들기만 하고 **붙이는 것은 탭 단추를 다 세운 뒤**이다 — 여기서 바로 붙이면 - // 탭 줄 **맨 앞**에 서서 「토적표」 앞에 탭 하나가 더 있는 것처럼 보인다(2026-09-12 실측: x=8). - const provenanceToggle = - (table as unknown as { provenance?: unknown } | null)?.provenance || - (material as unknown as { provenance?: unknown } | null)?.provenance - ? createProvenanceToggle(body) - : null; - - if (failed) { - body.append(tabs, message(L("B08_Quantity_Grid_Failed"))); - return body; - } - if (!table || !table.rows?.length) { - body.append(tabs, message(L("B08_Quantity_Grid_Empty"))); - return body; - } - - const sheets: { label: string; build: () => HTMLElement }[] = [ - { label: L("B08_Quantity_Tab_Earthwork"), build: () => renderEarthworkGrid(table) }, - { - label: L("B08_Quantity_Tab_Summary"), - build: () => - table.summary - ? renderSummaryGrid(table.summary, table.provenance?.sheets?.summary) - : message(L("B08_Quantity_Grid_Empty")), - }, - { - label: L("B08_Quantity_Tab_Haul"), - build: () => - table.haul - ? renderHaulGrid( - table.haul, - Boolean(table.haul_available), - table.provenance?.sheets?.haul, - ) - : message(L("B08_Quantity_Haul_Missing")), - }, - { - label: L("B08_Quantity_Tab_Preparation"), - build: () => { - const preparation = (table as unknown as { preparation?: PreparationTable }).preparation; - return preparation - ? renderPreparationGrid(preparation, table.provenance?.sheets?.preparation) - : message(L("B08_Quantity_Grid_Empty")); - }, - }, - // 구조물도 — 제원 조합 하나 = 한 장(PLAN 3장). 스스로 받아 오므로 표를 넘기지 않음. - // ⚠ 탭 차례 바꾸기·원단위 탭 흡수는 PLAN 5장 몫 — 여기서는 원단위 앞에 붙이기만 함. - // 구조물 집계표 — 측점별 한 줄(PLAN 2장). 구조물도의 앞 사슬이라 그 바로 앞에 둠. - { - label: L("B08_Quantity_Tab_StructureSummary"), - build: () => renderStructureSummary(projectId), - }, - { label: L("B08_Quantity_Tab_StructureSheet"), build: () => renderStructureSheets(projectId) }, - { - label: L("B08_Quantity_Tab_UnitQuantity"), - build: () => - material ? renderUnitQuantityGrid(material) : message(L("B08_Quantity_Material_Failed")), - }, - { - label: L("B08_Quantity_Tab_Material"), - build: () => - material - ? renderMaterialGrid( - material.material, - { - choices: draft.material_supply, - surcharge: draft.material_surcharge, - onChange: () => { - draft.dirty = true; - }, - }, - material.provenance?.sheets?.material, - ) - : message(L("B08_Quantity_Material_Failed")), - }, - ]; - - const buttons: HTMLButtonElement[] = []; - const show = (index: number): void => { - buttons.forEach((button, i) => button.classList.toggle("is-active", i === index)); - pane.replaceChildren(sheets[index].build()); - }; - sheets.forEach((sheet, index) => { - const button = document.createElement("button"); - button.type = "button"; - button.className = "b08-quantity__tab"; - button.textContent = sheet.label; - button.addEventListener("click", () => show(index)); - buttons.push(button); - tabs.append(button); - }); - // 토글은 탭 줄 **끝**에 둔다 — 표를 고르는 단추가 아니라 보기 방식을 바꾸는 단추라 섞이면 안 된다. - if (provenanceToggle) tabs.append(provenanceToggle); - - body.append(tabs, pane); - show(0); - return body; -} - -/* ----------------------------------------------------------------------------- - * 페이지 진입점 - * -------------------------------------------------------------------------- */ -export async function renderB08Quantity(root: HTMLElement): Promise { - injectEarthworkGridStyles(); +export function renderB08Quantity(root: HTMLElement): void { const projectId = localStorage.getItem(CURRENT_PROJECT_ID_KEY); - // 표는 한 번만 받아 좌측 패널(계수 표시)과 우측 그리드가 함께 쓴다. - let table: EarthworkTable | null = null; - let material: MaterialResponse | null = null; - let failed = false; - if (projectId) { - try { - table = await fetchEarthworkTable(projectId); - } catch { - failed = true; - } - // 자재총괄은 따로 받는다 — 구조물이 없어도 토적표는 서야 하므로 실패를 옮기지 않는다. - try { - material = await fetchMaterialSummary(projectId); - } catch { - material = null; - } - } - - // 저장 전 변경분 — 조작은 여기 쌓이고 [저장]에서만 정본으로 간다(CLAUDE.md 5장). - const stored = table?.settings ?? {}; - const draft: DraftSettings = { - rock_class_set: stored.rock_class_set, - rock_ratios_pct: { ...(stored.rock_ratios_pct ?? {}) }, - application_ratios_pct: { ...(stored.application_ratios_pct ?? {}) }, - rock_methods: { ...((stored.rock_methods ?? {}) as Record) }, - concrete_placing_method: (stored.concrete_placing_method as string) ?? "", - topsoil_thickness_m: (stored.topsoil_thickness_m as number | null) ?? null, - bench_cut_depth_m: (stored.bench_cut_depth_m as number | null) ?? null, - rubble_base_thickness_m: (stored.rubble_base_thickness_m as number | null) ?? null, - spoil_site_distance_m: (stored.spoil_site_distance_m as number | null) ?? null, - structure_trench_water: (stored.structure_trench_water as string) ?? "", - topsoil_haul_distance_m: (stored.topsoil_haul_distance_m as number | null) ?? null, - topsoil_target: (stored.topsoil_target as string) ?? "", - stand_volume_class: (stored.stand_volume_class as string) ?? "", - root_removal_excavator_m3: (stored.root_removal_excavator_m3 as string) ?? "", - seed_spray_ground: (stored.seed_spray_ground as string) ?? "", - face_dressing_cut_class: (stored.face_dressing_cut_class as string) ?? "", - face_dressing_fill_class: (stored.face_dressing_fill_class as string) ?? "", - face_dressing_fill_area_m2: (stored.face_dressing_fill_area_m2 as number | null) ?? null, - face_dressing_cut_area_m2: (stored.face_dressing_cut_area_m2 as number | null) ?? null, - frame_material: { ...((stored.frame_material ?? {}) as Record) }, - wood_chipping_enabled: Boolean(stored.wood_chipping_enabled), - wood_chipping_volume_m3: (stored.wood_chipping_volume_m3 as number | null) ?? null, - subgrade_compaction_enabled: Boolean(stored.subgrade_compaction_enabled), - tree_waste: { ...((stored.tree_waste ?? {}) as Record) }, - tree_waste_unit_price_krw_per_ton: - (stored.tree_waste_unit_price_krw_per_ton as number | null) ?? null, - waste_separate_order: Boolean(stored.waste_separate_order), - tree_waste_root_method: (stored.tree_waste_root_method as string) ?? "", - ancillary_counts: { - ...((stored.ancillary_counts ?? {}) as Record), - }, - material_supply: { ...((stored.material_supply ?? {}) as Record) }, - material_surcharge: { - ...((stored.material_surcharge ?? {}) as Record), - }, - // ⚠ 저장분에 있는 갈래만 담는다 — 기본값을 복사해 넣으면 「안 고름」이 사라진다. - conversion_factors: Object.fromEntries( - Object.entries(stored.conversion_factors_override ?? {}).map(([kind, entry]) => [ - kind, - { - compacted: typeof entry?.compacted === "number" ? entry.compacted : null, - reason: typeof entry?.reason === "string" ? entry.reason : "", - }, - ]), - ), - dozer_haul_limit_m: (stored.dozer_haul_limit_m as number | null) ?? null, - ...gravelDraftFrom(stored as Record), - dirty: false, - }; - const reload = (): void => { - root.replaceChildren(); - void renderB08Quantity(root); - }; - // 저장 안 한 값이 조용히 사라지지 않게 나갈 때 알린다 — 이 구조의 대가다. - const warnUnsaved = (event: BeforeUnloadEvent): void => { - if (!draft.dirty) return; - event.preventDefault(); - event.returnValue = L("B08_Quantity_Unsaved"); - }; - window.addEventListener("beforeunload", warnUnsaved); - - let workflowState: Awaited> | undefined; - if (projectId) { - try { - workflowState = await fetchWorkflowState(projectId); - } catch { - /* 조회 실패 시 stages 미전달 → 전체 이동 허용 (다른 워크플로 페이지와 같음) */ - } - } + const note = document.createElement("p"); + note.className = "b08-quantity__rebuilding"; + note.textContent = L("B08_Quantity_Rebuilding"); + const body = document.createElement("div"); + body.append(note); const layout = createWorkflowLayout({ title: L("B08_Quantity_Title"), steps: workflowSteps(), activeStep: 5, - leftPanel: buildQuantitySidePanel(projectId, table, draft, reload), - mainContent: buildQuantityBody(table, failed, material, draft, projectId), - stages: workflowState?.stages, - currentStage: workflowState?.current_stage, + mainContent: body, routes: WORKFLOW_STEP_ROUTES, onStepClick: (stepIndex: number) => { if (projectId) goToWorkflowStage(projectId, WORKFLOW_STEP_ROUTES[stepIndex]); diff --git a/B09_Estimation/B09_Estimation_UI_Shell.ts b/B09_Estimation/B09_Estimation_UI_Shell.ts index 5b40dc7d..a82ec689 100644 --- a/B09_Estimation/B09_Estimation_UI_Shell.ts +++ b/B09_Estimation/B09_Estimation_UI_Shell.ts @@ -1,106 +1,39 @@ /* ============================================================================= * B09_Estimation_UI_Shell.ts - * 로그인 후 09: 6차 워크플로우 (원가계산) — 화면 **틀** (PLAN 12장 · 2026-09-14 브레인 판정) + * 로그인 후 09: 6차 워크플로우 (원가계산) — **빈 화면** (PLAN 7-3) * - * - 틀은 탭 줄과 등록만. 탭마다 파일 하나(`B09_Estimation_UI_Tab_<이름>.ts`) — 계약은 `_Shell_Types`. - * - ⚠ 등록 권한 = 랩탑_서브. 다른 창은 탭 파일을 만들고 이름을 알려 등록을 부탁함. - * - 탭을 고를 때마다 본문·좌측 칸을 비우고 `render(ctx, arg)`. `ctx.open(key, arg)` = 다른 탭으로 들어가기. + * - 옛 원가계산 코드는 통째로 `old_code/B09_Estimation/` 에 있음(지우지 않음). + * - 다시 짤 때까지 메뉴와 주소만 남김 — 제목·단계 막대만 뜨고 탭·계산은 없음. * ========================================================================== */ import { createWorkflowLayout } from "@ui/ui_template_workflow_layout"; +import { ui_locales, currentLanguageIndex } from "@ui/ui_template_locale"; import { CURRENT_PROJECT_ID_KEY } from "@config/config_frontend"; import { workflowSteps } from "../A00_Common/b_page_scaffold"; import { goToWorkflowStage, WORKFLOW_STEP_ROUTES } from "../A00_Common/b_workflow_nav"; -import type { B09Tab, B09TabContext } from "./B09_Estimation_UI_Shell_Types"; -import { L, el, injectSheetStyles } from "./B09_Estimation_UI_Sheet"; -import { costSheetTab } from "./B09_Estimation_UI_Tab_CostSheet"; -import { rateTableTab } from "./B09_Estimation_UI_Tab_RateTable"; -import { billTab } from "./B09_Estimation_UI_Tab_Bill"; -import { unitPriceTab } from "./B09_Estimation_UI_Tab_UnitPrice"; -import { priceBasisTab } from "./B09_Estimation_UI_Tab_PriceBasis"; -import { machineTab } from "./B09_Estimation_UI_Tab_Machine"; -import { priceCompareTab } from "./B09_Estimation_UI_Tab_PriceCompare"; -import { materialPricesTab } from "./B09_Estimation_UI_Tab_MaterialPrices"; -import { summaryTab } from "./B09_Estimation_UI_Tab_Summary"; -import { listsTab } from "./B09_Estimation_UI_Tab_Lists"; -import { designDocTab } from "./B09_Estimation_UI_Tab_DesignDoc"; -import { basisSheetTab } from "./B09_Estimation_UI_Tab_BasisSheet"; -import { supplyTab } from "./B09_Estimation_UI_Tab_Supply"; -import { baseDataTab } from "./B09_Estimation_UI_Tab_BaseData"; -import { contractTab } from "./B09_Estimation_UI_Tab_Contract"; -import { executionTab } from "./B09_Estimation_UI_Tab_Execution"; -import { progressTab } from "./B09_Estimation_UI_Tab_Progress"; -import { completionTab } from "./B09_Estimation_UI_Tab_Completion"; -/** 탭 등록 — 한 줄에 탭 하나. */ -const TABS: B09Tab[] = [ - costSheetTab, // 랩탑_메인 - rateTableTab, // 랩탑_메인 - billTab, - unitPriceTab, - priceBasisTab, - machineTab, - priceCompareTab, - materialPricesTab, // 랩탑_메인 — 자재단가대비표 옆(자재 수동 단가) - summaryTab, - listsTab, - supplyTab, - baseDataTab, - designDocTab, - basisSheetTab, - contractTab, // 랩탑_메인 — 설계 뒤 계약 단계라 맨 끝 - executionTab, // 랩탑_메인 — 계약 뒤 실행 단계 - progressTab, // 랩탑_메인 — 실행 뒤 기성 단계 - completionTab, // 랩탑_메인 — 기성 뒤 준공 단계 -]; +function L(key: keyof typeof ui_locales): string { + return ui_locales[key][currentLanguageIndex]; +} -export async function renderB09Estimation(root: HTMLElement): Promise { - injectSheetStyles(); +export function renderB09Estimation(root: HTMLElement): void { const projectId = localStorage.getItem(CURRENT_PROJECT_ID_KEY); - const page = el("div", "b09s-page"); - const bar = el("div", "b09s-tabs"); - const body = el("div", "b09s-body"); - const panel = el("div"); - page.append(bar, body); - let active = TABS[0].key; - const open = (key: string, arg?: string): void => { - const tab = TABS.find((item) => item.key === key); - if (!tab) return; - active = key; - drawBar(); - body.replaceChildren(); - panel.replaceChildren(); - const ctx: B09TabContext = { projectId, body, panel, root, open }; - tab.render(ctx, arg); - }; - const drawBar = (): void => { - bar.replaceChildren( - ...TABS.map((tab) => { - const button = el( - "button", - `b09s-tab${tab.key === active ? " is-active" : ""}`, - tab.label(), - ); - button.type = "button"; - button.dataset.tab = tab.key; - button.addEventListener("click", () => open(tab.key)); - return button; - }), - ); - }; + const note = document.createElement("p"); + note.className = "b09-estimation__rebuilding"; + note.textContent = L("B09_Estimation_Rebuilding"); + const body = document.createElement("div"); + body.append(note); const layout = createWorkflowLayout({ title: L("B09_Estimation_Title"), steps: workflowSteps(), activeStep: 6, - leftPanel: panel, - mainContent: page, + mainContent: body, routes: WORKFLOW_STEP_ROUTES, - onStepClick: (stepIndex) => { + onStepClick: (stepIndex: number) => { if (projectId) goToWorkflowStage(projectId, WORKFLOW_STEP_ROUTES[stepIndex]); }, }); root.append(layout.root); - open(active); } diff --git a/main.py b/main.py index 4fadc776..4ead3c7e 100644 --- a/main.py +++ b/main.py @@ -60,21 +60,6 @@ from B06_Section.B06_Section_Router_HaulPlan import ( ) from B07_DesignDetail.B07_DesignDetail_Router import router as b07_design_router from B07_DesignDetail.B07_DesignDetail_Router_Frame import router as b07_frame_router -from B08_Quantity.B08_Quantity_Router import router as b08_quantity_router -from B08_Quantity.B08_Quantity_Router_Earthwork import router as b08_earthwork_router -from B08_Quantity.B08_Quantity_Router_Material import router as b08_material_router -from B08_Quantity.B08_Quantity_Router_StructureSheet import router as b08_structure_sheet_router -from B08_Quantity.B08_Quantity_Router_StmateLibrary import router as b08_stmate_library_router -from B09_Estimation.B09_Estimation_Router import router as b09_estimation_router -from B09_Estimation.B09_Estimation_Router_Contract import router as b09_contract_router -from B09_Estimation.B09_Estimation_Router_Execution import router as b09_execution_router -from B09_Estimation.B09_Estimation_Router_Progress import router as b09_progress_router -from B09_Estimation.B09_Estimation_Router_CostSheet import router as b09_cost_sheet_router -from B09_Estimation.B09_Estimation_Router_Edits import router as b09_edits_router -from B09_Estimation.B09_Estimation_Router_Factors import router as b09_factors_router -from B09_Estimation.B09_Estimation_Router_MaterialPrices import ( - router as b09_material_prices_router, -) from M01_MasterData.M01_MasterData_Router import router as m01_master_data_router from common_util.common_util_audit import note_api_call, record_call_burst from common_util.common_util_auth import ( @@ -643,19 +628,7 @@ app.include_router(b06_section_confirm_router, dependencies=protected_with_compa app.include_router(b06_section_haul_plan_router, dependencies=protected_with_company) app.include_router(b07_design_router, dependencies=protected_with_company) app.include_router(b07_frame_router, dependencies=protected_with_company) -app.include_router(b08_quantity_router, dependencies=protected_with_company) -app.include_router(b08_earthwork_router, dependencies=protected_with_company) -app.include_router(b08_material_router, dependencies=protected_with_company) -app.include_router(b08_structure_sheet_router, dependencies=protected_with_company) -app.include_router(b08_stmate_library_router, dependencies=protected_with_company) -app.include_router(b09_estimation_router, dependencies=protected_with_company) -app.include_router(b09_cost_sheet_router, dependencies=protected_with_company) -app.include_router(b09_contract_router, dependencies=protected_with_company) -app.include_router(b09_execution_router, dependencies=protected_with_company) -app.include_router(b09_progress_router, dependencies=protected_with_company) -app.include_router(b09_edits_router, dependencies=protected_with_company) -app.include_router(b09_factors_router, dependencies=protected_with_company) -app.include_router(b09_material_prices_router, dependencies=protected_with_company) +# B08 수량·B09 원가계산 라우터 13 개는 끊음 — 코드는 `old_code/` 에 있고 화면은 빈 틀만 뜬다(PLAN 7-3). # 마스터 데이터 — 회사·프로젝트가 아니라 시스템 관리자만 본다. system_admin_only = [Depends(verify_session), Depends(require_system_admin)] app.include_router(m01_master_data_router, dependencies=system_admin_only) diff --git a/B07_DesignDetail/B07_DesignDetail_Engine_Cad_StandardSheet.py b/old_code/B07_DesignDetail/B07_DesignDetail_Engine_Cad_StandardSheet.py similarity index 100% rename from B07_DesignDetail/B07_DesignDetail_Engine_Cad_StandardSheet.py rename to old_code/B07_DesignDetail/B07_DesignDetail_Engine_Cad_StandardSheet.py diff --git a/B08_Quantity/0_old_260726_codex.zip b/old_code/B08_Quantity/0_old_260726_codex.zip similarity index 100% rename from B08_Quantity/0_old_260726_codex.zip rename to old_code/B08_Quantity/0_old_260726_codex.zip diff --git a/B08_Quantity/B08_Quantity_Build_WorkItemMaster.py b/old_code/B08_Quantity/B08_Quantity_Build_WorkItemMaster.py similarity index 100% rename from B08_Quantity/B08_Quantity_Build_WorkItemMaster.py rename to old_code/B08_Quantity/B08_Quantity_Build_WorkItemMaster.py diff --git a/B08_Quantity/B08_Quantity_Build_WorkItemMaster_Const.py b/old_code/B08_Quantity/B08_Quantity_Build_WorkItemMaster_Const.py similarity index 100% rename from B08_Quantity/B08_Quantity_Build_WorkItemMaster_Const.py rename to old_code/B08_Quantity/B08_Quantity_Build_WorkItemMaster_Const.py diff --git a/B08_Quantity/B08_Quantity_Build_WorkItemMaster_Forms.py b/old_code/B08_Quantity/B08_Quantity_Build_WorkItemMaster_Forms.py similarity index 100% rename from B08_Quantity/B08_Quantity_Build_WorkItemMaster_Forms.py rename to old_code/B08_Quantity/B08_Quantity_Build_WorkItemMaster_Forms.py diff --git a/B08_Quantity/B08_Quantity_Build_WorkItemMaster_FormsConst.py b/old_code/B08_Quantity/B08_Quantity_Build_WorkItemMaster_FormsConst.py similarity index 100% rename from B08_Quantity/B08_Quantity_Build_WorkItemMaster_FormsConst.py rename to old_code/B08_Quantity/B08_Quantity_Build_WorkItemMaster_FormsConst.py diff --git a/B08_Quantity/B08_Quantity_Build_WorkItemMaster_Keys.py b/old_code/B08_Quantity/B08_Quantity_Build_WorkItemMaster_Keys.py similarity index 100% rename from B08_Quantity/B08_Quantity_Build_WorkItemMaster_Keys.py rename to old_code/B08_Quantity/B08_Quantity_Build_WorkItemMaster_Keys.py diff --git a/B08_Quantity/B08_Quantity_Build_WorkItemMaster_Parents.py b/old_code/B08_Quantity/B08_Quantity_Build_WorkItemMaster_Parents.py similarity index 100% rename from B08_Quantity/B08_Quantity_Build_WorkItemMaster_Parents.py rename to old_code/B08_Quantity/B08_Quantity_Build_WorkItemMaster_Parents.py diff --git a/B08_Quantity/B08_Quantity_Build_WorkItemMaster_Table.py b/old_code/B08_Quantity/B08_Quantity_Build_WorkItemMaster_Table.py similarity index 100% rename from B08_Quantity/B08_Quantity_Build_WorkItemMaster_Table.py rename to old_code/B08_Quantity/B08_Quantity_Build_WorkItemMaster_Table.py diff --git a/B08_Quantity/B08_Quantity_Build_WorkItemMaster_Variants.py b/old_code/B08_Quantity/B08_Quantity_Build_WorkItemMaster_Variants.py similarity index 100% rename from B08_Quantity/B08_Quantity_Build_WorkItemMaster_Variants.py rename to old_code/B08_Quantity/B08_Quantity_Build_WorkItemMaster_Variants.py diff --git a/B08_Quantity/B08_Quantity_Engine_BasisUnit.py b/old_code/B08_Quantity/B08_Quantity_Engine_BasisUnit.py similarity index 100% rename from B08_Quantity/B08_Quantity_Engine_BasisUnit.py rename to old_code/B08_Quantity/B08_Quantity_Engine_BasisUnit.py diff --git a/B08_Quantity/B08_Quantity_Engine_EarthworkSummary.py b/old_code/B08_Quantity/B08_Quantity_Engine_EarthworkSummary.py similarity index 100% rename from B08_Quantity/B08_Quantity_Engine_EarthworkSummary.py rename to old_code/B08_Quantity/B08_Quantity_Engine_EarthworkSummary.py diff --git a/B08_Quantity/B08_Quantity_Engine_EarthworkTable.py b/old_code/B08_Quantity/B08_Quantity_Engine_EarthworkTable.py similarity index 100% rename from B08_Quantity/B08_Quantity_Engine_EarthworkTable.py rename to old_code/B08_Quantity/B08_Quantity_Engine_EarthworkTable.py diff --git a/B08_Quantity/B08_Quantity_Engine_Formula.py b/old_code/B08_Quantity/B08_Quantity_Engine_Formula.py similarity index 100% rename from B08_Quantity/B08_Quantity_Engine_Formula.py rename to old_code/B08_Quantity/B08_Quantity_Engine_Formula.py diff --git a/B08_Quantity/B08_Quantity_Engine_Formwork.py b/old_code/B08_Quantity/B08_Quantity_Engine_Formwork.py similarity index 100% rename from B08_Quantity/B08_Quantity_Engine_Formwork.py rename to old_code/B08_Quantity/B08_Quantity_Engine_Formwork.py diff --git a/B08_Quantity/B08_Quantity_Engine_GravelSurfacing.py b/old_code/B08_Quantity/B08_Quantity_Engine_GravelSurfacing.py similarity index 100% rename from B08_Quantity/B08_Quantity_Engine_GravelSurfacing.py rename to old_code/B08_Quantity/B08_Quantity_Engine_GravelSurfacing.py diff --git a/B08_Quantity/B08_Quantity_Engine_Handoff.py b/old_code/B08_Quantity/B08_Quantity_Engine_Handoff.py similarity index 100% rename from B08_Quantity/B08_Quantity_Engine_Handoff.py rename to old_code/B08_Quantity/B08_Quantity_Engine_Handoff.py diff --git a/B08_Quantity/B08_Quantity_Engine_Handoff_Boundaries.py b/old_code/B08_Quantity/B08_Quantity_Engine_Handoff_Boundaries.py similarity index 100% rename from B08_Quantity/B08_Quantity_Engine_Handoff_Boundaries.py rename to old_code/B08_Quantity/B08_Quantity_Engine_Handoff_Boundaries.py diff --git a/B08_Quantity/B08_Quantity_Engine_Handoff_Mapping.py b/old_code/B08_Quantity/B08_Quantity_Engine_Handoff_Mapping.py similarity index 100% rename from B08_Quantity/B08_Quantity_Engine_Handoff_Mapping.py rename to old_code/B08_Quantity/B08_Quantity_Engine_Handoff_Mapping.py diff --git a/B08_Quantity/B08_Quantity_Engine_Handoff_Rows.py b/old_code/B08_Quantity/B08_Quantity_Engine_Handoff_Rows.py similarity index 100% rename from B08_Quantity/B08_Quantity_Engine_Handoff_Rows.py rename to old_code/B08_Quantity/B08_Quantity_Engine_Handoff_Rows.py diff --git a/B08_Quantity/B08_Quantity_Engine_Handoff_Rows_Prep.py b/old_code/B08_Quantity/B08_Quantity_Engine_Handoff_Rows_Prep.py similarity index 100% rename from B08_Quantity/B08_Quantity_Engine_Handoff_Rows_Prep.py rename to old_code/B08_Quantity/B08_Quantity_Engine_Handoff_Rows_Prep.py diff --git a/B08_Quantity/B08_Quantity_Engine_Handoff_Spoil.py b/old_code/B08_Quantity/B08_Quantity_Engine_Handoff_Spoil.py similarity index 100% rename from B08_Quantity/B08_Quantity_Engine_Handoff_Spoil.py rename to old_code/B08_Quantity/B08_Quantity_Engine_Handoff_Spoil.py diff --git a/B08_Quantity/B08_Quantity_Engine_Handoff_Trench.py b/old_code/B08_Quantity/B08_Quantity_Engine_Handoff_Trench.py similarity index 100% rename from B08_Quantity/B08_Quantity_Engine_Handoff_Trench.py rename to old_code/B08_Quantity/B08_Quantity_Engine_Handoff_Trench.py diff --git a/B08_Quantity/B08_Quantity_Engine_HaulInputs.py b/old_code/B08_Quantity/B08_Quantity_Engine_HaulInputs.py similarity index 100% rename from B08_Quantity/B08_Quantity_Engine_HaulInputs.py rename to old_code/B08_Quantity/B08_Quantity_Engine_HaulInputs.py diff --git a/B08_Quantity/B08_Quantity_Engine_HaulSummary.py b/old_code/B08_Quantity/B08_Quantity_Engine_HaulSummary.py similarity index 100% rename from B08_Quantity/B08_Quantity_Engine_HaulSummary.py rename to old_code/B08_Quantity/B08_Quantity_Engine_HaulSummary.py diff --git a/B08_Quantity/B08_Quantity_Engine_MaterialSummary.py b/old_code/B08_Quantity/B08_Quantity_Engine_MaterialSummary.py similarity index 100% rename from B08_Quantity/B08_Quantity_Engine_MaterialSummary.py rename to old_code/B08_Quantity/B08_Quantity_Engine_MaterialSummary.py diff --git a/B08_Quantity/B08_Quantity_Engine_ObservedUnit.py b/old_code/B08_Quantity/B08_Quantity_Engine_ObservedUnit.py similarity index 100% rename from B08_Quantity/B08_Quantity_Engine_ObservedUnit.py rename to old_code/B08_Quantity/B08_Quantity_Engine_ObservedUnit.py diff --git a/B08_Quantity/B08_Quantity_Engine_Pipe.py b/old_code/B08_Quantity/B08_Quantity_Engine_Pipe.py similarity index 100% rename from B08_Quantity/B08_Quantity_Engine_Pipe.py rename to old_code/B08_Quantity/B08_Quantity_Engine_Pipe.py diff --git a/B08_Quantity/B08_Quantity_Engine_Preparation.py b/old_code/B08_Quantity/B08_Quantity_Engine_Preparation.py similarity index 100% rename from B08_Quantity/B08_Quantity_Engine_Preparation.py rename to old_code/B08_Quantity/B08_Quantity_Engine_Preparation.py diff --git a/B08_Quantity/B08_Quantity_Engine_Preparation_Ancillary.py b/old_code/B08_Quantity/B08_Quantity_Engine_Preparation_Ancillary.py similarity index 100% rename from B08_Quantity/B08_Quantity_Engine_Preparation_Ancillary.py rename to old_code/B08_Quantity/B08_Quantity_Engine_Preparation_Ancillary.py diff --git a/B08_Quantity/B08_Quantity_Engine_Preparation_FrameMaterial.py b/old_code/B08_Quantity/B08_Quantity_Engine_Preparation_FrameMaterial.py similarity index 100% rename from B08_Quantity/B08_Quantity_Engine_Preparation_FrameMaterial.py rename to old_code/B08_Quantity/B08_Quantity_Engine_Preparation_FrameMaterial.py diff --git a/B08_Quantity/B08_Quantity_Engine_Preparation_Status.py b/old_code/B08_Quantity/B08_Quantity_Engine_Preparation_Status.py similarity index 100% rename from B08_Quantity/B08_Quantity_Engine_Preparation_Status.py rename to old_code/B08_Quantity/B08_Quantity_Engine_Preparation_Status.py diff --git a/B08_Quantity/B08_Quantity_Engine_Preparation_TreeWaste.py b/old_code/B08_Quantity/B08_Quantity_Engine_Preparation_TreeWaste.py similarity index 100% rename from B08_Quantity/B08_Quantity_Engine_Preparation_TreeWaste.py rename to old_code/B08_Quantity/B08_Quantity_Engine_Preparation_TreeWaste.py diff --git a/B08_Quantity/B08_Quantity_Engine_RockSplit.py b/old_code/B08_Quantity/B08_Quantity_Engine_RockSplit.py similarity index 100% rename from B08_Quantity/B08_Quantity_Engine_RockSplit.py rename to old_code/B08_Quantity/B08_Quantity_Engine_RockSplit.py diff --git a/B08_Quantity/B08_Quantity_Engine_SlopeArea.py b/old_code/B08_Quantity/B08_Quantity_Engine_SlopeArea.py similarity index 100% rename from B08_Quantity/B08_Quantity_Engine_SlopeArea.py rename to old_code/B08_Quantity/B08_Quantity_Engine_SlopeArea.py diff --git a/B08_Quantity/B08_Quantity_Engine_SlopeLength.py b/old_code/B08_Quantity/B08_Quantity_Engine_SlopeLength.py similarity index 100% rename from B08_Quantity/B08_Quantity_Engine_SlopeLength.py rename to old_code/B08_Quantity/B08_Quantity_Engine_SlopeLength.py diff --git a/B08_Quantity/B08_Quantity_Engine_StmateRecipe.py b/old_code/B08_Quantity/B08_Quantity_Engine_StmateRecipe.py similarity index 100% rename from B08_Quantity/B08_Quantity_Engine_StmateRecipe.py rename to old_code/B08_Quantity/B08_Quantity_Engine_StmateRecipe.py diff --git a/B08_Quantity/B08_Quantity_Engine_StructureFigure.py b/old_code/B08_Quantity/B08_Quantity_Engine_StructureFigure.py similarity index 100% rename from B08_Quantity/B08_Quantity_Engine_StructureFigure.py rename to old_code/B08_Quantity/B08_Quantity_Engine_StructureFigure.py diff --git a/B08_Quantity/B08_Quantity_Engine_StructureFigure_Small.py b/old_code/B08_Quantity/B08_Quantity_Engine_StructureFigure_Small.py similarity index 100% rename from B08_Quantity/B08_Quantity_Engine_StructureFigure_Small.py rename to old_code/B08_Quantity/B08_Quantity_Engine_StructureFigure_Small.py diff --git a/B08_Quantity/B08_Quantity_Engine_StructureLibrary.py b/old_code/B08_Quantity/B08_Quantity_Engine_StructureLibrary.py similarity index 100% rename from B08_Quantity/B08_Quantity_Engine_StructureLibrary.py rename to old_code/B08_Quantity/B08_Quantity_Engine_StructureLibrary.py diff --git a/B08_Quantity/B08_Quantity_Engine_StructurePriceLink.py b/old_code/B08_Quantity/B08_Quantity_Engine_StructurePriceLink.py similarity index 100% rename from B08_Quantity/B08_Quantity_Engine_StructurePriceLink.py rename to old_code/B08_Quantity/B08_Quantity_Engine_StructurePriceLink.py diff --git a/B08_Quantity/B08_Quantity_Engine_StructureSheet.py b/old_code/B08_Quantity/B08_Quantity_Engine_StructureSheet.py similarity index 100% rename from B08_Quantity/B08_Quantity_Engine_StructureSheet.py rename to old_code/B08_Quantity/B08_Quantity_Engine_StructureSheet.py diff --git a/B08_Quantity/B08_Quantity_Engine_StructureSheet_Edit.py b/old_code/B08_Quantity/B08_Quantity_Engine_StructureSheet_Edit.py similarity index 100% rename from B08_Quantity/B08_Quantity_Engine_StructureSheet_Edit.py rename to old_code/B08_Quantity/B08_Quantity_Engine_StructureSheet_Edit.py diff --git a/B08_Quantity/B08_Quantity_Engine_StructureSummary.py b/old_code/B08_Quantity/B08_Quantity_Engine_StructureSummary.py similarity index 100% rename from B08_Quantity/B08_Quantity_Engine_StructureSummary.py rename to old_code/B08_Quantity/B08_Quantity_Engine_StructureSummary.py diff --git a/B08_Quantity/B08_Quantity_Engine_StructureTemplate.py b/old_code/B08_Quantity/B08_Quantity_Engine_StructureTemplate.py similarity index 100% rename from B08_Quantity/B08_Quantity_Engine_StructureTemplate.py rename to old_code/B08_Quantity/B08_Quantity_Engine_StructureTemplate.py diff --git a/B08_Quantity/B08_Quantity_Engine_StructureUnitPrice.py b/old_code/B08_Quantity/B08_Quantity_Engine_StructureUnitPrice.py similarity index 100% rename from B08_Quantity/B08_Quantity_Engine_StructureUnitPrice.py rename to old_code/B08_Quantity/B08_Quantity_Engine_StructureUnitPrice.py diff --git a/B08_Quantity/B08_Quantity_Engine_UnitQuantity.py b/old_code/B08_Quantity/B08_Quantity_Engine_UnitQuantity.py similarity index 100% rename from B08_Quantity/B08_Quantity_Engine_UnitQuantity.py rename to old_code/B08_Quantity/B08_Quantity_Engine_UnitQuantity.py diff --git a/B08_Quantity/B08_Quantity_Engine_UnitQuantity_Base.py b/old_code/B08_Quantity/B08_Quantity_Engine_UnitQuantity_Base.py similarity index 100% rename from B08_Quantity/B08_Quantity_Engine_UnitQuantity_Base.py rename to old_code/B08_Quantity/B08_Quantity_Engine_UnitQuantity_Base.py diff --git a/B08_Quantity/B08_Quantity_Engine_UnitQuantity_Box.py b/old_code/B08_Quantity/B08_Quantity_Engine_UnitQuantity_Box.py similarity index 100% rename from B08_Quantity/B08_Quantity_Engine_UnitQuantity_Box.py rename to old_code/B08_Quantity/B08_Quantity_Engine_UnitQuantity_Box.py diff --git a/B08_Quantity/B08_Quantity_Engine_UnitQuantity_Masonry.py b/old_code/B08_Quantity/B08_Quantity_Engine_UnitQuantity_Masonry.py similarity index 100% rename from B08_Quantity/B08_Quantity_Engine_UnitQuantity_Masonry.py rename to old_code/B08_Quantity/B08_Quantity_Engine_UnitQuantity_Masonry.py diff --git a/B08_Quantity/B08_Quantity_Engine_UnitQuantity_Pavement.py b/old_code/B08_Quantity/B08_Quantity_Engine_UnitQuantity_Pavement.py similarity index 100% rename from B08_Quantity/B08_Quantity_Engine_UnitQuantity_Pavement.py rename to old_code/B08_Quantity/B08_Quantity_Engine_UnitQuantity_Pavement.py diff --git a/B08_Quantity/B08_Quantity_Engine_UnitQuantity_Revegetation.py b/old_code/B08_Quantity/B08_Quantity_Engine_UnitQuantity_Revegetation.py similarity index 100% rename from B08_Quantity/B08_Quantity_Engine_UnitQuantity_Revegetation.py rename to old_code/B08_Quantity/B08_Quantity_Engine_UnitQuantity_Revegetation.py diff --git a/B08_Quantity/B08_Quantity_Engine_UnitQuantity_Revetment.py b/old_code/B08_Quantity/B08_Quantity_Engine_UnitQuantity_Revetment.py similarity index 100% rename from B08_Quantity/B08_Quantity_Engine_UnitQuantity_Revetment.py rename to old_code/B08_Quantity/B08_Quantity_Engine_UnitQuantity_Revetment.py diff --git a/B08_Quantity/B08_Quantity_Engine_UnitQuantity_StoneSpec.py b/old_code/B08_Quantity/B08_Quantity_Engine_UnitQuantity_StoneSpec.py similarity index 100% rename from B08_Quantity/B08_Quantity_Engine_UnitQuantity_StoneSpec.py rename to old_code/B08_Quantity/B08_Quantity_Engine_UnitQuantity_StoneSpec.py diff --git a/B08_Quantity/B08_Quantity_Formula.ts b/old_code/B08_Quantity/B08_Quantity_Formula.ts similarity index 100% rename from B08_Quantity/B08_Quantity_Formula.ts rename to old_code/B08_Quantity/B08_Quantity_Formula.ts diff --git a/B08_Quantity/B08_Quantity_Formula_Node.ts b/old_code/B08_Quantity/B08_Quantity_Formula_Node.ts similarity index 100% rename from B08_Quantity/B08_Quantity_Formula_Node.ts rename to old_code/B08_Quantity/B08_Quantity_Formula_Node.ts diff --git a/B08_Quantity/B08_Quantity_Provenance.py b/old_code/B08_Quantity/B08_Quantity_Provenance.py similarity index 100% rename from B08_Quantity/B08_Quantity_Provenance.py rename to old_code/B08_Quantity/B08_Quantity_Provenance.py diff --git a/B08_Quantity/B08_Quantity_Router.py b/old_code/B08_Quantity/B08_Quantity_Router.py similarity index 100% rename from B08_Quantity/B08_Quantity_Router.py rename to old_code/B08_Quantity/B08_Quantity_Router.py diff --git a/B08_Quantity/B08_Quantity_Router_Earthwork.py b/old_code/B08_Quantity/B08_Quantity_Router_Earthwork.py similarity index 100% rename from B08_Quantity/B08_Quantity_Router_Earthwork.py rename to old_code/B08_Quantity/B08_Quantity_Router_Earthwork.py diff --git a/B08_Quantity/B08_Quantity_Router_Earthwork_HaulPlan.py b/old_code/B08_Quantity/B08_Quantity_Router_Earthwork_HaulPlan.py similarity index 100% rename from B08_Quantity/B08_Quantity_Router_Earthwork_HaulPlan.py rename to old_code/B08_Quantity/B08_Quantity_Router_Earthwork_HaulPlan.py diff --git a/B08_Quantity/B08_Quantity_Router_Earthwork_Settings.py b/old_code/B08_Quantity/B08_Quantity_Router_Earthwork_Settings.py similarity index 100% rename from B08_Quantity/B08_Quantity_Router_Earthwork_Settings.py rename to old_code/B08_Quantity/B08_Quantity_Router_Earthwork_Settings.py diff --git a/B08_Quantity/B08_Quantity_Router_Material.py b/old_code/B08_Quantity/B08_Quantity_Router_Material.py similarity index 100% rename from B08_Quantity/B08_Quantity_Router_Material.py rename to old_code/B08_Quantity/B08_Quantity_Router_Material.py diff --git a/B08_Quantity/B08_Quantity_Router_StmateLibrary.py b/old_code/B08_Quantity/B08_Quantity_Router_StmateLibrary.py similarity index 100% rename from B08_Quantity/B08_Quantity_Router_StmateLibrary.py rename to old_code/B08_Quantity/B08_Quantity_Router_StmateLibrary.py diff --git a/B08_Quantity/B08_Quantity_Router_StructureSheet.py b/old_code/B08_Quantity/B08_Quantity_Router_StructureSheet.py similarity index 100% rename from B08_Quantity/B08_Quantity_Router_StructureSheet.py rename to old_code/B08_Quantity/B08_Quantity_Router_StructureSheet.py diff --git a/B08_Quantity/B08_Quantity_UI_ConversionFactors.ts b/old_code/B08_Quantity/B08_Quantity_UI_ConversionFactors.ts similarity index 100% rename from B08_Quantity/B08_Quantity_UI_ConversionFactors.ts rename to old_code/B08_Quantity/B08_Quantity_UI_ConversionFactors.ts diff --git a/B08_Quantity/B08_Quantity_UI_EarthworkGrid.ts b/old_code/B08_Quantity/B08_Quantity_UI_EarthworkGrid.ts similarity index 100% rename from B08_Quantity/B08_Quantity_UI_EarthworkGrid.ts rename to old_code/B08_Quantity/B08_Quantity_UI_EarthworkGrid.ts diff --git a/B08_Quantity/B08_Quantity_UI_EarthworkGrid_Style.ts b/old_code/B08_Quantity/B08_Quantity_UI_EarthworkGrid_Style.ts similarity index 100% rename from B08_Quantity/B08_Quantity_UI_EarthworkGrid_Style.ts rename to old_code/B08_Quantity/B08_Quantity_UI_EarthworkGrid_Style.ts diff --git a/B08_Quantity/B08_Quantity_UI_MaterialGrid.ts b/old_code/B08_Quantity/B08_Quantity_UI_MaterialGrid.ts similarity index 100% rename from B08_Quantity/B08_Quantity_UI_MaterialGrid.ts rename to old_code/B08_Quantity/B08_Quantity_UI_MaterialGrid.ts diff --git a/old_code/B08_Quantity/B08_Quantity_UI_Page.ts b/old_code/B08_Quantity/B08_Quantity_UI_Page.ts new file mode 100644 index 00000000..a68a30e7 --- /dev/null +++ b/old_code/B08_Quantity/B08_Quantity_UI_Page.ts @@ -0,0 +1,1116 @@ +/* ============================================================================= + * B08_Quantity_UI_Page.ts + * 로그인 후 08: 5차 워크플로우 (수량 산출) + * + * 우측 = 실무 수량산출서의 시트를 탭으로 옮긴 것. 지금은 **토적표** 한 장이 서 있고 + * 나머지(토적집계·구조물위치·수량집계표·총괄집계·수리계산·운반거리)는 차례로 붙인다. + * 확정 = 워크플로 stage 5(QUANTITY) 완료 처리 후 B09 설계도서로 이동. + * ========================================================================== */ + +import { ui_locales, currentLanguageIndex } from "@ui/ui_template_locale"; +import { createButton, showToast } from "@ui/ui_template_elements"; +import { API_BASE_URL, CURRENT_PROJECT_ID_KEY } from "@config/config_frontend"; +import { createWorkflowLayout } from "@ui/ui_template_workflow_layout"; +import { attachCollapsible } from "@ui/ui_template_collapsible"; +import { groupPanelSections } from "./B08_Quantity_UI_SidePanel_Sections"; +import { createProvenanceToggle } from "@ui/ui_template_provenance"; +import { workflowSteps } from "../A00_Common/b_page_scaffold"; +import { + fetchWorkflowState, + goToWorkflowStage, + WORKFLOW_STEP_ROUTES, +} from "../A00_Common/b_workflow_nav"; +import { renderEarthworkGrid, type EarthworkTable } from "./B08_Quantity_UI_EarthworkGrid"; +import { + conversionOverridePayload, + renderConversionFactorFields, + type FactorDraft, +} from "./B08_Quantity_UI_ConversionFactors"; +import { injectEarthworkGridStyles } from "./B08_Quantity_UI_EarthworkGrid_Style"; +import { + renderHaulGrid, + renderPreparationGrid, + renderSummaryGrid, + type PreparationTable, +} from "./B08_Quantity_UI_SummaryGrid"; +import { + renderMaterialGrid, + renderUnitQuantityGrid, + type MaterialResponse, +} from "./B08_Quantity_UI_MaterialGrid"; +import { renderStructureSheets } from "./B08_Quantity_UI_StructureSheet"; +import { renderStructureSummary } from "./B08_Quantity_UI_StructureSummary"; +import { appendTreeWasteFields } from "./B08_Quantity_UI_Side_TreeWaste"; +import { appendFaceDressingFields } from "./B08_Quantity_UI_Side_FaceDressing"; +import { appendRootRemovalFields } from "./B08_Quantity_UI_Side_RootRemoval"; +import { appendSeedSprayField } from "./B08_Quantity_UI_Side_SeedSpray"; +import type { GravelDraft } from "./B08_Quantity_UI_Side_Gravel"; +import { appendGravelFields, gravelDraftFrom, gravelPayload } from "./B08_Quantity_UI_Side_Gravel"; +import { appendBenchCutFields } from "./B08_Quantity_UI_Side_BenchCut"; + +/** locale 헬퍼 */ +function L(key: keyof typeof ui_locales): string { + return ui_locales[key][currentLanguageIndex]; +} + +/** stage 5(QUANTITY) 완료 요청 — 본문 미구현 상태의 유일한 백엔드 연동. */ +async function confirmQuantityStage(projectId: string): Promise { + const response = await fetch( + `${API_BASE_URL}/projects/${encodeURIComponent(projectId)}/quantity/confirm`, + { method: "POST", credentials: "include" }, + ); + if (!response.ok) { + throw new Error(`quantity confirm failed: ${response.status}`); + } +} + +/** 토적표를 받아 온다. 노선을 안 주면 워크플로가 보고 있는 최신 노선으로 나온다. */ +async function fetchEarthworkTable(projectId: string): Promise { + const response = await fetch( + `${API_BASE_URL}/projects/${encodeURIComponent(projectId)}/quantity/earthwork-table`, + { credentials: "include" }, + ); + if (!response.ok) throw new Error(`earthwork table failed: ${response.status}`); + return (await response.json()) as EarthworkTable; +} + +/** 구조물 원단위·자재총괄을 받아 온다. 한 번에 받는 까닭은 자재총괄이 원단위의 부분집합이라서다. */ +async function fetchMaterialSummary(projectId: string): Promise { + const response = await fetch( + `${API_BASE_URL}/projects/${encodeURIComponent(projectId)}/quantity/material-summary`, + { credentials: "include" }, + ); + if (!response.ok) throw new Error(`material summary failed: ${response.status}`); + return (await response.json()) as MaterialResponse; +} + +/** [저장] — 산출 조건을 정본에 남긴다. `quantity` 구획만 간다(서버가 막고 있다). */ +async function saveQuantitySettings(projectId: string, draft: DraftSettings): Promise { + const response = await fetch( + `${API_BASE_URL}/projects/${encodeURIComponent(projectId)}/quantity/settings`, + { + method: "PUT", + credentials: "include", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ + rock_class_set: draft.rock_class_set ?? null, + rock_ratios_pct: draft.rock_ratios_pct, + application_ratios_pct: draft.application_ratios_pct, + // ⚠ 「안 정함」으로 되돌린 갈래까지 **통째로** 보낸다. 정한 것만 보내면 서버가 + // 병합해 옛 값이 남아 되돌릴 길이 없다(화면에서 걸린 자리). 빈 값은 서버가 버린다. + rock_methods: draft.rock_methods, + material_supply: draft.material_supply, + material_surcharge: draft.material_surcharge, + concrete_placing_method: draft.concrete_placing_method, + // ⚠ `null` 도 그대로 보낸다 — 「안 정함」으로 되돌릴 길이 있어야 한다(시공법과 같은 규칙). + topsoil_thickness_m: draft.topsoil_thickness_m, + // 확정 2차 ① · 3차 ②③④ — 값이 화면 칸에서 오고, 비면 그 줄이 막힌다. + bench_cut_depth_m: draft.bench_cut_depth_m, + rubble_base_thickness_m: draft.rubble_base_thickness_m, + spoil_site_distance_m: draft.spoil_site_distance_m, + structure_trench_water: draft.structure_trench_water, + topsoil_haul_distance_m: draft.topsoil_haul_distance_m, + // `""` 는 기본(노면 + 절토)으로 되돌림 — 서버가 None 으로 둔다. + topsoil_target: draft.topsoil_target, + stand_volume_class: draft.stand_volume_class, + root_removal_excavator_m3: draft.root_removal_excavator_m3, + seed_spray_ground: draft.seed_spray_ground, + // 면고르기 — 갈래 `""`·면적 `null` 도 그대로(「안 정함」·「파종 면적 그대로」로 되돌리는 길). + face_dressing_cut_class: draft.face_dressing_cut_class, + face_dressing_fill_class: draft.face_dressing_fill_class, + face_dressing_fill_area_m2: draft.face_dressing_fill_area_m2, + face_dressing_cut_area_m2: draft.face_dressing_cut_area_m2, + frame_material: draft.frame_material, + wood_chipping_enabled: draft.wood_chipping_enabled, + wood_chipping_volume_m3: draft.wood_chipping_volume_m3, + subgrade_compaction_enabled: draft.subgrade_compaction_enabled, + // 조사값은 **통째로** — 지운 칸까지 가야 되돌릴 길이 있다. + tree_waste: draft.tree_waste, + tree_waste_unit_price_krw_per_ton: draft.tree_waste_unit_price_krw_per_ton, + waste_separate_order: draft.waste_separate_order, + tree_waste_root_method: draft.tree_waste_root_method, + // 개소는 **통째로** 보낸다 — 지운 항목까지 그대로 가야 되돌릴 길이 있다. + ancillary_counts: draft.ancillary_counts, + // 토량환산계수 — 고른 갈래만 담긴다. 빈 dict 는 「전부 기본값으로 되돌림」이다. + conversion_factors_override: conversionOverridePayload(draft.conversion_factors), + // 도쟈 한계거리 — `null` 도 보낸다(기본값으로 되돌리는 길). + dozer_haul_limit_m: draft.dozer_haul_limit_m, + ...gravelPayload(draft), // 혼합석 부설 — 칸은 `_Side_Gravel` + }), + }, + ); + if (!response.ok) throw new Error(`quantity settings save failed: ${response.status}`); +} + +/** 좌측 패널의 한 줄 — 이름과 값. 산출 조건을 읽기 전용으로 보인다. */ +function field(label: string, value: string): HTMLElement { + const row = document.createElement("div"); + row.className = "b08-quantity__field"; + const name = document.createElement("span"); + name.textContent = label; + const amount = document.createElement("span"); + amount.className = "b08-quantity__field-value"; + amount.textContent = value; + row.append(name, amount); + return row; +} + +/** 규준틀 재료 칸 — 이름은 **서버 `FRAME_MATERIAL_SUGGESTED` 와 같은 낱말**이라야 한다. */ +const FRAME_MATERIAL_FIELDS: ReadonlyArray = [ + ["각재 50×50", "B08_Quantity_Frame_Square", "0.0001"], + ["판재 T12", "B08_Quantity_Frame_Board", "0.0001"], + ["못", "B08_Quantity_Frame_Nail", "0.01"], +]; +/** 채워 보이는 **제안값**(실무 관측). ⚠ 법정 기준이 아니라 「고치라고 보이는 값」이다. */ +const FRAME_MATERIAL_SUGGESTED: Record = { + "각재 50×50": 0.0044, + "판재 T12": 0.0029, + 못: 0.03, +}; + +/** 부대시설 항목 키 — 서버 `ANCILLARY_ITEMS` 와 **같은 차례·같은 낱말**이라야 한다. */ +const ANCILLARY_KEYS = [ + "national_point_sign", + "guide_sign", + "gate", + "site_container", + "flood_supplies", +] as const; + +/** 칸 밑에 붙는 **근거 한 줄** — 왜 그 값인지 화면에서 보이게 한다(2026-09-09 사용자 지시). */ +function hintRow(text: string): HTMLElement { + const row = document.createElement("p"); + row.className = "b08-quantity__hint"; + row.textContent = text; + return row; +} + +/** 반영률 키 → 사람이 읽는 이름. 서버 키를 그대로 보이면 설계자가 못 읽는다. */ +const RATIO_LABEL_KEYS: Record = { + fill_slope_compaction: "B08_Quantity_Ratio_FillCompaction", + seed_spray_fill: "B08_Quantity_Ratio_SeedFill", + seed_spray_cut: "B08_Quantity_Ratio_SeedCut", + obstacle_removal: "B08_Quantity_Ratio_TreeRemoval", +}; + +function ratioLabel(key: string): string { + const localeKey = RATIO_LABEL_KEYS[key]; + return localeKey ? L(localeKey) : key; +} + +/** 반영률·비율 입력 한 칸. 값은 **캐시에만** 쌓이고 [저장]에서 정본으로 간다(5장). */ +function numberField(label: string, value: number, onInput: (value: number) => void): HTMLElement { + const row = document.createElement("label"); + row.className = "b08-quantity__field"; + const name = document.createElement("span"); + name.textContent = label; + const input = document.createElement("input"); + input.type = "number"; + input.className = "b08-quantity__input"; + input.min = "0"; + input.step = "1"; + input.value = String(value); + // 자동저장은 만들지 않는다 — 입력은 캐시에만 남는다(CLAUDE.md 5장). + input.addEventListener("input", () => onInput(Number(input.value))); + row.append(name, input); + return row; +} + +/** 비워 둘 수 있는 숫자 칸 — **빈 값은 「안 정함」**이고 0 과 다르다. + * + * ⚠ `numberField` 로 두면 빈 칸이 0 으로 읽혀 「두께 0m」와 「안 정함」이 같아진다. + * 표토제거처럼 **안 정하면 줄이 아예 안 서는** 값은 그 둘을 갈라야 한다. + */ +function optionalNumberField( + label: string, + value: number | null, + step: string, + onInput: (value: number | null) => void, +): HTMLElement { + const row = document.createElement("label"); + row.className = "b08-quantity__field"; + const name = document.createElement("span"); + name.textContent = label; + const input = document.createElement("input"); + input.type = "number"; + input.className = "b08-quantity__input"; + input.min = "0"; + input.step = step; + input.placeholder = L("B08_Quantity_Unset_Placeholder"); + input.value = value === null || value === undefined ? "" : String(value); + // 자동저장은 만들지 않는다 — 입력은 캐시에만 남는다(CLAUDE.md 5장). + input.addEventListener("input", () => { + const text = input.value.trim(); + onInput(text === "" ? null : Number(text)); + }); + row.append(name, input); + return row; +} + +/** 타설 방식 표기 — 코드가 아니라 사람이 읽는 이름으로 보인다. */ +const PLACING_LABELS: Record = { + ready_mixed: "레디믹스트", + machine_mixed: "기계비빔", + hand_mixed: "인력비빔", +}; + +/** 고르는 칸. 첫 보기는 **「안 정함」**이고 그것이 기본이다 — 찍으면 값이 조용히 틀린다. */ +function selectField( + label: string, + value: string, + options: { value: string; label: string }[], + onChange: (value: string) => void, +): HTMLElement { + const row = document.createElement("label"); + row.className = "b08-quantity__field"; + const name = document.createElement("span"); + name.textContent = label; + const select = document.createElement("select"); + select.className = "b08-quantity__input"; + for (const option of options) { + const element = document.createElement("option"); + element.value = option.value; + element.textContent = option.label; + select.append(element); + } + select.value = value; + // 자동저장은 만들지 않는다 — 고른 값은 캐시에만 남는다(CLAUDE.md 5장). + select.addEventListener("change", () => onChange(select.value)); + row.append(name, select); + return row; +} + +/** 지반 종류 표기 — 서버 키가 화면에 새지 않게. 모르는 키는 그대로 보인다. */ +/** 자재 한 줄의 관급/사급. `install_by` 는 **관급 줄에만** 뜻이 있다. */ +export interface SupplyChoice { + supply: string; + install_by: string | null; +} + +/** 저장 전 변경분 — 조작은 여기 쌓이고 [저장]에서만 정본으로 간다. */ +interface DraftSettings extends GravelDraft { + rock_class_set?: string; + rock_ratios_pct: Record; + application_ratios_pct: Record; + // 갈래별 시공법 — `""` 는 「안 정함」이고 저장에서 빠진다. + rock_methods: Record; + // 콘크리트 타설 방식 — `""` 는 「안 정함」이고 저장에서 지워진다. + concrete_placing_method: string; + // 표토 두께(m) — `null` 은 「안 정함」. 정해야 표토제거 줄이 선다(품셈 9-15 [주]② 의 T). + topsoil_thickness_m: number | null; + // 층따기 길이(m) — 면적 × 이 값 = ㎥ (확정 2차 ①). `null` 이면 층따기 줄이 막힌다. + bench_cut_depth_m: number | null; + // 기초잡석 두께(m) — 확정 3차 ② 0.2. 폭은 버림 폭과 같다(KCS 34 50 05). + rubble_base_thickness_m: number | null; + // 사토장까지 거리(m) — 현장값. `null` 이면 사토 운반 줄이 막힌다(확정 3차 ③ 는 미정). + spoil_site_distance_m: number | null; + // 구조물터파기 용수 — "육상"·"용수". ⚠ 「육상」은 **통상값**이지 사용자 확정이 아니다. + structure_trench_water: string; + // 표토 운반거리(m) — 별표2 가 요구하는 운반·적치의 밑수. `null` 은 「안 정함」. + topsoil_haul_distance_m: number | null; + // 표토제거 대상 — `""` 기본(노면 + 절토, 별표2 문언) · "road_only" 노면만(설계자 선택). + topsoil_target: string; + // 부대시설 개소 — `{항목키: 개소}`. ⚠ 산식으로 만들지 않는다(확정 13). + ancillary_counts: Record; + // 임목축적 등급 — "소림"·"중림"·"밀림". ⚠ 본수가 아니라 축적이다(품셈 9-21 [주]①). + stand_volume_class: string; + // 제근 굴착기 크기 — "0.2"·"0.7"(서버 선택지) · `""` 는 「안 정함」. 칸은 `_Side_RootRemoval`. + root_removal_excavator_m3: string; + // 초류종자살포 비탈면 토질 — "일반"·"마사토"(5-24 잎) · `""` 는 「안 정함」. 칸은 `_Side_SeedSpray`. + seed_spray_ground: string; + // 면고르기 — 갈래 둘(서버 선택지) · 면적 덮어쓰기 둘(`null` = 파종 면적). 칸은 `_Side_FaceDressing`. + face_dressing_cut_class: string; + face_dressing_fill_class: string; + face_dressing_fill_area_m2: number | null; + face_dressing_cut_area_m2: number | null; + // 규준틀 개소당 재료 — 비우면 **제안값(실무 관측)**이 선다. 값이 아니라 「고칠 수 있음」이 요점. + frame_material: Record; + // 임목파쇄 — **기본 꺼짐**(확정 5차 5번). 켜야 줄이 선다. 근주이식은 칸 자체가 없다. + wood_chipping_enabled: boolean; + wood_chipping_volume_m3: number | null; + // 노체다짐 — **기본 꺼짐**(9-16-2 [주]⑤ 조건부). 켜면 토공집계에 별도 줄(수량 = 성토량). + subgrade_compaction_enabled: boolean; + // 임목폐기물 — 조사값 넷 · 수동 처리단가(미확정) · 분리발주(기본 아님). + tree_waste: Record; + tree_waste_unit_price_krw_per_ton: number | null; + waste_separate_order: boolean; + tree_waste_root_method: string; + // 자재별 관급/사급 — 표 안에서 줄마다 고른 값. + material_supply: Record; + // 자재별 할증률(%) — 표값 이내에서 고른 값만 · `null` 은 표값. + material_surcharge: Record; + // 갈래별 토량환산계수(다짐) — `compacted` 가 `null` 이면 「안 고름」이라 기본값이 선다. + conversion_factors: Record; + // 도쟈 한계거리(m) — `null` 은 기본값(서버 정본 60 m)이 선다. + dozer_haul_limit_m: number | null; + dirty: boolean; +} + +/** 개발 전용 「확정 없이 다음으로」 한 줄 — 단추 둘 + 지금 상태 안내. + * + * ⚠ **조용히 넘어가지 않는다.** 우회로 열린 상태면 「확정을 건너뛴 상태입니다」를 띄운다 — + * 안 그러면 다음 사람이 「왜 값이 없나」로 헤맨다. + * ⚠ **되돌리는 단추를 같은 줄에 둔다.** 되돌릴 길이 안 보이면 검증용 프로젝트가 굳는다. + */ +function devUnlockRow(projectId: string, reload: () => void): HTMLElement { + const row = document.createElement("div"); + row.className = "b08-quantity__dev"; + + const title = document.createElement("p"); + title.className = "b08-quantity__note"; + title.textContent = L("B08_Quantity_Dev_Title"); + row.append(title); + + const state = document.createElement("p"); + state.className = "b08-quantity__note"; + row.append(state); + + const paint = (): void => { + fetch(`/api/projects/${projectId}/dev/unlock`, { credentials: "include" }) + .then((response) => (response.ok ? response.json() : null)) + .then((body: { bypassed_stages?: number[] } | null) => { + const stages = body?.bypassed_stages ?? []; + state.textContent = stages.length + ? `${L("B08_Quantity_Dev_Bypassed")} (${stages.join(", ")})` + : L("B08_Quantity_Dev_Normal"); + }) + .catch(() => { + state.textContent = L("B08_Quantity_Dev_Normal"); + }); + }; + paint(); + + const call = (method: "POST" | "DELETE", button: HTMLButtonElement): void => { + button.disabled = true; + fetch(`/api/projects/${projectId}/dev/unlock`, { method, credentials: "include" }) + .then((response) => { + if (!response.ok) throw new Error(String(response.status)); + showToast(L("B08_Quantity_Dev_Done"), "success"); + paint(); + reload(); + }) + .catch(() => showToast(L("B08_Quantity_Dev_Failed"), "error")) + .finally(() => { + button.disabled = false; + }); + }; + + const unlock = createButton({ + label: L("B08_Quantity_Dev_Unlock"), + variant: "ghost", + onClick: () => call("POST", unlock), + }); + const relock = createButton({ + label: L("B08_Quantity_Dev_Relock"), + variant: "ghost", + onClick: () => call("DELETE", relock), + }); + const buttons = document.createElement("div"); + // ⚠ 공용 `ui-sidebar-actions` 를 쓰지 않는다 — 공용 코드가 **첫 번째** 그 클래스를 + // 패널 바닥 액션 줄로 집어(`ui_template_overlay.ts` splitSidebarActions), + // 스크롤 영역이 이 줄 안에 갇히고 아래 칸들이 통째로 잘린다(2026-09-12). + buttons.className = "b08-quantity__dev-actions"; + buttons.append(unlock, relock); + row.append(buttons); + return row; +} + +/** 좌측 패널: 산출 조건 + 하단 [저장]·[확정] 액션 행. + * `reload` 는 저장 뒤 표를 다시 그리는 손잡이다 — 조건이 바뀌면 집계·운반 값이 달라진다. */ +function buildQuantitySidePanel( + projectId: string | null, + table: EarthworkTable | null, + draft: DraftSettings, + reload: () => void, +): HTMLElement { + const panel = document.createElement("div"); + panel.className = "b08-quantity__panel"; + + // ── 개발 전용 「확정 없이 다음으로」 ──────────────────────────────────────── + // ⚠ **맨 위에 둔다.** 패널이 `overflow: hidden` 이라 아래에 붙이면 화면 밖으로 + // 밀려 **눌리지 않는다**(2026-09-08 화면에서 실제로 그랬다 — 단추 y=772, + // 패널 높이 740). 상태 경고이기도 하니 자리도 여기가 맞다. + // ⚠ **이것은 보조 문일 뿐이다.** 진짜 문은 서버가 `ENVIRONMENT` 로 막는다 + // (`common_util_dev_unlock`). 화면만 숨기면 API 는 그대로 뚫려 있다. + // ⚠ **계산을 대신 돌리지 않는다** — 워크플로 잠금만 푼다. 값이 비어 보이는 것은 + // 정상이고, 그것을 「미확보」로 보이는 것이 이 화면이 이미 하는 일이다. + if (import.meta.env.DEV && projectId) { + panel.append(devUnlockRow(projectId, reload)); + } + + panel.append(field(L("B08_Quantity_Side_Method"), L("B08_Quantity_Side_Method_Value"))); + // 토량환산계수 — **고를 수 있는 값**이다(오솔길 대조 06절 3번). 기본값 정의처는 서버 한 곳이고, + // 화면은 고른 값만 보낸다. 유토곡선·운반표·기초단가가 같이 읽는다는 안내도 그 칸이 낸다. + const factorFields = renderConversionFactorFields( + table?.conversion_factor_choices, + table?.conversion_factor_pumsem_ranges, + draft.conversion_factors, + () => { + draft.dirty = true; + }, + ); + if (factorFields) panel.append(factorFields); + + // ── 운반장비 거리 경계 — 도쟈 한계거리는 설계 조건이라 칸으로(2026-09-13 판정) ── + // ⚠ 기본값과 근거를 **서버가 준 그대로** 옆에 보인다 — 「이 값이 어디서 왔나」가 화면에 있어야 함. + const haul = table?.haul_limit_choice; + if (haul) { + panel.append(field(L("B08_Quantity_Side_HaulLimits"), "")); + const dozer = optionalNumberField( + L("B08_Quantity_Side_DozerLimit_Label"), + draft.dozer_haul_limit_m, + "5", + (value) => { + draft.dozer_haul_limit_m = value; + draft.dirty = true; + }, + ); + const input = dozer.querySelector("input"); + if (input) input.placeholder = String(haul.default); + panel.append(dozer); + panel.append(hintRow(`${L("B08_Quantity_Factor_Default")} ${haul.default} m — ${haul.basis}`)); + panel.append( + hintRow(`${L("B08_Quantity_Side_FreeHaul")} ${haul.free_haul_m} m — ${haul.free_haul_basis}`), + ); + panel.append(hintRow(L("B08_Quantity_Side_HaulLimits_Reach"))); + } + + // ── 지반 구성비 — 갈래 수는 프로젝트 세트가 정한다(코드에 안 박음, PLAN 8-13) ── + const classes = [...(table?.summary?.rock_classes ?? [])]; + // ⚠ 비율을 아직 안 넣었으면 집계가 **「암」 한 줄**로 나온다(갈래로 안 갈림). 그 줄에도 + // 시공법을 정할 수 있어야 공종이 선다 — 그때만 칸을 하나 더 낸다. + const hasRockFallback = (table?.summary?.rows ?? []).some((row) => row.item === "암"); + if (hasRockFallback && !classes.includes("암")) classes.push("암"); + if (classes.length) { + panel.append(field(L("B08_Quantity_Side_RockRatios"), "")); + for (const name of classes) { + // 「암」은 비율을 넣으면 사라지는 되메움 줄이라 비율 칸을 두지 않는다. + // 「토사」도 비율 칸을 두지 않는다 — 토사 물량은 토적표의 흙깎기 값이 그대로 서고, + // 비율은 **암 총량을 갈래로 나누는 데만** 쓰인다(집계 엔진 `_rock_split`). + // 칸을 두면 넣은 값이 조용히 버려져 「입력 합 60 %」 같은 안내가 뜬다(2026-09-08 통과). + if (name !== "암" && name !== "토사") { + panel.append( + numberField(name, draft.rock_ratios_pct[name] ?? 0, (value) => { + draft.rock_ratios_pct[name] = value; + draft.dirty = true; + }), + ); + } + // ⚠ 암 갈래는 **시공법까지 정해야** 공종이 갈린다 — 품셈이 긁어내기(암절취)와 + // 터뜨리기(발파암)를 다른 공종으로 두기 때문이다. 「토사」에는 안 붙인다. + if (name !== "토사") { + panel.append( + selectField( + ` ${name} ${L("B08_Quantity_Side_Method_Label")}`, + draft.rock_methods[name] ?? "", + [ + { value: "", label: L("B08_Quantity_Method_Unset") }, + { value: "ripping", label: L("B08_Quantity_Method_Ripping") }, + { value: "blasting", label: L("B08_Quantity_Method_Blasting") }, + ], + (value) => { + draft.rock_methods[name] = value; + draft.dirty = true; + }, + ), + ); + } + } + } + + // ── 반영률 — 기본 100 %. 실무 관측 80/50/80 은 기본값이 아니다(PLAN 8-11) ── + const ratios = table?.settings?.application_ratios_pct ?? {}; + if (Object.keys(ratios).length) { + panel.append(field(L("B08_Quantity_Side_Ratios"), "")); + for (const key of Object.keys(ratios)) { + panel.append( + numberField(ratioLabel(key), draft.application_ratios_pct[key] ?? 100, (value) => { + draft.application_ratios_pct[key] = value; + draft.dirty = true; + }), + ); + } + } + + // ── 초류종자살포 토질 · 면고르기 — 밑수가 파종 면적이라 반영률 바로 밑. 선택지는 서버 목록 그대로. + const sideHelpers = { field, optionalNumberField, selectField, hintRow }; + appendSeedSprayField(panel, draft, table?.seed_spray_choices, sideHelpers); + appendFaceDressingFields(panel, draft, table?.face_dressing_choices, sideHelpers); + + // ── 표토 두께 — 표토 운반 부피(제거 ㎡ × T)에 씀 · 제거 줄은 ㎡ 라 안 곱함(2026-09-13 「실무대로」) ── + // ⚠ 2026-09-08 ㉘ 자기 감사: 서버·엔진은 이 값을 받고 있었는데 **화면에 넣을 칸이 없었다.** + // 「죽은 칸」(넣어도 안 쓰임)의 반대 짝이다 — 쓰이는데 넣을 데가 없던 자리. + panel.append(field(L("B08_Quantity_Side_Topsoil"), "")); + // 대상 — 기본은 법 문언(노면 + 절토). 영월 한 건이 노면만이라 3배 차이 → 설계자가 고름(2026-09-14). + panel.append( + selectField( + L("B08_Quantity_Side_TopsoilTarget_Label"), + draft.topsoil_target, + [ + { value: "", label: L("B08_Quantity_TopsoilTarget_RoadAndCut") }, + { value: "road_only", label: L("B08_Quantity_TopsoilTarget_RoadOnly") }, + ], + (value) => { + draft.topsoil_target = value; + draft.dirty = true; + }, + ), + ); + panel.append(hintRow(L("B08_Quantity_Side_TopsoilTarget_Hint"))); + panel.append( + optionalNumberField( + L("B08_Quantity_Side_Topsoil_Label"), + draft.topsoil_thickness_m, + "0.01", + (value) => { + draft.topsoil_thickness_m = value; + draft.dirty = true; + }, + ), + ); + + // ── 구조물·사토 — 확정 2차 ① · 3차 ②③④ 의 값들 ────────────────────── + // ⚠ 사용자 지시(2026-09-09): **값을 코드에 박고 끝내지 말고 화면에 칸으로 세우고 + // 지금 값과 근거를 보이고 바꿀 수 있게 할 것.** 정한 값이 화면에 안 보이면 다음 사람이 + // 왜 그 값인지 모른다. + panel.append(field(L("B08_Quantity_Side_Structure"), "")); + // 층따기 칸 · KDS 근거 줄 · [제안값 넣기] — 누른 때만 채움(교본 6-4 「설계도서에 명시」). + appendBenchCutFields(panel, draft, { field, optionalNumberField, selectField, hintRow }); + panel.append( + optionalNumberField( + L("B08_Quantity_Side_Rubble_Label"), + draft.rubble_base_thickness_m, + "0.05", + (value) => { + draft.rubble_base_thickness_m = value; + draft.dirty = true; + }, + ), + ); + panel.append(hintRow(L("B08_Quantity_Side_Rubble_Hint"))); + panel.append( + optionalNumberField( + L("B08_Quantity_Side_SpoilDistance_Label"), + draft.spoil_site_distance_m, + "10", + (value) => { + draft.spoil_site_distance_m = value; + draft.dirty = true; + }, + ), + ); + panel.append(hintRow(L("B08_Quantity_Side_SpoilDistance_Hint"))); + panel.append( + selectField( + L("B08_Quantity_Side_Water_Label"), + draft.structure_trench_water, + [ + { value: "", label: L("B08_Quantity_Water_Unset") }, + { value: "육상", label: L("B08_Quantity_Water_Dry") }, + { value: "용수", label: L("B08_Quantity_Water_Wet") }, + ], + (value) => { + draft.structure_trench_water = value; + draft.dirty = true; + }, + ), + ); + panel.append(hintRow(L("B08_Quantity_Side_Water_Hint"))); + panel.append( + optionalNumberField( + L("B08_Quantity_Side_TopsoilHaul_Label"), + draft.topsoil_haul_distance_m, + "10", + (value) => { + draft.topsoil_haul_distance_m = value; + draft.dirty = true; + }, + ), + ); + panel.append(hintRow(L("B08_Quantity_Side_TopsoilHaul_Hint"))); + panel.append( + selectField( + L("B08_Quantity_Side_StandVolume_Label"), + draft.stand_volume_class, + [ + { value: "", label: L("B08_Quantity_StandVolume_Unset") }, + { value: "소림", label: L("B08_Quantity_StandVolume_Low") }, + { value: "중림", label: L("B08_Quantity_StandVolume_Mid") }, + { value: "밀림", label: L("B08_Quantity_StandVolume_High") }, + ], + (value) => { + draft.stand_volume_class = value; + draft.dirty = true; + }, + ), + ); + panel.append(hintRow(L("B08_Quantity_Side_StandVolume_Hint"))); + appendRootRemovalFields(panel, draft, table?.root_removal_excavator_choices, { + field, + optionalNumberField, + selectField, + hintRow, + }); + + // ── 부대시설 개소 — ⚠ **산식으로 만들지 않는다**(확정 13). 넣어야 줄이 선다 ── + panel.append(field(L("B08_Quantity_Side_Ancillary"), "")); + for (const key of ANCILLARY_KEYS) { + panel.append( + optionalNumberField( + L(`B08_Quantity_Ancillary_${key}` as keyof typeof ui_locales), + draft.ancillary_counts[key] ?? null, + "1", + (value) => { + draft.ancillary_counts[key] = value; + draft.dirty = true; + }, + ), + ); + } + panel.append(hintRow(L("B08_Quantity_Side_Ancillary_Hint"))); + + // ── 규준틀 재료 — ⚠ **세는 것은 확정이고 수량만 모르던 자리**(품셈 [주]④ 「설계수량에 따른다」). + // 그래서 **제안값을 채워 보이고 고칠 수 있게** 둔다(확정 ⑨·⑩ 과 같은 틀). + // ⚠ 값만 박고 근거를 안 보이면 사용자 지시(「대신 페이지에 남길 것」)를 어기는 것이라 + // 칸 밑에 **관측값임**과 **손율 원문값**을 함께 적는다. + panel.append(field(L("B08_Quantity_Side_Frame"), "")); + for (const [key, label, step] of FRAME_MATERIAL_FIELDS) { + panel.append( + optionalNumberField( + L(label as keyof typeof ui_locales), + draft.frame_material[key] ?? FRAME_MATERIAL_SUGGESTED[key], + step, + (value) => { + draft.frame_material[key] = value; + draft.dirty = true; + }, + ), + ); + } + panel.append(hintRow(L("B08_Quantity_Side_Frame_Hint"))); + + // ── 임목파쇄 — ⚠ **기본 꺼짐**(확정 5차 5번). 「셀지 말지가 설계 판단」이라 켜야 줄이 선다. + // ⚠ 근주이식은 **칸 자체를 안 만든다** — 켤 자리가 없으면 물을 일도 없다. + panel.append(field(L("B08_Quantity_Side_Chipping"), "")); + panel.append( + selectField( + L("B08_Quantity_Chipping_Label"), + draft.wood_chipping_enabled ? "on" : "", + [ + { value: "", label: L("B08_Quantity_Chipping_Off") }, + { value: "on", label: L("B08_Quantity_Chipping_On") }, + ], + (value) => { + draft.wood_chipping_enabled = value === "on"; + draft.dirty = true; + }, + ), + ); + panel.append( + optionalNumberField( + L("B08_Quantity_Chipping_Volume"), + draft.wood_chipping_volume_m3, + "1", + (value) => { + draft.wood_chipping_volume_m3 = value; + draft.dirty = true; + }, + ), + ); + panel.append(hintRow(L("B08_Quantity_Side_Chipping_Hint"))); + + // ── 노체다짐 — ⚠ **기본 꺼짐**(2026-09-13 판정). 9-16-2 [주]⑤ 조건부라 켜야 토공집계에 줄이 선다. + panel.append(field(L("B08_Quantity_Side_SubgradeCompaction"), "")); + panel.append( + selectField( + L("B08_Quantity_SubgradeCompaction_Label"), + draft.subgrade_compaction_enabled ? "on" : "", + [ + { value: "", label: L("B08_Quantity_Chipping_Off") }, + { value: "on", label: L("B08_Quantity_Chipping_On") }, + ], + (value) => { + draft.subgrade_compaction_enabled = value === "on"; + draft.dirty = true; + }, + ), + ); + panel.append(hintRow(L("B08_Quantity_Side_SubgradeCompaction_Hint"))); + // ── 혼합석 부설 — 법령 조건 자동 · 연약·습윤 칸 · 전 구간 고르개(2026-09-15 브레인 ⓑ). + appendGravelFields(panel, draft, table?.gravel, { + field, + optionalNumberField, + selectField, + hintRow, + }); + + // ── 임목폐기물 — 조사값 넷 · 수동 처리단가 · 분리발주(2026-09-14). 칸은 따로 뺀 파일에서. + appendTreeWasteFields(panel, draft, { field, optionalNumberField, selectField, hintRow }); + + // ── 콘크리트 타설 방식 — ⚠ **금액에 바로 걸리는 값**이라 잠정임을 조용히 두지 않는다 ── + panel.append(field(L("B08_Quantity_Side_Placing"), "")); + panel.append( + selectField( + L("B08_Quantity_Side_Placing_Label"), + draft.concrete_placing_method, + [ + { value: "", label: L("B08_Quantity_Placing_Unset") }, + { value: "ready_mixed", label: L("B08_Quantity_Placing_Ready") }, + { value: "machine_mixed", label: L("B08_Quantity_Placing_Machine") }, + { value: "hand_mixed", label: L("B08_Quantity_Placing_Hand") }, + ], + (value) => { + draft.concrete_placing_method = value; + draft.dirty = true; + }, + ), + ); + // ⚠ `?.` 이 빠지면 표를 못 받은 때(`table === null`) 여기서 터져 **페이지가 통째로 + // 백지**가 된다 — 정작 보여야 할 「표를 못 불렀다」 안내까지 같이 사라진다(2026-09-12 실측). + const placing = ( + table as unknown as { + concrete_placing?: { + method: string; + is_default: boolean; + price_hint?: { basis?: string; values?: Record }; + }; + } | null + )?.concrete_placing; + if (placing) { + // ⚠ 방식 이름을 **늘** 값 옆에 보인다 — 코드(`12-01-01`)만으로는 무엇을 쓰는지 모른다. + const label = PLACING_LABELS[placing.method] ?? placing.method; + panel.append( + field( + L("B08_Quantity_Placing_Current"), + placing.is_default ? `${label} (${L("B08_Quantity_Placing_Default_Tag")})` : label, + ), + ); + // 2026-09-08 ㉙: 인계가 **타설 공종 줄을 실제로 세운다.** 겹치지 않는 것이 확인됐다 — + // 품셈 12-1-1 표는 직종·품만 주고 재료를 안 줘서, 품은 이 줄 · 재료는 자재 쪽이다. + // ⚠ 돌쌓기 뒤채움(채움콘크리트)은 뺐다 — 그 공종 품에 이미 들어 있을 수 있다. + // ⚠ **기본값일 때는 싣지 않는다** — 바로 아래 「기본값으로 계산 중」 경고가 더 많은 것을 + // 말하는데, 둘을 겹쳐 실으면 긴 문구가 그 경고를 화면 밖으로 밀어낸다 + // (2026-09-08 ㉕ 화면 통과에서 실제로 잘려 있었다). + if (!placing.is_default) { + const applied = document.createElement("p"); + applied.className = "b08-quantity__note"; + applied.textContent = L("B08_Quantity_Placing_NotApplied"); + panel.append(applied); + } + } + // ⚠ 「정하면 얼마나 달라지는지」까지 보여야 사용자가 판단한다. 이 값은 **참고 표시 전용**이고 + // B08 의 어떤 계산에도 안 들어간다(금액은 B09 몫). + const hint = placing?.price_hint; + if (hint?.values) { + const line = document.createElement("p"); + line.className = "b08-quantity__notice"; + const parts = Object.entries(hint.values).map( + ([key, value]) => + `${PLACING_LABELS[key] ?? key} ${Math.round(value).toLocaleString("ko-KR")}원`, + ); + line.textContent = `${L("B08_Quantity_Placing_Hint")} ${hint.basis ?? ""} — ${parts.join(" · ")}`; + panel.append(line); + } + if (placing?.is_default) { + // 「확인 필요」만 있으면 무엇을 정해야 하는지 모른다 — **지금 무엇으로 돌고 있는지**를 함께 적는다. + const notice = document.createElement("p"); + notice.className = "b08-quantity__notice"; + notice.textContent = L("B08_Quantity_Placing_Default_Notice"); + panel.append(notice); + } + + const saveButton = createButton({ + label: L("B08_Quantity_Btn_Save"), + variant: "ghost", + onClick: () => { + if (!projectId) { + showToast(L("B08_Quantity_Save_Failed"), "error"); + return; + } + saveButton.disabled = true; + saveQuantitySettings(projectId, draft) + .then(() => { + draft.dirty = false; + showToast(L("B08_Quantity_Save_Success"), "success"); + // 조건이 바뀌면 집계·운반 값이 달라진다 — 표를 다시 받아 그린다. + reload(); + }) + .catch(() => { + showToast(L("B08_Quantity_Save_Failed"), "error"); + saveButton.disabled = false; + }); + }, + }); + + const confirmButton = createButton({ + label: L("B08_Quantity_Btn_Confirm"), + variant: "filled", + onClick: () => { + if (!projectId) { + showToast(L("B08_Quantity_Confirm_Failed"), "error"); + return; + } + confirmButton.disabled = true; + confirmQuantityStage(projectId) + .then(() => { + showToast(L("B08_Quantity_Confirm_Success"), "success"); + goToWorkflowStage(projectId, WORKFLOW_STEP_ROUTES[6]); + }) + .catch(() => { + showToast(L("B08_Quantity_Confirm_Failed"), "error"); + confirmButton.disabled = false; + }); + }, + }); + + const actions = document.createElement("div"); + actions.className = "b08-quantity__actions ui-sidebar-actions"; + // [초기화]는 이번에 달지 않는다 — 5장의 [초기화]는 초기값(`initial_snapshot/`)을 작업본에 + // 덮어쓰는 것인데 설정에는 대응하는 초기값이 아직 없다. 재계산 단추로 오해될 자리다. + // TODO(미결) — 설정의 초기값을 무엇으로 볼지 사용자 확인 뒤에 붙인다. + actions.append(saveButton, confirmButton); + panel.append(actions); + + // 조건 칸을 B03~B07 공통 상자로 묶고 제목 클릭으로 접히게 한다. + groupPanelSections(panel); + attachCollapsible(panel); + return panel; +} + +/** 우측 본문 — 시트 탭 + 고른 장의 표. 실무 산출서의 시트를 탭으로 옮긴 것이다. */ +function buildQuantityBody( + table: EarthworkTable | null, + failed: boolean, + material: MaterialResponse | null, + draft: DraftSettings, + projectId: string | null, +): HTMLElement { + const body = document.createElement("div"); + body.className = "b08-quantity__body"; + + const tabs = document.createElement("div"); + tabs.className = "b08-quantity__tabs"; + const pane = document.createElement("div"); + pane.className = "b08-quantity__pane"; + + const message = (text: string): HTMLElement => { + const element = document.createElement("p"); + element.className = "b08-quantity__message"; + element.textContent = text; + return element; + }; + + // 등급색 토글 — ⚠ **사전이 왔을 때만** 만든다(개발환경). 배포 빌드에서는 단추 자체가 없다. + // ⚠ 만들기만 하고 **붙이는 것은 탭 단추를 다 세운 뒤**이다 — 여기서 바로 붙이면 + // 탭 줄 **맨 앞**에 서서 「토적표」 앞에 탭 하나가 더 있는 것처럼 보인다(2026-09-12 실측: x=8). + const provenanceToggle = + (table as unknown as { provenance?: unknown } | null)?.provenance || + (material as unknown as { provenance?: unknown } | null)?.provenance + ? createProvenanceToggle(body) + : null; + + if (failed) { + body.append(tabs, message(L("B08_Quantity_Grid_Failed"))); + return body; + } + if (!table || !table.rows?.length) { + body.append(tabs, message(L("B08_Quantity_Grid_Empty"))); + return body; + } + + const sheets: { label: string; build: () => HTMLElement }[] = [ + { label: L("B08_Quantity_Tab_Earthwork"), build: () => renderEarthworkGrid(table) }, + { + label: L("B08_Quantity_Tab_Summary"), + build: () => + table.summary + ? renderSummaryGrid(table.summary, table.provenance?.sheets?.summary) + : message(L("B08_Quantity_Grid_Empty")), + }, + { + label: L("B08_Quantity_Tab_Haul"), + build: () => + table.haul + ? renderHaulGrid( + table.haul, + Boolean(table.haul_available), + table.provenance?.sheets?.haul, + ) + : message(L("B08_Quantity_Haul_Missing")), + }, + { + label: L("B08_Quantity_Tab_Preparation"), + build: () => { + const preparation = (table as unknown as { preparation?: PreparationTable }).preparation; + return preparation + ? renderPreparationGrid(preparation, table.provenance?.sheets?.preparation) + : message(L("B08_Quantity_Grid_Empty")); + }, + }, + // 구조물도 — 제원 조합 하나 = 한 장(PLAN 3장). 스스로 받아 오므로 표를 넘기지 않음. + // ⚠ 탭 차례 바꾸기·원단위 탭 흡수는 PLAN 5장 몫 — 여기서는 원단위 앞에 붙이기만 함. + // 구조물 집계표 — 측점별 한 줄(PLAN 2장). 구조물도의 앞 사슬이라 그 바로 앞에 둠. + { + label: L("B08_Quantity_Tab_StructureSummary"), + build: () => renderStructureSummary(projectId), + }, + { label: L("B08_Quantity_Tab_StructureSheet"), build: () => renderStructureSheets(projectId) }, + { + label: L("B08_Quantity_Tab_UnitQuantity"), + build: () => + material ? renderUnitQuantityGrid(material) : message(L("B08_Quantity_Material_Failed")), + }, + { + label: L("B08_Quantity_Tab_Material"), + build: () => + material + ? renderMaterialGrid( + material.material, + { + choices: draft.material_supply, + surcharge: draft.material_surcharge, + onChange: () => { + draft.dirty = true; + }, + }, + material.provenance?.sheets?.material, + ) + : message(L("B08_Quantity_Material_Failed")), + }, + ]; + + const buttons: HTMLButtonElement[] = []; + const show = (index: number): void => { + buttons.forEach((button, i) => button.classList.toggle("is-active", i === index)); + pane.replaceChildren(sheets[index].build()); + }; + sheets.forEach((sheet, index) => { + const button = document.createElement("button"); + button.type = "button"; + button.className = "b08-quantity__tab"; + button.textContent = sheet.label; + button.addEventListener("click", () => show(index)); + buttons.push(button); + tabs.append(button); + }); + // 토글은 탭 줄 **끝**에 둔다 — 표를 고르는 단추가 아니라 보기 방식을 바꾸는 단추라 섞이면 안 된다. + if (provenanceToggle) tabs.append(provenanceToggle); + + body.append(tabs, pane); + show(0); + return body; +} + +/* ----------------------------------------------------------------------------- + * 페이지 진입점 + * -------------------------------------------------------------------------- */ +export async function renderB08Quantity(root: HTMLElement): Promise { + injectEarthworkGridStyles(); + const projectId = localStorage.getItem(CURRENT_PROJECT_ID_KEY); + + // 표는 한 번만 받아 좌측 패널(계수 표시)과 우측 그리드가 함께 쓴다. + let table: EarthworkTable | null = null; + let material: MaterialResponse | null = null; + let failed = false; + if (projectId) { + try { + table = await fetchEarthworkTable(projectId); + } catch { + failed = true; + } + // 자재총괄은 따로 받는다 — 구조물이 없어도 토적표는 서야 하므로 실패를 옮기지 않는다. + try { + material = await fetchMaterialSummary(projectId); + } catch { + material = null; + } + } + + // 저장 전 변경분 — 조작은 여기 쌓이고 [저장]에서만 정본으로 간다(CLAUDE.md 5장). + const stored = table?.settings ?? {}; + const draft: DraftSettings = { + rock_class_set: stored.rock_class_set, + rock_ratios_pct: { ...(stored.rock_ratios_pct ?? {}) }, + application_ratios_pct: { ...(stored.application_ratios_pct ?? {}) }, + rock_methods: { ...((stored.rock_methods ?? {}) as Record) }, + concrete_placing_method: (stored.concrete_placing_method as string) ?? "", + topsoil_thickness_m: (stored.topsoil_thickness_m as number | null) ?? null, + bench_cut_depth_m: (stored.bench_cut_depth_m as number | null) ?? null, + rubble_base_thickness_m: (stored.rubble_base_thickness_m as number | null) ?? null, + spoil_site_distance_m: (stored.spoil_site_distance_m as number | null) ?? null, + structure_trench_water: (stored.structure_trench_water as string) ?? "", + topsoil_haul_distance_m: (stored.topsoil_haul_distance_m as number | null) ?? null, + topsoil_target: (stored.topsoil_target as string) ?? "", + stand_volume_class: (stored.stand_volume_class as string) ?? "", + root_removal_excavator_m3: (stored.root_removal_excavator_m3 as string) ?? "", + seed_spray_ground: (stored.seed_spray_ground as string) ?? "", + face_dressing_cut_class: (stored.face_dressing_cut_class as string) ?? "", + face_dressing_fill_class: (stored.face_dressing_fill_class as string) ?? "", + face_dressing_fill_area_m2: (stored.face_dressing_fill_area_m2 as number | null) ?? null, + face_dressing_cut_area_m2: (stored.face_dressing_cut_area_m2 as number | null) ?? null, + frame_material: { ...((stored.frame_material ?? {}) as Record) }, + wood_chipping_enabled: Boolean(stored.wood_chipping_enabled), + wood_chipping_volume_m3: (stored.wood_chipping_volume_m3 as number | null) ?? null, + subgrade_compaction_enabled: Boolean(stored.subgrade_compaction_enabled), + tree_waste: { ...((stored.tree_waste ?? {}) as Record) }, + tree_waste_unit_price_krw_per_ton: + (stored.tree_waste_unit_price_krw_per_ton as number | null) ?? null, + waste_separate_order: Boolean(stored.waste_separate_order), + tree_waste_root_method: (stored.tree_waste_root_method as string) ?? "", + ancillary_counts: { + ...((stored.ancillary_counts ?? {}) as Record), + }, + material_supply: { ...((stored.material_supply ?? {}) as Record) }, + material_surcharge: { + ...((stored.material_surcharge ?? {}) as Record), + }, + // ⚠ 저장분에 있는 갈래만 담는다 — 기본값을 복사해 넣으면 「안 고름」이 사라진다. + conversion_factors: Object.fromEntries( + Object.entries(stored.conversion_factors_override ?? {}).map(([kind, entry]) => [ + kind, + { + compacted: typeof entry?.compacted === "number" ? entry.compacted : null, + reason: typeof entry?.reason === "string" ? entry.reason : "", + }, + ]), + ), + dozer_haul_limit_m: (stored.dozer_haul_limit_m as number | null) ?? null, + ...gravelDraftFrom(stored as Record), + dirty: false, + }; + const reload = (): void => { + root.replaceChildren(); + void renderB08Quantity(root); + }; + // 저장 안 한 값이 조용히 사라지지 않게 나갈 때 알린다 — 이 구조의 대가다. + const warnUnsaved = (event: BeforeUnloadEvent): void => { + if (!draft.dirty) return; + event.preventDefault(); + event.returnValue = L("B08_Quantity_Unsaved"); + }; + window.addEventListener("beforeunload", warnUnsaved); + + let workflowState: Awaited> | undefined; + if (projectId) { + try { + workflowState = await fetchWorkflowState(projectId); + } catch { + /* 조회 실패 시 stages 미전달 → 전체 이동 허용 (다른 워크플로 페이지와 같음) */ + } + } + + const layout = createWorkflowLayout({ + title: L("B08_Quantity_Title"), + steps: workflowSteps(), + activeStep: 5, + leftPanel: buildQuantitySidePanel(projectId, table, draft, reload), + mainContent: buildQuantityBody(table, failed, material, draft, projectId), + stages: workflowState?.stages, + currentStage: workflowState?.current_stage, + routes: WORKFLOW_STEP_ROUTES, + onStepClick: (stepIndex: number) => { + if (projectId) goToWorkflowStage(projectId, WORKFLOW_STEP_ROUTES[stepIndex]); + }, + }); + root.append(layout.root); +} diff --git a/B08_Quantity/B08_Quantity_UI_SidePanel_Sections.ts b/old_code/B08_Quantity/B08_Quantity_UI_SidePanel_Sections.ts similarity index 100% rename from B08_Quantity/B08_Quantity_UI_SidePanel_Sections.ts rename to old_code/B08_Quantity/B08_Quantity_UI_SidePanel_Sections.ts diff --git a/B08_Quantity/B08_Quantity_UI_Side_BenchCut.ts b/old_code/B08_Quantity/B08_Quantity_UI_Side_BenchCut.ts similarity index 100% rename from B08_Quantity/B08_Quantity_UI_Side_BenchCut.ts rename to old_code/B08_Quantity/B08_Quantity_UI_Side_BenchCut.ts diff --git a/B08_Quantity/B08_Quantity_UI_Side_FaceDressing.ts b/old_code/B08_Quantity/B08_Quantity_UI_Side_FaceDressing.ts similarity index 100% rename from B08_Quantity/B08_Quantity_UI_Side_FaceDressing.ts rename to old_code/B08_Quantity/B08_Quantity_UI_Side_FaceDressing.ts diff --git a/B08_Quantity/B08_Quantity_UI_Side_Gravel.ts b/old_code/B08_Quantity/B08_Quantity_UI_Side_Gravel.ts similarity index 100% rename from B08_Quantity/B08_Quantity_UI_Side_Gravel.ts rename to old_code/B08_Quantity/B08_Quantity_UI_Side_Gravel.ts diff --git a/B08_Quantity/B08_Quantity_UI_Side_RootRemoval.ts b/old_code/B08_Quantity/B08_Quantity_UI_Side_RootRemoval.ts similarity index 100% rename from B08_Quantity/B08_Quantity_UI_Side_RootRemoval.ts rename to old_code/B08_Quantity/B08_Quantity_UI_Side_RootRemoval.ts diff --git a/B08_Quantity/B08_Quantity_UI_Side_SeedSpray.ts b/old_code/B08_Quantity/B08_Quantity_UI_Side_SeedSpray.ts similarity index 100% rename from B08_Quantity/B08_Quantity_UI_Side_SeedSpray.ts rename to old_code/B08_Quantity/B08_Quantity_UI_Side_SeedSpray.ts diff --git a/B08_Quantity/B08_Quantity_UI_Side_TreeWaste.ts b/old_code/B08_Quantity/B08_Quantity_UI_Side_TreeWaste.ts similarity index 100% rename from B08_Quantity/B08_Quantity_UI_Side_TreeWaste.ts rename to old_code/B08_Quantity/B08_Quantity_UI_Side_TreeWaste.ts diff --git a/B08_Quantity/B08_Quantity_UI_StructureFigure.ts b/old_code/B08_Quantity/B08_Quantity_UI_StructureFigure.ts similarity index 100% rename from B08_Quantity/B08_Quantity_UI_StructureFigure.ts rename to old_code/B08_Quantity/B08_Quantity_UI_StructureFigure.ts diff --git a/B08_Quantity/B08_Quantity_UI_StructureSheet.ts b/old_code/B08_Quantity/B08_Quantity_UI_StructureSheet.ts similarity index 100% rename from B08_Quantity/B08_Quantity_UI_StructureSheet.ts rename to old_code/B08_Quantity/B08_Quantity_UI_StructureSheet.ts diff --git a/B08_Quantity/B08_Quantity_UI_StructureSheet_Formula.ts b/old_code/B08_Quantity/B08_Quantity_UI_StructureSheet_Formula.ts similarity index 100% rename from B08_Quantity/B08_Quantity_UI_StructureSheet_Formula.ts rename to old_code/B08_Quantity/B08_Quantity_UI_StructureSheet_Formula.ts diff --git a/B08_Quantity/B08_Quantity_UI_StructureSheet_Library.ts b/old_code/B08_Quantity/B08_Quantity_UI_StructureSheet_Library.ts similarity index 100% rename from B08_Quantity/B08_Quantity_UI_StructureSheet_Library.ts rename to old_code/B08_Quantity/B08_Quantity_UI_StructureSheet_Library.ts diff --git a/B08_Quantity/B08_Quantity_UI_StructureSheet_Spec.ts b/old_code/B08_Quantity/B08_Quantity_UI_StructureSheet_Spec.ts similarity index 100% rename from B08_Quantity/B08_Quantity_UI_StructureSheet_Spec.ts rename to old_code/B08_Quantity/B08_Quantity_UI_StructureSheet_Spec.ts diff --git a/B08_Quantity/B08_Quantity_UI_StructureSheet_Stmate.ts b/old_code/B08_Quantity/B08_Quantity_UI_StructureSheet_Stmate.ts similarity index 100% rename from B08_Quantity/B08_Quantity_UI_StructureSheet_Stmate.ts rename to old_code/B08_Quantity/B08_Quantity_UI_StructureSheet_Stmate.ts diff --git a/B08_Quantity/B08_Quantity_UI_StructureSheet_UnitPrice.ts b/old_code/B08_Quantity/B08_Quantity_UI_StructureSheet_UnitPrice.ts similarity index 100% rename from B08_Quantity/B08_Quantity_UI_StructureSheet_UnitPrice.ts rename to old_code/B08_Quantity/B08_Quantity_UI_StructureSheet_UnitPrice.ts diff --git a/B08_Quantity/B08_Quantity_UI_StructureSheet_UnitPriceEdit.ts b/old_code/B08_Quantity/B08_Quantity_UI_StructureSheet_UnitPriceEdit.ts similarity index 100% rename from B08_Quantity/B08_Quantity_UI_StructureSheet_UnitPriceEdit.ts rename to old_code/B08_Quantity/B08_Quantity_UI_StructureSheet_UnitPriceEdit.ts diff --git a/B08_Quantity/B08_Quantity_UI_StructureSummary.ts b/old_code/B08_Quantity/B08_Quantity_UI_StructureSummary.ts similarity index 100% rename from B08_Quantity/B08_Quantity_UI_StructureSummary.ts rename to old_code/B08_Quantity/B08_Quantity_UI_StructureSummary.ts diff --git a/B08_Quantity/B08_Quantity_UI_SummaryGrid.ts b/old_code/B08_Quantity/B08_Quantity_UI_SummaryGrid.ts similarity index 100% rename from B08_Quantity/B08_Quantity_UI_SummaryGrid.ts rename to old_code/B08_Quantity/B08_Quantity_UI_SummaryGrid.ts diff --git a/B08_Quantity/B08_Quantity_Wording.py b/old_code/B08_Quantity/B08_Quantity_Wording.py similarity index 100% rename from B08_Quantity/B08_Quantity_Wording.py rename to old_code/B08_Quantity/B08_Quantity_Wording.py diff --git a/B09_Estimation/B09_Estimation_BasisSheet.py b/old_code/B09_Estimation/B09_Estimation_BasisSheet.py similarity index 100% rename from B09_Estimation/B09_Estimation_BasisSheet.py rename to old_code/B09_Estimation/B09_Estimation_BasisSheet.py diff --git a/B09_Estimation/B09_Estimation_BillOfQuantities.py b/old_code/B09_Estimation/B09_Estimation_BillOfQuantities.py similarity index 100% rename from B09_Estimation/B09_Estimation_BillOfQuantities.py rename to old_code/B09_Estimation/B09_Estimation_BillOfQuantities.py diff --git a/B09_Estimation/B09_Estimation_BillOfQuantities_Input.py b/old_code/B09_Estimation/B09_Estimation_BillOfQuantities_Input.py similarity index 100% rename from B09_Estimation/B09_Estimation_BillOfQuantities_Input.py rename to old_code/B09_Estimation/B09_Estimation_BillOfQuantities_Input.py diff --git a/B09_Estimation/B09_Estimation_BillOfQuantities_Materials.py b/old_code/B09_Estimation/B09_Estimation_BillOfQuantities_Materials.py similarity index 100% rename from B09_Estimation/B09_Estimation_BillOfQuantities_Materials.py rename to old_code/B09_Estimation/B09_Estimation_BillOfQuantities_Materials.py diff --git a/B09_Estimation/B09_Estimation_BillOfQuantities_Rows.py b/old_code/B09_Estimation/B09_Estimation_BillOfQuantities_Rows.py similarity index 100% rename from B09_Estimation/B09_Estimation_BillOfQuantities_Rows.py rename to old_code/B09_Estimation/B09_Estimation_BillOfQuantities_Rows.py diff --git a/B09_Estimation/B09_Estimation_BillRates.py b/old_code/B09_Estimation/B09_Estimation_BillRates.py similarity index 100% rename from B09_Estimation/B09_Estimation_BillRates.py rename to old_code/B09_Estimation/B09_Estimation_BillRates.py diff --git a/B09_Estimation/B09_Estimation_Brushcutting.py b/old_code/B09_Estimation/B09_Estimation_Brushcutting.py similarity index 100% rename from B09_Estimation/B09_Estimation_Brushcutting.py rename to old_code/B09_Estimation/B09_Estimation_Brushcutting.py diff --git a/B09_Estimation/B09_Estimation_Chisel.py b/old_code/B09_Estimation/B09_Estimation_Chisel.py similarity index 100% rename from B09_Estimation/B09_Estimation_Chisel.py rename to old_code/B09_Estimation/B09_Estimation_Chisel.py diff --git a/B09_Estimation/B09_Estimation_Consumables.py b/old_code/B09_Estimation/B09_Estimation_Consumables.py similarity index 100% rename from B09_Estimation/B09_Estimation_Consumables.py rename to old_code/B09_Estimation/B09_Estimation_Consumables.py diff --git a/B09_Estimation/B09_Estimation_Contract.py b/old_code/B09_Estimation/B09_Estimation_Contract.py similarity index 100% rename from B09_Estimation/B09_Estimation_Contract.py rename to old_code/B09_Estimation/B09_Estimation_Contract.py diff --git a/B09_Estimation/B09_Estimation_CostSheet.py b/old_code/B09_Estimation/B09_Estimation_CostSheet.py similarity index 100% rename from B09_Estimation/B09_Estimation_CostSheet.py rename to old_code/B09_Estimation/B09_Estimation_CostSheet.py diff --git a/B09_Estimation/B09_Estimation_CostSheet_Forms.py b/old_code/B09_Estimation/B09_Estimation_CostSheet_Forms.py similarity index 100% rename from B09_Estimation/B09_Estimation_CostSheet_Forms.py rename to old_code/B09_Estimation/B09_Estimation_CostSheet_Forms.py diff --git a/B09_Estimation/B09_Estimation_CrewOutput.py b/old_code/B09_Estimation/B09_Estimation_CrewOutput.py similarity index 100% rename from B09_Estimation/B09_Estimation_CrewOutput.py rename to old_code/B09_Estimation/B09_Estimation_CrewOutput.py diff --git a/B09_Estimation/B09_Estimation_DesignDocIndex.py b/old_code/B09_Estimation/B09_Estimation_DesignDocIndex.py similarity index 100% rename from B09_Estimation/B09_Estimation_DesignDocIndex.py rename to old_code/B09_Estimation/B09_Estimation_DesignDocIndex.py diff --git a/B09_Estimation/B09_Estimation_Edits.py b/old_code/B09_Estimation/B09_Estimation_Edits.py similarity index 100% rename from B09_Estimation/B09_Estimation_Edits.py rename to old_code/B09_Estimation/B09_Estimation_Edits.py diff --git a/B09_Estimation/B09_Estimation_Engine_Cost.py b/old_code/B09_Estimation/B09_Estimation_Engine_Cost.py similarity index 100% rename from B09_Estimation/B09_Estimation_Engine_Cost.py rename to old_code/B09_Estimation/B09_Estimation_Engine_Cost.py diff --git a/B09_Estimation/B09_Estimation_Engine_Cost_Input.py b/old_code/B09_Estimation/B09_Estimation_Engine_Cost_Input.py similarity index 100% rename from B09_Estimation/B09_Estimation_Engine_Cost_Input.py rename to old_code/B09_Estimation/B09_Estimation_Engine_Cost_Input.py diff --git a/B09_Estimation/B09_Estimation_Engine_Cost_Options.py b/old_code/B09_Estimation/B09_Estimation_Engine_Cost_Options.py similarity index 100% rename from B09_Estimation/B09_Estimation_Engine_Cost_Options.py rename to old_code/B09_Estimation/B09_Estimation_Engine_Cost_Options.py diff --git a/B09_Estimation/B09_Estimation_Euroform.py b/old_code/B09_Estimation/B09_Estimation_Euroform.py similarity index 100% rename from B09_Estimation/B09_Estimation_Euroform.py rename to old_code/B09_Estimation/B09_Estimation_Euroform.py diff --git a/B09_Estimation/B09_Estimation_Execution.py b/old_code/B09_Estimation/B09_Estimation_Execution.py similarity index 100% rename from B09_Estimation/B09_Estimation_Execution.py rename to old_code/B09_Estimation/B09_Estimation_Execution.py diff --git a/B09_Estimation/B09_Estimation_Explosives.py b/old_code/B09_Estimation/B09_Estimation_Explosives.py similarity index 100% rename from B09_Estimation/B09_Estimation_Explosives.py rename to old_code/B09_Estimation/B09_Estimation_Explosives.py diff --git a/B09_Estimation/B09_Estimation_FactorChoices.py b/old_code/B09_Estimation/B09_Estimation_FactorChoices.py similarity index 100% rename from B09_Estimation/B09_Estimation_FactorChoices.py rename to old_code/B09_Estimation/B09_Estimation_FactorChoices.py diff --git a/B09_Estimation/B09_Estimation_Guards.py b/old_code/B09_Estimation/B09_Estimation_Guards.py similarity index 100% rename from B09_Estimation/B09_Estimation_Guards.py rename to old_code/B09_Estimation/B09_Estimation_Guards.py diff --git a/B09_Estimation/B09_Estimation_KnownGaps.py b/old_code/B09_Estimation/B09_Estimation_KnownGaps.py similarity index 100% rename from B09_Estimation/B09_Estimation_KnownGaps.py rename to old_code/B09_Estimation/B09_Estimation_KnownGaps.py diff --git a/B09_Estimation/B09_Estimation_LaborSurcharge.py b/old_code/B09_Estimation/B09_Estimation_LaborSurcharge.py similarity index 100% rename from B09_Estimation/B09_Estimation_LaborSurcharge.py rename to old_code/B09_Estimation/B09_Estimation_LaborSurcharge.py diff --git a/B09_Estimation/B09_Estimation_Lists.py b/old_code/B09_Estimation/B09_Estimation_Lists.py similarity index 100% rename from B09_Estimation/B09_Estimation_Lists.py rename to old_code/B09_Estimation/B09_Estimation_Lists.py diff --git a/B09_Estimation/B09_Estimation_Lists_Sources.py b/old_code/B09_Estimation/B09_Estimation_Lists_Sources.py similarity index 100% rename from B09_Estimation/B09_Estimation_Lists_Sources.py rename to old_code/B09_Estimation/B09_Estimation_Lists_Sources.py diff --git a/B09_Estimation/B09_Estimation_MachineCost.py b/old_code/B09_Estimation/B09_Estimation_MachineCost.py similarity index 100% rename from B09_Estimation/B09_Estimation_MachineCost.py rename to old_code/B09_Estimation/B09_Estimation_MachineCost.py diff --git a/B09_Estimation/B09_Estimation_MachineExpenseSheet.py b/old_code/B09_Estimation/B09_Estimation_MachineExpenseSheet.py similarity index 100% rename from B09_Estimation/B09_Estimation_MachineExpenseSheet.py rename to old_code/B09_Estimation/B09_Estimation_MachineExpenseSheet.py diff --git a/B09_Estimation/B09_Estimation_MachineOperating.py b/old_code/B09_Estimation/B09_Estimation_MachineOperating.py similarity index 100% rename from B09_Estimation/B09_Estimation_MachineOperating.py rename to old_code/B09_Estimation/B09_Estimation_MachineOperating.py diff --git a/B09_Estimation/B09_Estimation_MachineProductivity.py b/old_code/B09_Estimation/B09_Estimation_MachineProductivity.py similarity index 100% rename from B09_Estimation/B09_Estimation_MachineProductivity.py rename to old_code/B09_Estimation/B09_Estimation_MachineProductivity.py diff --git a/B09_Estimation/B09_Estimation_MachineProductivity_Compaction.py b/old_code/B09_Estimation/B09_Estimation_MachineProductivity_Compaction.py similarity index 100% rename from B09_Estimation/B09_Estimation_MachineProductivity_Compaction.py rename to old_code/B09_Estimation/B09_Estimation_MachineProductivity_Compaction.py diff --git a/B09_Estimation/B09_Estimation_MachineProductivity_Data.py b/old_code/B09_Estimation/B09_Estimation_MachineProductivity_Data.py similarity index 100% rename from B09_Estimation/B09_Estimation_MachineProductivity_Data.py rename to old_code/B09_Estimation/B09_Estimation_MachineProductivity_Data.py diff --git a/B09_Estimation/B09_Estimation_MachineProductivity_Dozer.py b/old_code/B09_Estimation/B09_Estimation_MachineProductivity_Dozer.py similarity index 100% rename from B09_Estimation/B09_Estimation_MachineProductivity_Dozer.py rename to old_code/B09_Estimation/B09_Estimation_MachineProductivity_Dozer.py diff --git a/B09_Estimation/B09_Estimation_MachineProductivity_Dump.py b/old_code/B09_Estimation/B09_Estimation_MachineProductivity_Dump.py similarity index 100% rename from B09_Estimation/B09_Estimation_MachineProductivity_Dump.py rename to old_code/B09_Estimation/B09_Estimation_MachineProductivity_Dump.py diff --git a/B09_Estimation/B09_Estimation_MachineProductivity_Reference.py b/old_code/B09_Estimation/B09_Estimation_MachineProductivity_Reference.py similarity index 100% rename from B09_Estimation/B09_Estimation_MachineProductivity_Reference.py rename to old_code/B09_Estimation/B09_Estimation_MachineProductivity_Reference.py diff --git a/B09_Estimation/B09_Estimation_MaterialCatalog.py b/old_code/B09_Estimation/B09_Estimation_MaterialCatalog.py similarity index 100% rename from B09_Estimation/B09_Estimation_MaterialCatalog.py rename to old_code/B09_Estimation/B09_Estimation_MaterialCatalog.py diff --git a/B09_Estimation/B09_Estimation_MaterialPrices.py b/old_code/B09_Estimation/B09_Estimation_MaterialPrices.py similarity index 100% rename from B09_Estimation/B09_Estimation_MaterialPrices.py rename to old_code/B09_Estimation/B09_Estimation_MaterialPrices.py diff --git a/B09_Estimation/B09_Estimation_MaterialSheet.py b/old_code/B09_Estimation/B09_Estimation_MaterialSheet.py similarity index 100% rename from B09_Estimation/B09_Estimation_MaterialSheet.py rename to old_code/B09_Estimation/B09_Estimation_MaterialSheet.py diff --git a/B09_Estimation/B09_Estimation_ParentSteps.py b/old_code/B09_Estimation/B09_Estimation_ParentSteps.py similarity index 100% rename from B09_Estimation/B09_Estimation_ParentSteps.py rename to old_code/B09_Estimation/B09_Estimation_ParentSteps.py diff --git a/B09_Estimation/B09_Estimation_PriceBasis.py b/old_code/B09_Estimation/B09_Estimation_PriceBasis.py similarity index 100% rename from B09_Estimation/B09_Estimation_PriceBasis.py rename to old_code/B09_Estimation/B09_Estimation_PriceBasis.py diff --git a/B09_Estimation/B09_Estimation_PriceBook.py b/old_code/B09_Estimation/B09_Estimation_PriceBook.py similarity index 100% rename from B09_Estimation/B09_Estimation_PriceBook.py rename to old_code/B09_Estimation/B09_Estimation_PriceBook.py diff --git a/B09_Estimation/B09_Estimation_Progress.py b/old_code/B09_Estimation/B09_Estimation_Progress.py similarity index 100% rename from B09_Estimation/B09_Estimation_Progress.py rename to old_code/B09_Estimation/B09_Estimation_Progress.py diff --git a/B09_Estimation/B09_Estimation_Provenance.py b/old_code/B09_Estimation/B09_Estimation_Provenance.py similarity index 100% rename from B09_Estimation/B09_Estimation_Provenance.py rename to old_code/B09_Estimation/B09_Estimation_Provenance.py diff --git a/B09_Estimation/B09_Estimation_Provenance_Common.py b/old_code/B09_Estimation/B09_Estimation_Provenance_Common.py similarity index 100% rename from B09_Estimation/B09_Estimation_Provenance_Common.py rename to old_code/B09_Estimation/B09_Estimation_Provenance_Common.py diff --git a/B09_Estimation/B09_Estimation_Provenance_Sources.py b/old_code/B09_Estimation/B09_Estimation_Provenance_Sources.py similarity index 100% rename from B09_Estimation/B09_Estimation_Provenance_Sources.py rename to old_code/B09_Estimation/B09_Estimation_Provenance_Sources.py diff --git a/B09_Estimation/B09_Estimation_QuantityDigits.py b/old_code/B09_Estimation/B09_Estimation_QuantityDigits.py similarity index 100% rename from B09_Estimation/B09_Estimation_QuantityDigits.py rename to old_code/B09_Estimation/B09_Estimation_QuantityDigits.py diff --git a/B09_Estimation/B09_Estimation_RateOverride.py b/old_code/B09_Estimation/B09_Estimation_RateOverride.py similarity index 100% rename from B09_Estimation/B09_Estimation_RateOverride.py rename to old_code/B09_Estimation/B09_Estimation_RateOverride.py diff --git a/B09_Estimation/B09_Estimation_RateTable.py b/old_code/B09_Estimation/B09_Estimation_RateTable.py similarity index 100% rename from B09_Estimation/B09_Estimation_RateTable.py rename to old_code/B09_Estimation/B09_Estimation_RateTable.py diff --git a/B09_Estimation/B09_Estimation_Rates.py b/old_code/B09_Estimation/B09_Estimation_Rates.py similarity index 100% rename from B09_Estimation/B09_Estimation_Rates.py rename to old_code/B09_Estimation/B09_Estimation_Rates.py diff --git a/B09_Estimation/B09_Estimation_ResourceAxis.py b/old_code/B09_Estimation/B09_Estimation_ResourceAxis.py similarity index 100% rename from B09_Estimation/B09_Estimation_ResourceAxis.py rename to old_code/B09_Estimation/B09_Estimation_ResourceAxis.py diff --git a/B09_Estimation/B09_Estimation_ResourceAxis_ChooseOne.py b/old_code/B09_Estimation/B09_Estimation_ResourceAxis_ChooseOne.py similarity index 100% rename from B09_Estimation/B09_Estimation_ResourceAxis_ChooseOne.py rename to old_code/B09_Estimation/B09_Estimation_ResourceAxis_ChooseOne.py diff --git a/B09_Estimation/B09_Estimation_ResourceAxis_Join.py b/old_code/B09_Estimation/B09_Estimation_ResourceAxis_Join.py similarity index 100% rename from B09_Estimation/B09_Estimation_ResourceAxis_Join.py rename to old_code/B09_Estimation/B09_Estimation_ResourceAxis_Join.py diff --git a/B09_Estimation/B09_Estimation_ResourceAxis_JudgedTable.py b/old_code/B09_Estimation/B09_Estimation_ResourceAxis_JudgedTable.py similarity index 100% rename from B09_Estimation/B09_Estimation_ResourceAxis_JudgedTable.py rename to old_code/B09_Estimation/B09_Estimation_ResourceAxis_JudgedTable.py diff --git a/B09_Estimation/B09_Estimation_ResourceAxis_Labels.py b/old_code/B09_Estimation/B09_Estimation_ResourceAxis_Labels.py similarity index 100% rename from B09_Estimation/B09_Estimation_ResourceAxis_Labels.py rename to old_code/B09_Estimation/B09_Estimation_ResourceAxis_Labels.py diff --git a/B09_Estimation/B09_Estimation_ResourceAxis_ProcessSum.py b/old_code/B09_Estimation/B09_Estimation_ResourceAxis_ProcessSum.py similarity index 100% rename from B09_Estimation/B09_Estimation_ResourceAxis_ProcessSum.py rename to old_code/B09_Estimation/B09_Estimation_ResourceAxis_ProcessSum.py diff --git a/B09_Estimation/B09_Estimation_ResourceAxis_Sources.py b/old_code/B09_Estimation/B09_Estimation_ResourceAxis_Sources.py similarity index 100% rename from B09_Estimation/B09_Estimation_ResourceAxis_Sources.py rename to old_code/B09_Estimation/B09_Estimation_ResourceAxis_Sources.py diff --git a/B09_Estimation/B09_Estimation_ResourceAxis_ThreeAxis.py b/old_code/B09_Estimation/B09_Estimation_ResourceAxis_ThreeAxis.py similarity index 100% rename from B09_Estimation/B09_Estimation_ResourceAxis_ThreeAxis.py rename to old_code/B09_Estimation/B09_Estimation_ResourceAxis_ThreeAxis.py diff --git a/B09_Estimation/B09_Estimation_ResourceAxis_Transposed.py b/old_code/B09_Estimation/B09_Estimation_ResourceAxis_Transposed.py similarity index 100% rename from B09_Estimation/B09_Estimation_ResourceAxis_Transposed.py rename to old_code/B09_Estimation/B09_Estimation_ResourceAxis_Transposed.py diff --git a/B09_Estimation/B09_Estimation_ResourceAxis_TwoLevelHeader.py b/old_code/B09_Estimation/B09_Estimation_ResourceAxis_TwoLevelHeader.py similarity index 100% rename from B09_Estimation/B09_Estimation_ResourceAxis_TwoLevelHeader.py rename to old_code/B09_Estimation/B09_Estimation_ResourceAxis_TwoLevelHeader.py diff --git a/B09_Estimation/B09_Estimation_ResourceAxis_UnitBasis.py b/old_code/B09_Estimation/B09_Estimation_ResourceAxis_UnitBasis.py similarity index 100% rename from B09_Estimation/B09_Estimation_ResourceAxis_UnitBasis.py rename to old_code/B09_Estimation/B09_Estimation_ResourceAxis_UnitBasis.py diff --git a/B09_Estimation/B09_Estimation_RockLoss.py b/old_code/B09_Estimation/B09_Estimation_RockLoss.py similarity index 100% rename from B09_Estimation/B09_Estimation_RockLoss.py rename to old_code/B09_Estimation/B09_Estimation_RockLoss.py diff --git a/B09_Estimation/B09_Estimation_Rounding.py b/old_code/B09_Estimation/B09_Estimation_Rounding.py similarity index 100% rename from B09_Estimation/B09_Estimation_Rounding.py rename to old_code/B09_Estimation/B09_Estimation_Rounding.py diff --git a/B09_Estimation/B09_Estimation_Router.py b/old_code/B09_Estimation/B09_Estimation_Router.py similarity index 100% rename from B09_Estimation/B09_Estimation_Router.py rename to old_code/B09_Estimation/B09_Estimation_Router.py diff --git a/B09_Estimation/B09_Estimation_Router_Contract.py b/old_code/B09_Estimation/B09_Estimation_Router_Contract.py similarity index 100% rename from B09_Estimation/B09_Estimation_Router_Contract.py rename to old_code/B09_Estimation/B09_Estimation_Router_Contract.py diff --git a/B09_Estimation/B09_Estimation_Router_CostSheet.py b/old_code/B09_Estimation/B09_Estimation_Router_CostSheet.py similarity index 100% rename from B09_Estimation/B09_Estimation_Router_CostSheet.py rename to old_code/B09_Estimation/B09_Estimation_Router_CostSheet.py diff --git a/B09_Estimation/B09_Estimation_Router_Edits.py b/old_code/B09_Estimation/B09_Estimation_Router_Edits.py similarity index 100% rename from B09_Estimation/B09_Estimation_Router_Edits.py rename to old_code/B09_Estimation/B09_Estimation_Router_Edits.py diff --git a/B09_Estimation/B09_Estimation_Router_Execution.py b/old_code/B09_Estimation/B09_Estimation_Router_Execution.py similarity index 100% rename from B09_Estimation/B09_Estimation_Router_Execution.py rename to old_code/B09_Estimation/B09_Estimation_Router_Execution.py diff --git a/B09_Estimation/B09_Estimation_Router_Factors.py b/old_code/B09_Estimation/B09_Estimation_Router_Factors.py similarity index 100% rename from B09_Estimation/B09_Estimation_Router_Factors.py rename to old_code/B09_Estimation/B09_Estimation_Router_Factors.py diff --git a/B09_Estimation/B09_Estimation_Router_MaterialPrices.py b/old_code/B09_Estimation/B09_Estimation_Router_MaterialPrices.py similarity index 100% rename from B09_Estimation/B09_Estimation_Router_MaterialPrices.py rename to old_code/B09_Estimation/B09_Estimation_Router_MaterialPrices.py diff --git a/B09_Estimation/B09_Estimation_Router_Progress.py b/old_code/B09_Estimation/B09_Estimation_Router_Progress.py similarity index 100% rename from B09_Estimation/B09_Estimation_Router_Progress.py rename to old_code/B09_Estimation/B09_Estimation_Router_Progress.py diff --git a/B09_Estimation/B09_Estimation_SeedSpray.py b/old_code/B09_Estimation/B09_Estimation_SeedSpray.py similarity index 100% rename from B09_Estimation/B09_Estimation_SeedSpray.py rename to old_code/B09_Estimation/B09_Estimation_SeedSpray.py diff --git a/B09_Estimation/B09_Estimation_Statutory.py b/old_code/B09_Estimation/B09_Estimation_Statutory.py similarity index 100% rename from B09_Estimation/B09_Estimation_Statutory.py rename to old_code/B09_Estimation/B09_Estimation_Statutory.py diff --git a/B09_Estimation/B09_Estimation_Storage.py b/old_code/B09_Estimation/B09_Estimation_Storage.py similarity index 100% rename from B09_Estimation/B09_Estimation_Storage.py rename to old_code/B09_Estimation/B09_Estimation_Storage.py diff --git a/B09_Estimation/B09_Estimation_TopsoilRemoval.py b/old_code/B09_Estimation/B09_Estimation_TopsoilRemoval.py similarity index 100% rename from B09_Estimation/B09_Estimation_TopsoilRemoval.py rename to old_code/B09_Estimation/B09_Estimation_TopsoilRemoval.py diff --git a/B09_Estimation/B09_Estimation_Transport.py b/old_code/B09_Estimation/B09_Estimation_Transport.py similarity index 100% rename from B09_Estimation/B09_Estimation_Transport.py rename to old_code/B09_Estimation/B09_Estimation_Transport.py diff --git a/B09_Estimation/B09_Estimation_UI_BillRates.ts b/old_code/B09_Estimation/B09_Estimation_UI_BillRates.ts similarity index 100% rename from B09_Estimation/B09_Estimation_UI_BillRates.ts rename to old_code/B09_Estimation/B09_Estimation_UI_BillRates.ts diff --git a/B09_Estimation/B09_Estimation_UI_Detail.ts b/old_code/B09_Estimation/B09_Estimation_UI_Detail.ts similarity index 100% rename from B09_Estimation/B09_Estimation_UI_Detail.ts rename to old_code/B09_Estimation/B09_Estimation_UI_Detail.ts diff --git a/B09_Estimation/B09_Estimation_UI_DetailEdit.ts b/old_code/B09_Estimation/B09_Estimation_UI_DetailEdit.ts similarity index 100% rename from B09_Estimation/B09_Estimation_UI_DetailEdit.ts rename to old_code/B09_Estimation/B09_Estimation_UI_DetailEdit.ts diff --git a/B09_Estimation/B09_Estimation_UI_Factors.ts b/old_code/B09_Estimation/B09_Estimation_UI_Factors.ts similarity index 100% rename from B09_Estimation/B09_Estimation_UI_Factors.ts rename to old_code/B09_Estimation/B09_Estimation_UI_Factors.ts diff --git a/B09_Estimation/B09_Estimation_UI_MachineExpense.ts b/old_code/B09_Estimation/B09_Estimation_UI_MachineExpense.ts similarity index 100% rename from B09_Estimation/B09_Estimation_UI_MachineExpense.ts rename to old_code/B09_Estimation/B09_Estimation_UI_MachineExpense.ts diff --git a/B09_Estimation/B09_Estimation_UI_Sheet.ts b/old_code/B09_Estimation/B09_Estimation_UI_Sheet.ts similarity index 100% rename from B09_Estimation/B09_Estimation_UI_Sheet.ts rename to old_code/B09_Estimation/B09_Estimation_UI_Sheet.ts diff --git a/old_code/B09_Estimation/B09_Estimation_UI_Shell.ts b/old_code/B09_Estimation/B09_Estimation_UI_Shell.ts new file mode 100644 index 00000000..5b40dc7d --- /dev/null +++ b/old_code/B09_Estimation/B09_Estimation_UI_Shell.ts @@ -0,0 +1,106 @@ +/* ============================================================================= + * B09_Estimation_UI_Shell.ts + * 로그인 후 09: 6차 워크플로우 (원가계산) — 화면 **틀** (PLAN 12장 · 2026-09-14 브레인 판정) + * + * - 틀은 탭 줄과 등록만. 탭마다 파일 하나(`B09_Estimation_UI_Tab_<이름>.ts`) — 계약은 `_Shell_Types`. + * - ⚠ 등록 권한 = 랩탑_서브. 다른 창은 탭 파일을 만들고 이름을 알려 등록을 부탁함. + * - 탭을 고를 때마다 본문·좌측 칸을 비우고 `render(ctx, arg)`. `ctx.open(key, arg)` = 다른 탭으로 들어가기. + * ========================================================================== */ + +import { createWorkflowLayout } from "@ui/ui_template_workflow_layout"; +import { CURRENT_PROJECT_ID_KEY } from "@config/config_frontend"; +import { workflowSteps } from "../A00_Common/b_page_scaffold"; +import { goToWorkflowStage, WORKFLOW_STEP_ROUTES } from "../A00_Common/b_workflow_nav"; +import type { B09Tab, B09TabContext } from "./B09_Estimation_UI_Shell_Types"; +import { L, el, injectSheetStyles } from "./B09_Estimation_UI_Sheet"; +import { costSheetTab } from "./B09_Estimation_UI_Tab_CostSheet"; +import { rateTableTab } from "./B09_Estimation_UI_Tab_RateTable"; +import { billTab } from "./B09_Estimation_UI_Tab_Bill"; +import { unitPriceTab } from "./B09_Estimation_UI_Tab_UnitPrice"; +import { priceBasisTab } from "./B09_Estimation_UI_Tab_PriceBasis"; +import { machineTab } from "./B09_Estimation_UI_Tab_Machine"; +import { priceCompareTab } from "./B09_Estimation_UI_Tab_PriceCompare"; +import { materialPricesTab } from "./B09_Estimation_UI_Tab_MaterialPrices"; +import { summaryTab } from "./B09_Estimation_UI_Tab_Summary"; +import { listsTab } from "./B09_Estimation_UI_Tab_Lists"; +import { designDocTab } from "./B09_Estimation_UI_Tab_DesignDoc"; +import { basisSheetTab } from "./B09_Estimation_UI_Tab_BasisSheet"; +import { supplyTab } from "./B09_Estimation_UI_Tab_Supply"; +import { baseDataTab } from "./B09_Estimation_UI_Tab_BaseData"; +import { contractTab } from "./B09_Estimation_UI_Tab_Contract"; +import { executionTab } from "./B09_Estimation_UI_Tab_Execution"; +import { progressTab } from "./B09_Estimation_UI_Tab_Progress"; +import { completionTab } from "./B09_Estimation_UI_Tab_Completion"; + +/** 탭 등록 — 한 줄에 탭 하나. */ +const TABS: B09Tab[] = [ + costSheetTab, // 랩탑_메인 + rateTableTab, // 랩탑_메인 + billTab, + unitPriceTab, + priceBasisTab, + machineTab, + priceCompareTab, + materialPricesTab, // 랩탑_메인 — 자재단가대비표 옆(자재 수동 단가) + summaryTab, + listsTab, + supplyTab, + baseDataTab, + designDocTab, + basisSheetTab, + contractTab, // 랩탑_메인 — 설계 뒤 계약 단계라 맨 끝 + executionTab, // 랩탑_메인 — 계약 뒤 실행 단계 + progressTab, // 랩탑_메인 — 실행 뒤 기성 단계 + completionTab, // 랩탑_메인 — 기성 뒤 준공 단계 +]; + +export async function renderB09Estimation(root: HTMLElement): Promise { + injectSheetStyles(); + const projectId = localStorage.getItem(CURRENT_PROJECT_ID_KEY); + const page = el("div", "b09s-page"); + const bar = el("div", "b09s-tabs"); + const body = el("div", "b09s-body"); + const panel = el("div"); + page.append(bar, body); + + let active = TABS[0].key; + const open = (key: string, arg?: string): void => { + const tab = TABS.find((item) => item.key === key); + if (!tab) return; + active = key; + drawBar(); + body.replaceChildren(); + panel.replaceChildren(); + const ctx: B09TabContext = { projectId, body, panel, root, open }; + tab.render(ctx, arg); + }; + const drawBar = (): void => { + bar.replaceChildren( + ...TABS.map((tab) => { + const button = el( + "button", + `b09s-tab${tab.key === active ? " is-active" : ""}`, + tab.label(), + ); + button.type = "button"; + button.dataset.tab = tab.key; + button.addEventListener("click", () => open(tab.key)); + return button; + }), + ); + }; + + const layout = createWorkflowLayout({ + title: L("B09_Estimation_Title"), + steps: workflowSteps(), + activeStep: 6, + leftPanel: panel, + mainContent: page, + routes: WORKFLOW_STEP_ROUTES, + onStepClick: (stepIndex) => { + if (projectId) goToWorkflowStage(projectId, WORKFLOW_STEP_ROUTES[stepIndex]); + }, + }); + root.append(layout.root); + open(active); +} diff --git a/B09_Estimation/B09_Estimation_UI_Shell_Types.ts b/old_code/B09_Estimation/B09_Estimation_UI_Shell_Types.ts similarity index 100% rename from B09_Estimation/B09_Estimation_UI_Shell_Types.ts rename to old_code/B09_Estimation/B09_Estimation_UI_Shell_Types.ts diff --git a/B09_Estimation/B09_Estimation_UI_Store.ts b/old_code/B09_Estimation/B09_Estimation_UI_Store.ts similarity index 100% rename from B09_Estimation/B09_Estimation_UI_Store.ts rename to old_code/B09_Estimation/B09_Estimation_UI_Store.ts diff --git a/B09_Estimation/B09_Estimation_UI_Tab_BaseData.ts b/old_code/B09_Estimation/B09_Estimation_UI_Tab_BaseData.ts similarity index 100% rename from B09_Estimation/B09_Estimation_UI_Tab_BaseData.ts rename to old_code/B09_Estimation/B09_Estimation_UI_Tab_BaseData.ts diff --git a/B09_Estimation/B09_Estimation_UI_Tab_BasisSheet.ts b/old_code/B09_Estimation/B09_Estimation_UI_Tab_BasisSheet.ts similarity index 100% rename from B09_Estimation/B09_Estimation_UI_Tab_BasisSheet.ts rename to old_code/B09_Estimation/B09_Estimation_UI_Tab_BasisSheet.ts diff --git a/B09_Estimation/B09_Estimation_UI_Tab_Bill.ts b/old_code/B09_Estimation/B09_Estimation_UI_Tab_Bill.ts similarity index 100% rename from B09_Estimation/B09_Estimation_UI_Tab_Bill.ts rename to old_code/B09_Estimation/B09_Estimation_UI_Tab_Bill.ts diff --git a/B09_Estimation/B09_Estimation_UI_Tab_Completion.ts b/old_code/B09_Estimation/B09_Estimation_UI_Tab_Completion.ts similarity index 100% rename from B09_Estimation/B09_Estimation_UI_Tab_Completion.ts rename to old_code/B09_Estimation/B09_Estimation_UI_Tab_Completion.ts diff --git a/B09_Estimation/B09_Estimation_UI_Tab_Contract.ts b/old_code/B09_Estimation/B09_Estimation_UI_Tab_Contract.ts similarity index 100% rename from B09_Estimation/B09_Estimation_UI_Tab_Contract.ts rename to old_code/B09_Estimation/B09_Estimation_UI_Tab_Contract.ts diff --git a/B09_Estimation/B09_Estimation_UI_Tab_CostSheet.ts b/old_code/B09_Estimation/B09_Estimation_UI_Tab_CostSheet.ts similarity index 100% rename from B09_Estimation/B09_Estimation_UI_Tab_CostSheet.ts rename to old_code/B09_Estimation/B09_Estimation_UI_Tab_CostSheet.ts diff --git a/B09_Estimation/B09_Estimation_UI_Tab_DesignDoc.ts b/old_code/B09_Estimation/B09_Estimation_UI_Tab_DesignDoc.ts similarity index 100% rename from B09_Estimation/B09_Estimation_UI_Tab_DesignDoc.ts rename to old_code/B09_Estimation/B09_Estimation_UI_Tab_DesignDoc.ts diff --git a/B09_Estimation/B09_Estimation_UI_Tab_Execution.ts b/old_code/B09_Estimation/B09_Estimation_UI_Tab_Execution.ts similarity index 100% rename from B09_Estimation/B09_Estimation_UI_Tab_Execution.ts rename to old_code/B09_Estimation/B09_Estimation_UI_Tab_Execution.ts diff --git a/B09_Estimation/B09_Estimation_UI_Tab_Lists.ts b/old_code/B09_Estimation/B09_Estimation_UI_Tab_Lists.ts similarity index 100% rename from B09_Estimation/B09_Estimation_UI_Tab_Lists.ts rename to old_code/B09_Estimation/B09_Estimation_UI_Tab_Lists.ts diff --git a/B09_Estimation/B09_Estimation_UI_Tab_Machine.ts b/old_code/B09_Estimation/B09_Estimation_UI_Tab_Machine.ts similarity index 100% rename from B09_Estimation/B09_Estimation_UI_Tab_Machine.ts rename to old_code/B09_Estimation/B09_Estimation_UI_Tab_Machine.ts diff --git a/B09_Estimation/B09_Estimation_UI_Tab_MaterialPrices.ts b/old_code/B09_Estimation/B09_Estimation_UI_Tab_MaterialPrices.ts similarity index 100% rename from B09_Estimation/B09_Estimation_UI_Tab_MaterialPrices.ts rename to old_code/B09_Estimation/B09_Estimation_UI_Tab_MaterialPrices.ts diff --git a/B09_Estimation/B09_Estimation_UI_Tab_PriceBasis.ts b/old_code/B09_Estimation/B09_Estimation_UI_Tab_PriceBasis.ts similarity index 100% rename from B09_Estimation/B09_Estimation_UI_Tab_PriceBasis.ts rename to old_code/B09_Estimation/B09_Estimation_UI_Tab_PriceBasis.ts diff --git a/B09_Estimation/B09_Estimation_UI_Tab_PriceCompare.ts b/old_code/B09_Estimation/B09_Estimation_UI_Tab_PriceCompare.ts similarity index 100% rename from B09_Estimation/B09_Estimation_UI_Tab_PriceCompare.ts rename to old_code/B09_Estimation/B09_Estimation_UI_Tab_PriceCompare.ts diff --git a/B09_Estimation/B09_Estimation_UI_Tab_Progress.ts b/old_code/B09_Estimation/B09_Estimation_UI_Tab_Progress.ts similarity index 100% rename from B09_Estimation/B09_Estimation_UI_Tab_Progress.ts rename to old_code/B09_Estimation/B09_Estimation_UI_Tab_Progress.ts diff --git a/B09_Estimation/B09_Estimation_UI_Tab_RateTable.ts b/old_code/B09_Estimation/B09_Estimation_UI_Tab_RateTable.ts similarity index 100% rename from B09_Estimation/B09_Estimation_UI_Tab_RateTable.ts rename to old_code/B09_Estimation/B09_Estimation_UI_Tab_RateTable.ts diff --git a/B09_Estimation/B09_Estimation_UI_Tab_Summary.ts b/old_code/B09_Estimation/B09_Estimation_UI_Tab_Summary.ts similarity index 100% rename from B09_Estimation/B09_Estimation_UI_Tab_Summary.ts rename to old_code/B09_Estimation/B09_Estimation_UI_Tab_Summary.ts diff --git a/B09_Estimation/B09_Estimation_UI_Tab_Supply.ts b/old_code/B09_Estimation/B09_Estimation_UI_Tab_Supply.ts similarity index 100% rename from B09_Estimation/B09_Estimation_UI_Tab_Supply.ts rename to old_code/B09_Estimation/B09_Estimation_UI_Tab_Supply.ts diff --git a/B09_Estimation/B09_Estimation_UI_Tab_UnitPrice.ts b/old_code/B09_Estimation/B09_Estimation_UI_Tab_UnitPrice.ts similarity index 100% rename from B09_Estimation/B09_Estimation_UI_Tab_UnitPrice.ts rename to old_code/B09_Estimation/B09_Estimation_UI_Tab_UnitPrice.ts diff --git a/B09_Estimation/B09_Estimation_UI_Table.ts b/old_code/B09_Estimation/B09_Estimation_UI_Table.ts similarity index 100% rename from B09_Estimation/B09_Estimation_UI_Table.ts rename to old_code/B09_Estimation/B09_Estimation_UI_Table.ts diff --git a/B09_Estimation/B09_Estimation_UnitPrice.py b/old_code/B09_Estimation/B09_Estimation_UnitPrice.py similarity index 100% rename from B09_Estimation/B09_Estimation_UnitPrice.py rename to old_code/B09_Estimation/B09_Estimation_UnitPrice.py diff --git a/B09_Estimation/B09_Estimation_UnitPriceSheet.py b/old_code/B09_Estimation/B09_Estimation_UnitPriceSheet.py similarity index 100% rename from B09_Estimation/B09_Estimation_UnitPriceSheet.py rename to old_code/B09_Estimation/B09_Estimation_UnitPriceSheet.py diff --git a/B09_Estimation/B09_Estimation_UnitPrice_View.py b/old_code/B09_Estimation/B09_Estimation_UnitPrice_View.py similarity index 100% rename from B09_Estimation/B09_Estimation_UnitPrice_View.py rename to old_code/B09_Estimation/B09_Estimation_UnitPrice_View.py diff --git a/B09_Estimation/B09_Estimation_UnreadTables.py b/old_code/B09_Estimation/B09_Estimation_UnreadTables.py similarity index 100% rename from B09_Estimation/B09_Estimation_UnreadTables.py rename to old_code/B09_Estimation/B09_Estimation_UnreadTables.py diff --git a/B09_Estimation/B09_Estimation_WoodChipping.py b/old_code/B09_Estimation/B09_Estimation_WoodChipping.py similarity index 100% rename from B09_Estimation/B09_Estimation_WoodChipping.py rename to old_code/B09_Estimation/B09_Estimation_WoodChipping.py diff --git a/B09_Estimation/B09_Estimation_WorkItemUnit.py b/old_code/B09_Estimation/B09_Estimation_WorkItemUnit.py similarity index 100% rename from B09_Estimation/B09_Estimation_WorkItemUnit.py rename to old_code/B09_Estimation/B09_Estimation_WorkItemUnit.py diff --git a/B09_Estimation/B09_Estimation_WorkItems_AX.py b/old_code/B09_Estimation/B09_Estimation_WorkItems_AX.py similarity index 100% rename from B09_Estimation/B09_Estimation_WorkItems_AX.py rename to old_code/B09_Estimation/B09_Estimation_WorkItems_AX.py diff --git a/resources/tester/test_b05_empty_means_select.py b/old_code/resources/tester/test_b05_empty_means_select.py similarity index 100% rename from resources/tester/test_b05_empty_means_select.py rename to old_code/resources/tester/test_b05_empty_means_select.py diff --git a/resources/tester/test_b05_not_in_table_marks.py b/old_code/resources/tester/test_b05_not_in_table_marks.py similarity index 100% rename from resources/tester/test_b05_not_in_table_marks.py rename to old_code/resources/tester/test_b05_not_in_table_marks.py diff --git a/resources/tester/test_b05_registry_fill_a2_a3.py b/old_code/resources/tester/test_b05_registry_fill_a2_a3.py similarity index 100% rename from resources/tester/test_b05_registry_fill_a2_a3.py rename to old_code/resources/tester/test_b05_registry_fill_a2_a3.py diff --git a/resources/tester/test_b05_retaining_wall_height_default.py b/old_code/resources/tester/test_b05_retaining_wall_height_default.py similarity index 100% rename from resources/tester/test_b05_retaining_wall_height_default.py rename to old_code/resources/tester/test_b05_retaining_wall_height_default.py diff --git a/resources/tester/test_b05_revetment_sabang_basis.py b/old_code/resources/tester/test_b05_revetment_sabang_basis.py similarity index 100% rename from resources/tester/test_b05_revetment_sabang_basis.py rename to old_code/resources/tester/test_b05_revetment_sabang_basis.py diff --git a/resources/tester/test_b06_b08_wall_thickness_match.py b/old_code/resources/tester/test_b06_b08_wall_thickness_match.py similarity index 100% rename from resources/tester/test_b06_b08_wall_thickness_match.py rename to old_code/resources/tester/test_b06_b08_wall_thickness_match.py diff --git a/resources/tester/test_b06_wall_lean_table.py b/old_code/resources/tester/test_b06_wall_lean_table.py similarity index 100% rename from resources/tester/test_b06_wall_lean_table.py rename to old_code/resources/tester/test_b06_wall_lean_table.py diff --git a/resources/tester/test_b07_sheet_key_place.py b/old_code/resources/tester/test_b07_sheet_key_place.py similarity index 100% rename from resources/tester/test_b07_sheet_key_place.py rename to old_code/resources/tester/test_b07_sheet_key_place.py diff --git a/resources/tester/test_b07_standard_figure_reason.py b/old_code/resources/tester/test_b07_standard_figure_reason.py similarity index 100% rename from resources/tester/test_b07_standard_figure_reason.py rename to old_code/resources/tester/test_b07_standard_figure_reason.py diff --git a/resources/tester/test_b07_standard_sheet_rows.py b/old_code/resources/tester/test_b07_standard_sheet_rows.py similarity index 100% rename from resources/tester/test_b07_standard_sheet_rows.py rename to old_code/resources/tester/test_b07_standard_sheet_rows.py diff --git a/resources/tester/test_b07_standard_spec_blinding_weep.py b/old_code/resources/tester/test_b07_standard_spec_blinding_weep.py similarity index 100% rename from resources/tester/test_b07_standard_spec_blinding_weep.py rename to old_code/resources/tester/test_b07_standard_spec_blinding_weep.py diff --git a/resources/tester/test_b10a_rules_visible_b05_b09.py b/old_code/resources/tester/test_b10a_rules_visible_b05_b09.py similarity index 100% rename from resources/tester/test_b10a_rules_visible_b05_b09.py rename to old_code/resources/tester/test_b10a_rules_visible_b05_b09.py diff --git a/resources/tester/test_common_aliases.py b/old_code/resources/tester/test_common_aliases.py similarity index 100% rename from resources/tester/test_common_aliases.py rename to old_code/resources/tester/test_common_aliases.py diff --git a/resources/tester/test_double_count_boundaries.py b/old_code/resources/tester/test_double_count_boundaries.py similarity index 100% rename from resources/tester/test_double_count_boundaries.py rename to old_code/resources/tester/test_double_count_boundaries.py diff --git a/resources/tester/test_form_const.py b/old_code/resources/tester/test_form_const.py similarity index 100% rename from resources/tester/test_form_const.py rename to old_code/resources/tester/test_form_const.py diff --git a/resources/tester/test_haul_limit_setting.py b/old_code/resources/tester/test_haul_limit_setting.py similarity index 100% rename from resources/tester/test_haul_limit_setting.py rename to old_code/resources/tester/test_haul_limit_setting.py diff --git a/resources/tester/test_haul_state_natural.py b/old_code/resources/tester/test_haul_state_natural.py similarity index 100% rename from resources/tester/test_haul_state_natural.py rename to old_code/resources/tester/test_haul_state_natural.py diff --git a/resources/tester/test_pum_edition.py b/old_code/resources/tester/test_pum_edition.py similarity index 100% rename from resources/tester/test_pum_edition.py rename to old_code/resources/tester/test_pum_edition.py diff --git a/resources/tester/test_revetment_stone_fields.py b/old_code/resources/tester/test_revetment_stone_fields.py similarity index 100% rename from resources/tester/test_revetment_stone_fields.py rename to old_code/resources/tester/test_revetment_stone_fields.py diff --git a/resources/tester/test_work_item_key_gate.py b/old_code/resources/tester/test_work_item_key_gate.py similarity index 100% rename from resources/tester/test_work_item_key_gate.py rename to old_code/resources/tester/test_work_item_key_gate.py diff --git a/resources/tester/test_work_item_key_registry.py b/old_code/resources/tester/test_work_item_key_registry.py similarity index 100% rename from resources/tester/test_work_item_key_registry.py rename to old_code/resources/tester/test_work_item_key_registry.py diff --git a/resources/tester/test_work_item_link_choice.py b/old_code/resources/tester/test_work_item_link_choice.py similarity index 100% rename from resources/tester/test_work_item_link_choice.py rename to old_code/resources/tester/test_work_item_link_choice.py diff --git a/resources/tester/test_work_item_master_const.py b/old_code/resources/tester/test_work_item_master_const.py similarity index 100% rename from resources/tester/test_work_item_master_const.py rename to old_code/resources/tester/test_work_item_master_const.py diff --git a/resources/tester/test_work_item_master_toc.py b/old_code/resources/tester/test_work_item_master_toc.py similarity index 100% rename from resources/tester/test_work_item_master_toc.py rename to old_code/resources/tester/test_work_item_master_toc.py diff --git a/resources/tester/test_work_item_master_variants.py b/old_code/resources/tester/test_work_item_master_variants.py similarity index 100% rename from resources/tester/test_work_item_master_variants.py rename to old_code/resources/tester/test_work_item_master_variants.py diff --git a/resources/tester/test_b07_slope_geometry.py b/resources/tester/test_b07_slope_geometry.py new file mode 100644 index 00000000..648df4b2 --- /dev/null +++ b/resources/tester/test_b07_slope_geometry.py @@ -0,0 +1,53 @@ +"""B07 로 옮겨 적은 사면 기하 — 옮기며 값이 바뀌지 않았는지 (PLAN 7-3). + +옛 자리 `B08_Quantity_Engine_SlopeLength`·`_SlopeArea` 에서 **B07 이 쓰는 몫만** 옮겨 적었다 +(`B07_DesignDetail_Engine_SlopeGeometry`). 여기 둔 것은 옮긴 로직이 깨지면 바로 걸리는 판정 +둘 — 소단에서 사면이 끊기지 않을 것 · 층따기 밑수가 성토 비탈면이 아닐 것. +""" + +from __future__ import annotations + +import pytest + +from B06_Section.B06_Section_Engine_Design import compute_cross_design +from B07_DesignDetail.B07_DesignDetail_Engine_SlopeGeometry import ( + PROTECTION_SOURCE, + StationSlope, + length_of, + series_key, + station_slope, +) +from common_util.common_util_cross_berm import BermSpec + +# 노면보다 8m 낮은 평지 — 양쪽 성토 사면이 길게 섬. +SAMPLES = [{"offset_m": x / 2, "elevation_m": -8.0} for x in range(-160, 161)] + + +def _design(berm: BermSpec | None) -> dict: + return compute_cross_design( + SAMPLES, 0.0, ground_type="soil", section_mode="both_fill", berm=berm + ) + + +@pytest.mark.parametrize("slope_deg", [0.0, 2.0]) +def test_소단이_있어도_사면길이가_끝까지_이어진다(slope_deg: float) -> None: + """설계선이 격자로 찍혀 소단 평탄부가 여러 조각으로 쪼개져도 거기서 끊기지 않을 것.""" + plain = station_slope(0.0, _design(None)) + stepped = station_slope(0.0, _design(BermSpec(0.5, 3.0, slope_deg))) + assert plain.fill_length_m > 15 # 양쪽 합 — 시험이 뜻을 가지는 크기 + # 소단 평탄부는 사면길이에 안 들고, 비탈 조각은 끝까지 셈 — 같은 낙차라 거의 같음. + assert stepped.fill_length_m == pytest.approx(plain.fill_length_m, rel=0.05) + assert stepped.fill_height_m == pytest.approx(plain.fill_height_m, abs=0.2) + + +def test_층따기_밑수는_성토_비탈면이_아니다() -> None: + """층따기는 성토부 **아래 원지반**을 깎는 일 — 성토 사면길이로 대신 채우면 다른 면을 센다.""" + slope = StationSlope( + chainage_m=0.0, cut_length_m=7.0, fill_length_m=9.0, bench_cut_length_m=4.0 + ) + assert length_of(slope, "bench_cut", "fill") == 4.0 + assert length_of(slope, "bench_cut", "cut") == 0.0 + # 법면보호공은 면고르기를 참조 — 같은 사면길이를 쓴다. + assert length_of(slope, PROTECTION_SOURCE, "fill") == 9.0 + assert length_of(slope, "face_dressing", "cut") == 7.0 + assert series_key("face_dressing", "cut") == "face_dressing_cut" diff --git a/ui_template/ui_template_locale_b3.ts b/ui_template/ui_template_locale_b3.ts index f5282c25..9ba996d8 100644 --- a/ui_template/ui_template_locale_b3.ts +++ b/ui_template/ui_template_locale_b3.ts @@ -139,4 +139,14 @@ export const ui_locales_b3 = { /* --- B01 시스템 설정 --- */ B01_Dashboard_SystemSettings: ["시스템 설정", "System Settings"], B01_Dashboard_MasterData: ["마스터 데이터", "Master Data"], + + /* --- B08·B09 빈 화면 (PLAN 7-3) --- */ + B08_Quantity_Rebuilding: [ + "수량 산출은 마스터를 쓰는 얼개로 다시 짓는 중입니다 — 표와 계산은 아직 없습니다.", + "Quantity take-off is being rebuilt on the master data — no tables or calculations yet.", + ], + B09_Estimation_Rebuilding: [ + "원가계산은 마스터를 쓰는 얼개로 다시 짓는 중입니다 — 표와 계산은 아직 없습니다.", + "Cost estimation is being rebuilt on the master data — no tables or calculations yet.", + ], } as const;