Files
Pier_v1/RAG/design-model-box-sample_2/tools/publish_quantity_review.py
T
ahnyongjooandClaude Sonnet 5.5 964a841de1 chore(rebar): Pier_T_rebar 작업 폴더 초기 구성 및 외부 의존 파일 통합
- 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>
2026-10-28 11:20:18 +09:00

74 lines
4.1 KiB
Python

"""Publish independently rechecked rows, preserving unrelated saved geometry."""
import argparse
import copy
import json
import os
from pathlib import Path
import sys
import tempfile
import time
root=Path(__file__).resolve().parents[1]
sys.path.insert(0,str(root/'src/backend' if (root/'src/backend').is_dir() else root))
from quantity_row_identity import matching_rows, same_legacy_row
def run(args):
import server
from quantity_review_validation import validate
server.require_modeling_rules()
target=args.job.resolve();raw=target.read_bytes();job=json.loads(raw)
if job.get('status')=='running':raise ValueError('active job preserved')
plan=job['result'];review=json.loads(args.review.read_text());candidate=review['result']
# Re-evaluate the same source-compatible shared contact definition on an
# older review. This never changes vertices or accepts changed hosts.
from haunch_contact_surface import bind as bind_haunch_contact
bind_haunch_contact(candidate['concrete_components'],server.component_surface_z_at_xy)
ids=set(candidate['quantityDistributionReview']['group_ids'])
groups=[g for g in candidate['rebar_groups'] if g.get('id') in ids]
if not groups:raise ValueError('no validated rows to publish')
if plan['concrete_components']!=candidate['concrete_components']:
raise ValueError('host geometry changed since generation')
# Merge only incoming rows into the latest plan. Unrelated edits are
# preserved; each incoming row is revalidated against current targets.
replacements=[];remove=set()
for g in groups:
validate(plan,g,node=args.node)
matches=matching_rows(plan['rebar_groups'],g)
for old in matches:remove.add(old.get('id'))
replacements.append({'mark':g['mark'],'block':g['rebar_block'],'count':g['count'],
'replaced_ids':[old.get('id') for old in matches]})
result=copy.deepcopy(plan)
result['rebar_groups']=[g for g in plan['rebar_groups'] if g.get('id') not in remove]+groups
evidence=result.setdefault('drawingEvidence',{})
if all(g.get('quantity_distribution',{}).get('preserve_cycle_rhythm') for g in groups):
evidence['quantity_rhythm_authorization']={**review['authorization'],'group_ids':sorted(ids)}
# Preserve other explicitly authorized row policies on partial updates.
marks=sorted({g['mark'] for g in result['rebar_groups']
if g.get('quantity_distribution',{}).get('explicit_user_authorization')
and not g.get('quantity_distribution',{}).get('preserve_cycle_rhythm')
and not g.get('source_body_tie_binding',{}).get('reference_rule')})
if marks:evidence['quantity_distribution_authorization']={**review['authorization'],'marks':marks,
'scope':'source N on actual contact positions; not literal drawing station reconstruction'}
result['drawingEvidence'].setdefault('preview_geometry',{})['rebar_groups']=copy.deepcopy(result['rebar_groups'])
result['quantityDistributionReview']=copy.deepcopy(candidate['quantityDistributionReview'])
result['quantityDistributionReview']['replacements']=replacements
result['ready_for_geometry']=False
job.update(result=result,partialResult={'progressivePlan':copy.deepcopy(result)},geometryRevision=time.time_ns())
if target.read_bytes()!=raw:raise ValueError('concurrent job update')
backup=target.with_name(target.stem+f'.before-checked-quantity-{time.time_ns()}.json')
with backup.open('xb') as stream:stream.write(raw)
with tempfile.NamedTemporaryFile(mode='w',dir=target.parent,delete=False) as stream:
json.dump(job,stream,ensure_ascii=False,allow_nan=False);temporary=stream.name
if target.read_bytes()!=raw:raise ValueError('concurrent job update before publication')
os.replace(temporary,target)
print(json.dumps({'saved':True,'replacements':replacements,'backup':str(backup)},ensure_ascii=False))
if __name__=='__main__':
parser=argparse.ArgumentParser(description=__doc__)
parser.add_argument('job',type=Path);parser.add_argument('review',type=Path)
parser.add_argument('--node',required=True)
run(parser.parse_args())