Рассчитать эту смету не Хабаровск а Сахалинская об...
Erstellt am: 11. November 2026
Beantwortet mit GPT-6 Thinking von Chat01
Erstellt am: 11. November 2026
Beantwortet mit GPT-6 Thinking von Chat01
Рассчитать эту смету не Хабаровск а Сахалинская область (1 зона ) 3 квартал 2025год через Сплит-форму
Used spreadsheets skill
bash -lc cat /home/oai/skills/spreadsheets/SKILL.md | head -n 180; printf '\n---PDF---\n'; cat /home/oai/skills/pdfs/SKILL.md | head -n 120; printf '\n---DOCX---\n'; cat /home/oai/skills/docx/SKILL.md | head -n 110; printf '\n---FILES---\n'; ls -lh /mnt/data
bash -lc python - <<'PY' from openpyxl import load_workbook from pathlib import Path for p in Path('/mnt/data').glob('*.xlsx'): print('\nWORKBOOK',p.name) for read_only in [True]: w=load_workbook(p,read_only=read_only,data_only=False) print('Sheetnames:',w.sheetnames) for s in w: print('sheet:',repr(s.title),'dimensions',s.max_row,s.max_column) print('top nonempty rows:') for row in s.iter_rows(min_row=1,max_row=min(s.max_row,24),max_col=min(s.max_column,18),values_only=True): non=[f'{i+1}:{str(v)[:110]}' for i,v in enumerate(row) if v is not None] if non: print(' ',' ; '.join(non)[:380]) w.close() PY
bash -lc python - <<'PY' from openpyxl import load_workbook from pathlib import Path p=next(Path('/mnt/data').glob('Lokalnaia*.xlsx')) w=load_workbook(p,read_only=True,data_only=False) for s in w: s.reset_dimensions() print('\n###',s.title) if 'Локальная смета (16)' in s.title: rows=range(20,119) else: rows=range(1,65) for row in s.iter_rows(min_row=min(rows),max_row=max(rows)): r=row[0].row populated=[f'{c.column_letter}={str(c.value)[:115]}' for c in row if c.value is not None] if populated: print(r,' | '.join(populated[:13])[:380]) PY
bash -lc python - <<'PY' from openpyxl import load_workbook from pathlib import Path p=next(Path('/mnt/data').glob('Lokalnaia*.xlsx'));w=load_workbook(p,read_only=True) for s in list(w)[1:]: print('\n###',s.title) for row in s: parts=['%s:%s'%(c.column_letter,str(c.value)[:145]) for c in row if c.value is not None] if parts:print(row[0].row,' | '.join(parts)[:500]) PY
bash -lc cat > /mnt/data/inspect_split.py <<'PY' from openpyxl import load_workbook from pathlib import Path import sqlite3, time p=next(Path('/mnt/data').glob('Сплит-форма*.xlsx')) start=time.time(); w=load_workbook(p,read_only=True,data_only=True);s=w.active;s.reset_dimensions() db=sqlite3.connect('/mnt/data/split_lookup.sqlite') db.execute('CREATE TABLE IF NOT EXISTS resources (row INTEGER PRIMARY KEY, code TEXT, name TEXT, unit TEXT, base_price TEXT, price_2022 TEXT, groupcode TEXT, groupname TEXT, price_2025 TEXT, idx TEXT)') db.execute('DELETE FROM resources') batch=[] for ix,row in enumerate(s.iter_rows(min_row=1,max_col=9,values_only=True),1): if ix<=30: print('HEADER',ix,' || '.join(f'{chr(65+j)}={str(v)[:290]}' for j,v in enumerate(row) if v is not None)[:900]) if ix % 25000==0: print('PROGRESS',ix,round(time.time()-start,1),'sec',flush=True) code=row[0] if isinstance(code,(str,int)) and len(str(code))<100 and str(code).strip(): batch.append((ix, [str(v) if v is not None else None for v in row])) if len(batch)>2000: db.executemany('INSERT INTO resources VALUES (?,?,?,?,?,?,?,?,?,?)',batch);batch=[] if batch: db.executemany('INSERT INTO resources VALUES (?,?,?,?,?,?,?,?,?,?)',batch) db.commit() print('all rows',ix,'stored',db.execute('SELECT COUNT() FROM resources').fetchone()[0],'time',time.time()-start) codes=['1-100-22','1-100-27','1-100-36','2-100-00','4-100-060','1-100-20','91.06.06-048','91.07.04-002','91.14.02-001','91.01.05-087','01.7.03.01-0001','04.3.01.09-0015','01.6.03.04-0001','01.7.20.08-0051','14.1.02.04','01.7.03.04-0001','01.7.15.07-0021','01.7.15.14-0302','11.3.03.06-0002','11.3.03.14-0001','11.3.03.14-0021','11.3.03.14-0031'] for code in codes: print('\nMATCH',code) rs=db.execute('SELECT * FROM resources WHERE code=? LIMIT 10',(code,)).fetchall() if not rs: rs=db.execute('SELECT * FROM resources WHERE code LIKE ? LIMIT 8',(code+'%',)).fetchall() for r in rs:print(r) print('\n=== LABOR & HEADER candidates ===') for r in db.execute("SELECT row,code,name,unit,price_2022,price_2025,idx FROM resources WHERE code LIKE '1-%' OR code LIKE '4-%' OR name LIKE '%Затраты труда%' OR name LIKE '%оплаты труда%' LIMIT 90"): print(r) print('\n=== MISSING EXACT PREFIX families (selection) ===') for prefix in ['14.1.02.04%','11.3.03.06%','04.3.01.09%','01.6.03.04%','01.7.15.07%','01.7.15.14%','11.3.03.14%']: print('FAMILY',prefix,db.execute('SELECT COUNT(*) FROM resources WHERE code LIKE ?',(prefix,)).fetchone()[0]); for r in db.execute('SELECT row,code,name,unit,price_2022,price_2025,idx FROM resources WHERE code LIKE ? LIMIT 12',(prefix,)):print(r) PY python /mnt/data/inspect_split.py > /mnt/data/split_report.txt 2>&1 wc -l /mnt/data/split_report.txt; sed -n '1,130p' /mnt/data/split_report.txt
bash -lc sed -n '122,300p' /mnt/data/split_report.txt
Checking machine wages
I’m verifying whether machine-operator wages are already included in machinery costs. If they are, adding a separate wage component would double-count costs; where the grade is unavailable, I’ll mark it as an assumption rather than inventing a rate.
Correcting wage treatment
I’m correcting the cost model: separately adding machine-operator wages would double-count them if machinery prices already include operator labor. I’ll document the grade assumption explicitly unless the applicable normative source confirms each machine’s rate.
Checked and corrected machine wage treatmentInspected workbook files, worksheets, estimates, resources, and report detailsVerifying regional standards
I’m checking the applicable 2022 regional coefficients and wage references against the project’s original values. I’ll preserve those values when supported, while separating confirmed norms from any provisional assumptions.
Selecting provisional rates
I’m completing the calculation with an editable grade-4 operator rate as a clearly labeled assumption. This keeps the estimate usable without presenting an unverified machine-specific wage as authoritative.
Reassessing machine costs
I’m revising the earlier conclusion: machinery prices may exclude operator wages, which are listed separately in the estimate. I’ll retain the separate operator component and verify the applicable machine-specific grade before finalizing.
Verified regional standards, selected provisional rates, and reassessed machine costsSearched 22 websitesPreparing the estimate
I’m checking the machinery source rates and preserving the separate operator-wage component where the pricing system requires it. I’ll keep the original workbook available as a clearly labeled source sheet while presenting the corrected regional estimate separately.
Structuring the workbook
I’m organizing the deliverable into an overview, regional estimate, resource statement, input data, and preserved source sheet. I’ll update current prices transparently, apply regional coefficients, and retain formulas for auditability.
Building calculation links
I’m linking each estimate resource to a dedicated split-form price table, including indices, current values, resource groups, and source references. Regional coefficients and machine wages will remain editable and formula-driven.
Refining editable inputs
I’m adding optional manual overrides beside formula-derived prices, so users can adjust a value without losing the source calculation. Each resource will retain its code, unit, index, source row, and pricing method for traceability.
Разделяя машинистов
Я уточняю оплату машинистов по видам машин и разрядам. Для агрегированной нормы сохраню нормативный объём, а распределение стоимости покажу отдельно, чтобы расчёт оставался прозрачным и проверяемым.
Prepared and structured the estimate workbook with linked calculations and editable inputs****Searched 1 website
bash -lc python - <<'PY' from decimal import Decimal as D, ROUND_HALF_UP p=lambda x: D(str(x)); rnd=lambda x: x.quantize(D('.01'),ROUND_HALF_UP) price={'1-100-22':p(796.69),'1-100-27':p(832.58),'1-100-36':p(918.71),'4-100-030':p(854.11),'4-100-040':p(961.77),'91.06.06-048':rnd(p(37.32)*p(1.83)),'91.07.04-002':rnd(p(8.54)*p(1.91)),'91.14.02-001':p(860.26),'01.7.03.01-0001':rnd(p(35.71)*p(2.34)),'04.3.01.09-0015':p(9869.34),'01.6.03.04-0001':rnd(p(239)*p(1.49)),'01.7.20.08-0051':rnd(p(56.11)*p(2.09)),'14.1.02.04-0102':rnd(p(223.54)*p(2.27)),'01.7.03.04-0001':p(9.61),'01.7.15.07-0021':rnd(p(174.13)*p(1.53)),'01.7.15.14-0302':rnd(p(88.86)*p(1.53)),'11.3.03.06-0002':rnd(p(29.45)*p(1.27)),'11.3.03.14-0001':rnd(p(1815.16)*p(1.27)),'11.3.03.14-0021':rnd(p(1448.4)*p(1.27)),'11.3.03.14-0031':rnd(p(1275.12)*p(1.27))} for k,v in price.items():print(k,v) sections=[ ([(p(35.6),'1-100-22')], [('91.06.06-048',p(1.27)),('91.07.04-002',p(7.82))], [('01.7.03.01-0001',p(3.5)),('04.3.01.09-0015',p(2.04))],p(1.27), [('4-100-030',p(1.27))]), ([(p(38.2),'1-100-27')], [('91.06.06-048',p(.35)),('91.14.02-001',p(.5))], [('01.6.03.04-0001',p(102)),('01.7.20.08-0051',p(.5)),('14.1.02.04-0102',p(50))],p(.85), [('4-100-030',p(.35)),('4-100-040',p(.5))]), ([(p(6.68)*p(.4),'1-100-36')], [('91.06.06-048',p(.005)*p(.4)),('91.14.02-001',p(.03)*p(.4))], [('01.7.03.04-0001',p(2.5408)*p(.4)),('01.7.15.07-0021',p(.263)*p(.4)),('01.7.15.14-0302',p(2.63)*p(.4)),('11.3.03.06-0002',p(101)p(.4)),('11.3.03.14-0001',p(.16)p(.4)),('11.3.03.14-0021',p(.4)p(.4)),('11.3.03.14-0031',p(.14)p(.4))],p(.04)p(.4), [('4-100-030',p(.005)p(.4)),('4-100-040',p(.03)p(.4))])] totals=[] for i,(workers,machines,mats,machhours,wages) in enumerate(sections,1): ot=sum((qprice[c] for q,c in workers),p(0)) em=sum((qprice[c] for c,q in machines),p(0)) mt=sum((qprice[c] for c,q in mats),p(0)) avg=sum((qprice[c] for c,q in wages),p(0))/sum((q for c,q in wages),p(0)) otm=machhoursavg fot=ot+otm pz=ot+otm+em+mt nr=fotp(1.12);sp=fotp(.65);cost=pz+nr+sp print('FLOOR section',i,'ОТ',rnd(ot),'ОТм',rnd(otm),'ЭМ',rnd(em),'М',rnd(mt),'ПЗ',rnd(pz),'ФОТ',rnd(fot),'НР',rnd(nr),'СП',rnd(sp),'TOTAL',rnd(cost)) totals.append((ot,otm,em,mt,pz,nr,sp,cost)) print('ALL', [rnd(sum(row[x] for row in totals)) for x in range(8)]) PY
bash -lc ls -lah /mnt/data | head -35; python - <<'PY' import sqlite3 c=sqlite3.connect('/mnt/data/split_lookup.sqlite') for code in ['4-100-030','4-100-040','4-100-060','1-100-22','14.1.02.04-0102']: print(code,c.execute('select * from resources where code=?',(code,)).fetchall()[:2]) PY
bash -lc python - <<'PY' from openpyxl import load_workbook p='/mnt/data/Lokalnaia_smeta_resursnyi_metod_poly_linoleum_19324678.xlsx' w=load_workbook(p) for s in w: print('\nSHEET',s.title,s.max_row,s.max_column) if s.title=='Исходные данные РМ': for row in s: print(row[0].row,' | '.join(f'{c.column_letter}:{str(c.value)[:130]}' for c in row if c.value is not None)) if s.title=='Локальная смета РМ': for row in s: print(row[0].row, ' | '.join(f'{c.column_letter}:{str(c.value)[:80]}' for c in row[:9] if c.value is not None)) if s.title=='Ресурсная ведомость РМ': for row in s: print(row[0].row, ' | '.join(f'{c.column_letter}:{str(c.value)[:70]}' for c in row[:9] if c.value is not None)) PY
bash -lc python - <<'PY' import sqlite3 c=sqlite3.connect('/mnt/data/split_lookup.sqlite') L=['1-100-22','1-100-27','1-100-36','4-100-030','4-100-040','4-100-060','1-100-20','91.06.06-048','91.07.04-002','91.14.02-001','91.01.05-087','01.7.03.01-0001','04.3.01.09-0015','01.6.03.04-0001','01.7.20.08-0051','14.1.02.04-0102','01.7.03.04-0001','01.7.15.07-0021','01.7.15.14-0302','11.3.03.06-0002','11.3.03.14-0001','11.3.03.14-0021','11.3.03.14-0031'] for code in L: rows=c.execute('select row,name,unit,price_2022,price_2025,idx from resources where code=?',(code,)).fetchall() print(code,rows[:2]) PY
bash -lc python - <<'PY' import importlib.util for p in ['artifact_tool','formulas','xlsx_evaluate','pycel','lxml','openpyxl','xlsxwriter']: print(p, bool(importlib.util.find_spec(p))) PY
Removing duplicate costs
I’m excluding separately shown machine-operator wages from direct-cost totals when machinery costs already include them, while retaining them in the payroll breakdown. This prevents overstating the estimate.
Removed duplicate costs
bash -lc python - <<'PY' from openpyxl import load_workbook w=load_workbook('/mnt/data/Lokalnaia_smeta_resursnyi_metod_poly_linoleum_19324678.xlsx',read_only=False) s=w.worksheets[0] for row in range(1,118): vals=['%s=%s'%(s.cell(row,col).column_letter,str(s.cell(row,col).value)[:90]) for col in range(1,32) if s.cell(row,col).value is not None] if vals: print(row,' | '.join(vals)[:400]) PY
Built and validated Sakhalin estimate workbook with recalculated formulas****Resolving cost treatment
I’m retaining separate operator wages because the machinery rates exclude them in this pricing model. Direct costs will therefore include materials, labor, machinery, and operator wages once each—without duplication.
Finalizing the model
I’m treating machinery prices as excluding operator wages, so both components remain itemized once. I’ll preserve the source workbook, flag assumptions, and ensure regional coefficients and formulas remain auditable and editable.
Finalized the model and resolved cost treatment****Searched 8 websites
bash -lc cat > /mnt/data/make_estimate.py <<'PY' from future import annotations import os, re, math, sqlite3, io, copy, zipfile, tempfile from collections import defaultdict from decimal import Decimal, ROUND_HALF_UP from lxml import etree from openpyxl import load_workbook from openpyxl.styles import PatternFill, Font, Alignment, Border, Side from openpyxl.comments import Comment from openpyxl.utils import get_column_letter from openpyxl.workbook.properties import CalcProperties
ROOT='/mnt/data' SRC=f'{ROOT}/Lokalnaia_smeta_resursnyi_metod_poly_linoleum_19324678.xlsx' OUT=f'{ROOT}/Смета_Сахалин_1зона_IIIкв2025_РИМ.xlsx' SPLIT='Сплит-форма Сахалинская область (1 зона) на 3 квартал 2025 года.xlsx' DB=sqlite3.connect(f'{ROOT}/split_lookup.sqlite') DB.row_factory=sqlite3.Row codes=[ ('1-100-22','ОТ'),('1-100-27','ОТ'),('1-100-36','ОТ'),('1-100-20','ОТ'), ('4-100-030','ОТм'),('4-100-040','ОТм'),('4-100-060','ОТм'), ('91.06.06-048','ЭМ'),('91.07.04-002','ЭМ'),('91.14.02-001','ЭМ'),('91.01.05-087','ЭМ'), ('01.7.03.01-0001','М'),('04.3.01.09-0015','М'),('01.6.03.04-0001','М'), ('01.7.20.08-0051','М'),('14.1.02.04-0102','М'),('01.7.03.04-0001','М'), ('01.7.15.07-0021','М'),('01.7.15.14-0302','М'),('11.3.03.06-0002','М'), ('11.3.03.14-0001','М'),('11.3.03.14-0021','М'),('11.3.03.14-0031','М')] book=load_workbook(SRC) book.worksheets[0].title='Архив Хабаровск' book['Архив Хабаровск'].sheet_state='hidden' params=book['Исходные данные РМ'] resource=book['Ресурсная ведомость РМ'] local=book['Локальная смета РМ'] price=book.create_sheet('Цены Сахалин') earth=book.create_sheet('Земляные работы РИМ') summary=book.create_sheet('Итоги',0) method=book.create_sheet('Источники и оговорки')
NAVY='17324D'; BLUE='224A70'; TEAL='0F766E'; TEAL_BG='E3F2F0'; LIGHT='EDF3F9'; GRAY='586574'; ORANGE='B65A00'; ORANGE_BG='FFF0D4'; WHITE='FFFFFF'; DARK='192D41'; INPUT='0C58B6'; GREEN='167144'; PURPLE='6A4BA3' fmt='#,##0.00;Red;–' fmt_q='#,##0.####;Red;–' fmt_price='#,##0.00 "₽";Red "₽";–'
def d(s): try: if s in (None,'-','', '—'): return None return float(s) except (TypeError,ValueError): return None
def rnd(s): return float(Decimal(str(s)).quantize(Decimal('.01'),rounding=ROUND_HALF_UP))
def style_sheet(ws, widths, freeze='A5', landscape=True): ws.sheet_view.showGridLines=False ws.freeze_panes=freeze ws.sheet_properties.pageSetUpPr.fitToPage=True ws.page_setup.fitToWidth=1 ws.page_setup.fitToHeight=0 if landscape: ws.page_setup.orientation='landscape' for col,width in widths.items(): ws.column_dimensions[col].width=width ws.sheet_properties.outlinePr.summaryBelow=True ws.print_options.horizontalCentered=True ws.sheet_view.zoomScale=85 ws.sheet_format.defaultRowHeight=21 ws.sheet_properties.pageSetUpPr.autoPageBreaks=False
def banner(ws, row, start,end,txt,color=NAVY,height=34): ws.merge_cells(start_row=row,start_column=start,end_row=row,end_column=end) c=ws.cell(row,start,txt);c.fill=PatternFill('solid',fgColor=color);c.font=Font(name='Aptos Display',size=15,bold=True,color=WHITE) c.alignment=Alignment(vertical='center',horizontal='left',indent=1) ws.row_dimensions[row].height=height for col in range(start,end+1): ws.cell(row,col).fill=PatternFill('solid',fgColor=color)
def head(ws,row, labels): for j,text in enumerate(labels,1): c=ws.cell(row,j,text);c.fill=PatternFill('solid',fgColor=BLUE);c.font=Font(name='Aptos',size=10,color=WHITE,bold=True) c.alignment=Alignment(wrap_text=True,horizontal='left',vertical='center') ws.row_dimensions[row].height=33
def total(ws,row,label_col,description,value_col='G',strong=False): for col in range(1,ws.max_column+1): c=ws.cell(row,col) c.fill=PatternFill('solid',fgColor=TEAL_BG if strong else LIGHT) c.font=Font(name='Aptos',bold=True,color=NAVY,size=10) c.border=Border(top=Side(style='thin',color='B6C9DB')) ws.cell(row,label_col).value=description ws[f'{value_col}{row}'].number_format=fmt_price ws.row_dimensions[row].height=24
def source_comment(code, sr): return Comment(f'Источник: {SPLIT}, лист «Сплит-форма», строка {sr}; код {code}. Сахалинская область, 1-я ценовая зона, III квартал 2025 года.', 'Источник')
price_map={} pricevalues={} style_sheet(price,{'A':20,'B':79,'C':14,'D':22,'E':18,'F':23,'G':23,'H':23,'I':16,'J':41,'K':52},freeze='D7') banner(price,1,1,11,'РЕСУРСЫ И ЦЕНЫ • САХАЛИНСКАЯ ОБЛАСТЬ • 1-Я ЗОНА') price.merge_cells('A2:K2');price['A2']='III квартал 2025 года | Сплит-форма Минстроя от 22.08.2025 № 49743-АЛ/09' price['A2'].font=Font(color=TEAL,bold=True,size=11) price.merge_cells('A3:K3');price['A3']='При наличии цены III кв. 2025 — используем её; иначе стоимость 01.01.2022 × индекс группы (с округлением цены до копеек).' price['A3'].font=Font(color=GRAY,italic=True) price.merge_cells('A4:K4');price['A4']='Коды ресурсов сопоставлены с приложенной сплит-формой; материалы, не учтённые в ГЭСН, подобраны по номенклатуре пользователя.' price['A4'].font=Font(color=GRAY,italic=True) head(price,6,['Код ресурса','Наименование по сплит-форме','Ед. изм.','Цена 01.01.2022, ₽','Индекс группы','Цена III кв. 2025, ₽','Принятая цена, ₽','Способ определения','Строка сплит-формы','Группа однородных ресурсов','Вид затрат / источник']) for idx,(code,kind) in enumerate(codes,start=7): rows=DB.execute('select * from resources where code=?',(code,)).fetchall() if len(rows)!=1: raise RuntimeError(f'Нет точного совпадения: {code}, найдено {len(rows)}') sr=rows[0]; base=d(sr['price_2022']); ind=d(sr['idx']); current=d(sr['price_2025']) if current is None and (base is None or ind is None): raise RuntimeError(f'Нет цены и индекса: {code}') pval=rnd(base*ind) if current is None else current price_map[code]=idx;pricevalues[code]=pval cells=[code,sr['name'],sr['unit'],base,ind,current,f'=IF(ISNUMBER(F{idx}),F{idx},IF(AND(ISNUMBER(D{idx}),ISNUMBER(E{idx})),ROUND(D{idx}*E{idx},2),""))', 'Текущая цена' if current is not None else 'База × индекс',sr['row'],sr['groupname'] if sr['groupname']!='-' else '—',kind] for j,v in enumerate(cells,1): c=price.cell(idx,j,v) c.font=Font(name='Aptos',size=10,color= GREEN if j in [4,5,6] and isinstance(v,(float,int)) else DARK) c.alignment=Alignment(vertical='center',wrap_text=(j in [2,10])) if idx%2==0: c.fill=PatternFill('solid',fgColor='F7FAFD') for j in (4,5,6): if price.cell(idx,j).value is not None:price.cell(idx,j).comment=source_comment(code,sr['row']) price[f'G{idx}'].fill=PatternFill('solid',fgColor=TEAL_BG) price[f'G{idx}'].number_format=fmt_price for col in ['D','F']:price[f'{col}{idx}'].number_format=fmt_price price[f'E{idx}'].number_format='0.00' price[f'H{idx}'].font=Font(size=10,color=GREEN if current is not None else ORANGE) price[f'K{idx}'].comment=source_comment(code,sr['row']) price.row_dimensions[idx].height=29 if len(sr['name'])>54 else 22 price.auto_filter.ref=f'A6:K{6+len(codes)}' price.print_title_rows='1:6'
params['B4']='Сахалинская область (1-я ценовая зона)' params['B5']='III квартал 2025 года' params['E8']='ФГИС ЦС / сплит-форма: Сахалинская область (1 зона)' params['D9']='Редакция и исходные ресурсные нормы ГЭСН — по приложенной смете' params['E9']='Состав и нормы исходной сметы сохранены; цены пересчитаны' params['A12']='Текущие цены ресурсов III кв. 2025, руб. без НДС — ссылки на сплит-форму' for r in list(range(14,33)): code=params[f'A{r}'].value if r==17: code='4-100-030';params['A17']=code params['B17']='Оплата труда машинистов подъёмника, разряд 3,0' if r==25: code='14.1.02.04-0102';params['A25']=code params['B25']='Клей для укладки ПВХ-покрытий (уточняемый по проекту)' if code not in price_map: raise ValueError(f'Нет цены input {r} {code}') sr=DB.execute('select row from resources where code=?',(code,)).fetchone()['row'] params[f'D{r}']=f"='Цены Сахалин'!G{price_map[code]}" params[f'F{r}']=f'Сплит-форма, стр. {sr}; ' + ('текущая цена' if d(DB.execute('select price_2025 from resources where code=?',(code,)).fetchone()['price_2025']) is not None else 'цена 2022 × индекс') params[f'D{r}'].number_format=fmt_price params[f'D{r}'].font=Font(color=GREEN) params[f'D{r}'].comment=source_comment(code,sr) params['A33']='4-100-040';params['B33']='Оплата труда машиниста бортового автомобиля, разряд 4,0';params['C33']='чел.-ч' params['D33']=f"='Цены Сахалин'!G{price_map['4-100-040']}";params['E33']='ОТм' params['F33']='Сплит-форма, стр. 46004; текущая цена';params['D33'].number_format=fmt_price;params['D33'].font=Font(color=GREEN) params['A35']='Норматив НР — устройство полов, МПРКС';params['B35']=1.12 params['A36']='Норматив СП — устройство полов';params['B36']=.65 params['B35'].number_format='0%';params['B36'].number_format='0%' for r in (35,36): params[f'B{r}'].font=Font(color=INPUT,bold=True) params[f'B{r}'].fill=PatternFill('solid',fgColor=LIGHT) params['D35']='Применение 812/пр: полы, МПРКС — 112%' params['D36']='Применение 774/пр: полы — 65%' params['A38']='Примечание к клею' params['B38']='Принят код 14.1.02.04-0102 (клей для ПВХ-покрытий); проверить соответствие проектному составу.' params.merge_cells('B38:F38');params['B38'].font=Font(color=ORANGE,bold=True,size=10) params['B38'].alignment=Alignment(wrap_text=True,vertical='center') params.row_dimensions[38].height=36 params['A40']='Оплата труда машинистов' params['B40']='По разрядам машин: подъёмник 3,0; автомобиль 4,0; средневзвешенная ставка с сохранением трудозатрат ГЭСН.' params.merge_cells('B40:F40');params['B40'].alignment=Alignment(wrap_text=True) params.row_dimensions[40].height=31 params['B7'].font=Font(color=INPUT,bold=True);params['B9'].font=Font(color=INPUT,bold=True) style_sheet(params,{'A':36,'B':70,'C':17,'D':56,'E':42,'F':59},freeze='D14') for r in (1,12): params[f'A{r}'].font=Font(bold=True,color=NAVY,size=13) params.print_options.horizontalCentered=True
resource['F4']="='Исходные данные РМ'!B7" resource['F11']="='Исходные данные РМ'!B7" resource['F19']="='Исходные данные РМ'!B9" resource['B18']='14.1.02.04-0102';resource['C18']='Клей для укладки ПВХ-покрытий';resource['I18']='Неучтенный ресурс: выбран код 14.1.02.04-0102; уточнить по проекту' resource['I6']='Затраты машинистов по норме ГЭСН; расценка по разрядам машин' resource['I13']='Машинисты: подъёмник 3 разр., бортовой автомобиль 4 разр.' resource['I21']='Машинисты по ГЭСН; распределение по машино-часам 2 механизмов' style_sheet(resource,{'A':6,'B':24,'C':89,'D':18,'E':20,'F':18,'G':21,'H':22,'I':71},freeze='E4') for r in [4,11,19]: for col in range(1,10): c=resource.cell(r,col);c.fill=PatternFill('solid',fgColor=LIGHT);c.font=Font(bold=True,color=NAVY) for r in [4,11,19]:resource[f'F{r}'].font=Font(color=GREEN,bold=True) for r in range(5,31): resource[f'H{r}'].number_format=fmt_q resource[f'E{r}'].number_format=fmt_q
local['A2']='Сахалинская область, 1-я зона • III квартал 2025 • пол 100 м², плинтус 40 м • без НДС' for r, machine_start, machine_end in [(7,8,8),(20,21,22),(34,35,36)]: local[f'B{r}']='ОТм (разряд по применяемым машинам)' if r==7: local[f'F{r}']="='Исходные данные РМ'!D17" else: local[f'F{r}']=f"=IFERROR((E{machine_start}'Исходные данные РМ'!D17+E{machine_end}'Исходные данные РМ'!D33)/(E{machine_start}+E{machine_end}),0)" local[f'I{r}']='Затраты машинистов по ГЭСН × ставка с учетом разрядов машин (3,0 и 4,0)' for row,col in [(25,'B')]:local[f'{col}{row}']='14.1.02.04-0102' for row,gF in [(14,13),(28,27),(46,45)]: local[f'G{row}']=f"=G{gF}'Исходные данные РМ'!B35" for row,gF in [(15,13),(29,27),(47,45)]: local[f'G{row}']=f"=G{gF}'Исходные данные РМ'!B36" for r in [6,7,8,9,10,11,19,20,21,22,23,24,25,*range(33,44)]: local[f'G{r}'].number_format=fmt_price local[f'F{r}'].number_format=fmt_price local[f'E{r}'].number_format=fmt_q for r in (5,18,32): for col in range(1,10): c=local.cell(r,col);c.fill=PatternFill('solid',fgColor=BLUE);c.font=Font(bold=True,color=WHITE) local.row_dimensions[r].height=28 for r in [12,13,14,15,16,26,27,28,29,30,44,45,46,47,48,50]: for col in range(1,10): c=local.cell(r,col) c.fill=PatternFill('solid',fgColor=TEAL_BG if r in (16,30,48,50) else LIGHT) c.font=Font(color=NAVY,bold=True,size=11 if r==50 else 10) local[f'G{r}'].number_format=fmt_price local.row_dimensions[r].height=26 style_sheet(local,{'A':10,'B':29,'C':69,'D':16,'E':20,'F':25,'G':26,'H':21,'I':77},freeze='E5') local['G50'].font=Font(color=TEAL,bold=True,size=15) local.print_title_rows='1:4'
style_sheet(earth,{'A':21,'B':84,'C':16,'D':21,'E':17,'F':23,'G':26,'H':18,'I':35,'J':48},freeze='D8') banner(earth,1,1,10,'ЛОКАЛЬНАЯ СМЕТА • ЗЕМЛЯНЫЕ РАБОТЫ') earth.merge_cells('A2:J2');earth['A2']='ГЭСН01-01-010-22 • Разработка грунта в отвал экскаватором с ковшом 1–1,2 м³, группа грунтов 4' earth['A2'].font=Font(bold=True,color=NAVY,size=11) earth['A4']='Объём, 1000 м³';earth['B4']=5;earth['B4'].font=Font(color=INPUT,bold=True) earth['C4']='Регион / период';earth.merge_cells('D4:G4');earth['D4']='Сахалинская область (1-я зона), III кв. 2025';earth['D4'].font=Font(color=TEAL,bold=True) earth['A5']='Норматив НР, %';earth['B5']=.93;earth['B5'].number_format='0%' earth['C5']='Норматив СП, %';earth['D5']=.46;earth['D5'].number_format='0%' for cell in ['B4','B5','D5']:earth[cell].font=Font(color=INPUT,bold=True) head(earth,7,['Код ресурса','Описание ресурса / затрат','Ед. изм.','Норма на 1000 м³','Объём, 1000 м³','Цена за ед., ₽','Стоимость, ₽','Вид затрат','Основание','Примечание']) for r,code,norm,desc,kind in [ (8,'1-100-20',9.98,'Рабочие-строители, средний разряд 2,0','ОТ'), (9,'4-100-060',21.7,'Машинисты, средний разряд 6,0','ОТм'), (10,'91.01.05-087',21.7,'Экскаватор одноковшовый дизельный гусеничный, ковш 1,0 м³','ЭМ')]: source_r=DB.execute('select row from resources where code=?',(code,)).fetchone()['row'] earth[f'A{r}']=code;earth[f'B{r}']=desc;earth[f'C{r}']='чел.-ч' if r in (8,9) else 'маш.-ч' earth[f'D{r}']=norm;earth[f'E{r}']='=B4' earth[f'F{r}']=f"='Цены Сахалин'!G{price_map[code]}" earth[f'G{r}']=f'=D{r}E{r}F{r}';earth[f'H{r}']=kind earth[f'I{r}']=f'ГЭСН01-01-010-22';earth[f'J{r}']=f'Сплит-форма: строка {source_r}' earth[f'D{r}'].font=Font(color=INPUT);earth[f'F{r}'].font=Font(color=GREEN) for col in ['F','G']:earth[f'{col}{r}'].number_format=fmt_price earth[f'D{r}'].number_format=fmt_q earth.row_dimensions[r].height=27 for r,label,formula in [ (12,'Итого прямые затраты (ОТ + ОТм + ЭМ)', '=SUM(G8:G10)'), (13,'ФОТ (ОТ + ОТм)', '=G8+G9'), (14,'Накладные расходы 93% от ФОТ', '=G13B5'), (15,'Сметная прибыль 46% от ФОТ', '=G13D5'), (16,'ПОЛНАЯ СТОИМОСТЬ ЗЕМЛЯНЫХ РАБОТ (без НДС)', '=SUM(G12,G14:G15)')]: total(earth,r,2,label,'G',r==16);earth[f'G{r}']=formula earth[f'G{r}'].number_format=fmt_price if r==16:earth[f'G{r}'].font=Font(color=TEAL,bold=True,size=15) earth.merge_cells('A19:J19');earth['A19']='Прямые затраты РИМ включают ОТ, ОТм и ЭМ отдельно: тариф эксплуатации машины в сплит-форме не включает зарплату машиниста.' earth['A19'].font=Font(color=GRAY,italic=True,size=11) earth.merge_cells('A20:J20');earth['A20']='Источник исходных объёмов и норм — исходный лист «Архив Хабаровск». Региональные цены и ФОТ пересчитаны по сплит-форме Сахалинской области.' earth['A20'].font=Font(color=GRAY,italic=True,size=11) earth.print_area='A1:J20'
style_sheet(summary,{'A':30,'B':42,'C':20,'D':20,'E':20,'F':20,'G':20,'H':20,'I':21,'J':25},freeze='C7') banner(summary,1,1,10,'ИТОГИ ПЕРЕСЧЁТА СМЕТ • САХАЛИНСКАЯ ОБЛАСТЬ') summary.merge_cells('A2:J2');summary['A2']='Сахалинская область • 1-я ценовая зона • III квартал 2025 года • ресурсно-индексный метод (РИМ)' summary['A2'].font=Font(name='Aptos',size=12,color=TEAL,bold=True) summary.merge_cells('A3:J3');summary['A3']='Цены: сплит-форма III кв. 2025; при отсутствии текущей цены — сметная цена 01.01.2022 × индекс группы.' summary['A3'].font=Font(color=GRAY,size=11) summary.merge_cells('A4:J4');summary['A4']='В исходном файле два самостоятельных расчёта. Они показаны РАЗДЕЛЬНО, без необоснованного суммирования.' summary['A4'].font=Font(color=ORANGE,bold=True) summary['A5']='СМЕТА № 1 • ПОЛ ИЗ ЛИНОЛЕУМА (100 м², плинтус 40 м)';summary.merge_cells('A5:J5');summary['A5'].font=Font(color=NAVY,bold=True,size=12) head(summary,6,['Норма','Наименование','ОТ, ₽','ОТм, ₽','ЭМ, ₽','Материалы, ₽','Прямые затраты, ₽','НР, ₽','СП, ₽','Полная стоимость, ₽']) sections=[(7,'ГЭСН11-01-011-01','Цементная стяжка 20 мм',6,7,[8,9],[10,11],12,14,15,16), (8,'ГЭСН11-01-036-01','Линолеум на клее',19,20,[21,22],[23,24,25],26,28,29,30), (9,'ГЭСН11-01-040-03','Плинтус ПВХ на саморезах',33,34,[35,36],[37,38,39,40,41,42,43],44,46,47,48)] for dest,code,desc,otr,otm,em,mat,pz,nr,sp,full in sections: summary[f'A{dest}']=code;summary[f'B{dest}']=desc for col,source_rows in [('C',[otr]),('D',[otm]),('E',em),('F',mat)]: summary[f'{col}{dest}']='='+ '+'.join(f"'Локальная смета РМ'!G{x}" for x in source_rows) for col,r in [('G',pz),('H',nr),('I',sp),('J',full)]:summary[f'{col}{dest}']=f"='Локальная смета РМ'!G{r}" summary.row_dimensions[dest].height=28 for col in 'CDEFGHIJ':summary[f'{col}{dest}'].number_format=fmt_price for col in 'CDEFGHIJ':summary[f'{col}10']=f'=SUM({col}7:{col}9)' total(summary,10,2,'ИТОГО ПО ПОЛУ (без НДС)','J',True) summary['A13']='СМЕТА № 2 • ЗЕМЛЯНЫЕ РАБОТЫ (5000 м³)';summary.merge_cells('A13:J13');summary['A13'].font=Font(color=NAVY,bold=True,size=12) head(summary,14,['Норма','Наименование','ОТ, ₽','ОТм, ₽','ЭМ, ₽','Материалы, ₽','Прямые затраты, ₽','НР, ₽','СП, ₽','Полная стоимость, ₽']) summary['A15']='ГЭСН01-01-010-22';summary['B15']='Разработка грунта, 4-я группа' for col,r in [('C',8),('D',9),('E',10),('G',12),('H',14),('I',15),('J',16)]: summary[f'{col}15']=f"='Земляные работы РИМ'!G{r}" summary['F15']=0 for col in 'CDEFGHIJ':summary[f'{col}15'].number_format=fmt_price summary.row_dimensions[15].height=28 summary['A17']='ОБЕ СМЕТЫ СОСТАВЛЕНЫ В ЦЕНАХ III КВ. 2025 • БЕЗ НДС';summary.merge_cells('A17:J17');summary['A17'].font=Font(bold=True,color=TEAL,size=12) summary['A19']='Проверить по проекту:';summary.merge_cells('A19:B19');summary['A19'].font=Font(color=ORANGE,bold=True) summary.merge_cells('C19:J19');summary['C19']='Точный состав клея для линолеума (использован код 14.1.02.04-0102) и соответствие материалов проекту.' summary['C19'].alignment=Alignment(wrap_text=True,vertical='center');summary['C19'].font=Font(color=ORANGE,size=11) summary.row_dimensions[19].height=38 summary.merge_cells('A21:J21');summary['A21']='Откуда цены: лист «Цены Сахалин» с полными кодами, индексами, ценами и номерами строк исходной сплит-формы.' summary['A21'].font=Font(color=GRAY,italic=True) summary.merge_cells('A22:J22');summary['A22']='Расход ресурсов и расчёт по трём нормам ГЭСН — листы «Ресурсная ведомость РМ» и «Локальная смета РМ».' summary['A22'].font=Font(color=GRAY,italic=True) summary.print_area='A1:J22'
style_sheet(method,{'A':30,'B':86,'C':66,'D':33},freeze='B5') banner(method,1,1,4,'ИСТОЧНИКИ, МЕТОДИКА И ДОПУЩЕНИЯ') head(method,3,['Тема','Принято / формула','Норматив / обоснование','Проверка']) notes=[ ('Регион','Сахалинская область, 1-я ценовая зона','Предоставленная пользователем сплит-форма',''), ('Уровень цен','III квартал 2025 г.','Письмо Минстроя 22.08.2025 № 49743-АЛ/09; реквизиты формы',''), ('Региональный приказ','№ 1-3.08-157/25 от 10.03.2025','Титульная часть сплит-формы',''), ('Цены ресурсов','Если актуальная H числовая — H; иначе ROUND(базовая E × индекс I, 2)','Сплит-форма: код, цена 2022, текущая цена, индекс (содержатся на листе «Цены Сахалин»)','Формулы листа цен'), ('Труд','ОТ = нормо-часы × региональная ставка среднего разряда','Сплит-форма: блок «Сметные цены на затраты труда»','По кодам 1-100-xx'), ('Труд машинистов','ОТм = нормо-часы × региональная ставка машинистов','Сплит-форма: 4-100-030, 4-100-040, 4-100-060','Разряд машин проверен'), ('Работа машин','ЭМ = машино-часы × региональная цена эксплуатации','Сплит-форма по кодам 91.*','ОТм учитывается отдельно'), ('Прямые затраты','ПЗ = ОТ + ОТм + ЭМ + М','Методика 421/пр, ресурсно-индексный метод','По каждой норме'), ('Фонд оплаты труда','ФОТ = ОТ + ОТм','Методики 812/пр и 774/пр','База начисления'), ('Пол: накладные','112% от ФОТ','812/пр — Полы, территории, приравненные к Крайнему Северу (МПРКС)',''), ('Пол: прибыль','65% от ФОТ','774/пр — Полы',''), ('Земляные: накладные','93% от ФОТ','812/пр — Земляные работы, механизированный способ, МПРКС',''), ('Земляные: прибыль','46% от ФОТ','774/пр — Земляные работы, механизированный способ',''), ('Материал клея','14.1.02.04-0102: Клей для укладки ПВХ-покрытий','В исходном файле указана только группа 14.1.02.04','УТОЧНИТЬ ПО ПРОЕКТУ'), ('Исходные объёмы','100 м² пола, 40 м плинтуса, 5000 м³ грунта','Исходная загруженная смета; объёмы не изменены','Можно менять на листах ввода'), ('Исходные нормы','ГЭСН11-01-011-01, ГЭСН11-01-036-01, ГЭСН11-01-040-03; ГЭСН01-01-010-22','Нормы из загруженного пользователем XLSX','Нормы и объёмы сохранены'), ('НДС','Не включён','Условия исходной пользовательской сметы',''), ('Взаимная независимость','Суммы полов и земляных работ не складываются в единый объект','Два самостоятельных расчёта в одном исходном файле',''), ('Отражение копии','Исходная смета ГРАНД-Сметы сохранена на скрытом листе «Архив Хабаровск»','Для сравнения, без изменения исходных сведений','Раскрыть лист при необходимости'), ('Ссылка на ФГИС ЦС','https://fgiscs.minstroyrf.ru/','Ресурсно-индексный метод',''), ('Методика 421/пр','https://www.consultant.ru/document/cons_doc_LAW_362957/','Определение сметной стоимости',''), ('Накладные 812/пр','https://base.garant.ru/400499913/','Нормативы накладных расходов',''), ('Сметная прибыль 774/пр','https://base.garant.ru/400320624/','Нормативы сметной прибыли',''), ('ГЭСН 2022','https://minstroyrf.gov.ru/trades/tsenoobrazovanie/','Минстрой РФ, сметно-нормативная база',''), ] for r,record in enumerate(notes,start=4): for col,v in enumerate(record,start=1): c=method.cell(r,col,v) c.alignment=Alignment(wrap_text=True,vertical='center') c.font=Font(name='Aptos',size=10,color=ORANGE if v=='УТОЧНИТЬ ПО ПРОЕКТУ' else DARK,bold=col==1) if r%2==0: c.fill=PatternFill('solid',fgColor='F6F9FC') if isinstance(v,str) and v.startswith('https://'): c.hyperlink=v;c.font=Font(color='0563C1',underline='single') method.row_dimensions[r].height=34 if len(record[1])>90 else 26 method.auto_filter.ref=f'A3:D{3+len(notes)}' method.print_title_rows='1:3'
book.calculation=CalcProperties(calcMode='auto',fullCalcOnLoad=True,forceFullCalc=True)
book._sheets.remove(book['Архив Хабаровск']);book.sheets.append(book['Архив Хабаровск']) book.active=0 book.save(OUT) print('CREATED',OUT,os.path.getsize(OUT)) print('PRICES',len(price_map),'MISSING', [c for c, in codes if c not in pricevalues]) for c in codes:print(c[0],pricevalues[c[0]]) PY python /mnt/data/make_estimate.py
bash -lc python - <<'PY' from openpyxl import load_workbook w=load_workbook('/mnt/data/Lokalnaia_smeta_resursnyi_metod_poly_linoleum_19324678.xlsx') s=w['Исходные данные РМ'] print(list(str(x) for x in s.merged_cells.ranges)) for r in range(31,39): print(r,[(s.cell(r,c).coordinate,s.cell(r,c).class.name) for c in range(1,7)]) PY
bash -lc python - <<'PY' from openpyxl import load_workbook w=load_workbook('/mnt/data/Lokalnaia_smeta_resursnyi_metod_poly_linoleum_19324678.xlsx') s=w['Исходные данные РМ'] print(repr(s['A35'].value)) PY sed -i "/params′A35′=/i params.unmerge_cells('A35:F35')" /mnt/data/make_estimate.py python /mnt/data/make_estimate.py | tail -28
bash -lc python - <<'PY' p='/mnt/data/make_estimate.py' s=open(p,encoding='utf-8').read().replace("book._sheets.remove(book['Архив Хабаровск']);book._sheets.append(book['Архив Хабаровск'])","archive=book['Архив Хабаровск'];book._sheets.remove(archive);book._sheets.append(archive)") open(p,'w',encoding='utf-8').write(s) PY python /mnt/data/make_estimate.py | tail -30
Created Sakhalin estimate workbook and fixed merged ranges and worksheet ordering
bash -lc python - <<'PY' from artifact_tool import SpreadsheetArtifact p='/mnt/data/Смета_Сахалин_1зона_IIIкв2025_РИМ.xlsx' try: a=SpreadsheetArtifact.load(p) print('LOADED',a.sheets()) a.recalculate() for sn,coord in [('Итоги','J10'),('Локальная смета РМ','G50'),('Земляные работы РИМ','G16')]: print(sn,coord,a.sheet(sn).cell(coord).value) a.export('/mnt/data/Смета_Сахалин_cached_trial.xlsx',overwrite=True) print('EXPORTED') except Exception as e: print('ERROR',type(e).name,str(e)[:2000]) PY
bash -lc python - <<'PY' import artifact_tool print([x for x in dir(artifact_tool) if not x.startswith('_')][:100]) PY
bash -lc python - <<'PY' import artifact_tool print([x for x in dir(artifact_tool) if any(s in x.lower() for s in ('spreadsheet','workbook','xlsx'))][:110]) PY
bash -lc python - <<'PY' import formulas, inspect print(formulas.version) print(inspect.signature(formulas.ExcelModel.loads)) print(inspect.signature(formulas.ExcelModel.calculate)) PY
bash -lc python - <<'PY' import formulas, time, os p='/mnt/data/Смета_Сахалин_1зона_IIIкв2025_РИМ.xlsx' t=time.time() try: m=formulas.ExcelModel().loads(p).finish() print('LOADED',len(m.dsp.nodes),'time',time.time()-t) sol=m.calculate() print('CALC',len(sol),'time',time.time()-t) for k,v in sol.items(): if 'G50' in k or 'J10' in k or 'G16' in k and ('ЛОКАЛЬНАЯ СМЕТА' in k or 'ЗЕМЛЯНЫЕ' in k):print(k,v) except Exception as e: print('ERROR',type(e).name,str(e)[:3000],'time',time.time()-t) PY
bash -lc cat > /mnt/data/cache_check.py <<'PY' from future import annotations import os,re,zipfile,tempfile,collections,math,shutil from lxml import etree import formulas from openpyxl import load_workbook import numpy as np path='/mnt/data/Смета_Сахалин_1зона_IIIкв2025_РИМ.xlsx' model=formulas.ExcelModel().loads(path).finish() solution=model.calculate() values={} for key,v in solution.items(): m=re.match(r"'[]]+([^']+)'!([A-Z]+\d+)$",str(key)) if not m: continue val=v.value try:val=val[0][0] except (TypeError,IndexError,KeyError):pass values[(m.group(1).upper(),m.group(2))]=val print('calculated cell values',len(values)) wb=load_workbook(path) formulacells=[] for ws in wb: for row in ws: for cell in row: if cell.data_type=='f': formulacells.append((ws.title.upper(),cell.coordinate)) missing=[c for c in formulacells if c not in values] print('formula cells',len(formulacells),'uncached',len(missing),missing[:60]) if missing: raise ValueError('Missing formula results') errors=[(key,v) for key,v in values.items() if isinstance(v,str) and v.startswith(('#REF!','#N/A','#VALUE!','#DIV/0!','#NUM!','#NAME?','#NULL!'))] print('errors',errors[:30]);assert not errors for sn,addr in [('ЛОКАЛЬНАЯ СМЕТА РМ','G50'),('ЗЕМЛЯНЫЕ РАБОТЫ РИМ','G16'),('ИТОГИ','J10')]:print('TEST',sn,addr,values[(sn,addr)]) ns={'s':'http://schemas.openxmlformats.org/spreadsheetml/2006/main','r':'http://schemas.openxmlformats.org/officeDocument/2006/relationships'} RELID='{http://schemas.openxmlformats.org/officeDocument/2006/relationships}id' P='{http://schemas.openxmlformats.org/spreadsheetml/2006/main}' with zipfile.ZipFile(path,'r') as zin: wbxml=etree.fromstring(zin.read('xl/workbook.xml')) relroot=etree.fromstring(zin.read('xl/rels/workbook.xml.rels')) relns='{http://schemas.openxmlformats.org/package/2006/relationships}' relpaths={rel.get('Id'):rel.get('Target') for rel in relroot.findall(f'{relns}Relationship')} mapping={} for sheet in wbxml.findall('.//s:sheet',ns): target=relpaths[sheet.attrib[RELID]].lstrip('/') path_in_zip=target if target.startswith('xl/') else ('xl/'+target) mapping[path_in_zip]=sheet.get('name').upper() print('MAPPING',mapping) tmp=path+'.working' cached=0 with zipfile.ZipFile(tmp,'w') as zout: for entry in zin.infolist(): data=zin.read(entry.filename) if entry.filename in mapping: sheet_name=mapping[entry.filename] root=etree.fromstring(data) for cell in root.findall('.//s:c',ns): if cell.find(f'{P}f') is None:continue key=(sheet_name,cell.get('r')) if key not in values:raise ValueError(('Cannot calculate',key)) val=values[key] v=cell.find(f'{P}v') if v is None: v=etree.SubElement(cell,f'{P}v') if isinstance(val,(bool,np.bool)): cell.set('t','b');v.text='1' if val else '0' elif isinstance(val,(int,float,np.integer,np.floating)): x=float(val) if not math.isfinite(x):raise ValueError(('nonfinite',key,x)) cell.attrib.pop('t',None) v.text=format(x,'.16g') elif val is None: cell.set('t','str');v.text='' else: text=str(val) if text.startswith(('#REF!','#N/A','#VALUE!','#DIV/0!','#NUM!','#NAME?','#NULL!')):raise ValueError(('Error',key,text)) cell.set('t','str');v.text=text cached+=1 data=etree.tostring(root,encoding='utf-8',xml_declaration=False) zout.writestr(entry,data) assert cached==len(formulacells),(cached,len(formulacells)) os.replace(tmp,path) print('cached',cached,'size',os.path.getsize(path)) wbf=load_workbook(path,data_only=False) wbc=load_workbook(path,data_only=True) for sheet,coord in [('Итоги','J10'),('Итоги','J15'),('Локальная смета РМ','G50'),('Земляные работы РИМ','G16'),('Цены Сахалин','G14'),('Локальная смета РМ','C6')]: print('VALIDATE',sheet,coord,'formula',wbf[sheet][coord].value,'cached',wbc[sheet][coord].value) for ws in wbc: for row in ws: for cell in row: if cell.data_type=='e':raise ValueError((ws.title,cell.coordinate,cell.value)) assert abs(wbc['Итоги']['J10'].value - 263751.1902704)<.01 assert abs(wbc['Итоги']['J15'].value - 667581.50869)<.01 assert wbf['Архив Хабаровск'].sheet_state=='hidden' assert wbc['Исходные данные РМ']['B4'].value=='Сахалинская область (1-я ценовая зона)' print('ALL VERIFIED') PY python /mnt/data/cache_check.py
bash -lc python - <<'PY' from openpyxl import load_workbook p='/mnt/data/Смета_Сахалин_1зона_IIIкв2025_РИМ.xlsx' w=load_workbook(p,data_only=True) for s in w: print(s.title,s.sheet_state,s.max_row,s.max_column) q=w['Итоги'] print('\nСТОИМОСТИ ПО ПОЗИЦИЯМ:') for row in (7,8,9,10,15): print(row, [q.cell(row,j).value for j in range(1,11)]) for r in [12,13,14,15,16,26,27,28,29,30,44,45,46,47,48,50]: print('Floor',r,w['Локальная смета РМ'][f'G{r}'].value) print('grand',q['J10'].value,q['J15'].value) PY
Tested artifact_tool Excel formulas, patched caches, and validated rendered outputs
Worked for 15m 53s
Готово. Пересчитала смету для Сахалинской области (1-я ценовая зона) на III квартал 2025 года по приложенной сплит-форме ресурсно-индексным методом (РИМ).
Скачать пересчитанную смету в Excel
В исходном Excel-файле обнаружены две отдельные сметы, поэтому я пересчитала обе.
| Вид работ | Полная сметная стоимость |
|---|---|
| Устройство пола из линолеума, 100 м², с плинтусом | 263 751,19 ₽ |
| Разработка грунта экскаватором, 5 000 м³ | 667 581,51 ₽ |
Суммы указаны без НДС. Две самостоятельные сметы не объединялись в одну стоимость.
В Excel включены подробные расчёты по нормам ГЭСН, затраты на оплату труда рабочих и машинистов, эксплуатацию машин, материалы, накладные расходы и сметную прибыль.
На отдельном листе «Цены Сахалин» указаны коды ресурсов, цены, индексы и номера строк сплит-формы. Если текущая цена отсутствовала, она рассчитана умножением базовой цены на соответствующий индекс.
Важно: для линолеума принят клей с кодом 14.1.02.04-0102. Его соответствие проекту нужно проверить. Исходная смета для Хабаровска сохранена в файле на скрытом листе для сравнения.