Flow 작성
Flow는 module top level의 declarative object입니다. 한 .py Flow module은 정확히 flow = px.Flow("flow_key", ...) 하나를 export합니다. 계산 body는 target 함수에 두고, Flow source에는 input, Node placement, connection, result와 선택적인 presentation membership을 명시합니다.
현재 Component 경계
현재 @px.component는 process-local companion metadata와 Python annotation에서 읽은 port shape를 등록할 뿐 installed release를 resolve하거나 durable ComponentRef를 만들지 않습니다. production은 새 third-party Component placement를 제공하지 않고 저장돼 있던 exact target을 read-only unsupported로 보존합니다. current internal/test resolver primitive는 live px.Client로 exact ref를 저장할 수 있지만 production third-party 절차가 아닙니다. 같은 exact ProjectScope data-path isolation은 구현됐지만 primitive를 제품에 노출하는 일은 Project authorization과 execution-trust closure가 닫힌 post-closure successor semantics입니다. 현재 V1의 기존 presentation·settings wire는 호환을 위해 보존합니다. 후속 V2 Canvas는 package UI를 마운트하지 않고 host-owned component.declarative@2만 사용하며, settings는 component-declared-settings@1로 선언당 최대 8개까지 받고 host control registry가 그립니다. rich surface는 RFC-016이 소유하는 component-rich-surface-declaration@1 범위이고, bridge는 Node context 읽기와 settings 제안까지만 허용합니다.
기본 Flow
from pipelinexlab import px
from structural_checks.functions import girder_resistance
flow = px.Flow(
"girder_review",
label="거더 검토",
description="거더 저항을 검토합니다.",
)
span = flow.input("span", float, unit="m", description="순경간입니다.")
design_cases = flow.input(
"design_cases",
list[float],
description="검토할 설계 하중 case입니다.",
)
resistance_check = flow.node("resistance_check", girder_resistance)
flow.connect(span, resistance_check.inputs.span)
flow.connect(design_cases, resistance_check.inputs.design_cases)
flow.result(
"resistance_summary",
resistance_check.results.resistance_summary,
)
flow.result(
"governing_utilization",
resistance_check.results.governing_utilization,
)2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
"girder_review"는 stableflowKey입니다.flow.input(...)은 Flow-level source handle을 반환합니다.resistance_check는 이 Flow 안에서 고유한nodeKey입니다.girder_resistance는@px.function으로 등록된 reusable target입니다.node.inputs.<portKey>는 connection destination입니다.node.results.<portKey>는 다음 connection 또는 Flow result의 source입니다.
@px.function은 Function definition만 등록합니다. 위 flow.node(...)가 resistance_check Function Reference Node를 실제 Canvas에 배치합니다. decorator와 import는 Node를 만들거나 계산을 실행하지 않습니다.
여러 Node 연결
flow = px.Flow(
"bridge_review",
label="교량 검토",
description="하중을 조합한 뒤 거더 저항을 검토합니다.",
)
span = flow.input("span", float, unit="m", description="순경간입니다.")
load_cases = flow.input(
"load_cases",
list[float],
description="조합할 설계 하중 case입니다.",
)
combinations = flow.node("load_combinations", combine_loads)
resistance = flow.node("resistance_check", girder_resistance)
flow.connect(load_cases, combinations.inputs.load_cases)
flow.connect(span, resistance.inputs.span)
flow.connect(
combinations.results.design_cases,
resistance.inputs.design_cases,
)
flow.result(
"governing_utilization",
resistance.results.governing_utilization,
)2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
모든 value 관계가 flow.connect(source, destination)에 드러납니다. flow.node(...)에는 input binding을 arbitrary keyword로 전달하지 않습니다.
Node key 규칙
- Flow 안에서 고유한 stable
lower_snake_case입니다. - target 이름과 독립적으로 배치 목적을 나타냅니다.
- 호출 순서, source line, 변수명과 Canvas 위치에서 파생하지 않습니다.
- 같은 reusable target을 여러 번 배치할 때 의미를 구분합니다.
- key rename은 연결 영향 preview와 원자적 Apply가 필요한 breaking change입니다.
uls = flow.node("uls_check", girder_resistance)
sls = flow.node("sls_check", girder_resistance)2
이 예에서 Function definition은 하나이고 실제 Canvas Node는 둘입니다.
flow.node(...)의 현재 경로와 successor 경로
현재 production에서 새 Node를 배치하는 공개 경로는 project 또는 package의 @px.function target입니다. flow.node(node_key, function_target)이 FunctionRef를 저장하고 Canvas에 Function Reference Node를 만듭니다.
third-party Component는 다릅니다. 현재 reader는 이미 저장돼 있던 exact ComponentRef와 Node·connection만 read-only unsupported로 보존하며, 새 install·resolve·placement·activation은 production caller에 제공하지 않습니다. current internal/test flow.node(...) resolver primitive는 live px.Client로 product.component.resolve를 호출해 exact ComponentRef를 저장할 수 있지만, 이 primitive의 존재는 production third-party admission이 아닙니다. Project authorization과 execution-trust closure가 모두 닫힌 post-closure successor에서만 같은 spelling과 resolver를 사용자 제품 경로에 노출합니다.
| 시점과 target | 저장 variant | 배치 결과 |
|---|---|---|
current · project 또는 package의 @px.function metadata | FunctionRef | Function Reference Node |
| current · 이미 저장된 exact Component target | 기존 ComponentRef 보존 | read-only unsupported; 신규 배치 없음 |
post-closure successor · admitted @px.component target | 새 exact ComponentRef | host-owned Component Node |
px.Flow object는 flow.node(...) target resolution에서 거부합니다. imported exported Flow는 flow.subflow(...)로 배치합니다.
Project가 소유하는 Function Node
@px.function(
key="custom_section_check",
version="1.0.0",
description="검토 단면의 사용률을 계산합니다.",
)
def custom_section_check(section: list[float]) -> SectionCheckResults:
return run_check(section)
check = flow.node("section_check", custom_section_check)
flow.connect(section, check.inputs.section)
flow.result(
"utilization",
check.results.utilization,
description="검토 단면의 사용률입니다.",
)2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
Project source의 Function은 Definitions → Functions에서 편집합니다. Node는 exact Function key와 version을 참조하고 source 영역을 읽기 전용으로 보여 줍니다. Function을 바꾸면 사용처 영향을 함께 검토하며 특정 Node만 다르게 만들 때는 Function을 복제·분기합니다.
post-closure Component placement 계약 예제
contract_only
아래 코드는 current production의 설치·배치 절차가 아닙니다. current internal/test resolver primitive와 혼동하지 마세요. exact ProjectScope data-path isolation을 유지하고 Project authorization과 execution-trust closure를 닫은 뒤 같은 Project에 admitted된 exact release를 배치하는 제품 authoring shape만 보여 줍니다.
이 예제는 contract_only이며 현재 SDK에서 실행할 수 없습니다.
from midas_civil.components import model_viewer
view = flow.node(
"model_view",
model_viewer,
)
flow.connect(model, view.inputs.model)
flow.result(
"selection",
view.results.selection,
description="모델 화면에서 선택한 부재입니다.",
)2
3
4
5
6
7
8
9
10
11
12
13
model_viewer는 import 시점에는 Component companion target입니다. current internal/test primitive는 live px.Client로 admitted exact release를 resolve하고 새 exact ComponentRef를 저장할 수 있습니다. 그러나 caller/release policy가 이 코드를 production third-party placement로 노출하지 않습니다. 제품 경로는 두 P0 closure 뒤에만 열며, 그전에는 이미 저장된 exact target만 reader가 unsupported로 보존합니다. settings={...} member는 기존 V1 instance 값을 보존할 때만 사용합니다. 후속 V2는 settings를 component-declared-settings@1로 선언당 최대 8개까지 받고 host control registry가 그립니다. input/result 관계는 반드시 flow.connect(...)와 flow.result(...)로 작성합니다.
같은 Flow Group과 외부 Flow
두 구조는 source ownership과 편집 경계가 다릅니다.
flow.group(...): presentation membership
flow.group(
"member_check",
members=[combinations, resistance],
label="Member check",
)2
3
4
5
- Group은 같은 Flow의 기존 Node를 시각적으로 묶습니다.
- Group은 Node나 별도 Flow가 아닙니다.
- member Node, connection, public interface와 실행 unit은 그대로 유지됩니다.
- group/ungroup은 presentation membership만 바꿉니다.
flow.subflow(...): external Flow
별도 .py module이 export한 flow object는 일반 Python import 뒤 flow.subflow(...)로 배치합니다.
from structural_checks.flows.section_properties import flow as section_properties_flow
section_properties = flow.subflow(
"section_properties",
section_properties_flow,
)
flow.connect(geometry, section_properties.inputs.geometry)
flow.result("area", section_properties.results.area)2
3
4
5
6
7
8
9
external Flow는 caller에서 읽기 전용인 nested Flow입니다. 내부를 수정할 때는 Edit source Flow로 owning module을 엽니다. imported Flow를 flow.node(...) target으로 전달하지 않습니다.
Flow source에 두는 것
flow = px.Flow(...)flow.input(...)Flow boundaryflow.node(...)current Function Node placement; Component는 post-closure successor 계약flow.connect(...)result → input 연결flow.result(...)public result- 같은 Flow의
flow.group(...)presentation membership - external Flow의
flow.subflow(...) - Flow metadata를 위한 명시적 literal/config
복잡한 Flow도 큰 decorated function으로 감싸지 않고 위 선언을 top level에 유지합니다.
실행 코드를 둘 위치
실제 계산, file/network 접근과 runtime-dependent control flow는 target body에 둡니다.
- Node에서 실행할 Python 계산은 explicit stable key가 있는
@px.function - decorator 없는
def는 Function·Component body 내부 helper - Component companion target은
@px.component; current에는 local metadata 등록과 저장된 exact ref의 read-only 복구만 제공하고, post-closure V2 Canvas projection은 host-ownedcomponent.declarative@2만 사용하며 rich surface는 RFC-016의component-rich-surface-declaration@1이 소유
Flow composition에는 runtime 값에 따라 Node 수가 바뀌는 if, while, for, dynamic registry lookup, eval, reflection과 동적 Node key 생성을 두지 않습니다.
Flow-level port와 Node-level port
| 범위 | input | result |
|---|---|---|
| Node · current | target의 일반 parameter annotation | target px.Results의 annotated field |
| Node · current metadata marker | px.input(...) | px.result(...) |
| Flow | flow.input("key", ValueType, ...) | flow.result("key", source) |
AI와 다른 Flow는 기본적으로 connection=True인 Flow-level port를 먼저 사용합니다. Node-level 주소에는 flowKey/nodeKey/portKey, Flow-level 주소에는 flowKey/portKey를 사용합니다.
Collection 전체 처리와 여러 Run
현재 Flow input이 list[T]이면 compatible collection 전체를 값 하나로 전달하고 target body가 이를 공학 의미에 맞게 처리합니다. planned px.Table[RecordType]과 @px.record는 별도 contract_only rich type 계약이며 현재 facade에서 실행할 수 있는 문법이 아닙니다.
@px.function(
key="check_cases",
version="1.0.0",
description="Checks all rows in one typed case table.",
)
def check_cases(cases: list[float]) -> CaseResults:
...2
3
4
5
6
7
여러 scalar input set을 계산할 때는 batch caller가 같은 saved Flow revision에 각 input set을 명시적으로 제출합니다. authoring Flow에 행별 Node를 자동 생성하지 않습니다.
다중 Result 사용
Node의 result는 이름으로 선택합니다.
check = flow.node("resistance_check", girder_resistance)
flow.connect(span, check.inputs.span)
flow.connect(design_cases, check.inputs.design_cases)
flow.result("resistance_summary", check.results.resistance_summary)
flow.result(
"governing_utilization",
check.results.governing_utilization,
)2
3
4
5
6
7
8
Flow는 px.Results를 반환하는 함수가 아닙니다. public result마다 flow.result("result_key", source)를 선언합니다.
SDK-linked source-safe 편집
Studio나 AI가 SDK-linked Flow를 바꿀 때는 semantic command, source diff, Apply와 declaration-only rematerialization 순서로 source와 Canvas 의미를 맞춥니다. visual edit를 source-safe action으로 표현할 수 없으면 사용자가 PXFLOW-native 사본을 만든 뒤 적용합니다.
Flow 검증 체크리스트
- [ ] module이 top-level
flowobject 하나를 export합니다. - [ ] 모든 Node가 explicit stable
nodeKey를 가집니다. - [ ] 모든 Python Node target은 explicit stable key를 가진
@px.function입니다. - [ ] decorator 없는 함수는 target body 내부 helper로만 사용합니다.
- [ ] 현재 imported
@px.component는 companion metadata만 소유하며 신규 third-party install·placement·activation 경로로 사용하지 않습니다. - [ ] 이미 저장된 exact
ComponentRef는 production Canvas에서 read-onlyunsupported로 보존합니다. - [ ] 후속 V2 Component는 host-owned
component.declarative@2만 사용하고 settings를component-declared-settings@1로 선언당 최대 8개까지 받고 host control registry가 그립니다. - [ ] input binding을
flow.node(...)keyword로 숨기지 않았습니다. - [ ] 모든 connection이
flow.connect(...)에 명시되어 있습니다. - [ ] required input이 연결되었거나 valid default를 가집니다.
- [ ] 모든 connection의 type, shape와 unit이 호환됩니다.
- [ ] 모든 public result를
flow.result(...)로 선언했습니다. - [ ] Group은 같은 Flow의 presentation membership만 바꿉니다.
- [ ] external Flow는 exported object를 import해
flow.subflow(...)로 배치했습니다. - [ ] 계산과 runtime-dependent control flow는 target body에 있습니다.
- [ ] Table은 하나의 typed 값이고 반복 실행은 별도 Batch Run입니다.