iris-pex-embedded-python 4.1.0b1__py3-none-any.whl → 4.1.1b2__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (35) hide show
  1. iop/ai/__init__.py +9 -0
  2. iop/ai/guidance/instructions.md +72 -0
  3. iop/ai/guidance/testing.md +48 -0
  4. iop/ai/installer.py +186 -0
  5. iop/ai/skills/build-iop-app/SKILL.md +54 -0
  6. iop/ai/skills/build-iop-app/references/agent-guidance.md +69 -0
  7. iop/ai/skills/build-iop-app/references/cookbooks/add-business-operation.md +130 -0
  8. iop/ai/skills/build-iop-app/references/cookbooks/add-business-process.md +115 -0
  9. iop/ai/skills/build-iop-app/references/cookbooks/add-polling-service.md +100 -0
  10. iop/ai/skills/build-iop-app/references/cookbooks/code-index.md +104 -0
  11. iop/ai/skills/build-iop-app/references/cookbooks/fhir-submission-python-client.md +83 -0
  12. iop/ai/skills/build-iop-app/references/cookbooks/hello-world-production.md +103 -0
  13. iop/ai/skills/build-iop-app/references/cookbooks/hl7v2-native-input.md +70 -0
  14. iop/ai/skills/build-iop-app/references/cookbooks/hl7v2-to-fhir-with-fhir-converter.md +82 -0
  15. iop/ai/skills/build-iop-app/references/cookbooks/index.md +89 -0
  16. iop/ai/skills/build-iop-app/references/cookbooks/ingestion-pipeline.md +103 -0
  17. iop/ai/skills/build-iop-app/references/cookbooks/production-settings-and-targets.md +94 -0
  18. iop/ai/skills/build-iop-app/references/cookbooks/remote-migration.md +72 -0
  19. iop/ai/skills/build-iop-app/references/getting-started/register-component.md +277 -0
  20. iop/ai/skills/build-iop-app/references/healthcare-ai-coding.md +204 -0
  21. iop/ai/skills/build-iop-app/references/instructions.md +27 -0
  22. iop/ai/skills/build-iop-app/references/production-change-workflow.md +192 -0
  23. iop/ai/skills/validate-iop-app/SKILL.md +44 -0
  24. iop/ai/skills/validate-iop-app/references/testing.md +45 -0
  25. iop/cli/main.py +17 -0
  26. iop/cli/parser.py +32 -2
  27. iop/cli/types.py +3 -0
  28. iop/cls/IOP/Common.cls +32 -30
  29. iop/components/async_request.py +1 -2
  30. {iris_pex_embedded_python-4.1.0b1.dist-info → iris_pex_embedded_python-4.1.1b2.dist-info}/METADATA +38 -2
  31. {iris_pex_embedded_python-4.1.0b1.dist-info → iris_pex_embedded_python-4.1.1b2.dist-info}/RECORD +35 -11
  32. {iris_pex_embedded_python-4.1.0b1.dist-info → iris_pex_embedded_python-4.1.1b2.dist-info}/WHEEL +0 -0
  33. {iris_pex_embedded_python-4.1.0b1.dist-info → iris_pex_embedded_python-4.1.1b2.dist-info}/entry_points.txt +0 -0
  34. {iris_pex_embedded_python-4.1.0b1.dist-info → iris_pex_embedded_python-4.1.1b2.dist-info}/licenses/LICENSE +0 -0
  35. {iris_pex_embedded_python-4.1.0b1.dist-info → iris_pex_embedded_python-4.1.1b2.dist-info}/top_level.txt +0 -0
