- Code\rebar_pipeline, review_pipeline, pier-program을 정션에서 실제 폴더로 전환 - Code\rhino_mcp 및 CLAUDE_rhino배경.md 추가, .mcp.json은 PYTHONPATH로 새 위치 사용 - pier-program 내부 .git 제거(이력은 backup\pier-program_20261028.bundle에 보관) - worktree 가이드 반영(CLAUDE.local.md, HANDOFF.md) - D:\Rhino-MCP 경로 참조를 새 위치로 치환 Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
64 lines
3.0 KiB
Python
64 lines
3.0 KiB
Python
"""Assemble only end-balanced, validated tie rows from local review files.
|
|
|
|
Never writes a saved job. Publication must run the independent geometry gate.
|
|
"""
|
|
import argparse
|
|
import copy
|
|
import json
|
|
import sys
|
|
from pathlib import Path
|
|
root = Path(__file__).resolve().parents[1]
|
|
sys.path.insert(0, str(root/'src/backend' if (root/'src/backend').is_dir() else root))
|
|
from tie_end_spacing import envelope_margins
|
|
|
|
|
|
def prepare(paths):
|
|
reviews = [json.load(open(path)) for path in paths]
|
|
result = copy.deepcopy(reviews[0])
|
|
selected = {}
|
|
rejected = []
|
|
for review in reviews:
|
|
plan = review['result']
|
|
ids = set(plan['quantityDistributionReview']['group_ids'])
|
|
rejected.extend(plan['quantityDistributionReview'].get('failures', []))
|
|
for group in plan['rebar_groups']:
|
|
if group.get('id') not in ids:
|
|
continue
|
|
rhythm = group['quantity_distribution'].get('rhythm', {})
|
|
lengths = rhythm.get('side_lengths_m', [])
|
|
counts = rhythm.get('side_counts', [])
|
|
margins = rhythm.get('end_margins_m', [])
|
|
pitch = rhythm.get('source_cycle_pitch_m', 0)
|
|
errors = [abs(envelope_margins(side)[0]-envelope_margins(side)[1]) for side in margins]
|
|
reason = None
|
|
if len(lengths)!=2 or len(counts)!=2 or len(errors)!=2 or not pitch:
|
|
reason = 'missing physical-side balance proof'
|
|
elif (rhythm.get('end_spacing_priority') == 'equal_ends' and rhythm.get('end_balance_required',True)
|
|
and max(errors)>rhythm.get('end_balance_tolerance_m',1e-6)+1e-8):
|
|
reason = 'straight body end residual requires explicit acceptance'
|
|
elif abs(lengths[0]-lengths[1])>=rhythm['repeat_m']-1e-6 and counts[0]==counts[1]:
|
|
reason = 'longer side still has the same quantity'
|
|
if reason:
|
|
rejected.append({'block':group['rebar_block'],'mark':group['mark'],
|
|
'error':reason,'side_end_imbalance_m':errors})
|
|
continue
|
|
selected[group['id']] = group
|
|
plan = result['result']
|
|
plan['rebar_groups'] = [g for g in plan['rebar_groups'] if g.get('id') not in selected]+list(selected.values())
|
|
plan['quantityDistributionReview'] = {'group_ids':sorted(selected),'failures':rejected,
|
|
'engineering_approved':False,'scope':'body ties only; wing drawing errors preserved'}
|
|
result['failures'] = rejected
|
|
return result
|
|
|
|
|
|
if __name__=='__main__':
|
|
parser=argparse.ArgumentParser(description=__doc__)
|
|
parser.add_argument('reviews',nargs='+')
|
|
parser.add_argument('--output',required=True)
|
|
args=parser.parse_args()
|
|
result=prepare(args.reviews)
|
|
with open(args.output,'x') as stream:
|
|
json.dump(result,stream,ensure_ascii=False,allow_nan=False)
|
|
print(json.dumps({'eligible':len(result['result']['quantityDistributionReview']['group_ids']),
|
|
'rejected':result['failures']},ensure_ascii=False))
|