plan-manager-client-dev 0.2.186__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.
- plan_manager_client/__init__.py +10 -0
- plan_manager_client/_version.py +58 -0
- plan_manager_client/authoring_diagnostics.py +137 -0
- plan_manager_client/client.py +75 -0
- plan_manager_client/config.py +320 -0
- plan_manager_client/delivery/__init__.py +7 -0
- plan_manager_client/delivery/archive.py +140 -0
- plan_manager_client/delivery/atomicity.py +93 -0
- plan_manager_client/delivery/commit.py +113 -0
- plan_manager_client/delivery/destination.py +140 -0
- plan_manager_client/delivery/provenance.py +88 -0
- plan_manager_client/delivery/upload.py +118 -0
- plan_manager_client/dispatch.py +380 -0
- plan_manager_client/export_fetch_chunks.py +166 -0
- plan_manager_client/export_fetch_request.py +88 -0
- plan_manager_client/export_fetch_unpack.py +149 -0
- plan_manager_client/export_fetch_verify.py +101 -0
- plan_manager_client/facade_agent_config_mixin.py +224 -0
- plan_manager_client/facade_bug_project_mixin.py +202 -0
- plan_manager_client/facade_context_gate_mixin.py +303 -0
- plan_manager_client/facade_entity_common_mixin.py +38 -0
- plan_manager_client/facade_hrs_file_mixin.py +206 -0
- plan_manager_client/facade_plan_hrs_mixin.py +200 -0
- plan_manager_client/facade_runtime_mixin/__init__.py +11 -0
- plan_manager_client/facade_runtime_mixin/authoring_part.py +442 -0
- plan_manager_client/facade_runtime_mixin/facade.py +380 -0
- plan_manager_client/facade_runtime_mixin/overlay_part.py +192 -0
- plan_manager_client/facade_step_graph_mixin.py +158 -0
- plan_manager_client/id_rule.py +115 -0
- plan_manager_client/info_walker.py +133 -0
- plan_manager_client/scenario_runtime.py +65 -0
- plan_manager_client/server_api.py +427 -0
- plan_manager_client/server_api_mutating.py +105 -0
- plan_manager_client_dev-0.2.186.dist-info/METADATA +10 -0
- plan_manager_client_dev-0.2.186.dist-info/RECORD +37 -0
- plan_manager_client_dev-0.2.186.dist-info/WHEEL +5 -0
- plan_manager_client_dev-0.2.186.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,206 @@
|
|
|
1
|
+
"""HRS file-transfer client wrappers (import half): hrs_import_file.
|
|
2
|
+
|
|
3
|
+
Author: Vasiliy Zdanovskiy
|
|
4
|
+
email: vasilyvz@gmail.com
|
|
5
|
+
|
|
6
|
+
These two methods are client-side compositions of server commands, not
|
|
7
|
+
server commands themselves; they are exempted in
|
|
8
|
+
plan_manager_client.server_api.FILE_WRAPPER_NAMES.
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
from __future__ import annotations
|
|
12
|
+
|
|
13
|
+
import hashlib
|
|
14
|
+
import os
|
|
15
|
+
import uuid
|
|
16
|
+
from pathlib import Path
|
|
17
|
+
from typing import Any, Callable
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def _require_bare_filename(filename: str) -> None:
|
|
21
|
+
"""Refuse any filename that is not a single plain path component."""
|
|
22
|
+
if (
|
|
23
|
+
not filename
|
|
24
|
+
or filename in (".", "..")
|
|
25
|
+
or "/" in filename
|
|
26
|
+
or "\\" in filename
|
|
27
|
+
or os.path.basename(filename) != filename
|
|
28
|
+
):
|
|
29
|
+
raise ValueError(f"filename must be a bare file name, got {filename!r}")
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def _require_uploadable_file(path: str) -> None:
|
|
33
|
+
"""Check the local source before any request: regular, readable and non-empty."""
|
|
34
|
+
with open(path, "rb"):
|
|
35
|
+
pass
|
|
36
|
+
if os.path.getsize(path) == 0:
|
|
37
|
+
raise ValueError(f"file is empty: {path}")
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def _reply_payload(reply: Any) -> Any:
|
|
41
|
+
"""Return the command outcome ({success, data|error}) of a reply.
|
|
42
|
+
|
|
43
|
+
The client's _call returns the queued-command job envelope
|
|
44
|
+
{job_id, command, result: {success, data}, status}; a bare outcome is
|
|
45
|
+
returned unchanged.
|
|
46
|
+
"""
|
|
47
|
+
if isinstance(reply, dict) and isinstance(reply.get("result"), dict) and "success" in reply["result"]:
|
|
48
|
+
return reply["result"]
|
|
49
|
+
return reply
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
class HrsFileCommandsMixin:
|
|
53
|
+
"""Client-side wrappers that move a local file through the server transfer path.
|
|
54
|
+
|
|
55
|
+
Assumes it is mixed into a class that provides the held JSON-RPC client
|
|
56
|
+
as self._rpc (upload_file) and the coroutine
|
|
57
|
+
async def _call(self, command: str, params: dict[str, Any] | None = None) -> Any.
|
|
58
|
+
"""
|
|
59
|
+
|
|
60
|
+
async def hrs_import_file(
|
|
61
|
+
self,
|
|
62
|
+
plan: str,
|
|
63
|
+
path: str,
|
|
64
|
+
*,
|
|
65
|
+
dry_run: bool = True,
|
|
66
|
+
convert: bool = False,
|
|
67
|
+
filename: str | None = None,
|
|
68
|
+
compression: str = "identity",
|
|
69
|
+
chunk_size: int | None = None,
|
|
70
|
+
on_progress: Callable[[int, int], None] | None = None,
|
|
71
|
+
) -> Any:
|
|
72
|
+
"""Upload a local HRS file, then import it with hrs_import(source=filename).
|
|
73
|
+
|
|
74
|
+
Validation (bare filename, readable non-empty file) happens before any
|
|
75
|
+
request. The uploaded file's sha256 must match the transfer receipt
|
|
76
|
+
before hrs_import is called. A server error result is returned as is.
|
|
77
|
+
|
|
78
|
+
dry_run=true (the default) validates and reports without storing anything; convert=true converts the file
|
|
79
|
+
into structured fields by the model, after the svo paragraph split. The other hrs_import sources, document_id
|
|
80
|
+
and paragraphs, are not used by this call.
|
|
81
|
+
"""
|
|
82
|
+
if filename is None:
|
|
83
|
+
filename = f"hrs-import-{uuid.uuid4()}.md"
|
|
84
|
+
else:
|
|
85
|
+
_require_bare_filename(filename)
|
|
86
|
+
_require_uploadable_file(path)
|
|
87
|
+
|
|
88
|
+
receipt = await self._rpc.upload_file(
|
|
89
|
+
path,
|
|
90
|
+
filename=filename,
|
|
91
|
+
compression=compression,
|
|
92
|
+
chunk_size=chunk_size,
|
|
93
|
+
on_progress=on_progress,
|
|
94
|
+
)
|
|
95
|
+
saved = await self._call(
|
|
96
|
+
"export_upload_save",
|
|
97
|
+
{"transfer_id": receipt.transfer_id, "filename": filename},
|
|
98
|
+
)
|
|
99
|
+
payload = _reply_payload(saved)
|
|
100
|
+
if isinstance(payload, dict) and payload.get("success") is False:
|
|
101
|
+
return saved
|
|
102
|
+
body = payload.get("data") if isinstance(payload.get("data"), dict) else payload
|
|
103
|
+
if body.get("sha256") != receipt.checksum_value:
|
|
104
|
+
raise ValueError(
|
|
105
|
+
f"uploaded file checksum mismatch for {filename}: "
|
|
106
|
+
f"expected {receipt.checksum_value}, saved {body.get('sha256')}"
|
|
107
|
+
)
|
|
108
|
+
|
|
109
|
+
params: dict[str, Any] = {"plan": plan, "source": filename, "dry_run": dry_run}
|
|
110
|
+
if convert:
|
|
111
|
+
params["convert"] = True
|
|
112
|
+
return await self._call("hrs_import", params)
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
async def hrs_export_file(
|
|
116
|
+
self,
|
|
117
|
+
plan: str,
|
|
118
|
+
path: str,
|
|
119
|
+
*,
|
|
120
|
+
overwrite: bool = False,
|
|
121
|
+
compression: str = "identity",
|
|
122
|
+
chunk_size: int | None = None,
|
|
123
|
+
on_progress: Callable[[int, int], None] | None = None,
|
|
124
|
+
) -> Any:
|
|
125
|
+
"""Export a plan to a local file: hrs_export(target file), export_download_begin, download, sha256 check.
|
|
126
|
+
|
|
127
|
+
The export file stays on the server for its lifetime, by default 3600 s, and the sweep timer, which runs
|
|
128
|
+
every 300 s by default, removes it afterwards (both configurable on the server: hrs_export.file_ttl_s and
|
|
129
|
+
hrs_export.sweep_interval_s). The download goes to a temporary file next to the destination; the destination is replaced
|
|
130
|
+
only by the final rename, after the sha256 of the downloaded bytes equals the export sha256. Without
|
|
131
|
+
overwrite an existing destination is refused after the verified download. A server error reply is
|
|
132
|
+
returned unchanged. A path that is empty or None is refused before any request.
|
|
133
|
+
"""
|
|
134
|
+
destination = _require_destination(path)
|
|
135
|
+
_require_writable_destination(destination, overwrite)
|
|
136
|
+
exported = _reply_payload(await self._call("hrs_export", {"plan": plan, "target": "file"}))
|
|
137
|
+
if not isinstance(exported, dict) or exported.get("success") is not True:
|
|
138
|
+
return exported
|
|
139
|
+
data = exported["data"]
|
|
140
|
+
begin_params: dict[str, Any] = {"plan": plan, "file": data["file"], "compression": compression}
|
|
141
|
+
if chunk_size is not None:
|
|
142
|
+
begin_params["chunk_size"] = chunk_size
|
|
143
|
+
begun_reply = await self._call("export_download_begin", begin_params)
|
|
144
|
+
begun = _reply_payload(begun_reply)
|
|
145
|
+
if not isinstance(begun, dict) or begun.get("success") is not True:
|
|
146
|
+
return begun_reply
|
|
147
|
+
transfer_id = begun["data"]["transfer_id"]
|
|
148
|
+
|
|
149
|
+
temporary = destination.with_name(f".{destination.name}.{uuid.uuid4().hex}.part")
|
|
150
|
+
try:
|
|
151
|
+
receipt = await self._rpc.download_file(
|
|
152
|
+
transfer_id, str(temporary), chunk_size=chunk_size, on_progress=on_progress
|
|
153
|
+
)
|
|
154
|
+
if not (receipt.completed and receipt.checksum_verified):
|
|
155
|
+
raise ValueError(f"download of {data['file']} is not completed or its checksum is not verified")
|
|
156
|
+
body = temporary.read_bytes()
|
|
157
|
+
if len(body) != data["size_bytes"] or hashlib.sha256(body).hexdigest() != data["sha256"]:
|
|
158
|
+
raise ValueError(f"downloaded file does not match the export {data['file']}")
|
|
159
|
+
if destination.exists() and not overwrite:
|
|
160
|
+
raise FileExistsError(f"destination exists and overwrite is False: {destination}")
|
|
161
|
+
os.replace(temporary, destination)
|
|
162
|
+
except BaseException:
|
|
163
|
+
with_suppressed_unlink(temporary)
|
|
164
|
+
raise
|
|
165
|
+
return {
|
|
166
|
+
"plan": plan,
|
|
167
|
+
"path": str(destination),
|
|
168
|
+
"size_bytes": data["size_bytes"],
|
|
169
|
+
"sha256": data["sha256"],
|
|
170
|
+
"paragraphs": data["paragraphs"],
|
|
171
|
+
"transfer_id": transfer_id,
|
|
172
|
+
"server_file": data["file"],
|
|
173
|
+
"expires_at": data["expires_at"],
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
|
|
177
|
+
def _require_destination(path: Any) -> Path:
|
|
178
|
+
"""Return the destination path; an empty or missing path is refused before any request."""
|
|
179
|
+
if path is None or (isinstance(path, str) and not path.strip()):
|
|
180
|
+
raise ValueError("path must be a non-empty file path")
|
|
181
|
+
return Path(path)
|
|
182
|
+
|
|
183
|
+
|
|
184
|
+
def _require_writable_destination(destination: Path, overwrite: bool) -> None:
|
|
185
|
+
"""Refuse a destination that cannot be written, before any request: a directory, a missing parent, a read-only
|
|
186
|
+
parent, or an existing file without overwrite."""
|
|
187
|
+
if destination.is_dir():
|
|
188
|
+
raise IsADirectoryError(f"destination is a directory: {destination}")
|
|
189
|
+
parent = destination.parent
|
|
190
|
+
if not parent.is_dir():
|
|
191
|
+
raise FileNotFoundError(f"destination directory does not exist: {parent}")
|
|
192
|
+
if not os.access(parent, os.W_OK | os.X_OK):
|
|
193
|
+
raise PermissionError(f"destination directory is not writable: {parent}")
|
|
194
|
+
if destination.exists() and not overwrite:
|
|
195
|
+
raise FileExistsError(f"destination exists and overwrite is False: {destination}")
|
|
196
|
+
|
|
197
|
+
|
|
198
|
+
def with_suppressed_unlink(path: Path) -> None:
|
|
199
|
+
"""Remove a temporary file if it exists."""
|
|
200
|
+
try:
|
|
201
|
+
path.unlink()
|
|
202
|
+
except FileNotFoundError:
|
|
203
|
+
pass
|
|
204
|
+
|
|
205
|
+
|
|
206
|
+
__all__ = ["HrsFileCommandsMixin"]
|
|
@@ -0,0 +1,200 @@
|
|
|
1
|
+
"""Plan, export, HRS/MRS, and relation command-family facade mixin.
|
|
2
|
+
|
|
3
|
+
Author: Vasiliy Zdanovskiy
|
|
4
|
+
email: vasilyvz@gmail.com
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
from typing import Any
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class PlanHrsCommandsMixin:
|
|
13
|
+
"""One method per plan_manager command in the plan/export/HRS/MRS family.
|
|
14
|
+
|
|
15
|
+
Assumes it is mixed into a class that also inherits
|
|
16
|
+
plan_manager_client.dispatch._CommandDispatchMixin, which supplies the
|
|
17
|
+
coroutine async def _call(self, command: str, params: dict[str, Any] | None = None) -> Any
|
|
18
|
+
used by every method below.
|
|
19
|
+
"""
|
|
20
|
+
|
|
21
|
+
async def plan_create(self, **params: Any) -> Any:
|
|
22
|
+
"""Create a new plan aggregate with a unique name."""
|
|
23
|
+
return await self._call("plan_create", params)
|
|
24
|
+
|
|
25
|
+
async def plan_list(self, **params: Any) -> Any:
|
|
26
|
+
"""List a paginated page of plans in the catalog with their bound projects; soft-deleted plans are hidden unless show_deleted is true."""
|
|
27
|
+
return await self._call("plan_list", params)
|
|
28
|
+
|
|
29
|
+
async def plan_status(self, **params: Any) -> Any:
|
|
30
|
+
"""Return the dashboard for one plan: counts, gate, and scoring."""
|
|
31
|
+
return await self._call("plan_status", params)
|
|
32
|
+
|
|
33
|
+
async def plan_delete(self, **params: Any) -> Any:
|
|
34
|
+
"""Delete a plan: soft by default (hidden from the catalog, reversible), or permanently with hard=true."""
|
|
35
|
+
return await self._call("plan_delete", params)
|
|
36
|
+
|
|
37
|
+
async def plan_completed_set(self, **params: Any) -> Any:
|
|
38
|
+
"""Set or unset a plan's completion lock; always reachable regardless of freeze state or the current flag value."""
|
|
39
|
+
return await self._call("plan_completed_set", params)
|
|
40
|
+
|
|
41
|
+
async def plan_comment_set(self, **params: Any) -> Any:
|
|
42
|
+
"""Set, replace, or clear a plan's free-form comment; always reachable regardless of freeze state or the completion lock."""
|
|
43
|
+
return await self._call("plan_comment_set", params)
|
|
44
|
+
|
|
45
|
+
async def plan_context_budget_set(self, **params: Any) -> Any:
|
|
46
|
+
"""Set a plan's context budget for prompt assembly; always reachable regardless of freeze state or the completion lock."""
|
|
47
|
+
return await self._call("plan_context_budget_set", params)
|
|
48
|
+
|
|
49
|
+
async def plan_model_mapping_set(
|
|
50
|
+
self,
|
|
51
|
+
plan: str,
|
|
52
|
+
mappings: dict[str, dict[str, str] | None],
|
|
53
|
+
expected_plan_revision: str,
|
|
54
|
+
) -> Any:
|
|
55
|
+
"""Atomically replace a plan's complete G/T/A authoring-model mapping."""
|
|
56
|
+
return await self._call(
|
|
57
|
+
"plan_model_mapping_set",
|
|
58
|
+
{
|
|
59
|
+
"plan": plan,
|
|
60
|
+
"mappings": mappings,
|
|
61
|
+
"expected_plan_revision": expected_plan_revision,
|
|
62
|
+
},
|
|
63
|
+
)
|
|
64
|
+
|
|
65
|
+
async def plan_model_mapping_get(self, plan: str) -> Any:
|
|
66
|
+
"""Read the plan-owned G/T/A model mapping and header revision."""
|
|
67
|
+
return await self._call("plan_model_mapping_get", {"plan": plan})
|
|
68
|
+
|
|
69
|
+
async def plan_project_attach(self, **params: Any) -> Any:
|
|
70
|
+
"""Make an imported project the single project (owner) of a plan."""
|
|
71
|
+
return await self._call("plan_project_attach", params)
|
|
72
|
+
|
|
73
|
+
async def plan_export(self, **params: Any) -> Any:
|
|
74
|
+
"""Export a plan to the standard file layout under the configured export root."""
|
|
75
|
+
return await self._call("plan_export", params)
|
|
76
|
+
|
|
77
|
+
async def plan_snapshot(self, **params: Any) -> Any:
|
|
78
|
+
"""Export an importable snapshot of a plan's live working state."""
|
|
79
|
+
return await self._call("plan_snapshot", params)
|
|
80
|
+
|
|
81
|
+
async def plan_import(self, **params: Any) -> Any:
|
|
82
|
+
"""Import a plan from a standard file layout under the configured export root."""
|
|
83
|
+
return await self._call("plan_import", params)
|
|
84
|
+
|
|
85
|
+
async def export_upload_save(self, **params: Any) -> Any:
|
|
86
|
+
"""Promote a completed transfer upload session into the configured export root under a safe bare filename."""
|
|
87
|
+
return await self._call("export_upload_save", params)
|
|
88
|
+
|
|
89
|
+
async def export_read(self, **params: Any) -> Any:
|
|
90
|
+
"""Read a byte range of a file under <export_root>/<plan>/ as a base64 chunk, with the whole file's size and sha256 for byte-identical reassembly and verification."""
|
|
91
|
+
return await self._call("export_read", params)
|
|
92
|
+
|
|
93
|
+
async def export_archive(self, **params: Any) -> Any:
|
|
94
|
+
"""Pack the plan's export tree into a single gzip-tar archive under the fixed plan-relative name export.tar.gz; synchronous, returning plan, archive, size_bytes, sha256, and file_count."""
|
|
95
|
+
return await self._call("export_archive", params)
|
|
96
|
+
|
|
97
|
+
async def hrs_import(self, **params: Any) -> Any:
|
|
98
|
+
"""Replace a plan's HRS text from a Markdown file under the configured export root or from inline source_text.
|
|
99
|
+
|
|
100
|
+
Sources, mutually exclusive: source (bare file name under the export root), source_text (inline Markdown),
|
|
101
|
+
document_id (UUID4 of a doc-store document) and paragraphs (structured paragraph items). convert=true converts
|
|
102
|
+
the source into structured fields by the model, after the svo paragraph split. dry_run=true (the default)
|
|
103
|
+
validates and reports without storing anything.
|
|
104
|
+
"""
|
|
105
|
+
return await self._call("hrs_import", params)
|
|
106
|
+
|
|
107
|
+
async def hrs_export(self, **params: Any) -> Any:
|
|
108
|
+
"""Export the byte-identical HRS Markdown text of a plan."""
|
|
109
|
+
return await self._call("hrs_export", params)
|
|
110
|
+
|
|
111
|
+
async def export_download_begin(self, **params: Any) -> Any:
|
|
112
|
+
"""Open the download session of one hrs_export file of a plan (plan, file, compression, chunk_size); the server file stays until the sweep timer removes it."""
|
|
113
|
+
return await self._call("export_download_begin", params)
|
|
114
|
+
|
|
115
|
+
async def export_cleanup(self, **params: Any) -> Any:
|
|
116
|
+
"""Purge export artifacts under the configured export root, classified per plan; dry_run defaults to true and reports what would be removed without removing it."""
|
|
117
|
+
return await self._call("export_cleanup", params)
|
|
118
|
+
|
|
119
|
+
async def para_list(self, **params: Any) -> Any:
|
|
120
|
+
"""List a paginated page of a plan's HRS paragraphs: by default (view=summary) the compact items {id, broken, presentation}; view=full returns the whole record; fields_state filters."""
|
|
121
|
+
return await self._call("para_list", params)
|
|
122
|
+
|
|
123
|
+
async def para_get(self, **params: Any) -> Any:
|
|
124
|
+
"""Resolve one paragraph label of a plan's HRS to its text, fields and broken state."""
|
|
125
|
+
return await self._call("para_get", params)
|
|
126
|
+
|
|
127
|
+
async def para_label_assign(self, **params: Any) -> Any:
|
|
128
|
+
"""Assign fresh labels to unlabeled binding paragraphs of a plan's HRS."""
|
|
129
|
+
return await self._call("para_label_assign", params)
|
|
130
|
+
|
|
131
|
+
async def para_mark_non_binding(self, **params: Any) -> Any:
|
|
132
|
+
"""Wrap or unwrap the non-binding markers around one HRS block of a plan."""
|
|
133
|
+
return await self._call("para_mark_non_binding", params)
|
|
134
|
+
|
|
135
|
+
async def para_insert(self, **params: Any) -> Any:
|
|
136
|
+
"""Insert one new binding paragraph into a plan's HRS at a binding-order position."""
|
|
137
|
+
return await self._call("para_insert", params)
|
|
138
|
+
|
|
139
|
+
async def para_update(self, **params: Any) -> Any:
|
|
140
|
+
"""Replace the text of one binding HRS paragraph addressed by label."""
|
|
141
|
+
return await self._call("para_update", params)
|
|
142
|
+
|
|
143
|
+
async def para_delete(self, **params: Any) -> Any:
|
|
144
|
+
"""Delete one binding HRS paragraph addressed by label, shifting later paragraphs up."""
|
|
145
|
+
return await self._call("para_delete", params)
|
|
146
|
+
|
|
147
|
+
async def para_split(self, **params: Any) -> Any:
|
|
148
|
+
"""Split one binding HRS paragraph, addressed by label, into several binding paragraphs cut at the given offsets."""
|
|
149
|
+
return await self._call("para_split", params)
|
|
150
|
+
|
|
151
|
+
async def hrs_fields_fill(self, **params: Any) -> Any:
|
|
152
|
+
"""Fill the kind, title and statement of broken HRS paragraphs with one model call each (queued; the body never changes)."""
|
|
153
|
+
return await self._call("hrs_fields_fill", params)
|
|
154
|
+
|
|
155
|
+
async def concept_get(self, **params: Any) -> Any:
|
|
156
|
+
"""Return one concept of a plan by its id."""
|
|
157
|
+
return await self._call("concept_get", params)
|
|
158
|
+
|
|
159
|
+
async def concept_list(self, **params: Any) -> Any:
|
|
160
|
+
"""Return a paginated page of the concept list of a plan."""
|
|
161
|
+
return await self._call("concept_list", params)
|
|
162
|
+
|
|
163
|
+
async def concept_add(self, **params: Any) -> Any:
|
|
164
|
+
"""Add a new concept to the plan MRS."""
|
|
165
|
+
return await self._call("concept_add", params)
|
|
166
|
+
|
|
167
|
+
async def concept_update(self, **params: Any) -> Any:
|
|
168
|
+
"""Update fields of an existing concept."""
|
|
169
|
+
return await self._call("concept_update", params)
|
|
170
|
+
|
|
171
|
+
async def concept_remove(self, **params: Any) -> Any:
|
|
172
|
+
"""Remove an existing concept from the plan MRS."""
|
|
173
|
+
return await self._call("concept_remove", params)
|
|
174
|
+
|
|
175
|
+
async def relation_list(self, **params: Any) -> Any:
|
|
176
|
+
"""Return a paginated page of the relation list of a plan."""
|
|
177
|
+
return await self._call("relation_list", params)
|
|
178
|
+
|
|
179
|
+
async def relation_get(self, **params: Any) -> Any:
|
|
180
|
+
"""Return one relation of a plan by its id."""
|
|
181
|
+
return await self._call("relation_get", params)
|
|
182
|
+
|
|
183
|
+
async def relation_add(self, **params: Any) -> Any:
|
|
184
|
+
"""Add a new relation between two concepts."""
|
|
185
|
+
return await self._call("relation_add", params)
|
|
186
|
+
|
|
187
|
+
async def relation_update(self, **params: Any) -> Any:
|
|
188
|
+
"""Update the type of an existing relation between two concepts."""
|
|
189
|
+
return await self._call("relation_update", params)
|
|
190
|
+
|
|
191
|
+
async def relation_remove(self, **params: Any) -> Any:
|
|
192
|
+
"""Remove an existing relation between two concepts."""
|
|
193
|
+
return await self._call("relation_remove", params)
|
|
194
|
+
|
|
195
|
+
async def concept_coverage(self, **params: Any) -> Any:
|
|
196
|
+
"""Return the steps and HRS paragraphs that justify one concept."""
|
|
197
|
+
return await self._call("concept_coverage", params)
|
|
198
|
+
|
|
199
|
+
|
|
200
|
+
__all__ = ["PlanHrsCommandsMixin"]
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
"""Todo/runtime-link/comment and model-binding/execution/review/escalation command-family facade mixin.
|
|
2
|
+
|
|
3
|
+
Author: Vasiliy Zdanovskiy
|
|
4
|
+
email: vasilyvz@gmail.com
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from .authoring_part import RuntimeAuthoringPart
|
|
8
|
+
from .facade import RuntimeCommandsMixin
|
|
9
|
+
from .overlay_part import RuntimeOverlayPart
|
|
10
|
+
|
|
11
|
+
__all__ = ["RuntimeCommandsMixin"]
|