Skip to content

Commit cd57aa6

Browse files
authored
feat(analytics): result envelope, chart gating, param binders, manifest fields (ISSUE-138/139/140/149) (odpi#453)
* docs(issues): log ISSUE-148 (AssetMaker action queries default to IN_PROGRESS, dropping journal entries) Reported by the egeria-workspaces-fs session. Signed-off-by: Dan Wolfson <dan.wolfson@pdr-associates.com> * fix(asset-maker): default activity_status_list to None (no filter) (ISSUE-148) Seven AssetMaker action/process queries (find_processes, get_processes_by_category, get_action_targets, get_actions_for_action_target, get_assigned_actions, get_actions_for_sponsor, get_actions_for_requester) defaulted to ["IN_PROGRESS"], which silently dropped journal entries and every finished action. _async_find_processes defaulted to [] while its sync wrapper used ["IN_PROGRESS"], so the two disagreed. All 14 signatures now default to None. Behaviour change: callers that want only in-progress actions must pass ["IN_PROGRESS"] explicitly. No known caller relies on the old default (checked with the egeria-workspaces-fs and trellis sessions). Noted for the 6.2.3 release notes in BACKLOG.md. Signed-off-by: Dan Wolfson <dan.wolfson@pdr-associates.com> * docs(issues): log ISSUE-149/150 (analytic manifest fields, Analytic Function Dr.Egeria family); 137/141 shipped in 6.2.2 Handed off by the Resource Explorer PR/CI session on the owner's instruction, from trellis' analytic walking-skeleton brief. A1-A5 of the handoff were already logged as ISSUE-137..141 (137 and 141 are fixed and released in 6.2.2). Signed-off-by: Dan Wolfson <dan.wolfson@pdr-associates.com> * docs(issues): record ISSUE-149 design answers from the RE handoff Signed-off-by: Dan Wolfson <dan.wolfson@pdr-associates.com> * docs(issues): log ISSUE-151 (get_engine_actions calls a View Server path that doesn't exist) Reported by the RE PR/CI session; confirmed from AutomatedCurationResource and a read-only live check. find_engine_actions("*") is the working route. Signed-off-by: Dan Wolfson <dan.wolfson@pdr-associates.com> * fix(automated-curation): remove get_engine_actions; no View Server endpoint (ISSUE-151) get_engine_actions posted to automated-curation/engine-actions, which AutomatedCurationResource does not map, so it always failed with a 404 (reported as "unable to connect"). EngineAction is an Asset in 6.2, so find_engine_actions("*") is the list-all; print_engine_actions now uses it. No callers outside egeria-python (checked egeria-workspaces-fs and trellis). API removal noted for the 6.2.3 release notes. Signed-off-by: Dan Wolfson <dan.wolfson@pdr-associates.com> * feat(analytics): result envelope, chart gating, param binders, manifest fields (ISSUE-138/139/140/149) Requested by trellis-analytics (the Analytic Library walking skeleton): - ISSUE-149: AnalyticFunctionSpec gains optional kind (routine|step), source (open str; unknown values warn), version, envelope, annotation_type, resource_types, family and tags. Existing entries and trellis's manifest subclasses validate unchanged. - ISSUE-139: the executor reads a result's state envelope (several carrier shapes) and returns it on every analytic output; charts get it as a subtitle. - ISSUE-140: with an envelope, SERIES/BAR/PIE draw a chart only when the state is measured; otherwise the data and the state come back. - ISSUE-138: ActionParameter.analytic_binders builds a non-JSON argument (e.g. a reader) from call params via a named factory, so a routine can be named by its own dotted path. Results without an envelope behave exactly as before. Signed-off-by: Dan Wolfson <dan.wolfson@pdr-associates.com> * fix(analytics): chart nothing_found as well as measured (ISSUE-140) nothing_found is an established answer (a real zero), so a chart of zeros is honest; only not_established / not_applicable, whose numbers are unknown, are never charted. Requested by the RE PR/CI session for trellis-analytics, whose tile already charts nothing_found. Signed-off-by: Dan Wolfson <dan.wolfson@pdr-associates.com> * docs(issues): rewrite ISSUE-150 with the agreed direction Declaration as a GovernanceActionType that Resource Explorer runs as an engine host (async, recorded by Egeria); synchronous in-process runs for screens and reports bypass Egeria and are not recorded. Signed-off-by: Dan Wolfson <dan.wolfson@pdr-associates.com> * docs(issues): ISSUE-150 - RE engine-host answers and the Dr.Egeria gaps they expose Signed-off-by: Dan Wolfson <dan.wolfson@pdr-associates.com> * docs(issues): ISSUE-150 - three requirements from RE's design session Signed-off-by: Dan Wolfson <dan.wolfson@pdr-associates.com> --------- Signed-off-by: Dan Wolfson <dan.wolfson@pdr-associates.com>
1 parent 0d0d553 commit cd57aa6

13 files changed

Lines changed: 727 additions & 192 deletions

‎BACKLOG.md‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@ Once the Egeria release with the delete fixes is published and the local quickst
1616
2. **Other delete issues** to re-check at the same time: ISSUE-117 (Memento cascade delete), ISSUE-125 (`deleteEnumDef`) and ISSUE-132 (`cascadedDelete` field name).
1717
Also ISSUE-147, the valid-metadata clears: re-run the ISSUE-147 sequence (setup then clear for a value, a map name and a map value on a made-up property). Expect no 400-011 error after a successful clear, and expect the map value to actually be removed.
1818
3. Update each `PYEGERIA_ISSUES.md` entry with the result, then do the held 6.2.3 release. The owner wants outstanding issues fixed before releasing.
19+
**The 6.2.3 release notes must list this behaviour change:** ISSUE-148. `AssetMaker.find_processes`, `get_processes_by_category`, `get_action_targets`, `get_actions_for_action_target`, `get_assigned_actions`, `get_actions_for_sponsor` and `get_actions_for_requester` now default `activity_status_list` to `None` (no filter) instead of `["IN_PROGRESS"]`. A caller that relied on getting only in-progress actions by default now also gets completed, cancelled and status-less actions (e.g. journal entries), so it should pass `["IN_PROGRESS"]` explicitly.
20+
**Also list this API removal:** ISSUE-151. `AutomatedCuration.get_engine_actions()` is removed, because the View Server has no list-all endpoint for it (it always got a 404). Use `find_engine_actions("*")` instead.
1921

2022
---
2123

‎PYEGERIA_ISSUES.md‎

Lines changed: 229 additions & 53 deletions
Large diffs are not rendered by default.

‎docs/output-formats-and-report-specs.md‎

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -957,12 +957,40 @@ A `FormatSet` can declare either path, or both (in which case the analytic path
957957
958958
All three chart formats are dispatched *before* the normal Format-row lookup (`exec_report_spec`), since no `FormatSet` needs to declare a `SERIES`/`BAR`/`PIE` `Format` row for this to work — only `DICT`/`JSON` (or whatever find-path formats it also supports) need a declared row.
959959
960+
**Result state (the envelope).** A routine can say how sure it is by returning a state with its result: `measured`, `nothing_found`, `not_established` or `not_applicable`, plus a reason. The executor recognises a dict with `state`/`state_reason` (and an optional ready-made `subtitle`), a dict with `resultState`/`resultStateReason`, a nested `envelope` dict in either style, or an object whose `envelope` attribute has `result_state`/`result_state_reason`. When it finds one:
961+
- every output shape gets `"envelope": {"state", "reason", "subtitle"}` beside `kind`/`data`;
962+
- a chart gets the subtitle on it (`title: {"text", "subtitle"}`);
963+
- **a chart is drawn only for an established answer: `measured`, or `nothing_found`** (the routine read the record and the count really is zero, so a chart of zeros is honest). For `not_established` and `not_applicable` the numbers aren't known, so the caller gets the data and the state instead; zeros there would read as an answer.
964+
A result without an envelope is returned exactly as before. A result that is a pydantic model is returned as plain data (`model_dump`). See ISSUE-139/140.
965+
966+
**Building non-JSON arguments (`analytic_binders`).** Report-spec params are JSON, and only leading parameters named `mgr`/`ce`/`expert`/`client` get an Egeria client. To name a routine that takes some other object (e.g. a reader) by its own dotted path, map the parameter to a factory:
967+
968+
```python
969+
ActionParameter(
970+
analytic_function="trellis_analytics.routines.curation_coverage",
971+
analytic_binders={"ods": "trellis_analytics.readers.ods.OdsReader"},
972+
analytic_spec_params={"resource_type": "database"},
973+
)
974+
```
975+
976+
Here `OdsReader(url)` takes `url`, so the caller passes `url` and the routine receives the built reader as `ods`. The factory is called with the call params its own signature names, and those params are consumed unless the routine also takes them. A routine with no client parameter still connects to nothing (ISSUE-137, ISSUE-138).
977+
960978
**`analytic_spec_params` are defaults, not pins.** Unlike the find path's `spec_params` (always fixed — a caller-supplied value for the same key is silently overridden), `analytic_spec_params` set a *default* that a caller-supplied value for the same key overrides. This is what lets a report spec like the demo "Element Count by Type" (which pins `type_name: "GlossaryTerm"` as its default) actually be re-pointed at a different type at run time instead of being permanently locked to the demo's own default.
961979
962980
**The analytic function registry** (`pyegeria/view/analytic_registry.py`) is a separate, hand-written catalog of every analytic function available to point `analytic_function` at — `AnalyticFunctionSpec` entries carry `function` (dotted import path), `description`, `returns` (result shape), `params`, and a `generic: bool` flag with a `binding_note`:
963981
- `generic=True` — what the function counts/aggregates is itself a parameter (e.g. `count_elements(type_name=...)`), freely retargetable at any element type via `analytic_spec_params`.
964982
- `generic=False` — the vocabulary is hardcoded in the function body (e.g. `governed_coverage` always counts a fixed set of governance classifications); a report spec can still point at it, but always gets that one fixed metric.
965983
984+
**Manifest fields (ISSUE-149).** `AnalyticFunctionSpec` also carries optional fields for analytic libraries such as trellis-analytics. All have defaults, so existing entries are unchanged:
985+
- `kind`: `routine` (runs in the caller's process, the default) or `step` (a survey-style step);
986+
- `source`: where the data comes from, as free text. Known values are `ods`, `egeria` and `resource`; any other value logs a warning and is kept;
987+
- `version`;
988+
- `envelope: bool` (the result carries a state, see above);
989+
- `annotation_type` (a step's result type);
990+
- `resource_types`;
991+
- `family`: the same word and meaning as `FormatSet.family`; in Egeria a family is a Collection;
992+
- `tags`.
993+
966994
`get_analytic_registry()` combines a `BUILTINS` tier (the 10 `overview_metrics` functions shipped today) with optional `CONFIG`/`RUNTIME` tiers, mirroring `base_report_formats.py`'s report-spec registry shape.
967995
968996
**Demo report specs** (`pyegeria/view/analytic_demo_specs.py`) — one real, executable `FormatSet` per registered analytic function, proving each is actually runnable as a report spec (not just documented in the registry). Named `Analytic Demo - <Heading>`, family `"Analytic Function Demo"`. Each spec's `description` states plainly whether the underlying function is generic or a fixed metric (pulled from the registry, not duplicated by hand).

‎docs/parameter_cleanup_plan.md‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -340,7 +340,7 @@ async def _async_get_xxx_by_guid(
340340
- **Planned**: TODO
341341
- [ ] `get_create_csv_data_file_element_from_template(self, file_name, file_type, file_path_name, version_identifier, file_encoding='UTF-8', file_extension='csv', file_system_name=None, description=None)`
342342
- **Planned**: TODO
343-
- [ ] `get_engine_actions(self, start_from=0, page_size=0, output_format='JSON', report_spec='EngineAction', body=None)`
343+
- [x] ~~`get_engine_actions(...)`~~ removed (ISSUE-151): no View Server endpoint; use `find_engine_actions("*")`
344344
- **Planned**: TODO
345345
- [x] `get_engine_actions_by_name(self, name, start_from=0, page_size=0, output_format='JSON', report_spec='EngineAction', body=None)`
346346
- **Planned**: Standard Get By Name Pattern

‎pyegeria/omvs/asset_maker.py‎

Lines changed: 28 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -3781,7 +3781,7 @@ def detach_software_capability_from_asset(
37813781
async def _async_find_processes(
37823782
self,
37833783
search_string: str = "*",
3784-
activity_status_list: list[str] = [],
3784+
activity_status_list: Optional[list[str]] = None,
37853785
starts_with: bool = True,
37863786
ends_with: bool = False,
37873787
ignore_case: bool = True,
@@ -3814,7 +3814,7 @@ async def _async_find_processes(
38143814
----------
38153815
search_string: str, default = "*"
38163816
- search string for process properties
3817-
activity_status_list: list[str], default = []
3817+
activity_status_list: list[str], default = None (no filter: every activity status, including none)
38183818
- optional activity status list to filter by
38193819
starts_with: bool, default = True
38203820
ends_with: bool, default = False
@@ -3881,7 +3881,7 @@ async def _async_find_processes(
38813881
def find_processes(
38823882
self,
38833883
search_string: str = "*",
3884-
activity_status_list: list[str] = ["IN_PROGRESS"],
3884+
activity_status_list: Optional[list[str]] = None,
38853885
starts_with: bool = True,
38863886
ends_with: bool = False,
38873887
ignore_case: bool = True,
@@ -3914,7 +3914,7 @@ def find_processes(
39143914
----------
39153915
search_string: str, default = "*"
39163916
- the search string to use to find matching processes
3917-
activity_status_list: list[str], default = ["IN_PROGRESS"]
3917+
activity_status_list: list[str], default = None (no filter: every activity status, including none)
39183918
- optional activity status list to filter by
39193919
starts_with: bool, default = True
39203920
- if True, the search string must match the start of the property value
@@ -3988,7 +3988,7 @@ def find_processes(
39883988
async def _async_get_processes_by_category(
39893989
self,
39903990
category: str,
3991-
activity_status_list: list[str] = ["IN_PROGRESS"],
3991+
activity_status_list: Optional[list[str]] = None,
39923992
graph_query_depth: int = 3,
39933993
start_from: int = 0,
39943994
page_size: int = 0,
@@ -4003,7 +4003,7 @@ async def _async_get_processes_by_category(
40034003
Parameters
40044004
----------
40054005
category: str
4006-
activity_status_list: list[str], default = ["IN_PROGRESS"]
4006+
activity_status_list: list[str], default = None (no filter: every activity status, including none)
40074007
graph_query_depth: int, default = 3
40084008
- depth for relationship traversal
40094009
start_from: int, default = 0
@@ -4048,7 +4048,7 @@ async def _async_get_processes_by_category(
40484048
def get_processes_by_category(
40494049
self,
40504050
category: str,
4051-
activity_status_list: list[str] = ["IN_PROGRESS"],
4051+
activity_status_list: Optional[list[str]] = None,
40524052
graph_query_depth: int = 3,
40534053
start_from: int = 0,
40544054
page_size: int = 0,
@@ -4064,7 +4064,7 @@ def get_processes_by_category(
40644064
----------
40654065
category: str
40664066
- category name to filter by
4067-
activity_status_list: list[str], default = ["IN_PROGRESS"]
4067+
activity_status_list: list[str], default = None (no filter: every activity status, including none)
40684068
- optional activity status list to filter by
40694069
graph_query_depth: int, default = 3
40704070
- depth for relationship traversal
@@ -4526,7 +4526,7 @@ def get_action_target(
45264526
async def _async_get_action_targets(
45274527
self,
45284528
action_guid: str,
4529-
activity_status_list: list[str] = ["IN_PROGRESS"],
4529+
activity_status_list: Optional[list[str]] = None,
45304530
graph_query_depth: int = 3,
45314531
start_from: int = 0,
45324532
page_size: int = 0,
@@ -4544,7 +4544,7 @@ async def _async_get_action_targets(
45444544
Parameters
45454545
----------
45464546
action_guid: str
4547-
activity_status_list: list[str], default = ["IN_PROGRESS"]
4547+
activity_status_list: list[str], default = None (no filter: every activity status, including none)
45484548
graph_query_depth: int, default = 3
45494549
- depth for relationship traversal
45504550
body: dict | ActivityStatusRequestBody, optional
@@ -4577,7 +4577,7 @@ async def _async_get_action_targets(
45774577
def get_action_targets(
45784578
self,
45794579
action_guid: str,
4580-
activity_status_list: list[str] = ["IN_PROGRESS"],
4580+
activity_status_list: Optional[list[str]] = None,
45814581
graph_query_depth: int = 3,
45824582
start_from: int = 0,
45834583
page_size: int = 0,
@@ -4595,7 +4595,7 @@ def get_action_targets(
45954595
Parameters
45964596
----------
45974597
action_guid: str
4598-
activity_status_list: list[str], default = ["IN_PROGRESS"]
4598+
activity_status_list: list[str], default = None (no filter: every activity status, including none)
45994599
graph_query_depth: int, default = 3
46004600
- depth for relationship traversal
46014601
start_from: int, default = 0
@@ -4641,7 +4641,7 @@ def get_action_targets(
46414641
async def _async_get_actions_for_action_target(
46424642
self,
46434643
metadata_element_guid: str,
4644-
activity_status_list: list[str] = ["IN_PROGRESS"],
4644+
activity_status_list: Optional[list[str]] = None,
46454645
graph_query_depth: int = 3,
46464646
start_from: int = 0,
46474647
page_size: int = 0,
@@ -4659,7 +4659,7 @@ async def _async_get_actions_for_action_target(
46594659
Parameters
46604660
----------
46614661
metadata_element_guid: str
4662-
activity_status_list: list[str], default = ["IN_PROGRESS"]
4662+
activity_status_list: list[str], default = None (no filter: every activity status, including none)
46634663
graph_query_depth: int, default = 3
46644664
- depth for relationship traversal
46654665
body: dict | ActivityStatusRequestBody, optional
@@ -4692,7 +4692,7 @@ async def _async_get_actions_for_action_target(
46924692
def get_actions_for_action_target(
46934693
self,
46944694
metadata_element_guid: str,
4695-
activity_status_list: list[str] = ["IN_PROGRESS"],
4695+
activity_status_list: Optional[list[str]] = None,
46964696
graph_query_depth: int = 3,
46974697
start_from: int = 0,
46984698
page_size: int = 0,
@@ -4710,7 +4710,7 @@ def get_actions_for_action_target(
47104710
Parameters
47114711
----------
47124712
metadata_element_guid: str
4713-
activity_status_list: list[str], default = ["IN_PROGRESS"]
4713+
activity_status_list: list[str], default = None (no filter: every activity status, including none)
47144714
graph_query_depth: int, default = 3
47154715
- depth for relationship traversal
47164716
start_from: int, default = 0
@@ -4925,7 +4925,7 @@ def unassign_action(
49254925
async def _async_get_assigned_actions(
49264926
self,
49274927
actor_guid: str,
4928-
activity_status_list: list[str] = ["IN_PROGRESS"],
4928+
activity_status_list: Optional[list[str]] = None,
49294929
graph_query_depth: int = 3,
49304930
start_from: int = 0,
49314931
page_size: int = 0,
@@ -4943,7 +4943,7 @@ async def _async_get_assigned_actions(
49434943
Parameters
49444944
----------
49454945
actor_guid: str
4946-
activity_status_list: list[str], default = ["IN_PROGRESS"]
4946+
activity_status_list: list[str], default = None (no filter: every activity status, including none)
49474947
graph_query_depth: int, default = 3
49484948
- depth for relationship traversal
49494949
body: dict | ActivityStatusRequestBody, optional
@@ -4976,7 +4976,7 @@ async def _async_get_assigned_actions(
49764976
def get_assigned_actions(
49774977
self,
49784978
actor_guid: str,
4979-
activity_status_list: list[str] = ["IN_PROGRESS"],
4979+
activity_status_list: Optional[list[str]] = None,
49804980
graph_query_depth: int = 3,
49814981
start_from: int = 0,
49824982
page_size: int = 0,
@@ -4994,7 +4994,7 @@ def get_assigned_actions(
49944994
Parameters
49954995
----------
49964996
actor_guid: str
4997-
activity_status_list: list[str], default = ["IN_PROGRESS"]
4997+
activity_status_list: list[str], default = None (no filter: every activity status, including none)
49984998
graph_query_depth: int, default = 3
49994999
- depth for relationship traversal
50005000
start_from: int, default = 0
@@ -5040,7 +5040,7 @@ def get_assigned_actions(
50405040
async def _async_get_actions_for_sponsor(
50415041
self,
50425042
metadata_element_guid: str,
5043-
activity_status_list: list[str] = ["IN_PROGRESS"],
5043+
activity_status_list: Optional[list[str]] = None,
50445044
graph_query_depth: int = 3,
50455045
start_from: int = 0,
50465046
page_size: int = 0,
@@ -5058,7 +5058,7 @@ async def _async_get_actions_for_sponsor(
50585058
Parameters
50595059
----------
50605060
metadata_element_guid: str
5061-
activity_status_list: list[str], default = ["IN_PROGRESS"]
5061+
activity_status_list: list[str], default = None (no filter: every activity status, including none)
50625062
graph_query_depth: int, default = 3
50635063
- depth for relationship traversal
50645064
body: dict | ActivityStatusRequestBody, optional
@@ -5091,7 +5091,7 @@ async def _async_get_actions_for_sponsor(
50915091
def get_actions_for_sponsor(
50925092
self,
50935093
metadata_element_guid: str,
5094-
activity_status_list: list[str] = ["IN_PROGRESS"],
5094+
activity_status_list: Optional[list[str]] = None,
50955095
graph_query_depth: int = 3,
50965096
start_from: int = 0,
50975097
page_size: int = 0,
@@ -5109,7 +5109,7 @@ def get_actions_for_sponsor(
51095109
Parameters
51105110
----------
51115111
metadata_element_guid: str
5112-
activity_status_list: list[str], default = ["IN_PROGRESS"]
5112+
activity_status_list: list[str], default = None (no filter: every activity status, including none)
51135113
graph_query_depth: int, default = 3
51145114
- depth for relationship traversal
51155115
start_from: int, default = 0
@@ -5155,7 +5155,7 @@ def get_actions_for_sponsor(
51555155
async def _async_get_actions_for_requester(
51565156
self,
51575157
metadata_element_guid: str,
5158-
activity_status_list: list[str] = ["IN_PROGRESS"],
5158+
activity_status_list: Optional[list[str]] = None,
51595159
graph_query_depth: int = 3,
51605160
start_from: int = 0,
51615161
page_size: int = 0,
@@ -5173,7 +5173,7 @@ async def _async_get_actions_for_requester(
51735173
Parameters
51745174
----------
51755175
metadata_element_guid: str
5176-
activity_status_list: list[str], default = ["IN_PROGRESS"]
5176+
activity_status_list: list[str], default = None (no filter: every activity status, including none)
51775177
graph_query_depth: int, default = 3
51785178
- depth for relationship traversal
51795179
body: dict | ActivityStatusRequestBody, optional
@@ -5206,7 +5206,7 @@ async def _async_get_actions_for_requester(
52065206
def get_actions_for_requester(
52075207
self,
52085208
metadata_element_guid: str,
5209-
activity_status_list: list[str] = ["IN_PROGRESS"],
5209+
activity_status_list: Optional[list[str]] = None,
52105210
graph_query_depth: int = 3,
52115211
start_from: int = 0,
52125212
page_size: int = 0,
@@ -5224,7 +5224,7 @@ def get_actions_for_requester(
52245224
Parameters
52255225
----------
52265226
metadata_element_guid: str
5227-
activity_status_list: list[str], default = ["IN_PROGRESS"]
5227+
activity_status_list: list[str], default = None (no filter: every activity status, including none)
52285228
graph_query_depth: int, default = 3
52295229
- depth for relationship traversal
52305230
start_from: int, default = 0

0 commit comments

Comments
 (0)