Skip to content

Commit ac1607d

Browse files
committed
Insert intermediary Compilation classes between Investigation and UcoObject
One effect reflected back into the example script is that `Compilation`, `ContextualCompilation`, and their subclasses now must be initialized with at least one object `UcoObject` linked in the `core_objects` keyword parameter. Documentation is added to note this is to guarantee minimum cardinality constraints in the ontology are always satisfied. A follow-on patch will regenerate Make-managed files. Signed-off-by: Alex Nelson <[email protected]>
1 parent 223fd5a commit ac1607d

File tree

3 files changed

+78
-17
lines changed

3 files changed

+78
-17
lines changed

case_mapping/case/investigation.py

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55

66
from ..base import Facet, UcoObject
77
from ..uco.action import Action
8+
from ..uco.core import ContextualCompilation
89
from ..uco.location import Location
910

1011

@@ -46,10 +47,8 @@ def __init__(
4647
self["@type"] = "case-investigation:InvestigativeAction"
4748

4849

49-
class CaseInvestigation(UcoObject):
50-
def __init__(
51-
self, *args: Any, focus=None, core_objects=None, **kwargs: Any
52-
) -> None:
50+
class CaseInvestigation(ContextualCompilation):
51+
def __init__(self, *args: Any, focus=None, **kwargs: Any) -> None:
5352
"""
5453
An investigative action is a CASE object that represents the who, where, when of investigation
5554
:param name: The name of an investigation (e.g., Murder of Suspect B,.)
@@ -66,7 +65,6 @@ def __init__(
6665
"case-investigation:focus": focus,
6766
}
6867
)
69-
self.append_core_objects(core_objects)
7068

7169

7270
class ProvenanceRecord(UcoObject):

case_mapping/uco/core.py

Lines changed: 74 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,85 @@
11
from datetime import datetime
2-
from typing import Any, List, Optional, Union
2+
from typing import Any, Optional, Sequence, Union
33

44
from pytz import timezone
55

66
from ..base import UcoObject, unpack_args_array
77

88

9-
class Bundle(UcoObject):
9+
class Compilation(UcoObject):
10+
def __init__(
11+
self,
12+
*args: Any,
13+
core_objects: Optional[Sequence[UcoObject]] = None,
14+
**kwargs: Any,
15+
) -> None:
16+
"""
17+
A compilation is a grouping of things.
18+
"""
19+
super().__init__(*args, **kwargs)
20+
self["@type"] = "uco-core:Compilation"
21+
if core_objects is not None and len(core_objects) > 0:
22+
self.append_core_objects(core_objects)
23+
24+
@unpack_args_array
25+
def append_to_uco_object(self, *args) -> None:
26+
"""
27+
Add a single/tuple of result(s) to the list of outputs from an action
28+
:param args: A CASE object, or objects, often an observable. (e.g., one of many devices from a search operation)
29+
"""
30+
self._append_observable_objects("uco-core:object", *args)
31+
32+
33+
class ContextualCompilation(Compilation):
34+
def __init__(
35+
self,
36+
*args: Any,
37+
core_objects: Sequence[UcoObject],
38+
**kwargs: Any,
39+
) -> None:
40+
"""
41+
A contextual compilation is a grouping of things sharing some context (e.g., a set of network connections observed on a given day, all accounts associated with a given person).
42+
43+
Future implementation note: At and before CASE 1.3.0, at least one core:object must be supplied at instantiation time of a contextual compilation. At and after CASE 1.4.0, these objects will be optional.
44+
"""
45+
if len(core_objects) == 0:
46+
raise ValueError(
47+
"A ContextualCompilation is required to have at least one UcoObject to link at initiation time. This will become optional in CASE 1.4.0."
48+
)
49+
super().__init__(*args, **kwargs)
50+
self["@type"] = "uco-core:ContextualCompilation"
51+
self.append_core_objects(core_objects)
52+
53+
54+
class EnclosingCompilation(Compilation):
55+
def __init__(
56+
self,
57+
*args: Any,
58+
core_objects: Sequence[UcoObject],
59+
**kwargs: Any,
60+
) -> None:
61+
"""
62+
An enclosing compilation is a container for a grouping of things.
63+
"""
64+
if len(core_objects) == 0:
65+
raise ValueError(
66+
"An EnclosingCompilation is required to have at least one UcoObject to link at initiation time."
67+
)
68+
super().__init__(*args, **kwargs)
69+
self["@type"] = "uco-core:EnclosingCompilation"
70+
self.append_core_objects(core_objects)
71+
72+
73+
class Bundle(EnclosingCompilation):
1074
def __init__(
1175
self,
1276
*args: Any,
1377
**kwargs: Any,
1478
) -> None:
1579
"""
1680
The main CASE Object for representing a case and its activities and objects.
81+
82+
Instantiating this class requires a starter sequence (set, list, or tuple) to be passed using the core_objects parameter. (See EnclosingCompilation.) To confirm conformant CASE will be generated, at least one UcoObject must be passed in this list. However, this does not initially need to be the complete sequence of objects that will be in this Bundle. Other UcoObjects can be added after initialization with bundle.append_to_uco_object.
1783
"""
1884
super().__init__(*args, **kwargs)
1985
self.build = [] # type: ignore
@@ -39,6 +105,7 @@ def __init__(
39105
# Assign caller-selectible prefix label and IRI, after checking
40106
# for conflicts with hard-coded prefixes.
41107
# https://www.w3.org/TR/turtle/#prefixed-name
108+
assert isinstance(self["@context"], dict)
42109
if self.prefix_label in self["@context"]:
43110
raise ValueError(
44111
"Requested prefix label already in use in hard-coded dictionary: '%s'. Please revise caller to use another label."
@@ -51,14 +118,6 @@ def __init__(
51118
def append_to_case_graph(self, *args):
52119
self._append_observable_objects("@graph", *args)
53120

54-
@unpack_args_array
55-
def append_to_uco_object(self, *args):
56-
"""
57-
Add a single/tuple of result(s) to the list of outputs from an action
58-
:param args: A CASE object, or objects, often an observable. (e.g., one of many devices from a search operation)
59-
"""
60-
self._append_observable_objects("uco-core:object", *args)
61-
62121
@unpack_args_array
63122
def append_to_rdfs_comments(self, *args):
64123
self._append_strings("rdfs:comment", *args)
@@ -119,4 +178,8 @@ def _addtime(self, _type: str) -> None:
119178
}
120179

121180

122-
directory = {"uco-core:Bundle": Bundle}
181+
directory = {
182+
"uco-core:Bundle": Bundle,
183+
"uco-core:Compilation": Compilation,
184+
"uco-core:ContextualCompilation": ContextualCompilation,
185+
}

example.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ def _next_timestamp() -> datetime:
3939
bundle_modified_time = datetime.strptime("2024-05-02T21:38:19", "%Y-%m-%dT%H:%M:%S")
4040

4141
bundle = uco.core.Bundle(
42+
core_objects=[bundle_identity],
4243
created_by=bundle_identity,
4344
description="An Example Case File",
4445
modified_time=bundle_modified_time,
@@ -47,7 +48,6 @@ def _next_timestamp() -> datetime:
4748
spec_version="UCO/CASE 1.3",
4849
tag="Artifacts extracted from a mobile phone",
4950
)
50-
bundle.append_to_uco_object(bundle_identity)
5151

5252
investigation_items: list[base.UcoObject] = []
5353

0 commit comments

Comments
 (0)