feat(stmate-test): REA MCP(Ghidra) 계산 엔진 — 표준시장단가(A 코드 COSTA) 내역 연결 · EXRNM 켜짐 경로 · 손료 반올림 MROUNDUD · 적용기준 콤보 값

- A 코드 = COSTA · S_JDA 참 = 저장 단가 · 거짓 = AAMTn 노무비율 나눔 · ACTTYP·ACTOPT = PRJLST · calcBoq·api_load 연결
- EXRNM 참 = S 줄만 단가×율/1000 · ECD 자리 버림 · 천원 · 전각 $
- 손료 계수 반올림 = 첫 버림 자리 5 이상 올림
- 적용기준 국가 2·지방 0·행자부 1 · 행자부 RATE_M 조회 rateMLookup
- 지식DB 36번

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-11-14 15:44:22 +09:00
co-authored by Claude Opus 5.5
parent 2072805247
commit 4ff96a6771
17 changed files with 2936 additions and 23 deletions
@@ -0,0 +1,150 @@
# 36 계산 엔진 잔여 규칙 — 표준시장단가 연결 · 환율 EXRNM 두 경로 · 수량 계산기 반올림 · 적용기준 콤보
## 0. 범위·방법·한계
- 대상 항목: F2-61 표준시장단가 · F2-04 환율 EXRNM · F1-36 손료(수량 계산기) 반올림 · F2-24 적용기준 콤보·행자부 RATE_M.
- 방법: REA MCP(rea-agents 6.7.0 · Ghidra 12.1.4) — STmate.Cpm · ST_KanJub.CPM 디컴파일·어셈블리·xrefs·read_bytes. 원본 실행 없음.
- 근거 파일: `REA_MCP/` (evidence_ids.txt 에 증거 ID).
- 실물 대조: 실무 STC 7건 — COSTA.DBF 행 0 건(A 코드 표본 없음) · 외화 줄은 울진 3건($ · EXRNM 참)만.
## 1. 표준시장단가(A 코드) 단가 읽기 (F2-61) — 확인
### 1.1 자료 자리
- A 코드 단가표 = COSTA.DBF(실적단가 표 · 암호 0x06 꼴).
- 칸: AAMT1~4 · RATE_L · RATE_M · RATE_S · LAMT1~4·MAMT1~4·SAMT1~4·TAMT1~4 · LAMT1_R~LAMT4_R(·MAMTn_R·SAMTn_R) · DOLLOR 등.
- 구성(일위대가·중기) 줄 단가 다시 읽기 Main 0x43307c → 0x437a13:
- 계산 종류 1~3 · 6(계열 > 1) · 7·8(계열 3·칸 3)일 때 줄마다 0x430c60 호출.
- 줄 코드 첫 글자 'A' → 자료집합 [[0x5e6090]+0xc](COSTA) · 깃발 1. 그 밖 → [+8](COSTN) · 깃발 0.
### 1.2 0x430c60 (깃발 1)
- 설정 +0x7c8 = Start8x.Ini [Start] S_JDA(ReadBool 기본 거짓 · 로더 0x42afd9) 참 → 저장 LAMTn·MAMTn·SAMTn·TAMTn 그대로.
- 거짓 → L·M·S = 0 · T = AAMTn.
- T ≠ 0 · RATE_L = 0 · LAMTn_R ≠ 0 → L = LAMTn_R.
- 0x45a468(T 묶음, R = RATE_L, ACTTYP, ACTOPT) 호출 — 나눔 규칙은 34 §6 그대로.
- 끝(0x4312ad): T = L + M + S + 넷째 칸(+0x1e8 · OPTIMIZ 묶음의 A 칸).
### 1.3 ACTTYP·ACTOPT 출처
- 계산이 읽는 공사 레코드 +0x172c·+0x1730 = PRJLST 행 ACTTYP·ACTOPT (로더 0x45c848 · 0x45c938~0x45c956 · 같은 로더가 PCLASS → +0x1750).
- 34 §6 의 「화면 기본 1·2(PCLASS ≠ 9)」는 화면 0x528ac0 의 콤보 채우기 — 계산값은 PRJLST 저장값.
- 실물: 6건 ACTTYP 1·ACTOPT 2 · 울진 2025 1건 0·0.
### 1.4 미확인
- COSTA 칸 5(변경 4차 · 계열 5) 이름 없음 — 계열 5 의 A 줄 값.
- 넷째 칸 A 가 원가계산서(제잡비) 밑수에 들어가는 자리.
- 내역(ST_Boq) A 줄 금액 끝수 — 같은 몸 0x45d534 · 줄 금액 절사는 다른 비목과 같다고 둠.
## 2. 환율 적용 범위 EXRNM 두 경로 (F2-04) — 규칙 확인 · 금액은 같지 않음
### 2.1 설정 자리 (로더 0x42a828)
| 키 | 자리 |
|---|---|
| [EQUIPMENT] EXLNMn | +0x718 + n×8 |
| EXHNMn | +0x728 + n×8 |
| EXYENn | +0x738 + n×8 |
| EXEURn | +0x748 + n×8 |
| EXRNM | +0x7c9 (ReadBool) |
| [Start] ECDNM | +8 |
| [Start] S_JDA | +0x7c8 |
- n = 1·2 · 계산 쪽 첨자 = 단가 칸 번호([in_stack+0x18]).
### 2.2 경로 A — EXRNM 참 (0x437aa2~0x437e2f)
- 조건: 계산 종류 3~8·10 · 줄 코드 'S' · 줄 단위 $·$·€·¥.
- 율 r:
- € = EXEUR/1000.
- ¥ = EXYEN/100000.
- $·$ = IIF(줄 T < 100000, EXL, EXH)/1000.
- S = MROUNDUD(S × r, ECD, 0) — 천원 단위 · ECD 자리 아래 버림.
- 단위 → 「천원」 · T = S.
- 상위 B·천원 갈래(2.3 과 같은 자리 0x435600~0x43590d)에서는 수량 칸 = 1.0 · 단위 「천원」.
### 2.3 경로 B — EXRNM 거짓 (0x435600~0x43590d)
- 조건: 상위 코드 B · 「천원」 비교 참.
- 줄 단위:
- 「천원」 → 수량 칸(+0x128[계열]) = 1.0.
- $·$·€·¥ → 수량 칸 = r(2.2 와 같은 율) · 단가는 외화 그대로.
- 유효수량 +0x1a0 = 수량 칸(0x43590d).
- 비목 금액 = ADDBJVAL(단가 × 유효수량)(0x437303~).
- 상수: 0x439d2c 「천원」 · 0x439d3c 「$」 · 0x439d48 「$」 · 0x439d70 「€」 · 0x439d80 「¥」 · 0x439d64 100000.0f · 0x439d74 1000.0f.
### 2.4 두 경로 금액
- A = 줄끝수(1.0 × MROUNDUD(S × r, ECD, 0)) · B = 줄끝수(r × S).
- S × r 이 ECD 자리 안에서 끝나면 같음. 아니면 A 가 버린 만큼 작음.
- 예: $123.45 · EXL 1289 · ECD 0 → A 159 · B 159.12705(BPN 1 → 159.1).
- ⇒ 같지 않음(ECD 버림 차이).
### 2.5 미확인
- 경로 B 첫 「천원」 비교 문자열의 출처(상위 단위로 추정되나 SmallString 인자 미복원).
- EXRNM 거짓 · 상위 X 등 그 밖 줄의 금액 단계 환산 0x438130~(율 [in_stack−0x98]) 금액식.
## 3. 수량 계산기 반올림 (F1-36) — 확인
- wM_Edit_XRate 일일수량 Q_11 · 적용수량 Q_p = MROUNDUD(x, d, 선택 1) (REA 30).
- MROUNDUD 선택 1 = 첫 버림 자리 ≥ 5 이면 한 단위 올림 · 0 반대쪽 (REA 32 §1.1 · DP_Common 0x4544f0).
- 예: 1 ÷ 8 = 0.125 · 2자리 → 0.13.
## 4. 적용기준 콤보 · 행자부 RATE_M (F2-24) — 짝 확인 · 조회 규칙 확인
### 4.1 콤보 값
- TWM_KANJUB.dfm cmBx_Set_Base(태그 65):
- Items = 일반(국가계약법) · 일반(지방계약법) · 행자부기준.
- Values = 2 · 0 · 1.
- 34 §5.2·§17.5 「DFM 에 Values 없음」 정정.
- 저장 = TwCombo.GetInteger = Values 값.
- 방증: 같은 방식으로 읽는 공사종류를 원본이 8·9 와 비교(0x47fa24) — 8 항목 콤보 순번으로는 나올 수 없는 값.
- 방증: 건설기계 구분 −1 갈래.
- ⇒ I_SET_BASE 1 = 행자부기준 · 0 = 지방계약법 · 2 = 국가계약법.
- 실물 7건 모두 0.
### 4.2 행자부(SET_BASE 1) 조회 (ST_KanJub 0x47a1bc · 0x47acd8~0x47b100)
- 규모 b:
- 밑수 < G[2](50억) → 0 · < G[3](300억) → 1 · 그 밖 2.
- 밑수 = 원가 칸 0x4e4260 첫 값 — RATE_R 규모 조회와 같은 칸.
- 간접노무비 RATE_M:
- 공사종류 < 5 · I_ILBAN ≠ 1 → Go(b+11) · 칸 `RATE{기간 + IIF(공사종류 ∈ {1,3}, 5, 0)}`.
- I_ILBAN 1 또는 공사종류 ≥ 5 → Go(b+15) · `RATE{기간+5}`.
- 기간 = [0x504840].
- 일반관리비 = Go(23) RATE1.
- 이윤 = Go(24) RATE0.
- 그 밖 갈래(SET_BASE ≠ 1):
- 일반관리비 = RATE_R Go(21) RATE_11 · 공사종류 5 = Go(1) RATE_31.
- 이윤 = Go(21) RATE_16.
- 이윤 상한 10: (전역 [0x4d26cc+1] ≠ 0 또는 (S_JDA 거짓 · PCLASS 9)) · 이윤 > 10.
- 실물 RATE_M(25×10): 11~13·15~17 줄 간접노무비 · 23 줄 RATE1 6.0 · 24 줄 RATE0 15.0 → 줄 번호 1부터와 맞음.
### 4.3 앱과 다른 점(보고만)
- 앱 RATE_R 조회는 일반관리비·이윤을 규모 묶음 머리 줄에서 읽음 · 전문공사 일반관리비 15열.
- 원본 0x47a1bc 는 21줄 고정 · 전문공사 갈래 없음 → 50억 이상·전문공사에서 갈릴 수 있음.
- 실물 7건은 모두 50억 미만·I_ILBAN 0.
## 5. 앱 반영
- calc.js:
- `stdMarketUnit`(S_JDA · ACTTYP · ACTOPT · 칸 n).
- calcBoq A 줄 → COSTA 나눔 · 넷째 칸 A(aAmt · directA · T 포함).
- smt_engine.js:
- costnLookup 이 COSTA A 줄을 나눔 단가로 돌려줌.
- fxOf 키 없음 = 1000.
- compositionOptions.exrnm.
- smt_eval.js:
- EXRNM 참 → S 줄만 MROUNDUD(·, ECD, 0) · $ 인식 · 고액 환율 0 대체 제거.
- server.py api_load:
- COSTA 풀어 읽기(costa_rows) · sim_state.stdMarketItems · sysConfig.stdMarket.
- A 임시 일위대가 걷기.
- calc_xrate.js: q = MROUNDUD 선택 1(exec_calc 원형).
- kanjub_parts.js NATIVE: `SET_BASE`(Values↔Items) · `rateMLookup`.
- 시험:
- resources/tester/test_stmate_std_market.py.
- resources/tester/test_stmate_xrate_round.py.
@@ -0,0 +1,621 @@
/* WARNING: Removing unreachable block (ram,0x0042a950) */
void FUN_0042a828(void)
{
undefined1 *puVar1;
char cVar2;
undefined1 uVar3;
short sVar4;
int *piVar5;
undefined4 uVar6;
LPCSTR lpLibFileName;
FARPROC pFVar7;
int iVar8;
undefined4 *in_FS_OFFSET;
float10 in_ST0;
float10 in_ST1;
float10 in_ST2;
float10 in_ST3;
float10 in_ST4;
float10 in_ST5;
float10 in_ST6;
float10 in_ST7;
float10 fVar9;
undefined *puStackY_22c;
undefined4 uStack_21c;
undefined **ppuStack_218;
char *pcStack_1f4;
undefined4 uStack_1ec;
undefined4 ***local_184;
undefined4 local_180;
undefined4 local_17c;
undefined4 local_178;
undefined **local_174;
undefined *local_170;
undefined4 *local_16c;
undefined4 local_168;
undefined4 **local_164;
undefined4 local_160;
undefined **local_15c;
undefined4 local_158;
undefined **local_154;
undefined4 uStack_150;
undefined4 **ppuStack_14c;
undefined4 uStack_148;
undefined **local_144;
char *local_140;
undefined4 local_13c;
undefined ***local_138;
undefined4 ***local_134;
undefined *local_130;
undefined4 ***local_12c;
undefined4 ***local_128;
undefined *local_124;
undefined *local_120;
undefined **local_11c;
undefined *local_118;
undefined **local_114;
undefined ***local_110;
undefined *local_10c;
undefined ***local_108;
undefined4 ***local_104;
undefined *local_100;
undefined *local_fc;
undefined **local_f8;
undefined *local_f4;
undefined ***local_f0;
undefined4 ***local_ec;
undefined *local_e8;
undefined4 local_e4;
undefined4 *local_e0;
undefined *local_dc;
undefined *local_d8;
undefined **local_d4;
undefined *local_d0;
undefined ***local_cc;
undefined4 ***local_c8;
undefined *local_c4;
undefined **local_c0;
undefined ***local_bc;
undefined *local_b8;
undefined ***local_b4;
undefined4 local_b0;
undefined *local_ac;
undefined ***local_a8;
undefined4 ***local_a4;
undefined *local_a0;
undefined ***local_9c;
undefined4 ***local_98;
undefined *local_94;
undefined *local_90;
undefined ***local_8c;
undefined4 ***local_88;
undefined *local_84;
undefined *local_80;
undefined **local_7c;
undefined *puStack_78;
undefined *puStack_74;
undefined **ppuStack_70;
undefined *local_6c;
undefined **local_68;
undefined ***local_64;
undefined *local_60;
undefined *local_5c;
undefined **local_58;
undefined *local_54;
HMODULE local_50;
undefined4 ***local_4c;
HMODULE local_48;
undefined4 ***local_44;
undefined4 *local_40;
HMODULE local_3c;
HMODULE *local_38;
HMODULE local_34;
undefined4 *local_30;
HMODULE local_2c;
int local_28;
undefined4 local_24;
undefined1 *local_20;
undefined1 *local_1c;
undefined4 local_8;
local_1c = &stack0xfffffffc;
iVar8 = 0x30;
do {
local_8 = 0;
iVar8 = iVar8 + -1;
} while (iVar8 != 0);
local_20 = &LAB_0042b6f3;
local_24 = *in_FS_OFFSET;
*in_FS_OFFSET = &local_24;
puVar1 = &stack0xfffffffc;
if (*(int *)(*(int *)PTR_DAT_005e5f8c + 0x5c) != 0) {
local_28 = 0x42a868;
_Sysutils_FreeAndNil_qqrpv(*(int *)PTR_DAT_005e5f8c + 0x5c);
puVar1 = local_1c;
}
local_1c = puVar1;
local_28 = 0x42a87f;
_System__LStrCat3_qqrv(&local_30,*(undefined4 *)(*(int *)PTR_DAT_005e5f8c + 0x38),"STini.MEM");
local_28 = 0x42a887;
cVar2 = _Sysutils_FileExists_qqrx17System_AnsiString(local_30);
if (cVar2 == '\0') {
local_28 = 1;
local_2c = (HMODULE)0x42a8a4;
_System__LStrCat3_qqrv(&local_34,*(undefined4 *)(*(int *)PTR_DAT_005e5f8c + 0x38),"STini.MEM");
local_2c = local_34;
local_30 = (undefined4 *)0x42a8bf;
_System__LStrCat3_qqrv(&local_38,*(undefined4 *)(*(int *)PTR_DAT_005e5f8c + 0x38),"START8X.INI")
;
local_30 = local_38;
local_34 = (HMODULE)0x42a8c8;
COPY_FILE();
}
else {
local_28 = 1;
local_2c = (HMODULE)0x42a8e3;
_System__LStrCat3_qqrv(&local_3c,*(undefined4 *)(*(int *)PTR_DAT_005e5f8c + 0x38),"Start8x.Ini")
;
local_2c = local_3c;
local_30 = (undefined4 *)0x42a8fe;
_System__LStrCat3_qqrv(&local_40,*(undefined4 *)(*(int *)PTR_DAT_005e5f8c + 0x38),"STini.MEM");
local_30 = local_40;
local_34 = (HMODULE)0x42a907;
COPY_FILE();
}
local_34 = (HMODULE)0x42a91e;
_System__LStrCat3_qqrv(&local_44,*(undefined4 *)(*(int *)PTR_DAT_005e5f8c + 0x38),"STini.MEM");
local_34 = (HMODULE)0x42a92d;
piVar5 = (int *)_Inifiles_TCustomIniFile__bctr_qqrx17System_AnsiString
(_Inifiles_TIniFile__exref,1,local_44);
*(int **)(*(int *)PTR_DAT_005e5f8c + 0x5c) = piVar5;
puVar1 = *(undefined1 **)PTR_DAT_005e6148;
puVar1[1] = 1;
local_34 = (HMODULE)&DAT_0042b74c;
local_38 = &local_48;
local_3c = (HMODULE)0x42a96d;
(**(code **)*piVar5)(piVar5,"Start","ACDNM");
local_3c = local_48;
local_40 = (undefined4 *)0x42a976;
MSTRTODBL();
local_40 = (undefined4 *)0x42a97b;
uVar3 = _System__ROUND_qqrv();
puVar1[2] = uVar3;
local_40 = (undefined4 *)&DAT_0042b74c;
local_44 = (undefined4 ***)&local_50;
local_48 = (HMODULE)0x42a997;
(**(code **)*piVar5)(piVar5,"Start","ACDNM");
local_48 = local_50;
local_4c = &local_4c;
local_50 = (HMODULE)0x42a9af;
(**(code **)*piVar5)(piVar5,"Start","BPNNM");
local_50 = (HMODULE)local_4c;
local_54 = (undefined *)0x42a9b8;
MSTRTODBL();
local_54 = (undefined *)0x42a9bd;
uVar3 = _System__ROUND_qqrv();
puVar1[9] = uVar3;
local_54 = &DAT_0042b788;
local_58 = &local_54;
local_5c = (undefined *)0x42a9d9;
(**(code **)*piVar5)(piVar5,"Start","YCDNM");
local_5c = local_54;
local_60 = (undefined *)0x42a9e2;
MSTRTODBL();
local_60 = (undefined *)0x42a9e7;
uVar6 = _System__ROUND_qqrv();
*(undefined4 *)(puVar1 + 0x7c0) = uVar6;
local_60 = &DAT_0042b788;
local_64 = &local_58;
local_68 = (undefined **)0x42aa06;
(**(code **)*piVar5)(piVar5,"Start","BCDNM");
local_68 = local_58;
local_6c = (undefined *)0x42aa0f;
MSTRTODBL();
local_6c = (undefined *)0x42aa14;
uVar6 = _System__ROUND_qqrv();
*(undefined4 *)(puVar1 + 0x7a0) = uVar6;
local_6c = &DAT_0042b788;
ppuStack_70 = &local_5c;
puStack_74 = (undefined *)0x42aa33;
(**(code **)*piVar5)(piVar5,"Start","DCDNM");
puStack_74 = local_5c;
puStack_78 = (undefined *)0x42aa3c;
MSTRTODBL();
puStack_78 = (undefined *)0x42aa41;
uVar6 = _System__ROUND_qqrv();
*(undefined4 *)(puVar1 + 0x7a4) = uVar6;
puStack_78 = &DAT_0042b788;
local_7c = &local_60;
local_80 = (undefined *)0x42aa60;
(**(code **)*piVar5)(piVar5,"Start","ICDNM");
local_80 = local_60;
local_84 = (undefined *)0x42aa69;
MSTRTODBL();
local_84 = (undefined *)0x42aa6e;
uVar6 = _System__ROUND_qqrv();
*(undefined4 *)(puVar1 + 0x7a8) = uVar6;
local_84 = &DAT_0042b74c;
local_88 = &local_64;
local_8c = (undefined ***)0x42aa8d;
(**(code **)*piVar5)(piVar5,"Start","KCDNM");
local_8c = local_64;
local_90 = (undefined *)0x42aa96;
MSTRTODBL();
local_90 = (undefined *)0x42aa9b;
uVar3 = _System__ROUND_qqrv();
puVar1[5] = uVar3;
local_90 = (undefined *)0x42aabe;
_System__VarFromPStr_qqrr14System_Variantrx28System__SmallString_iuc_255_(&local_8c,&local_90);
local_90 = local_80;
local_94 = local_84;
local_98 = local_88;
local_9c = local_8c;
local_a4 = (undefined4 ***)CONCAT22(local_a4._2_2_,0x3200);
local_a4 = (undefined4 ***)CONCAT31(local_a4._1_3_,1);
local_a0 = (undefined *)0x42aaf0;
uVar6 = _System__VarFromPStr_qqrr14System_Variantrx28System__SmallString_iuc_255_
(&local_a0,&local_a4);
local_a0 = local_94;
local_a4 = local_98;
local_a8 = local_9c;
local_ac = local_94;
local_b0 = CONCAT31((int3)((uint)uVar6 >> 8),puVar1[5] == '\x02');
local_b4 = &local_7c;
local_b8 = (undefined *)0x42ab19;
IIF();
local_b8 = (undefined *)0x42ab24;
_System__VarToLStr_qqrr17System_AnsiStringrx14System_Variant(&local_6c,&local_7c);
local_b8 = local_6c;
local_bc = &local_68;
local_c0 = (undefined **)0x42ab3c;
(**(code **)*piVar5)(piVar5,"Start","JCDNM");
local_c0 = local_68;
local_c4 = (undefined *)0x42ab45;
MSTRTODBL();
local_c4 = (undefined *)0x42ab4a;
uVar3 = _System__ROUND_qqrv();
puVar1[7] = uVar3;
local_c4 = &DAT_0042b788;
local_c8 = &local_a8;
local_cc = (undefined ***)0x42ab69;
(**(code **)*piVar5)(piVar5,"Start","ECDNM");
local_cc = local_a8;
local_d0 = (undefined *)0x42ab75;
MSTRTODBL();
local_d0 = (undefined *)0x42ab7a;
uVar3 = _System__ROUND_qqrv();
puVar1[8] = uVar3;
local_d0 = &DAT_0042b788;
local_d4 = &local_ac;
local_d8 = (undefined *)0x42ab99;
(**(code **)*piVar5)(piVar5,"Start","LCDNM");
local_d8 = local_ac;
local_dc = (undefined *)0x42aba5;
MSTRTODBL();
local_dc = (undefined *)0x42abaa;
uVar6 = _System__ROUND_qqrv();
*(undefined4 *)(puVar1 + 0x7ac) = uVar6;
local_dc = &DAT_0042b788;
local_e0 = &local_b0;
local_e4 = 0x42abcc;
(**(code **)*piVar5)(piVar5,"Start","MCDNM");
local_e4 = local_b0;
local_e8 = (undefined *)0x42abd8;
MSTRTODBL();
local_e8 = (undefined *)0x42abdd;
uVar6 = _System__ROUND_qqrv();
*(undefined4 *)(puVar1 + 0x7b0) = uVar6;
local_e8 = &DAT_0042b824;
@@ -0,0 +1,526 @@
# Ghidra main 0x43307c 발췌 — 줄 단가 다시 읽기(0x437a13) · 상위 B·천원 환율 칸(0x435600~0x43590d) · EXRNM 참 S 환산(0x437aa2~)
## 1205-1232 (줄 수량 칸 +0x128[계열])
*(undefined2 *)(iVar15 + 0x1d5 + *puVar16 * 0x550) = 0x2001;
*(undefined2 *)(iVar15 + 0x1d9 + *puVar16 * 0x550) = 0x2001;
}
}
LAB_004353d3:
uVar5 = local_7c._2_2_;
uVar13 = *(int *)(in_stack_00000004 + 0x10) - 3;
local_7c._2_2_ = (undefined2)((uint)puVar16 >> 0x10);
uVar6 = local_7c._2_2_;
if ((uVar13 < 6) || (uVar13 == 7)) {
fVar26 = (float10)*(double *)(iVar15 + *puVar16 * 0x550 + 0x128 + *puVar19 * 8);
local_2c = (uint *)0x435407;
_System__VarFromReal_qqrv(local_608);
local_2c = local_5fc;
local_30._0_2_ = SUB42(local_608[2],0);
local_30._2_2_ = (undefined2)((uint)local_608[2] >> 0x10);
local_34._0_2_ = SUB42(local_608[1],0);
local_34._2_2_ = (undefined2)((uint)local_608[1] >> 0x10);
local_38 = local_608[0];
fVar28 = (float10)*(double *)(iVar15 + *puVar16 * 0x550 + 0x128 + *puVar19 * 8) /
(float10)100.0;
local_3c = 0x5442;
uStack_3a = 0x43;
in_ST6 = in_ST4;
_System__VarFromReal_qqrv(local_618);
local_3c = (undefined2)local_60c;
uStack_3a = (undefined2)((uint)local_60c >> 0x10);
local_40 = local_610;
## 1300-1490
local_7c._0_2_ = 0x5595;
local_7c._2_2_ = 0x43;
IIF();
local_7c._0_2_ = 0x55a0;
local_7c._2_2_ = 0x43;
_System__VarToReal_qqrrx14System_Variant(local_628);
*(double *)(iVar15 + 0x1a8 + *puVar16 * 0x550) = (double)fVar28;
}
else {
local_7c._2_2_ = uVar5;
if (*(char *)(*(int *)PTR_DAT_005e5f8c + 0x69) == 'B') {
uVar22 = *(int *)PTR_DAT_005e5f8c == -0x179;
local_2c = (uint *)0x4355de;
_System__LStrFromString_qqrr17System_AnsiStringrx28System__SmallString_iuc_255_
(&local_650);
local_2c = (uint *)0x4355ef;
_Sysutils_Trim_qqrx17System_AnsiString(local_650,&local_64c);
local_2c = (uint *)0x4355ff;
_System__LStrCmp_qqrv(local_64c,&DAT_00439d2c);
if ((bool)uVar22) {
local_2c = (uint *)0x43561f;
_Apodset_TApolloTable_GetString_qqr17System_AnsiString
(*(undefined4 *)(*(int *)PTR_DAT_005e6090 + 8),&DAT_00439c10,&local_654);
local_2c = (uint *)0x43562d;
_Sysutils_Trim_qqrx17System_AnsiString(local_654,&stack0xffffffe4);
local_2c = (uint *)0x43563a;
_System__LStrCmp_qqrv(unaff_EDI,&DAT_00439d2c);
if ((bool)uVar22) {
iVar12 = iVar15 + *puVar16 * 0x550;
uVar13 = *puVar19;
*(undefined4 *)(iVar12 + 0x128 + uVar13 * 8) = 0;
*(undefined4 *)(iVar12 + 300 + uVar13 * 8) = 0x3ff00000;
local_a0 = (undefined1 *)CONCAT22(local_a0._2_2_,(short)local_a0);
}
else {
local_7c._0_2_ = 0x566d;
local_7c._2_2_ = 0x43;
_System__LStrCmp_qqrv(unaff_EDI,&DAT_00439d3c);
if (!(bool)uVar22) {
local_7c._0_2_ = 0x567c;
local_7c._2_2_ = 0x43;
_System__LStrCmp_qqrv(unaff_EDI,&DAT_00439d48);
if (!(bool)uVar22) {
local_7c._0_2_ = 0x57ea;
local_7c._2_2_ = 0x43;
_System__LStrCmp_qqrv(unaff_EDI,&DAT_00439d70);
if ((bool)uVar22) {
if ((*(int *)PTR_DAT_005e6148 == 0) ||
(*(char *)(*(int *)PTR_DAT_005e6148 + 0x7c9) == '\0')) {
*(double *)(iVar15 + *puVar16 * 0x550 + 0x128 + *puVar19 * 8) =
*(double *)
(*(int *)PTR_DAT_005e6148 + 0x748 +
*(int *)(in_stack_00000004 + 0x18) * 8) / 1000.0;
local_a0 = (undefined1 *)CONCAT22(local_a0._2_2_,(short)local_a0);
}
else {
iVar12 = iVar15 + *puVar16 * 0x550;
uVar13 = *puVar19;
*(undefined4 *)(iVar12 + 0x128 + uVar13 * 8) = 0;
*(undefined4 *)(iVar12 + 300 + uVar13 * 8) = 0x3ff00000;
puVar18 = (undefined4 *)(iVar15 + 0x1c6 + *puVar16 * 0x550);
*puVar18 = 0xbfb5c304;
*(undefined1 *)(puVar18 + (uint)bVar25 * -2 + 1) =
(&DAT_00439d50)[(uint)bVar25 * -8];
local_80 = puVar19;
local_7c._2_2_ = uVar6;
local_a0 = (undefined1 *)CONCAT22(local_a0._2_2_,(short)local_a0);
}
}
else {
local_7c._0_2_ = 0x5886;
local_7c._2_2_ = 0x43;
_System__LStrCmp_qqrv(unaff_EDI,&DAT_00439d80);
local_a0 = (undefined1 *)CONCAT22(local_a0._2_2_,(short)local_a0);
if ((bool)uVar22) {
if ((*(int *)PTR_DAT_005e6148 == 0) ||
(*(char *)(*(int *)PTR_DAT_005e6148 + 0x7c9) == '\0')) {
*(double *)(iVar15 + *puVar16 * 0x550 + 0x128 + *puVar19 * 8) =
*(double *)
(*(int *)PTR_DAT_005e6148 + 0x738 +
*(int *)(in_stack_00000004 + 0x18) * 8) / 100000.0;
local_a0 = (undefined1 *)CONCAT22(local_a0._2_2_,(short)local_a0);
}
else {
iVar12 = iVar15 + *puVar16 * 0x550;
uVar13 = *puVar19;
*(undefined4 *)(iVar12 + 0x128 + uVar13 * 8) = 0;
*(undefined4 *)(iVar12 + 300 + uVar13 * 8) = 0x3ff00000;
puVar18 = (undefined4 *)(iVar15 + 0x1c6 + *puVar16 * 0x550);
*puVar18 = 0xbfb5c304;
*(undefined1 *)(puVar18 + (uint)bVar25 * -2 + 1) =
(&DAT_00439d50)[(uint)bVar25 * -8];
local_80 = puVar19;
local_7c._2_2_ = uVar6;
local_a0 = (undefined1 *)CONCAT22(local_a0._2_2_,(short)local_a0);
}
}
}
goto LAB_0043590d;
}
}
if ((*(int *)PTR_DAT_005e6148 == 0) ||
(*(char *)(*(int *)PTR_DAT_005e6148 + 0x7c9) == '\0')) {
fVar30 = (float10)*(double *)
(*(int *)PTR_DAT_005e6148 + 0x728 +
*(int *)(in_stack_00000004 + 0x18) * 8);
local_7c._0_2_ = 0x56f8;
local_7c._2_2_ = 0x43;
_System__VarFromReal_qqrv(local_674);
local_7c._0_2_ = (undefined2)local_668;
local_7c._2_2_ = (undefined2)((uint)local_668 >> 0x10);
local_80 = local_66c;
local_84._0_2_ = (undefined2)local_674._4_4_;
local_84._2_2_ = SUB42(local_674._4_4_,2);
local_88._0_2_ = (undefined2)local_674._0_4_;
local_88._2_2_ = SUB42(local_674._0_4_,2);
fVar28 = (float10)*(double *)
(*(int *)PTR_DAT_005e6148 + 0x718 +
*(int *)(in_stack_00000004 + 0x18) * 8);
local_8c = 0x435730;
_System__VarFromReal_qqrv(local_684);
local_8c = local_678;
local_90._0_2_ = (undefined2)local_684[2];
local_90._2_2_ = (undefined2)((uint)local_684[2] >> 0x10);
local_94 = (undefined2)local_684[1];
uStack_92 = (undefined2)((uint)local_684[1] >> 0x10);
local_98 = local_684[0];
local_9c = 0x5767;
uStack_9a = 0x43;
_System__LStrCat3_qqrv
(&local_688,&DAT_00439d5c,
*(undefined4 *)
(PTR_PTR_005e5f40 + *(int *)(in_stack_00000004 + 0x18) * 4));
local_9c = 0x577c;
uStack_9a = 0x43;
_Apodset_TApolloTable_GetDouble_qqr17System_AnsiString
(*(undefined4 *)(*(int *)PTR_DAT_005e6090 + 8),local_688);
fVar26 = (float10)100000.0;
local_9c = (undefined2)
CONCAT31((int3)(CONCAT22(extraout_var,
(ushort)(fVar28 < fVar26) << 8 |
(ushort)(NAN(fVar28) || NAN(fVar26)) << 10 |
(ushort)(fVar28 == fVar26) << 0xe) >> 8),
fVar28 < fVar26 != 0);
local_a0 = local_664;
local_a4._0_2_ = 0x5795;
local_a4._2_2_ = 0x43;
in_ST6 = in_ST5;
uStack_9a = extraout_var;
IIF();
local_a4 = local_664;
puStack_a8 = (undefined1 *)0x4357ae;
_System__VarFromInt_qqrr14System_Variantxixi(local_698,1000,2);
puVar10 = local_a4;
local_a4._0_2_ = 0x57ba;
local_a4._2_2_ = 0x43;
_System__VarRDiv_qqrr14System_Variantrx14System_Variant(puVar10,local_698);
local_a4._0_2_ = 0x57c5;
local_a4._2_2_ = 0x43;
_System__VarToReal_qqrrx14System_Variant(local_664);
*(double *)(iVar15 + *puVar16 * 0x550 + 0x128 + *puVar19 * 8) = (double)fVar30;
}
else {
iVar12 = iVar15 + *puVar16 * 0x550;
uVar13 = *puVar19;
*(undefined4 *)(iVar12 + 0x128 + uVar13 * 8) = 0;
*(undefined4 *)(iVar12 + 300 + uVar13 * 8) = 0x3ff00000;
puVar18 = (undefined4 *)(iVar15 + 0x1c6 + *puVar16 * 0x550);
*puVar18 = 0xbfb5c304;
*(undefined1 *)(puVar18 + (uint)bVar25 * -2 + 1) =
(&DAT_00439d50)[(uint)bVar25 * -8];
local_80 = puVar19;
local_7c._2_2_ = uVar6;
local_a0 = (undefined1 *)CONCAT22(local_a0._2_2_,(short)local_a0);
}
}
LAB_0043590d:
uVar13 = *puVar16;
puVar18 = (undefined4 *)(iVar15 + *puVar16 * 0x550 + 0x128 + *puVar19 * 8);
*(undefined4 *)(iVar15 + 0x1a0 + uVar13 * 0x550) = *puVar18;
*(undefined4 *)(iVar15 + 0x1a4 + uVar13 * 0x550) = puVar18[1];
uVar13 = *puVar16;
uVar4 = *puVar16;
*(undefined4 *)(iVar15 + 0x1a8 + uVar13 * 0x550) =
*(undefined4 *)(iVar15 + 0x1a0 + uVar4 * 0x550);
*(undefined4 *)(iVar15 + 0x1ac + uVar13 * 0x550) =
*(undefined4 *)(iVar15 + 0x1a4 + uVar4 * 0x550);
goto LAB_00436222;
}
}
local_2c = *(uint **)(*(int *)PTR_DAT_005e5f8c + 4);
## 1770-1800 · 1905-1930 (수량 칸 → 유효수량 +0x1a0)
*(undefined4 *)(iVar15 + 0x1a8 + uVar13 * 0x550) =
*(undefined4 *)(iVar15 + 0x1a0 + uVar4 * 0x550);
*(undefined4 *)(iVar15 + 0x1ac + uVar13 * 0x550) =
*(undefined4 *)(iVar15 + 0x1a4 + uVar4 * 0x550);
}
else {
if (*(double *)(iVar15 + *puVar16 * 0x550 + 0x128 + *puVar19 * 8) == 0.0) {
uVar13 = *puVar16;
*(undefined4 *)(iVar15 + 0x1a0 + uVar13 * 0x550) = 0;
*(undefined4 *)(iVar15 + 0x1a4 + uVar13 * 0x550) = 0;
}
else {
*(double *)(iVar15 + 0x1a0 + *puVar16 * 0x550) =
(double)(1.0 / (float)*(double *)
(iVar15 + *puVar16 * 0x550 + 0x128 +
*puVar19 * 8));
}
uVar13 = *puVar16;
uVar4 = *puVar16;
*(undefined4 *)(iVar15 + 0x1a8 + uVar13 * 0x550) =
*(undefined4 *)(iVar15 + 0x1a0 + uVar4 * 0x550);
*(undefined4 *)(iVar15 + 0x1ac + uVar13 * 0x550) =
*(undefined4 *)(iVar15 + 0x1a4 + uVar4 * 0x550);
}
}
else {
LAB_00435fbd:
iVar12 = *(int *)PTR_DAT_005e6148;
puVar18 = (undefined4 *)(iVar15 + *puVar16 * 0x550 + 0x128 + *puVar19 * 8);
*puVar18 = *(undefined4 *)(iVar12 + 0x790);
puVar18[1] = *(undefined4 *)(iVar12 + 0x794);
}
}
LAB_00436222:
fVar26 = in_ST0;
if (*(double *)(iVar15 + 0x428 + *puVar16 * 0x550) != 0.0) {
local_7c._0_2_ = 0;
local_7c._2_2_ = 0;
local_80 = (uint *)0xb;
fVar26 = (float10)*(double *)(iVar15 + 0x1a0 + *puVar16 * 0x550) *
(float10)*(double *)(iVar15 + 0x428 + *puVar16 * 0x550);
local_8c = SUB104(fVar26,0);
local_88._0_2_ = (undefined2)((unkuint10)fVar26 >> 0x20);
local_88._2_2_ = (undefined2)((unkuint10)fVar26 >> 0x30);
local_84._0_2_ = (undefined2)((unkuint10)fVar26 >> 0x40);
local_90._0_2_ = 0x6264;
local_90._2_2_ = 0x43;
fVar26 = in_ST1;
in_ST1 = in_ST2;
in_ST2 = in_ST3;
in_ST3 = in_ST4;
in_ST4 = in_ST5;
in_ST5 = in_ST6;
MROUNDUD();
*(double *)(iVar15 + 0x1a0 + *puVar16 * 0x550) = (double)in_ST0;
}
if (*(char *)(iVar15 + 0x1bd + *puVar16 * 0x550) == '%') {
## 2640-2725 (비목별 줄 금액 = 단가 × 유효수량 → ADDBJVAL)
_System__VarAdd_qqrr14System_Variantrx14System_Variant(puVar10,local_78c);
uStack_e4._0_2_ = 0x6675;
uStack_e4._2_2_ = 0x43;
_System__VarToReal_qqrrx14System_Variant(local_77c);
*(double *)(*(int *)PTR_DAT_005e5d84 + 200 + *puVar19 * 0x30) = (double)fVar28;
uVar13 = *puVar19;
iVar12 = *(int *)PTR_DAT_005e5d84;
*(undefined4 *)(iVar12 + 0xe8 + uVar13 * 0x30) = 0;
*(undefined4 *)(iVar12 + 0xec + uVar13 * 0x30) = 0;
*(double *)(*(int *)PTR_DAT_005e5d84 + 0xc0 + *puVar19 * 0x30) =
*(double *)(*(int *)PTR_DAT_005e5d84 + 200 + *puVar19 * 0x30) +
*(double *)(*(int *)PTR_DAT_005e5d84 + 0xd0 + *puVar19 * 0x30) +
*(double *)(*(int *)PTR_DAT_005e5d84 + 0xd8 + *puVar19 * 0x30) +
*(double *)(*(int *)PTR_DAT_005e5d84 + 0xe0 + *puVar19 * 0x30);
}
}
LAB_00437303:
fVar28 = (float10)*(double *)(*(int *)PTR_DAT_005e5d84 + 200 + *puVar19 * 0x30) *
(float10)*(double *)(iVar15 + 0x1a0 + *puVar16 * 0x550);
uStack_c4 = SUB104(fVar28,0);
uStack_c0 = (undefined4)((unkuint10)fVar28 >> 0x20);
uStack_bc._0_2_ = (undefined2)((unkuint10)fVar28 >> 0x40);
uStack_c8 = 0x7332;
uStack_c6 = 0x43;
fVar28 = in_ST6;
ADDBJVAL();
*(double *)(in_stack_00000004 + -0x70) = (double)fVar26;
fVar26 = in_ST1;
if ((*(char *)(*(int *)PTR_DAT_005e5e7c + 0x19ba) == '\x01') &&
(*(char *)(*(int *)PTR_DAT_005e5f8c + 0x57) == '\x05')) {
fVar26 = (float10)*(double *)(*(int *)PTR_DAT_005e5d84 + 0xe8 + *puVar19 * 0x30) *
(float10)*(double *)(iVar15 + 0x1a0 + *puVar16 * 0x550);
puStack_d0 = SUB104(fVar26,0);
uStack_cc = (undefined4)((unkuint10)fVar26 >> 0x20);
uStack_c8 = (undefined2)((unkuint10)fVar26 >> 0x40);
uStack_d4._0_2_ = 0x7385;
uStack_d4._2_2_ = 0x43;
fVar26 = in_ST2;
in_ST2 = in_ST3;
in_ST3 = in_ST4;
in_ST4 = in_ST5;
in_ST5 = in_ST6;
in_ST6 = fVar28;
ADDBJVAL();
*(double *)(in_stack_00000004 + -0x50) = (double)in_ST1;
}
fVar30 = (float10)*(double *)(*(int *)PTR_DAT_005e5d84 + 0xd0 + *puVar19 * 0x30) *
(float10)*(double *)(iVar15 + 0x1a0 + *puVar16 * 0x550);
puStack_d0 = SUB104(fVar30,0);
uStack_cc = (undefined4)((unkuint10)fVar30 >> 0x20);
uStack_c8 = (undefined2)((unkuint10)fVar30 >> 0x40);
uStack_d4._0_2_ = 0x73bb;
uStack_d4._2_2_ = 0x43;
ADDBJVAL();
*(double *)(in_stack_00000004 + -0x68) = (double)fVar26;
fVar26 = (float10)*(double *)(*(int *)PTR_DAT_005e5d84 + 0xd8 + *puVar19 * 0x30) *
(float10)*(double *)(iVar15 + 0x1a0 + *puVar16 * 0x550);
uStack_dc = SUB104(fVar26,0);
uStack_d8 = (undefined2)((unkuint10)fVar26 >> 0x20);
uStack_d6 = (undefined2)((unkuint10)fVar26 >> 0x30);
uStack_d4._0_2_ = (undefined2)((unkuint10)fVar26 >> 0x40);
uStack_e0._0_2_ = 0x73f1;
uStack_e0._2_2_ = 0x43;
ADDBJVAL();
*(double *)(in_stack_00000004 + -0x60) = (double)in_ST2;
fVar26 = (float10)*(double *)(*(int *)PTR_DAT_005e5d84 + 0xe0 + *puVar19 * 0x30) *
(float10)*(double *)(iVar15 + 0x1a0 + *puVar16 * 0x550);
uStack_e8 = SUB104(fVar26,0);
uStack_e4._0_2_ = (undefined2)((unkuint10)fVar26 >> 0x20);
uStack_e4._2_2_ = (undefined2)((unkuint10)fVar26 >> 0x30);
uStack_e0._0_2_ = (undefined2)((unkuint10)fVar26 >> 0x40);
uStack_ec = 0x7427;
uStack_ea = 0x43;
ADDBJVAL();
*(double *)(in_stack_00000004 + -0x58) = (double)in_ST3;
fVar26 = (float10)*(double *)(*(int *)PTR_DAT_005e5d84 + 0xc0 + *puVar19 * 0x30) *
(float10)*(double *)(iVar15 + 0x1a0 + *puVar16 * 0x550);
uStack_f4 = SUB104(fVar26,0);
uStack_f0 = (undefined2)((unkuint10)fVar26 >> 0x20);
uStack_ee = (undefined2)((unkuint10)fVar26 >> 0x30);
uStack_ec = (undefined2)((unkuint10)fVar26 >> 0x40);
uStack_f8 = 0x43745d;
ADDBJVAL();
*(double *)(in_stack_00000004 + -0x78) = (double)in_ST4;
cVar8 = *(char *)(iVar15 + 0x1d4 + *puVar16 * 0x550);
if (((byte)(cVar8 + 0xb4U) < 2) || (cVar8 == 'S')) {
## 2970-3100 (A 줄 = COSTA · EXRNM 참 환산)
}
}
else {
LAB_00437a13:
if (((*(int *)(in_stack_00000004 + 0x10) - 1U < 3) ||
((*(int *)(in_stack_00000004 + 0x10) == 6 && (1 < (int)*puVar19)))) ||
((*(int *)(in_stack_00000004 + 0x10) - 7U < 2 &&
((*(int *)(in_stack_00000004 + 0x18) == 3 && (*puVar19 == 3)))))) {
if (*(char *)(iVar15 + 0x1b + *puVar16 * 0x550) == 'A') {
local_7c._0_2_ = (undefined2)*puVar19;
local_7c._2_2_ = (undefined2)(*puVar19 >> 0x10);
local_80 = (uint *)0x1;
local_84._0_2_ = 0x7a7c;
local_84._2_2_ = 0x43;
FUN_00430c60(iVar15 + *puVar16 * 0x550,*(undefined4 *)(*(int *)PTR_DAT_005e6090 + 0xc),
*(undefined4 *)(in_stack_00000004 + 0x18));
}
else {
local_7c._0_2_ = (undefined2)*puVar19;
local_7c._2_2_ = (undefined2)(*puVar19 >> 0x10);
local_80 = (uint *)0x0;
local_84._0_2_ = 0x7aa2;
local_84._2_2_ = 0x43;
FUN_00430c60(iVar15 + *puVar16 * 0x550,*(undefined4 *)(*(int *)PTR_DAT_005e6090 + 8),
*(undefined4 *)(in_stack_00000004 + 0x18));
}
}
uVar13 = *(int *)(in_stack_00000004 + 0x10) - 3;
if ((uVar13 < 6) || (uVar13 == 7)) {
if ((*(int *)PTR_DAT_005e6148 != 0) &&
((*(char *)(*(int *)PTR_DAT_005e6148 + 0x7c9) != '\0' &&
(uVar22 = *(char *)(iVar15 + 0x1b + *puVar16 * 0x550) == 'S', (bool)uVar22)))) {
local_7c._0_2_ = 0x7b04;
local_7c._2_2_ = 0x43;
_System__LStrFromString_qqrr17System_AnsiStringrx28System__SmallString_iuc_255_
(&local_ad0,iVar15 + 0x1bb + *puVar16 * 0x550);
local_7c._0_2_ = 0x7b15;
local_7c._2_2_ = 0x43;
_Sysutils_Trim_qqrx17System_AnsiString(local_ad0,&local_acc);
local_7c._0_2_ = 0x7b25;
local_7c._2_2_ = 0x43;
_System__LStrCmp_qqrv(local_acc,&DAT_00439d3c);
if (!(bool)uVar22) {
local_7c._0_2_ = 0x7b43;
local_7c._2_2_ = 0x43;
_System__LStrFromString_qqrr17System_AnsiStringrx28System__SmallString_iuc_255_
(&local_ad8,iVar15 + 0x1bb + *puVar16 * 0x550);
local_7c._0_2_ = 0x7b54;
local_7c._2_2_ = 0x43;
_Sysutils_Trim_qqrx17System_AnsiString(local_ad8,&local_ad4);
local_7c._0_2_ = 0x7b64;
local_7c._2_2_ = 0x43;
_System__LStrCmp_qqrv(local_ad4,&DAT_00439d48);
if (!(bool)uVar22) {
local_7c._0_2_ = 0x7b7e;
local_7c._2_2_ = 0x43;
_System__LStrFromString_qqrr17System_AnsiStringrx28System__SmallString_iuc_255_
(&local_ae0,iVar15 + 0x1bb + *puVar16 * 0x550);
local_7c._0_2_ = 0x7b8f;
local_7c._2_2_ = 0x43;
_Sysutils_Trim_qqrx17System_AnsiString(local_ae0,&local_adc);
local_7c._0_2_ = 0x7b9f;
local_7c._2_2_ = 0x43;
_System__LStrCmp_qqrv(local_adc,&DAT_00439d70);
if (!(bool)uVar22) {
local_7c._0_2_ = 0x7bb9;
local_7c._2_2_ = 0x43;
_System__LStrFromString_qqrr17System_AnsiStringrx28System__SmallString_iuc_255_
(&local_ae8,iVar15 + 0x1bb + *puVar16 * 0x550);
local_7c._0_2_ = 0x7bca;
local_7c._2_2_ = 0x43;
_Sysutils_Trim_qqrx17System_AnsiString(local_ae8,&local_ae4);
local_7c._0_2_ = 0x7bda;
local_7c._2_2_ = 0x43;
_System__LStrCmp_qqrv(local_ae4,&DAT_00439d80);
if (!(bool)uVar22) goto LAB_00437e32;
}
}
}
local_7c._0_2_ = 0x7bf8;
local_7c._2_2_ = 0x43;
_System__LStrFromString_qqrr17System_AnsiStringrx28System__SmallString_iuc_255_
(&local_af0,iVar15 + 0x1bb + *puVar16 * 0x550);
local_7c._0_2_ = 0x7c09;
local_7c._2_2_ = 0x43;
_Sysutils_Trim_qqrx17System_AnsiString(local_af0,&local_aec);
local_7c._0_2_ = 0x7c19;
local_7c._2_2_ = 0x43;
_System__LStrCmp_qqrv(local_aec,&DAT_00439d70);
if ((bool)uVar22) {
*(double *)(in_stack_00000004 + -0x98) =
*(double *)
(*(int *)PTR_DAT_005e6148 + 0x748 + *(int *)(in_stack_00000004 + 0x18) * 8) /
1000.0;
fVar28 = in_ST1;
in_ST1 = in_ST3;
in_ST3 = in_ST5;
}
else {
local_7c._0_2_ = 0x7c5d;
local_7c._2_2_ = 0x43;
_System__LStrFromString_qqrr17System_AnsiStringrx28System__SmallString_iuc_255_
(&local_af8,iVar15 + 0x1bb + *puVar16 * 0x550);
local_7c._0_2_ = 0x7c6e;
local_7c._2_2_ = 0x43;
_Sysutils_Trim_qqrx17System_AnsiString(local_af8,&local_af4);
local_7c._0_2_ = 0x7c7e;
local_7c._2_2_ = 0x43;
_System__LStrCmp_qqrv(local_af4,&DAT_00439d80);
if ((bool)uVar22) {
*(double *)(in_stack_00000004 + -0x98) =
*(double *)
(*(int *)PTR_DAT_005e6148 + 0x738 + *(int *)(in_stack_00000004 + 0x18) * 8) /
100000.0;
fVar28 = in_ST1;
in_ST1 = in_ST3;
in_ST3 = in_ST5;
}
else {
fVar31 = (float10)*(double *)
(*(int *)PTR_DAT_005e6148 + 0x728 +
*(int *)(in_stack_00000004 + 0x18) * 8);
local_7c._0_2_ = 0x7cca;
local_7c._2_2_ = 0x43;
_System__VarFromReal_qqrv(local_b18);
local_7c._0_2_ = (undefined2)local_b0c;
local_7c._2_2_ = (undefined2)((uint)local_b0c >> 0x10);
local_80 = (uint *)local_b10;
local_84._0_2_ = (undefined2)local_b18._4_4_;
local_84._2_2_ = SUB42(local_b18._4_4_,2);
local_88._0_2_ = (undefined2)local_b18._0_4_;
## 4525-4550
_Sysutils_Trim_qqrx17System_AnsiString(local_1d0,&local_1cc);
local_30._0_2_ = 0x3635;
local_30._2_2_ = 0x43;
_System__LStrCmp_qqrv(local_1cc,&DAT_00439b64);
if ((bool)uVar22) {
uVar13 = *puVar16;
puVar18 = (undefined4 *)(iVar15 + *puVar16 * 0x550 + 0x128 + *puVar19 * 8);
*(undefined4 *)(iVar15 + 0x1a0 + uVar13 * 0x550) = *puVar18;
*(undefined4 *)(iVar15 + 0x1a4 + uVar13 * 0x550) = puVar18[1];
}
else {
*(double *)(iVar15 + 0x1a0 + *puVar16 * 0x550) =
*(double *)(iVar15 + *puVar16 * 0x550 + 0x128 + *puVar19 * 8) / 100.0;
}
}
else if (*(double *)(iVar15 + *puVar16 * 0x550 + 0x128 + *puVar19 * 8) == 0.0) {
uVar13 = *puVar16;
*(undefined4 *)(iVar15 + 0x1a0 + uVar13 * 0x550) = 0;
*(undefined4 *)(iVar15 + 0x1a4 + uVar13 * 0x550) = 0;
}
else {
*(double *)(iVar15 + 0x1a0 + *puVar16 * 0x550) =
(double)(1.0 / (float)*(double *)(iVar15 + *puVar16 * 0x550 + 0x128 + *puVar19 * 8));
}
if ((*(char *)(*(int *)PTR_DAT_005e5e7c + 0x19ba) == '\x01') &&
(*(char *)(*(int *)PTR_DAT_005e5f8c + 0x57) == '\x05')) {
@@ -0,0 +1,83 @@
0x45c848: PUSH EBP
0x45c849: MOV EBP, ESP
0x45c84b: PUSH 0x0
0x45c84d: PUSH 0x0
0x45c84f: PUSH 0x0
0x45c851: PUSH EBX
0x45c852: PUSH ESI
0x45c853: MOV ESI, EAX
0x45c855: XOR EAX, EAX
0x45c857: PUSH EBP
0x45c858: PUSH 0x45c977
0x45c85d: PUSH dword ptr FS:[EAX]
0x45c860: MOV dword ptr FS:[EAX], ESP
0x45c863: MOV EAX, [0x005e5dfc]
0x45c868: MOV EAX, dword ptr [EAX]
0x45c86a: MOV EBX, EAX
0x45c86c: LEA ECX, [EBP + -0x8]
0x45c86f: MOV EDX, 0x45c98c
0x45c874: MOV EAX, ESI
0x45c876: CALL 0x00404790
0x45c87b: MOV EAX, dword ptr [EBP + -0x8]
0x45c87e: LEA EDX, [EBP + -0x4]
0x45c881: CALL 0x00401e40
0x45c886: MOV EDX, dword ptr [EBP + -0x4]
0x45c889: LEA EAX, [EBX + 0x928]
0x45c88f: MOV ECX, 0xff
0x45c894: CALL 0x00401298
0x45c899: LEA ECX, [EBP + -0xc]
0x45c89c: MOV EDX, 0x45c99c
0x45c8a1: MOV EAX, ESI
0x45c8a3: CALL 0x00404790
0x45c8a8: MOV EDX, dword ptr [EBP + -0xc]
0x45c8ab: LEA EAX, [EBX + 0x128]
0x45c8b1: MOV ECX, 0xff
0x45c8b6: CALL 0x00401298
0x45c8bb: MOV EDX, 0x45c9ac
0x45c8c0: MOV EAX, ESI
0x45c8c2: CALL 0x00404778
0x45c8c7: MOV word ptr [EBX + 0x1750], AX
0x45c8ce: MOV EDX, 0x45c9bc
0x45c8d3: MOV EAX, ESI
0x45c8d5: CALL 0x00404778
0x45c8da: MOV word ptr [EBX + 0x1752], AX
0x45c8e1: MOV EDX, 0x45c9cc
0x45c8e6: MOV EAX, ESI
0x45c8e8: CALL 0x00404778
0x45c8ed: MOV word ptr [EBX + 0x1754], AX
0x45c8f4: MOV EDX, 0x45c9dc
0x45c8f9: MOV EAX, ESI
0x45c8fb: CALL 0x00404780
0x45c900: CMP AX, 0x1
0x45c904: SBB EAX, EAX
0x45c906: INC EAX
0x45c907: MOV byte ptr [EBX + 0x52bd], AL
0x45c90d: MOV EDX, 0x45c9ec
0x45c912: MOV EAX, ESI
0x45c914: CALL 0x00404780
0x45c919: CMP AX, 0x1
0x45c91d: SBB EAX, EAX
0x45c91f: INC EAX
0x45c920: MOV byte ptr [EBX + 0x52be], AL
0x45c926: MOV EDX, 0x45c9fc
0x45c92b: MOV EAX, ESI
0x45c92d: CALL 0x00404778
0x45c932: MOV dword ptr [EBX + 0x52b8], EAX
0x45c938: MOV EDX, 0x45ca0c
0x45c93d: MOV EAX, ESI
0x45c93f: CALL 0x00404778
0x45c944: MOV dword ptr [EBX + 0x172c], EAX
0x45c94a: MOV EDX, 0x45ca1c
0x45c94f: MOV EAX, ESI
0x45c951: CALL 0x00404778
0x45c956: MOV dword ptr [EBX + 0x1730], EAX
0x45c95c: XOR EAX, EAX
0x45c95e: POP EDX
0x45c95f: POP ECX
0x45c960: POP ECX
0x45c961: MOV dword ptr FS:[EAX], EDX
0x45c964: PUSH 0x45c97e
0x45c969: LEA EAX, [EBP + -0xc]
0x45c96c: MOV EDX, 0x3
0x45c971: CALL 0x00401258
0x45c976: RET
@@ -0,0 +1,9 @@
kj_47a1bc ev_4ded8d98d2a16037b53d3b40a8e7b733e5cc9b14d52aa0896f6c7fc1c44c596a
loader_42b166 ev_8112ff8e59578982e808a3ccd9a95c381010288faf9fb7ed48ccccfa3f165951
main_43307c ev_6a03bc19fb86d1f061dd4ccc6c3f9c8347924d406cf81a28294dcb93bef671a2
open_main
open_kj
as_0x45c938 ev_03492f4e870a253f1a153291962a982e91a70f934d28c231c497117d05de240e
xr_0x45ca0c ev_b47e243fa2378a168088bfb5ca2f687127dc8292fc50c686aab4a35279959d56
ss_acttyp ev_72931dd7a5ce247a89d29048e5af4bb76eecf82ee7ef88d48ab97c3debc65ed1
rb_439d28 ev_70b05c0574df5ec386ca25cdd756707ba26c912824f55cf0eeeae24e5f6f5333
@@ -0,0 +1,2 @@
ev_70b05c0574df5ec386ca25cdd756707ba26c912824f55cf0eeeae24e5f6f5333
04000000c3b5bff800000000ffffffff0100000024000000ffffffff02000000a3a4000004c3b5bff8000000ffffffff0400000054414d54000000000050c347ffffffff02000000a2e6000000007a44ffffffff02000000a1cd0000ffffffff02000000c0ce0000012c000001200000
+48
View File
@@ -0,0 +1,48 @@
// STMATE 재현 앱 — 표준시장단가(A) 내역·구성 · 외화 환산(EXRNM) · 행자부 RATE_M 조회 · stdin {mode, ...} → 결과
// boq: calc.calculateAll 내역 줄 · comp: smt_engine 단가 읽기(costnLookup) + 구성 조립 · ratem: kanjub_parts.NATIVE.rateMLookup
globalThis.window = globalThis;
const C = require("../../tools/stmate-test/calc.js");
const Parts = require("../../tools/stmate-test/kanjub_parts.js");
require("../../tools/stmate-test/smt_eval.js");
require("../../tools/stmate-test/smt_engine.js");
const E = window.STMATE_ENGINE;
const V = window.STMATE_SMT_EVAL;
const MODES = {
boq({ items, rows, std, slot }) {
const st = C.createDefaultState();
Object.assign(st, { stdMarketItems: items, boqItems: rows });
st.sysConfig.stdMarket = std;
const res = C.calculateAll(st);
const boq = slot === 2 ? res.boqRev : res.boqOrig;
const pick = (r) => ({ M: r.M, L: r.L, S: r.S, A: r.A, T: r.T, uL: r.lUnit, uM: r.mUnit });
return { rows: boq.rows.map(pick), directA: boq.directA, directT: boq.directT };
},
comp({ cases }) {
return cases.map((c) => {
const ctx = {
dbfData: Object.assign({ start_ini: {}, costn_rows: [], costa_rows: [] }, c.data),
state: { sysConfig: {} },
};
const code = c.code || "B00001";
const opts = E.compositionOptions(ctx, code, {});
const { lookup } = E.costnLookup(ctx);
const ev = V.evaluateComposition(c.rows, lookup, opts);
return { M: ev.M, L: ev.L, S: ev.S, unit: c.peek ? lookup(c.peek) : null };
});
},
ratem({ rows, cases }) {
return {
setBase: Parts.NATIVE.SET_BASE,
out: cases.map((o) => Parts.NATIVE.rateMLookup(rows, o)),
};
},
};
let buf = "";
process.stdin.setEncoding("utf8");
process.stdin.on("data", (d) => (buf += d));
process.stdin.on("end", () => {
const req = JSON.parse(buf);
process.stdout.write(JSON.stringify(MODES[req.mode](req)));
});
+1 -1
View File
@@ -1,7 +1,7 @@
"""STMATE 재현 앱 계산 도구 — 노무·장비 수량 계산기(wM_Edit_XRate) · 실적→일식 비목율(wM_Conv_A2W).
근거: REA 30 labor_equipment_quantity_rules.json · REA 27 conversion_component_rate_rules.json — 손셈 대조.
수량 계산기 반올림(MROUNDUD) 방향은 미확정 — 반올림으로 둔 값은 경계가 아닌 수로만 봄.
수량 계산기 반올림 = MROUNDUD 선택 1(사사오입 · REA 32 §1.1) — 경계 값은 test_stmate_xrate_round.
"""
import json
+200
View File
@@ -0,0 +1,200 @@
"""STMATE 재현 앱 — 표준시장단가(A 코드) 내역·구성 연결 · 외화 환산 EXRNM · 행자부 RATE_M 조회 · 적용기준 콤보 짝.
근거(지식DB REA 36 · 34 §6·§11·§17):
표준시장단가 단가 읽기 Main 0x430c60 — A 줄 = COSTA(0x437a4b) · S_JDA 참 = 저장 단가 · 거짓 = 0x45a468 나눔
L0 = T×R/100 · X = T×(100−R)/100 · ACTOPT 0 절사 · 1 OPTIMIZ{L0,0,0,X}[0] · 2 OPTIMIZ{X,0,0,L0}[3](동률 = 앞 칸)
R = 0 이고 LAMTn_R 있으면 그 값이 노무 · 나머지 ACTTYP 1 재료 · 2 경비 · 0 넷째 칸(A) · ACTTYP·ACTOPT = PRJLST(0x45c938)
EXRNM 참 — S 줄 단가 = MROUNDUD(단가 × 환율/1000, ECD, 0)(0x437aa2~0x437df2) · $ 10만 미만 EXL · € /1000 · ¥ /100000
행자부(SET_BASE 1) — ST_KanJub 0x47a1bc: 줄 b+11 / b+15 · 칸 RATE{기간(+5)} · 일반관리비 줄 23 RATE1 · 이윤 줄 24 RATE0
적용기준 콤보 = TWM_KANJUB.dfm cmBx_Set_Base Items/Values
기대값은 위 규칙 손셈 또는 실물 STC(RATE_M 표 · PRJLST 값) · DFM 원자료에서 읽음.
"""
import json
import subprocess
import sys
from pathlib import Path
ROOT = Path(__file__).resolve().parents[2]
sys.path.insert(0, str(ROOT / "tools/stmate-test"))
import server # noqa: E402
HELPER = Path(__file__).with_name("helper_stmate_std.cjs")
DOCS = ROOT / "resources/knowledge/original/실무문서"
STC = DOCS / (
"25년 산불진화임도(기번8)(울진소광)/2025년 산불진화임도 신설사업(기번8 울진.금강송.소광.산29외).STC"
)
DFM = (
ROOT / "resources/knowledge/original/경쟁사 프로그램/STmate/REA 분석/"
"21_제경비_입력화면연결_작업자료_20261025/ST_KanJub/resource-TWM_KANJUB.dfm.json"
)
def run(req):
out = subprocess.check_output(
["node", str(HELPER)],
input=json.dumps(req, ensure_ascii=False),
text=True,
encoding="utf-8",
)
return json.loads(out)
def item(aamt, rate_l, lamt_r=(0, 0, 0, 0), stored=None):
st = stored or {"L": [0] * 4, "M": [0] * 4, "S": [0] * 4}
base = {"code": "A00001", "name": "표준", "unit": "m", "aamt": aamt, "rateL": rate_l}
return base | {"lamtR": list(lamt_r), "stored": st}
ROW = [{"id": "BOQ_1", "code": "A00001", "refId": "A00001", "qty": 3, "changedQty": 3}]
def boq(it, std, slot=1, rows=ROW):
return run({"mode": "boq", "items": [it], "rows": rows, "std": std, "slot": slot})
def test_std_market_boq_actopt_tie():
# T 10001 · R 50 → L0 = X = 5000.5(동률) — 0 절사 5000 · 1 노무 칸 앞 → 5001 · 2 노무 칸 뒤 → 5000
it = item([10001, 0, 0, 0], 50)
r0, r1, r2 = (boq(it, {"acttyp": 1, "actopt": op})["rows"][0] for op in (0, 1, 2))
assert (r0["uL"], r0["uM"]) == (5000, 5001)
assert (r1["uL"], r1["uM"]) == (5001, 5000)
assert (r2["uL"], r2["uM"]) == (5000, 5001)
# 수량 3 — 비목별 원 미만 절사 · 합계 = 단가 합 × 3
assert (r1["L"], r1["M"], r1["S"], r1["T"]) == (15003, 15000, 0, 30003)
def test_std_market_boq_rest_column_and_preset():
it = item([10001, 0, 0, 0], 50)
s = boq(it, {"acttyp": 2, "actopt": 0})["rows"][0]
assert (s["L"], s["M"], s["S"]) == (15000, 0, 15003)
a = boq(it, {"acttyp": 0, "actopt": 0})
# ACTTYP 0 — 나머지는 넷째 칸 A(비목 밖) · 줄·내역 합계에는 들어감
assert (a["rows"][0]["M"], a["rows"][0]["A"], a["rows"][0]["T"]) == (0, 15003, 30003)
assert a["directA"] == 15003 and a["directT"] == 30003
# R 0 · LAMT1_R 4000 → 노무 4000 그대로 · 나머지 6001 재료
p = boq(item([10001, 0, 0, 0], 0, (4000, 0, 0, 0)), {"acttyp": 1, "actopt": 2})["rows"][0]
assert (p["uL"], p["uM"]) == (4000, 6001)
def test_std_market_boq_stored_and_slot2():
stored = {"L": [111, 0, 0, 0], "M": [222, 0, 0, 0], "S": [33, 0, 0, 0]}
s = boq(item([10001, 0, 0, 0], 50, stored=stored), {"useStored": True})["rows"][0]
assert (s["uL"], s["uM"], s["T"]) == (111, 222, (111 + 222 + 33) * 3)
# 변경(2칸) = AAMT2 로 다시 나눔
r = boq(item([10001, 20000, 0, 0], 50), {"acttyp": 1, "actopt": 0}, slot=2)["rows"][0]
assert (r["uL"], r["uM"]) == (10000, 10000)
COSTA = {"CODE": "A00001", "NAME": "표준", "UNIT": "m", "AAMT1": "10001", "RATE_L": "50"}
def test_std_market_composition_lookup():
data = {"costa_rows": [COSTA], "prjlst": {"ACTTYP": "1", "ACTOPT": "1"}}
(r,) = run(
{
"mode": "comp",
"cases": [
{"data": data, "rows": [{"CODE": "A00001", "QTY1": 2}], "peek": "A00001"},
],
}
)
assert (r["unit"]["L"], r["unit"]["M"], r["unit"]["S"]) == (5001, 5000, 0)
assert (r["L"], r["M"], r["S"]) == (10002, 10000, 0)
def test_exrnm_unit_price_rounds_to_ecd():
# $123.45 × 1289/1000 = 159.12705 천원 · 수량 10
costn = [{"CODE": "S00001", "UNIT": "$", "SAMT1": "123.45", "TAMT1": "123.45"}]
ini = {"EXLNM1": "1289", "EXHNM1": "1300"}
rows = [{"CODE": "S00001", "QTY1": 10}]
cases = [
{"data": {"costn_rows": costn, "start_ini": ini | {"EXRNM": "1"}}, "rows": rows},
{
"data": {"costn_rows": costn, "start_ini": ini | {"EXRNM": "1", "ECDNM": "2"}},
"rows": rows,
},
# 키 없음 = 로더 기본 1000.00 → 123.45 × 1 → ECD 0 절사 123
{"data": {"costn_rows": costn, "start_ini": {"EXRNM": "1"}}, "rows": rows},
]
a, b, c = run({"mode": "comp", "cases": cases})
assert a["S"] == 1590 # 159(ECD 0 버림) × 10
assert b["S"] == 1591 # 159.12 × 10 = 1591.2
assert c["S"] == 1230
def test_exrnm_only_s_lines_and_fullwidth_dollar():
ini = {"EXRNM": "1", "EXLNM1": "1500", "EXHNM1": "1500"}
costn = [
{"CODE": "M00009", "UNIT": "$", "MAMT1": "100", "TAMT1": "100"},
{"CODE": "S00002", "UNIT": "$", "SAMT1": "10", "TAMT1": "10"},
]
data = {"costn_rows": costn, "start_ini": ini}
m, s = run(
{
"mode": "comp",
"cases": [
{"data": data, "rows": [{"CODE": "M00009", "QTY1": 1}]},
{"data": data, "rows": [{"CODE": "S00002", "QTY1": 1}]},
],
}
)
# 원본 환산 조건 = 줄 코드 S(0x437adb) — 재료 $ 줄은 그대로 · 전각 $ 도 $ 와 같음
assert m["M"] == 100 and s["S"] == 15
def _rate_m_rows():
files = server.read_project_files(str(STC))
return server.parse_dbf_bytes(files["RATE_M.DBF"])[1]
def test_rate_m_lookup_against_stc_table():
rows = _rate_m_rows()
cases = [
{"scale": 40e8, "kongType": 0, "ilban": 0, "kikan": 1},
{"scale": 100e8, "kongType": 3, "ilban": 0, "kikan": 2},
{"scale": 500e8, "kongType": 0, "ilban": 1, "kikan": 0},
{"scale": 10e8, "kongType": 5, "ilban": 0, "kikan": -1},
]
res = run({"mode": "ratem", "rows": rows, "cases": cases})["out"]
want = [(11, 1), (12, 7), (17, 5), (15, 5)] # (줄, 칸) = 규칙 손셈
for got, (line, col) in zip(res, want):
assert got["cells"]["KANNO"] == [line, col]
assert got["rates"]["KANNO"] == server.fnum(rows[line - 1][f"RATE{col}"])
assert got["rates"]["ILBAN"] == server.fnum(rows[22]["RATE1"])
assert got["rates"]["IYUN"] == server.fnum(rows[23]["RATE0"])
assert [g["band"] for g in res] == [0, 1, 2, 0]
def test_set_base_combo_pairs_from_dfm():
nodes = json.loads(DFM.read_text(encoding="utf-8"))["nodes"]
combo = next(o for o in _walk(nodes) if o.get("name") == "cmBx_Set_Base")
p = combo["properties"]
want = [[int(v), t] for v, t in zip(p["Values.Strings"], p["Items.Strings"])]
got = run({"mode": "ratem", "rows": [], "cases": []})["setBase"]
assert got == want
def _walk(x):
if isinstance(x, dict):
if "name" in x and "properties" in x:
yield x
for v in x.values():
yield from _walk(v)
elif isinstance(x, list):
for v in x:
yield from _walk(v)
def test_api_load_passes_std_market_settings():
loaded = server.api_load(server.LoadReq(source_path=str(STC)))
files = server.read_project_files(str(STC))
prj = server.parse_dbf_bytes(files["PRJLST.DBF"])[1][0]
ini = server.parse_ini_bytes(files.get("START8X.INI") or b"")
std = loaded["sim_state"]["sysConfig"]["stdMarket"]
assert std["acttyp"] == int(server.fnum(prj["ACTTYP"]))
assert std["actopt"] == int(server.fnum(prj["ACTOPT"]))
assert std["useStored"] == (ini.get("S_JDA", "").strip() == "1")
costa = server.parse_dbf_bytes(files["COSTA.DBF"])[1]
assert len(loaded["sim_state"]["stdMarketItems"]) == len(costa)
assert len(loaded["costa_rows"]) == len(costa)
@@ -0,0 +1,34 @@
"""STMATE 재현 앱 수량 계산기(wM_Edit_XRate) 반올림 방향 — Q_d = MROUNDUD(x, d, 선택 1).
근거: REA 30 labor_equipment_quantity_rules.json(0x442f97 일일수량 Q_11 · 0x442fec 적용수량 Q_p · 선택 1)
REA 32 §1.1 MROUNDUD(DP_Common 0x454044) — 선택 1 = 첫 버림 자리 ≥ 5 이면 한 단위 올림(0 반대쪽)
기대값은 이 규칙의 손셈 — 경계(첫 버림 자리 5)에서 절사·은행가 반올림과 갈림.
"""
import json
import subprocess
from pathlib import Path
HELPER = Path(__file__).with_name("helper_stmate_smt_tools.cjs")
def call(*calls):
payload = [{"fn": f, "args": list(a)} for f, *a in calls]
out = subprocess.check_output(
["node", str(HELPER)], input=json.dumps(payload), text=True, encoding="utf-8"
)
return json.loads(out)
def test_applied_quantity_half_rounds_away_from_zero():
lab, eq, mid = call(
("compute", {"qty1": 8, "cond": "", "hrat": 100, "lqty": 1, "labor": True, "point": 2}),
("compute", {"qty1": 64, "cond": "", "hrat": 100, "lqty": 1, "labor": False, "point": 2}),
("compute", {"qty1": 16, "cond": "", "hrat": 100, "lqty": 1, "labor": True, "point": 3}),
)
# 1 ÷ 8 = 0.125 → 2자리 첫 버림 자리 5 → 0.13(절사 0.12 · 은행가 0.12)
assert lab["qday"] == 8 and lab["cqty"] == 0.13
# 장비 1 × 8 ÷ 64 = 0.125 → 0.13
assert eq["cqty"] == 0.13
# 1 ÷ 16 = 0.0625 → 3자리 첫 버림 자리 5 → 0.063
assert mid["cqty"] == 0.063
+36 -10
View File
@@ -333,7 +333,13 @@ const StmateCalc = (() => {
const sys = data.sysConfig || {},
chnMode = sys.chnMode || "bimok",
optOrder = (sys.optOrder || "SML").toUpperCase();
const stdMap = Object.fromEntries((data.stdMarketItems || []).map((x) => [x.code, x]));
const lookupUnit = (refId) => {
const sm = stdMap[refId];
if (sm) {
const meta = { name: sm.name, code: sm.code, spec: sm.spec || "", unit: sm.unit || "" };
return Object.assign(stdMarketUnit(sm, slot, sys.stdMarket || {}), meta);
}
const r = ilwiMap[refId] || sanMap[refId] || machMap[refId] || resMap[refId];
if (!r) return { M: 0, L: 0, S: 0, T: 0, name: "미연결", code: "-", spec: "", unit: "" };
const pick = (k, iProp, prop) =>
@@ -354,6 +360,7 @@ const StmateCalc = (() => {
let directM = 0,
directL = 0,
directS = 0,
directA = 0,
govRawTotal = 0,
govFeeTotal = 0;
// 내역 비율 줄(PR) 밑수 — 표시(PRPT) 줄 금액 누적 · PR 줄을 지나면 비움(영월 조달수수료 4줄 대조)
@@ -370,7 +377,7 @@ const StmateCalc = (() => {
}
const su = slot > 1 && row.units && row.units[slot];
const u = su
? Object.assign({}, lookupUnit(row.refId), { M: su[0], L: su[1], S: su[2] })
? Object.assign({}, lookupUnit(row.refId), { M: su[0], L: su[1], S: su[2], A: 0 })
: lookupUnit(row.refId);
// 행 고정 단가(fixedUnit) · 내역 할증율(wBoqRate RATE/RM·RL·RS = 비목별 단가 가산, 원 단위 절사)
// — 금액 가산(BDQTY X_QTY·X_HAL 은 손료계수 칸 · REA 정정)(07번 §4 · 34번 wBoqRate) · 저장 차수 단가는 이미 반영된 값이라 건너뜀
@@ -384,7 +391,8 @@ const StmateCalc = (() => {
(c) => pct(c) && (u[c] = Math.trunc(u[c] * Math.fround((100 + pct(c)) / 100))),
);
}
u.T = u.M + u.L + u.S;
u.A = num(u.A);
u.T = u.M + u.L + u.S + u.A;
const qty =
Number(
slot > 1 && row.qtys
@@ -397,16 +405,18 @@ const StmateCalc = (() => {
rawL = u.L * qty,
rawS = u.S * qty,
rawT = u.T * qty;
// 넷째 칸 A(표준시장단가 비목 밖 금액) — 다른 비목과 같은 줄 금액 절사 · 합계 T 에 넣음(0x4312ad)
let M = roundDown(rawM, 0),
L = roundDown(rawL, 0),
S = roundDown(rawS, 0),
T = M + L + S;
A = roundDown(u.A * qty, 0),
T = M + L + S + A;
// 코드 붙은 % 줄(조달수수료 M%·T%) = 같은 상위 공종 안 앞 말단 줄 비목 합 × 율 → 제 비목(server pct_row_base)
if (row.pctBase) {
const k = row.pctComp,
base = row.pctBase.reduce((a, id) => a + ((doneById[id] || {})[k] || 0), 0);
const own = /^L/.test(row.code) ? "L" : /^S/.test(row.code) ? "S" : "M";
((M = 0), (L = 0), (S = 0));
((M = 0), (L = 0), (S = 0), (A = 0));
const amt = roundDown((base * num((row.prQtys || [])[slot - 1])) / 100, 0);
if (own === "L") L = amt;
else if (own === "S") S = amt;
@@ -426,7 +436,7 @@ const StmateCalc = (() => {
.filter((k) => got[k] !== { M, L, S }[k])
.map((k) => `${k}(+${got[k] - { M, L, S }[k]})`)
.join("");
((M = got.M), (L = got.L), (S = got.S), (T = M + L + S));
((M = got.M), (L = got.L), (S = got.S), (T = M + L + S + A));
}
}
doneById[row.id] = { M, L, S, T };
@@ -437,7 +447,7 @@ const StmateCalc = (() => {
((prBase.M += M), (prBase.L += L), (prBase.S += S));
if (row.rowType === "gov_mat") govRawTotal += T;
else if (row.rowType === "gov_fee") govFeeTotal += T;
else ((directM += M), (directL += L), (directS += S));
else ((directM += M), (directL += L), (directS += S), (directA += A));
const out = Object.assign({}, row, {
spec: row.spec || u.spec || "",
unit: row.unit || u.unit || "",
@@ -451,13 +461,14 @@ const StmateCalc = (() => {
sUnit: u.S,
tUnit: u.T,
};
const u2 = { mAmt: M, lAmt: L, sAmt: S, tAmt: T, M, L, S, T, optimizDiff, optimizTarget };
const u2 = { mAmt: M, lAmt: L, sAmt: S, aAmt: A, tAmt: T, M, L, S, A, T };
Object.assign(u2, { aUnit: u.A, optimizDiff, optimizTarget });
return Object.assign(out, u1, u2, { aggM, aggL, aggS, aggT: aggM + aggL + aggS });
});
const directT = directM + directL + directS,
const directT = directM + directL + directS + directA,
govCombinedRaw = govRawTotal + govFeeTotal;
const totals = Object.assign(
{ directM, directL, directS, directT },
{ directM, directL, directS, directA, directT },
{ govRawTotal, govMatTotal: govRawTotal, govFeeTotal, govCombinedRaw },
);
return Object.assign({ rows, directTotal: directT, totals }, totals);
@@ -508,6 +519,21 @@ const StmateCalc = (() => {
return Object.assign(out, { T: out.L + out.M + out.S + out.N });
}
// 표준시장단가 코드(A · COSTA) 한 칸 단가 — 단가 읽기 Main 0x430c60(A 줄 = COSTA 자료집합 · 0x437a4b)
// S_JDA(Start8x.Ini · 설정 +0x7c8) 참 = 저장 LAMTn·MAMTn·SAMTn 그대로 · 거짓 = AAMTn 을 RATE_L 로 나눔(splitStdMarket)
// ACTTYP·ACTOPT = PRJLST 값(로더 0x45c938 → 공사 레코드 +0x172c·+0x1730) · 넷째 칸 A = 비목 밖 금액(합계엔 포함)
// item: { aamt[1..4], rateL, lamtR[1..4], stored: { L, M, S }[1..4] } · cfg: { acttyp, actopt, useStored }
function stdMarketUnit(item, slot = 1, cfg = {}) {
const at = (arr) => num((arr || [])[slot - 1]);
const st = (item && item.stored) || {};
if (cfg.useStored) {
const [L, M, S] = ["L", "M", "S"].map((k) => at(st[k]));
return { L, M, S, A: 0, T: L + M + S };
}
const sp = splitStdMarket(at(item.aamt), item.rateL, at(item.lamtR), cfg.acttyp, cfg.actopt);
return { L: sp.L, M: sp.M, S: sp.S, A: sp.N, T: sp.T };
}
function evalKOrdTransition(pclass, regMode, flag, currentK) {
const K = Number(currentK) || 1;
const nextK =
@@ -620,7 +646,7 @@ const StmateCalc = (() => {
const api = { KINDS, KIND_KEYS, num, fmt, evalFormula, evalLeftToRight };
Object.assign(api, { roundDown, roundUp, roundHalfUp, roundBanker });
Object.assign(api, { evalWageFormulaLeftToRight, evalQFormula, evalKOrdTransition });
Object.assign(api, { optimizParts, splitStdMarket });
Object.assign(api, { optimizParts, splitStdMarket, stdMarketUnit });
return Object.assign(api, { createDefaultState, calculateAll, computeAll: calculateAll });
})();
+9 -2
View File
@@ -6,15 +6,22 @@
* 적용수량 = 일일수량 0 이면 0 · 아니면 Qp(조수량 × (노무 1 · 장비 8) ÷ 일일수량)
* 자동 자리 = 기준수량 정수부 자릿수 + 1 (2~6 사이로 묶음) · 일일수량이 아니라 기준수량을 봄
* 조건식 = 빈칸 걷은 뒤 * + - . / 0~9 ^ 만 허용(괄호 없음) · 빈 식 = 1
* Q_d = DP_Common.MROUNDUD(x, d, 1) — 반올림 방향 미확정(REA 15 · 14) · 여기서는 반올림으로 둠(가정)
* Q_d = DP_Common.MROUNDUD(x, d, 1) — 선택 1 = 첫 버림 자리 ≥ 5 이면 한 단위 올림(0 반대쪽 · 사사오입)
* 근거: REA 32 §1.1(DP_Common 0x454044 · 0x4544f0 올림 판정) · 원형 = exec_calc.js mroundud(한 벌)
*/
const StmateXRate = (() => {
const EV = () =>
(typeof window !== "undefined" && window.STMATE_SMT_EVAL) ||
(typeof require !== "undefined" ? require("./smt_eval.js") : null);
// 반올림(가정) — 이진 오차를 십진 자리로 맞춤
const EX = () =>
(typeof window !== "undefined" && window.STMATE_EXEC) ||
(typeof require !== "undefined" ? require("./exec_calc.js") : null);
// Q_d = MROUNDUD(v, d, 1) — 원형(exec_calc) 이 안 실렸으면 같은 방향(사사오입 · 0 반대쪽)으로
function q(v, d) {
const ex = EX();
if (ex && ex.mroundud) return ex.mroundud(v, d, 1);
const f = 10 ** d,
r = Math.floor(Math.abs(Number(v) || 0) * f + 0.5 + 1e-9) / f;
return v < 0 ? -r : r;
+28 -1
View File
@@ -63,7 +63,34 @@ const STMATE_KANJUB_NATIVE = (() => {
const bf = on && Number(keys.I_KUNBO) !== 1 && Number(keys.I_YUNBO) !== 1 ? 1 : 0;
return { I_ANJUNAUTO: on ? 2 : 1, I_BF_CH_BO: bf };
}
return { chasuList, firstSeries, chgGate, CHG_ITEMS, changeDefaults };
// 적용기준 I_SET_BASE — cmBx_Set_Base(TWM_KANJUB.dfm) Items 순번 ↔ Values · 저장 = TwCombo.GetInteger = Values 값
// (같은 GetInteger 로 읽는 공사종류·건설기계 구분을 원본이 8·9·−1 값으로 비교 — 순번이면 나올 수 없는 값)
const SET_BASE = [
[2, "일반(국가계약법)"],
[0, "일반(지방계약법)"],
[1, "행자부기준"],
];
// 행자부기준(SET_BASE 1) 요율 조회 — ST_KanJub 0x47a1bc(0x47acd8~0x47b100) · RATE_M(JB_M) 줄 = Go 번호(1부터)
// 규모 b = 밑수 < 50억 → 0 · < 300억 → 1 · 그 밖 2(G[2]·G[3] · 밑수 = RATE_R 조회와 같은 원가 칸 0x4e4260)
// 간접노무비 = 공사종류 < 5 · I_ILBAN ≠ 1 → 줄 b+11 · 칸 RATE{기간 + (공사종류 1·3 이면 5)}
// I_ILBAN 1 또는 공사종류 ≥ 5 → 줄 b+15 · 칸 RATE{기간+5} (기간 = max(0, 화면순번−1))
// 일반관리비 = 줄 23 RATE1 · 이윤 = 줄 24 RATE0 · 그 밖 요율은 RATE_R 그대로
// rows: RATE_M 행(1번 줄 = rows[0]) · o: { scale, kongType, ilban, kikan }
function rateMLookup(rows, o = {}) {
const n = (v) => Number(v) || 0;
const at = (line, col) => n(((rows || [])[line - 1] || {})[`RATE${col}`]);
const b = n(o.scale) < 50e8 ? 0 : n(o.scale) < 300e8 ? 1 : 2;
const kt = n(o.kongType),
kik = Math.max(0, n(o.kikan));
const wide = n(o.ilban) === 1 || kt >= 5;
const line = b + (wide ? 15 : 11),
col = kik + (wide || kt === 1 || kt === 3 ? 5 : 0);
const cells = { KANNO: [line, col], ILBAN: [23, 1], IYUN: [24, 0] };
const rates = Object.fromEntries(Object.entries(cells).map(([k, [r, c]]) => [k, at(r, c)]));
return { band: b, rates, cells };
}
const out = { chasuList, firstSeries, chgGate, CHG_ITEMS, changeDefaults };
return Object.assign(out, { SET_BASE, rateMLookup });
})();
const StmateKanjubParts = ({ num, fmt, roundDown, roundUp, roundBanker }) => {
+32
View File
@@ -491,6 +491,37 @@ def api_load(req: LoadReq):
c for c in (conld[i : i + 6].strip() for i in range(0, len(conld), 6)) if c
)
sim_state["sysConfig"]["rxFormula"] = ini.get("RXXNM") or sim_state["sysConfig"]["rxFormula"]
# 표준시장단가(A 코드 · COSTA.DBF) — 노무비율 나눔 설정 = PRJLST ACTTYP·ACTOPT(Main 0x45c938) ·
# S_JDA 참 = 저장 단가 그대로(설정 +0x7c8 · 기본 거짓) — calc.stdMarketUnit
costa_rows = parse_dbf_bytes(files["COSTA.DBF"])[1] if "COSTA.DBF" in files else []
prj0 = prj_rows[0] if prj_rows else {}
sim_state["sysConfig"]["stdMarket"] = {
"acttyp": int(fnum(prj0.get("ACTTYP"))),
"actopt": int(fnum(prj0.get("ACTOPT"))),
"useStored": str(ini.get("S_JDA", "")).strip().upper() in ("1", "T", "TRUE", "Y"),
}
std_items = [
{
"id": (r.get("CODE") or "").strip(),
"code": (r.get("CODE") or "").strip(),
"name": r.get("NAME") or "",
"spec": r.get("SIZE") or "",
"unit": r.get("UNIT") or "",
"aamt": [fnum(r.get(f"AAMT{n}")) for n in range(1, 5)],
"rateL": fnum(r.get("RATE_L")),
"lamtR": [fnum(r.get(f"LAMT{n}_R")) for n in range(1, 5)],
"stored": {c: [fnum(r.get(f"{c}AMT{n}")) for n in range(1, 5)] for c in "LMS"},
}
for r in costa_rows
if (r.get("CODE") or "").strip()
]
sim_state["stdMarketItems"] = std_items
# 내역의 A 줄은 COSTA 로 계산 — 저장 단가로 만든 임시 일위대가·자원은 걷음
std_codes = {x["code"] for x in std_items}
sim_state["ilwiItems"] = [x for x in sim_state["ilwiItems"] if x["id"] not in std_codes]
sim_state["resources"] = [
x for x in sim_state["resources"] if str(x["id"]).rsplit("_", 1)[0] not in std_codes
]
return {
"source_path": req.source_path,
"boq_table_name": boq_name,
@@ -510,6 +541,7 @@ def api_load(req: LoadReq):
"start_ini": ini,
"sim_state": sim_state,
**extra_tables(files),
"costa_rows": costa_rows, # 암호 표도 풀어 읽은 행(smt_engine A 줄 단가)
}
+44 -6
View File
@@ -13,17 +13,51 @@
// 낙찰률(NAGCD) 단가는 2칸 원단가로 산근을 셈 — 1칸은 원단가 × 낙찰률
const costSlot = (r) => (num(r.NAGCD) > 0 ? 2 : 1);
// 표준시장단가(A) 줄 단가 설정 — PRJLST ACTTYP·ACTOPT(로더 0x45c938) · Start8x.Ini S_JDA(설정 +0x7c8, 기본 거짓)
const iniBool = (v) =>
["1", "T", "TRUE", "Y"].includes(
String(v ?? "")
.trim()
.toUpperCase(),
);
function stdMarketCfg(ctx) {
const d = ctx.dbfData || {},
prj = d.prjlst || {};
const useStored = iniBool((d.start_ini || {}).S_JDA);
return { acttyp: num(prj.ACTTYP), actopt: num(prj.ACTOPT), useStored };
}
// COSTA 행 → calc.stdMarketUnit 꼴(AAMTn · RATE_L · LAMTn_R · 저장 LAMTn·MAMTn·SAMTn)
const ns = [1, 2, 3, 4];
const stdItem = (r) => ({
aamt: ns.map((n) => num(r[`AAMT${n}`])),
rateL: num(r.RATE_L),
lamtR: ns.map((n) => num(r[`LAMT${n}_R`])),
stored: Object.fromEntries(
"LMS".split("").map((k) => [k, ns.map((n) => num(r[`${k}AMT${n}`]))]),
),
});
function costnLookup(ctx) {
const byCode = {};
const byCode = {},
byA = {};
((ctx.dbfData && ctx.dbfData.costn_rows) || []).forEach((r) => (byCode[rowCode(r)] = r));
((ctx.dbfData && ctx.dbfData.costa_rows) || []).forEach((r) => (byA[rowCode(r)] = r));
const smCfg = stdMarketCfg(ctx);
const lookup = (code) => {
// A 줄 = COSTA 에서 읽어 노무비율로 나눔(Main 0x437a4b → 0x430c60 → 0x45a468) · 넷째 칸 A 는 구성 합계 밖
const a = code && code[0] === "A" && !byCode[code] ? byA[code] : null;
if (a) {
const u = Calc.stdMarketUnit(stdItem(a), 1, smCfg);
const meta = { unit: String(a.UNIT || "").trim(), name: String(a.NAME || "") };
return Object.assign({ M: u.M, L: u.L, S: u.S, A: u.A }, meta);
}
const r = byCode[code];
if (!r) return null;
const s = costSlot(r);
const out = { M: num(r[`MAMT${s}`]), L: num(r[`LAMT${s}`]), S: num(r[`SAMT${s}`]) };
return Object.assign(out, { unit: String(r.UNIT || "").trim(), name: String(r.NAME || "") });
};
return { byCode, lookup };
return { byCode, byA, lookup };
}
// 결과금액조정(COSTN S_ADJ · ADJ_M/L/S) — 표시된 것만
@@ -61,13 +95,15 @@
const digitOf = (v, def = 1) => (/^[0-2]$/.test(String(v ?? "").trim()) ? Number(v) : def);
// 외산장비 환율 — 단가의 조사시점(ARENM 1 당초 · 2 변경)의 EXLNM·EXHNM·EXEUR·EXYEN(REA 14 cmBx_ARE)
// 키가 없으면 로더 기본 "1000.00"(Main 0x42b0xx [EQUIPMENT])
function fxOf(ini) {
const n = String(ini.ARENM || "1").trim() === "2" ? 2 : 1;
const v = (k) => (ini[k] == null || String(ini[k]).trim() === "" ? 1000 : num(ini[k]));
return {
usd: num(ini[`EXLNM${n}`]),
usdHigh: num(ini[`EXHNM${n}`]),
eur: num(ini[`EXEUR${n}`]),
yen: num(ini[`EXYEN${n}`]),
usd: v(`EXLNM${n}`),
usdHigh: v(`EXHNM${n}`),
eur: v(`EXEUR${n}`),
yen: v(`EXYEN${n}`),
};
}
@@ -163,6 +199,8 @@
rx: ini.RXXNM || sys.rxFormula,
usdRate: num(ini.EXLNM1),
fx: fxOf(ini),
// 환율 적용 범위 EXRNM([EQUIPMENT] · 설정 +0x7c9 · 기본 거짓) — 참이면 S 줄 단가를 천원으로 미리 환산(0x437aa2)
exrnm: iniBool(ini.EXRNM),
// 줄 금액 자리 = 중기 KCD · 일위대가 BPN · 중기 노임 단가 JCD · $장비 단가 ECD(22 §3.4 · REA 14 안내문)
lineDigits: digitOf(code[0] === "X" ? ini.KCDNM : ini.BPNNM),
// JCD 빈칸 기본 = KCD 2 이면 2 · 아니면 0(Main 0x42ab08)
+9 -3
View File
@@ -397,9 +397,10 @@ const StmateSmtEval = (() => {
// opts.fx = { usd, usdHigh, eur, yen } · 옛 opts.usdRate 는 usd 로 봄
function foreignUnit(unit, price, opts) {
const fx = opts.fx || { usd: opts.usdRate };
// $·$ = 단가 10만 미만 EXL · 그 밖 EXH(Main 0x437caa~0x437d45 · 고액 환율이 안 주어진 옛 호출만 EXL)
const rate =
unit === "$"
? Number(price) >= 100000 && Number(fx.usdHigh) > 0
unit === "$" || unit === "$"
? Number(price) >= 100000 && fx.usdHigh != null
? fx.usdHigh
: fx.usd
: unit === "€"
@@ -467,7 +468,12 @@ const StmateSmtEval = (() => {
p = { M: 0, L: hourlyWage(r.L, rx, Number(opts.wageDigits) || 0), S: 0 };
}
const fxRate = foreignUnit(unit, r.M + r.L + r.S, opts);
if (fxRate !== null)
// EXRNM 참 — S 줄만 단가 = MROUNDUD(단가 × 환율/1000, ECD, 0)(천원 · ECD 자리 버림 · Main 0x437aa2~0x437df2)
// 거짓 — 원본은 상위 B·천원 줄 수량 칸에 환율/1000 을 넣는 다른 경로(0x435660) · 여기는 옛 환산 유지(미확인)
if (fxRate !== null && opts.exrnm) {
if (code[0] === "S")
p = Object.assign({}, p, { S: trunc((p.S * fxRate) / 1000, Number(opts.fxDigits) || 0) });
} else if (fxRate !== null)
for (const k of "MLS")
p[k] =
opts.fxDigits == null