iop/ai/__init__.py ADDED
@@ -0,0 +1,9 @@
1
+ """Packaged AI guidance and project installer for IoP applications."""
2
+
3
+ from .installer import AgentGuidanceConflictError, InstallResult, install_agent_guidance
4
+
5
+ __all__ = [
6
+ "AgentGuidanceConflictError",
7
+ "InstallResult",
8
+ "install_agent_guidance",
9
+ ]
@@ -0,0 +1,72 @@
1
+ # IoP Application Guidance
2
+
3
+ This repository contains an IoP application. IoP means Interoperability On
4
+ Python: a Python-first framework for building InterSystems IRIS and Health
5
+ Connect interoperability productions.
6
+
7
+ ## Start With The Application
8
+
9
+ Before a major change, establish the business goal, inbound and outbound
10
+ systems, protocols or standards, routing and transformation behavior, runtime
11
+ constraints, and acceptance criteria. Infer facts from the repository before
12
+ asking the user.
13
+
14
+ Read the project README, `settings.py`, the production graph, message and
15
+ component modules, tests, fixtures, sample payloads, dependency files, and
16
+ available Docker or Compose lifecycle. Use the bundled `build-iop-app` skill
17
+ for implementation workflows and `validate-iop-app` for verification.
18
+
19
+ ## IoP Rules
20
+
21
+ - Prefer a Python `Production` exported through `PRODUCTIONS`.
22
+ - Declare topology with `prod.service(...)`, `prod.process(...)`,
23
+ `prod.operation(...)`, and graph connections.
24
+ - Declare configurable outbound routes with `target()` and connect them through
25
+ the production graph.
26
+ - Communicate between production components through messages and configured
27
+ targets. Do not instantiate another production component directly.
28
+ - Use `on_init()` for startup and `on_tear_down()` for cleanup. Do not put
29
+ component startup logic in `__init__()`.
30
+ - Use `@dataclass` on regular `Message` classes. Do not add `@dataclass` to
31
+ `PydanticMessage` classes.
32
+ - Use `PersistentMessage` only when IRIS requires a native persistent message
33
+ body.
34
+ - Prefer typed one-argument handlers or `@handler(MessageType)`. Use
35
+ `on_message(self, request)` as a simple fallback.
36
+ - Avoid raw `CLASSES` entries for components already declared in a production
37
+ graph. Keep them for standalone bindings and legacy migrations.
38
+ - Keep executable migration examples behind `if __name__ == "__main__":` so
39
+ `PRODUCTIONS` can be imported safely.
40
+
41
+ ## Project Imports
42
+
43
+ Treat the directory containing `settings.py` as the project import root. Use
44
+ modules reachable relative to that file. Fix package layout or imports instead
45
+ of modifying `PYTHONPATH`, `os.environ["PYTHONPATH"]`, or global `sys.path` in
46
+ application code.
47
+
48
+ ## Production Design
49
+
50
+ - Business Services are inbound entry points and triggers. A polling service
51
+ owns acquisition from the source it polls, including an HTTP, file, or
52
+ database read, and emits a data-bearing message.
53
+ - Business Processes own routing, decisions, transformations, and orchestration.
54
+ - Business Operations isolate destination side effects such as persistence,
55
+ file writes, submissions, and API calls made after processing. An API lookup
56
+ requested by a process is also an operation; acquisition by a polling service
57
+ from its configured source remains service work.
58
+ - Model a complete source-to-destination ingestion flow as `service -> process
59
+ -> operation` by default. Omit the process only for a genuinely trivial
60
+ pass-through and state why no orchestration boundary is needed.
61
+ - Prefer native IRIS or Health Connect components for established healthcare
62
+ standards and transports, then use Python for application-specific logic.
63
+
64
+ For an existing or shared IRIS production, inspect and plan changes before
65
+ mutation. Use the conservative plan, review, apply, verify, and backup workflow;
66
+ do not treat an imported graph as a lossless reconstruction of deployed intent.
67
+
68
+ ## Completion
69
+
70
+ Add or update focused tests, declare direct dependencies, and preserve working
71
+ project lifecycle automation when behavior changes. Run the `validate-iop-app`
72
+ skill and report commands, results, and remaining runtime verification clearly.
@@ -0,0 +1,48 @@
1
+ # Testing IoP Applications
2
+
3
+ Run the fastest relevant checks first from the project root.
4
+
5
+ ## Local Checks
6
+
7
+ 1. Run the project's Python tests:
8
+
9
+ ```bash
10
+ python -m pytest
11
+ ```
12
+
13
+ 2. Validate the migration without writing to IRIS:
14
+
15
+ ```bash
16
+ iop --migrate settings.py --dry-run --strict-production-validation
17
+ ```
18
+
19
+ Resolve import errors through package layout and imports, not `PYTHONPATH`
20
+ changes in application code.
21
+
22
+ ## Runtime Checks
23
+
24
+ Use a repository-owned disposable Docker or Compose environment for runtime
25
+ verification when it is available. Only mutate a shared, remote, or otherwise
26
+ non-disposable IRIS environment when the user requests it and the target is
27
+ known. Verify at least one observable result:
28
+
29
+ - production and component status;
30
+ - expected output file, API request, database write, or FHIR resource;
31
+ - production logs without blocking errors;
32
+ - Message Viewer flow or queue state.
33
+
34
+ Container health is not production health. Confirm initialization and migration
35
+ completed, use `iop --status` to verify the intended production is running, then
36
+ trigger or wait for a Business Service and inspect an actual destination effect.
37
+ Prefer `iop` commands for migration, start, status, logs, and queues. Do not use
38
+ an ObjectScript terminal when the IoP CLI supports the operation. Automated
39
+ checks must return control: use `iop --start <production-name> --detach` and a
40
+ finite log snapshot such as `iop --log 50`. Bare `--start` and bare `--log`
41
+ stream logs until interrupted.
42
+
43
+ Do not use `iop --test` as the normal way to test a Business Service. Use the
44
+ production runtime or director path so deployed settings, targets, and runtime
45
+ context are active.
46
+
47
+ For shared or brownfield productions, use a reviewed production change plan and
48
+ backup rather than full registration by default.
iop/ai/installer.py ADDED
@@ -0,0 +1,186 @@
1
+ from __future__ import annotations
2
+
3
+ from collections.abc import Iterable
4
+ from dataclasses import dataclass
5
+ from importlib.resources import files
6
+ from pathlib import Path
7
+
8
+ AGENTS = ("codex", "claude", "gemini")
9
+ MANAGED_START = "<!-- iop-agent-guidance:start -->"
10
+ MANAGED_END = "<!-- iop-agent-guidance:end -->"
11
+
12
+ _AGENT_PATHS = {
13
+ "codex": ("AGENTS.md", ".codex/skills"),
14
+ "claude": ("CLAUDE.md", ".claude/skills"),
15
+ "gemini": ("GEMINI.md", ".gemini/skills"),
16
+ }
17
+
18
+ _SKILL_DESCRIPTIONS = {
19
+ "build-iop-app": (
20
+ "Build or modify an IoP application using production graphs, messages, "
21
+ "components, tests, and all applicable bundled cookbooks. Use for new or "
22
+ "changed Business Services, Business Processes, Business Operations, "
23
+ "routes, settings, healthcare flows, or complete productions."
24
+ ),
25
+ "validate-iop-app": (
26
+ "Validate an IoP application with unit tests, strict migration dry-run, "
27
+ "and container-backed IRIS runtime checks when available. Use after changing "
28
+ "messages, components, production topology, settings, migration files, "
29
+ "container lifecycle, or runtime behavior."
30
+ ),
31
+ }
32
+
33
+
34
+ class AgentGuidanceConflictError(ValueError):
35
+ """Raised when installed framework-owned guidance would be overwritten."""
36
+
37
+ def __init__(self, paths: Iterable[Path]):
38
+ self.paths = tuple(paths)
39
+ joined = ", ".join(str(path) for path in self.paths)
40
+ super().__init__(
41
+ "agent guidance conflicts with existing files: "
42
+ f"{joined}. Re-run with --force-agent-guidance to replace them."
43
+ )
44
+
45
+
46
+ @dataclass(frozen=True)
47
+ class InstallResult:
48
+ target: Path
49
+ agents: tuple[str, ...]
50
+ written: tuple[Path, ...]
51
+ unchanged: tuple[Path, ...]
52
+
53
+
54
+ def install_agent_guidance(
55
+ target: str | Path = ".",
56
+ *,
57
+ agents: Iterable[str] | None = None,
58
+ force: bool = False,
59
+ ) -> InstallResult:
60
+ """Install version-matched IoP guidance into an application repository."""
61
+ target_path = Path(target).expanduser().resolve()
62
+ selected_agents = _normalize_agents(agents)
63
+ owned_files = _owned_file_manifest(target_path, selected_agents)
64
+ root_files = _root_file_manifest(target_path, selected_agents)
65
+
66
+ conflicts = [
67
+ path
68
+ for path, content in owned_files.items()
69
+ if path.exists() and path.read_bytes() != content
70
+ ]
71
+ if conflicts and not force:
72
+ raise AgentGuidanceConflictError(conflicts)
73
+
74
+ manifest = {**owned_files, **root_files}
75
+ written: list[Path] = []
76
+ unchanged: list[Path] = []
77
+ for path, content in manifest.items():
78
+ if path.exists() and path.read_bytes() == content:
79
+ unchanged.append(path)
80
+ continue
81
+ path.parent.mkdir(parents=True, exist_ok=True)
82
+ path.write_bytes(content)
83
+ written.append(path)
84
+
85
+ return InstallResult(
86
+ target=target_path,
87
+ agents=selected_agents,
88
+ written=tuple(written),
89
+ unchanged=tuple(unchanged),
90
+ )
91
+
92
+
93
+ def _normalize_agents(agents: Iterable[str] | None) -> tuple[str, ...]:
94
+ values = tuple(agents or AGENTS)
95
+ invalid = sorted(set(values) - set(AGENTS))
96
+ if invalid:
97
+ raise ValueError(f"unsupported agents: {', '.join(invalid)}")
98
+ return tuple(agent for agent in AGENTS if agent in values)
99
+
100
+
101
+ def _owned_file_manifest(
102
+ target: Path,
103
+ agents: tuple[str, ...],
104
+ ) -> dict[Path, bytes]:
105
+ manifest: dict[Path, bytes] = {}
106
+ resource_root = files("iop.ai")
107
+ for resource_dir, destination in (
108
+ (resource_root.joinpath("guidance"), target / ".config" / "AGENTS"),
109
+ (
110
+ resource_root.joinpath("skills"),
111
+ target / ".config" / "AGENTS" / "skills",
112
+ ),
113
+ ):
114
+ _add_resource_tree(manifest, resource_dir, destination)
115
+
116
+ for agent in agents:
117
+ _, skills_root = _AGENT_PATHS[agent]
118
+ for skill_name, description in _SKILL_DESCRIPTIONS.items():
119
+ wrapper = _skill_wrapper(skill_name, description)
120
+ path = target / skills_root / skill_name / "SKILL.md"
121
+ manifest[path] = wrapper.encode("utf-8")
122
+ return manifest
123
+
124
+
125
+ def _root_file_manifest(
126
+ target: Path,
127
+ agents: tuple[str, ...],
128
+ ) -> dict[Path, bytes]:
129
+ manifest: dict[Path, bytes] = {}
130
+ for agent in agents:
131
+ root_name, _ = _AGENT_PATHS[agent]
132
+ path = target / root_name
133
+ current = path.read_text(encoding="utf-8") if path.exists() else ""
134
+ merged = _merge_managed_block(current, _root_block(agent))
135
+ manifest[path] = merged.encode("utf-8")
136
+ return manifest
137
+
138
+
139
+ def _add_resource_tree(manifest, resource, destination: Path) -> None:
140
+ for child in resource.iterdir():
141
+ child_destination = destination / child.name
142
+ if child.is_dir():
143
+ _add_resource_tree(manifest, child, child_destination)
144
+ elif child.name != "__pycache__":
145
+ manifest[child_destination] = child.read_bytes()
146
+
147
+
148
+ def _merge_managed_block(content: str, block: str) -> str:
149
+ managed = f"{MANAGED_START}\n{block.rstrip()}\n{MANAGED_END}"
150
+ start = content.find(MANAGED_START)
151
+ end = content.find(MANAGED_END)
152
+ if start >= 0 and end >= start:
153
+ end += len(MANAGED_END)
154
+ return f"{content[:start]}{managed}{content[end:]}"
155
+ if not content.strip():
156
+ return f"{managed}\n"
157
+ return f"{content.rstrip()}\n\n{managed}\n"
158
+
159
+
160
+ def _root_block(agent: str) -> str:
161
+ import_line = {
162
+ "codex": "Read `.config/AGENTS/instructions.md` before changing code.",
163
+ "claude": "@.config/AGENTS/instructions.md",
164
+ "gemini": "@./.config/AGENTS/instructions.md",
165
+ }[agent]
166
+ return "\n".join(
167
+ (
168
+ "# IoP Agent Guidance",
169
+ "",
170
+ "This repository contains an IoP application.",
171
+ import_line,
172
+ "Use `build-iop-app` for application changes.",
173
+ "Use `validate-iop-app` for verification.",
174
+ )
175
+ )
176
+
177
+
178
+ def _skill_wrapper(skill_name: str, description: str) -> str:
179
+ return (
180
+ "---\n"
181
+ f"name: {skill_name}\n"
182
+ f"description: {description}\n"
183
+ "---\n\n"
184
+ f"Read and follow `.config/AGENTS/skills/{skill_name}/SKILL.md`. "
185
+ "Load its referenced files only when the workflow calls for them.\n"
186
+ )
@@ -0,0 +1,54 @@
1
+ ---
2
+ name: build-iop-app
3
+ description: Build or modify an IoP application using production graphs, messages, components, tests, and all applicable bundled cookbooks. Use for new or changed Business Services, Business Processes, Business Operations, routes, settings, healthcare flows, or complete productions.
4
+ ---
5
+
6
+ # Build An IoP Application
7
+
8
+ Read `references/instructions.md`, then inspect the project README,
9
+ `settings.py`, production graph, messages, components, tests, samples,
10
+ dependency manifests, and Docker or Compose lifecycle files.
11
+
12
+ ## Workflow
13
+
14
+ 1. Establish the business goal, inbound and outbound systems, protocols,
15
+ routing or transformation behavior, runtime constraints, and acceptance
16
+ criteria. Infer repository facts before asking questions.
17
+ 2. Start with `references/cookbooks/index.md` and read every cookbook implicated
18
+ by the requested flow. Complete ingestion work normally requires the
19
+ ingestion-pipeline, polling-service, process, and operation cookbooks. Use
20
+ `code-index.md` when starting from source symbols.
21
+ 3. Write a short component-role plan before coding: identify the source
22
+ acquisition service, orchestration process, destination operations, messages,
23
+ and graph edges. A polling service fetches from its configured source and
24
+ emits data; a process validates, transforms, and routes it; an operation
25
+ performs persistence or another destination effect. Model complete ingestion
26
+ as `service -> process -> operation` unless a trivial pass-through is
27
+ explicitly justified.
28
+ 4. Implement through public `iop` imports. Use `target()` and graph connections
29
+ instead of direct component calls. Use `on_init()`, not `__init__()`, for
30
+ startup.
31
+ 5. Add focused tests and representative samples for each component boundary and
32
+ the production topology. Declare every directly imported third-party package
33
+ in the project's dependency manifest.
34
+ 6. Ensure the migration entrypoint exports the desired production through
35
+ `PRODUCTIONS` and remains safe to import. Preserve existing container
36
+ initialization, migration, health, and startup automation; update production
37
+ paths and names instead of disabling that lifecycle.
38
+ 7. Run the `validate-iop-app` skill. Do not deploy unless the user explicitly
39
+ requests it. A repository-owned disposable container is a validation
40
+ environment, not a shared deployment.
41
+
42
+ For healthcare work, prefer native IRIS or Health Connect standards and
43
+ transport components. Use the healthcare cookbooks for HL7v2 and FHIR choices.
44
+
45
+ For an existing shared production, read the production change workflow and use
46
+ plan/review/apply/verify instead of assuming the Python graph is a complete
47
+ deployed-state replacement.
48
+
49
+ ## References
50
+
51
+ - Core IoP rules: `references/instructions.md`
52
+ - Task workflows: `references/cookbooks/index.md`
53
+ - Existing production safety: `references/production-change-workflow.md`
54
+ - Healthcare design: `references/healthcare-ai-coding.md`
@@ -0,0 +1,69 @@
1
+ # IoP Agent Guidance And Skills
2
+
3
+ IoP ships version-matched project guidance, Agent Skills, and offline copies of
4
+ the IoP cookbooks. Install them in any IoP application repository, including a
5
+ project that was not created from the IoP template:
6
+
7
+ ```bash
8
+ iop --install-agent-guidance
9
+ ```
10
+
11
+ The command configures Codex, Claude Code, and Gemini CLI by default. It adds a
12
+ small IoP-managed block to existing root instruction files without replacing
13
+ other project rules, installs canonical guidance in `.config/AGENTS`, and adds
14
+ the `build-iop-app` and `validate-iop-app` skills for each agent.
15
+
16
+ ## Select Agents
17
+
18
+ Use one or more `--agent` options when the project only needs specific clients:
19
+
20
+ ```bash
21
+ iop --install-agent-guidance --agent codex
22
+ iop --install-agent-guidance --agent claude --agent gemini
23
+ ```
24
+
25
+ Pass a project directory to configure a repository other than the current one:
26
+
27
+ ```bash
28
+ iop --install-agent-guidance /path/to/iop-application
29
+ ```
30
+
31
+ The installer is idempotent. If an IoP-managed file was changed locally, it
32
+ stops before writing anything. Review the conflict, then replace only the
33
+ IoP-managed snapshot when appropriate:
34
+
35
+ ```bash
36
+ iop --install-agent-guidance --force-agent-guidance
37
+ ```
38
+
39
+ Rerun that command after upgrading IoP to refresh the guides, skills, and
40
+ offline cookbooks to the installed framework version.
41
+
42
+ ## Install Skills Directly
43
+
44
+ Developers who only need the portable skills can install them from the IoP
45
+ repository with the cross-agent Skills CLI:
46
+
47
+ ```bash
48
+ npx skills add \
49
+ https://github.com/grongierisc/interoperability-embedded-python/tree/master/src/iop/ai/skills \
50
+ --skill build-iop-app \
51
+ --skill validate-iop-app
52
+ ```
53
+
54
+ Direct installation provides the self-contained skills and their references.
55
+ It does not add the always-on `AGENTS.md`, `CLAUDE.md`, or `GEMINI.md` project
56
+ guidance; use the IoP installer when those entrypoints are wanted.
57
+
58
+ ## Skill Responsibilities
59
+
60
+ - `build-iop-app` inspects the application, selects the relevant bundled
61
+ cookbook, and guides message, component, production graph, sample, and test
62
+ changes.
63
+ - `validate-iop-app` runs project tests and strict migration dry-run first, then
64
+ requires container-backed runtime verification when the project provides a
65
+ disposable environment, while protecting shared or remote IRIS instances.
66
+
67
+ The skill files use the shared Agent Skills `SKILL.md` format with only portable
68
+ frontmatter. Client-specific wrappers contain no duplicated IoP policy; the
69
+ installed `.config/AGENTS` snapshot remains the local source of truth.
@@ -0,0 +1,130 @@
1
+ # Cookbook: Add A BusinessOperation
2
+
3
+ ## When To Use This
4
+
5
+ Use this cookbook when a production needs a destination side-effect boundary:
6
+ calling a downstream API, writing a file, submitting FHIR, storing data, or
7
+ wrapping a technical operation behind a production message. Acquisition from
8
+ the configured source of a `PollingBusinessService` remains in that service;
9
+ the operation receives the acquired or processed data.
10
+
11
+ ## Files You Will Touch
12
+
13
+ - the operation module, such as `bo.py` or `operations.py`
14
+ - the message module, such as `msg.py` or `messages.py`
15
+ - `settings.py`
16
+ - the fastest relevant test or sample payload
17
+
18
+ ## Prompt To Give Your Agent
19
+
20
+ ```text
21
+ Add a new IoP BusinessOperation to this project.
22
+
23
+ Business goal:
24
+ <describe what the operation receives, validates, logs, transforms, or sends>
25
+
26
+ Implementation requirements:
27
+ - Reuse existing message classes if they already fit.
28
+ - If a new message is needed, define it as a Message dataclass unless there is
29
+ a specific need for PersistentMessage.
30
+ - Implement a fallback on_message(self, request) for simple operations, or route
31
+ by message type with typed one-argument methods or the @handler decorator.
32
+ - Return a response message or the original request when that matches the flow.
33
+ - Use self.log_info(), self.log_warning(), or self.log_error() for component
34
+ logging.
35
+ - Do not add startup work to __init__(); use on_init() only if startup work is
36
+ required.
37
+ - Update settings.py so the operation is added to the Production graph.
38
+ - Add or update the fastest relevant test.
39
+ - Show the exact verification command.
40
+ ```
41
+
42
+ ## Expected Implementation
43
+
44
+ A simple operation can use `on_message()`:
45
+
46
+ ```python
47
+ from iop import BusinessOperation
48
+
49
+ from messages import OrderRequest, OrderResponse
50
+
51
+
52
+ class OrderOperation(BusinessOperation):
53
+ def on_message(self, request: OrderRequest) -> OrderResponse:
54
+ self.log_info(f"Processing order {request.order_id}")
55
+ return OrderResponse(order_id=request.order_id, status="accepted")
56
+ ```
57
+
58
+ When one operation handles multiple message types, prefer typed one-argument
59
+ methods or the `@handler` decorator:
60
+
61
+ ```python
62
+ from iop import BusinessOperation, handler
63
+
64
+ from messages import CancelOrder, OrderRequest, OrderResponse
65
+
66
+
67
+ class OrderOperation(BusinessOperation):
68
+ def on_message(self, request):
69
+ self.log_warning(f"Unhandled message {type(request).__name__}")
70
+ return request
71
+
72
+ def submit_order(self, request: OrderRequest) -> OrderResponse:
73
+ self.log_info(f"Submitting order {request.order_id}")
74
+ return OrderResponse(order_id=request.order_id, status="accepted")
75
+
76
+ @handler(CancelOrder)
77
+ def cancel_order(self, request):
78
+ self.log_info(f"Cancelling order {request.order_id}")
79
+ return request
80
+ ```
81
+
82
+ IoP dispatches to:
83
+
84
+ - a method decorated with `@handler(MessageType)` first
85
+ - a typed one-argument method such as `submit_order(self, request: OrderRequest)`
86
+ - `on_message(self, request)` as the fallback
87
+
88
+ The production graph should register the operation:
89
+
90
+ ```python
91
+ operation = prod.operation("OrderOperation", OrderOperation)
92
+ ```
93
+
94
+ If another component sends to the operation, declare a `target()` setting on the
95
+ sender and connect it:
96
+
97
+ ```python
98
+ process = prod.process("OrderProcess", OrderProcess)
99
+ process.connect(OrderProcess.Orders, operation)
100
+ ```
101
+
102
+ ## Migration Command
103
+
104
+ ```bash
105
+ iop --migrate settings.py --dry-run
106
+ iop --migrate settings.py
107
+ ```
108
+
109
+ ## Verification
110
+
111
+ Use the fastest local test first:
112
+
113
+ ```bash
114
+ python -m pytest
115
+ ```
116
+
117
+ If there is no existing test suite, ask the agent to add a small test for the
118
+ pure Python transformation or validation logic.
119
+
120
+ ## Common Mistakes
121
+
122
+ - Creating an operation but not adding it to `settings.py`.
123
+ - Combining source acquisition, orchestration, and destination persistence in
124
+ one operation.
125
+ - Calling another production component as a normal Python object.
126
+ - Hiding connection names in strings instead of using `target()` and
127
+ `prod.connect(...)`.
128
+ - Using `PersistentMessage` when a regular `Message` dataclass is enough.
129
+ - Adding multiple handlers for the same message type without making the
130
+ intended precedence explicit.
@@ -0,0 +1,115 @@
1
+ # Cookbook: Add A BusinessProcess
2
+
3
+ ## When To Use This
4
+
5
+ Use this cookbook when a production needs routing, orchestration, decision
6
+ logic, enrichment, or coordination between services and operations.
7
+
8
+ Business Processes should not own external side effects directly when a
9
+ Business Operation is the better boundary. Keep the process focused on deciding
10
+ what happens next and sending messages to downstream targets.
11
+
12
+ ## Files You Will Touch
13
+
14
+ - the process module, such as `bp.py` or `processes.py`
15
+ - the message module, such as `msg.py` or `messages.py`
16
+ - operation modules only when downstream behavior is missing
17
+ - `settings.py`
18
+ - tests or sample payloads for routing decisions
19
+
20
+ ## Prompt To Give Your Agent
21
+
22
+ ```text
23
+ Add a new IoP BusinessProcess to this project.
24
+
25
+ Business goal:
26
+ <describe the routing, orchestration, enrichment, or decision logic>
27
+
28
+ Implementation requirements:
29
+ - Reuse existing message classes if they already fit.
30
+ - Declare outbound routes with target() attributes on the process class.
31
+ - Route messages with send_request_sync(), send_request_async(), or direct
32
+ response behavior according to the existing project pattern.
33
+ - Use on_message(self, request) for simple processes, or route by message type
34
+ with typed one-argument methods or the @handler decorator.
35
+ - Keep external API calls, database writes, file writes, and FHIR submission in
36
+ BusinessOperation classes.
37
+ - Do not add startup work to __init__(); use on_init() only if startup work is
38
+ required.
39
+ - Update settings.py so the process is added to the Production graph and its
40
+ targets are connected.
41
+ - Add or update tests for routing decisions.
42
+ - Show the exact migration and verification commands.
43
+ ```
44
+
45
+ ## Expected Implementation
46
+
47
+ A process declares outbound targets and sends messages to them:
48
+
49
+ ```python
50
+ from iop import BusinessProcess, Message, handler, target
51
+
52
+ from messages import OrderRequest, OrderResponse, RejectedOrder
53
+
54
+
55
+ class OrderProcess(BusinessProcess):
56
+ Accepted = target()
57
+ Rejected = target()
58
+
59
+ def on_message(self, request):
60
+ self.log_warning(f"Unhandled message {type(request).__name__}")
61
+ return request
62
+
63
+ def route_order(self, request: OrderRequest):
64
+ if not request.order_id:
65
+ return self.send_request_sync(self.Rejected, RejectedOrder(reason="missing id"))
66
+
67
+ return self.send_request_sync(self.Accepted, request)
68
+
69
+ @handler(RejectedOrder)
70
+ def route_rejected(self, request):
71
+ self.log_info(request.reason)
72
+ return request
73
+ ```
74
+
75
+ IoP dispatches to:
76
+
77
+ - a method decorated with `@handler(MessageType)` first
78
+ - a typed one-argument method such as `route_order(self, request: OrderRequest)`
79
+ - `on_message(self, request)` as the fallback
80
+
81
+ Wire the process in `settings.py`:
82
+
83
+ ```python
84
+ process = prod.process("OrderProcess", OrderProcess)
85
+ accepted = prod.operation("AcceptedOperation", AcceptedOperation)
86
+ rejected = prod.operation("RejectedOperation", RejectedOperation)
87
+
88
+ process.connect(OrderProcess.Accepted, accepted)
89
+ process.connect(OrderProcess.Rejected, rejected)
90
+ ```
91
+
92
+ ## Migration Command
93
+
94
+ ```bash
95
+ iop --migrate settings.py --dry-run
96
+ iop --migrate settings.py
97
+ ```
98
+
99
+ ## Verification
100
+
101
+ - Unit-test the routing decision with representative messages.
102
+ - Dry-run migration shows the process and all target settings.
103
+ - The production graph contains every expected process edge.
104
+ - Runtime trace shows messages passing from service to process to operation.
105
+
106
+ ## Common Mistakes
107
+
108
+ - Putting external API calls or database writes directly in the process.
109
+ - Forgetting `target()` declarations for outbound routes.
110
+ - Returning raw dictionaries instead of message objects when the downstream
111
+ component expects IoP messages.
112
+ - Adding multiple handlers for the same message type without making the
113
+ intended precedence explicit.
114
+ - Calling a downstream component directly instead of sending a production
115
+ message through a target.