heurist-api 0.1.2__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.

Potentially problematic release.


This version of heurist-api might be problematic. Click here for more details.

Files changed (80) hide show
  1. heurist/__init__.py +1 -0
  2. heurist/api/__init__.py +0 -0
  3. heurist/api/client.py +122 -0
  4. heurist/api/connection.py +71 -0
  5. heurist/api/constants.py +19 -0
  6. heurist/api/credentials.py +71 -0
  7. heurist/api/exceptions.py +45 -0
  8. heurist/api/url_builder.py +148 -0
  9. heurist/api/utils.py +24 -0
  10. heurist/cli/__init__.py +0 -0
  11. heurist/cli/__main__.py +227 -0
  12. heurist/cli/load.py +55 -0
  13. heurist/cli/records.py +49 -0
  14. heurist/cli/schema.py +94 -0
  15. heurist/database/__init__.py +3 -0
  16. heurist/database/basedb.py +125 -0
  17. heurist/database/database.py +96 -0
  18. heurist/models/__init__.py +0 -0
  19. heurist/models/dynamic/__init__.py +3 -0
  20. heurist/models/dynamic/annotation.py +143 -0
  21. heurist/models/dynamic/create_model.py +82 -0
  22. heurist/models/dynamic/date.py +61 -0
  23. heurist/models/dynamic/type.py +96 -0
  24. heurist/models/structural/DetailTypes.py +34 -0
  25. heurist/models/structural/RecStructure.py +27 -0
  26. heurist/models/structural/RecTypeGroups.py +27 -0
  27. heurist/models/structural/RecTypes.py +27 -0
  28. heurist/models/structural/Terms.py +27 -0
  29. heurist/models/structural/__init__.py +19 -0
  30. heurist/models/structural/dty.py +121 -0
  31. heurist/models/structural/hml_structure.py +36 -0
  32. heurist/models/structural/rst.py +141 -0
  33. heurist/models/structural/rtg.py +25 -0
  34. heurist/models/structural/rty.py +81 -0
  35. heurist/models/structural/trm.py +34 -0
  36. heurist/models/structural/utils.py +53 -0
  37. heurist/schema/__init__.py +27 -0
  38. heurist/schema/models.py +70 -0
  39. heurist/schema/rel_to_dict.py +39 -0
  40. heurist/sql/__init__.py +21 -0
  41. heurist/sql/joinRecordTypeIDNameByGroupType.sql +10 -0
  42. heurist/sql/joinRecordTypeMetadata.sql +17 -0
  43. heurist/sql/selectRecordTypeSchema.sql +51 -0
  44. heurist/sql/sql_safety.py +101 -0
  45. heurist/utils/constants.py +1 -0
  46. heurist/utils/rel_to_dict_array.py +8 -0
  47. heurist/validators/__init__.py +3 -0
  48. heurist/validators/detail_validator.py +142 -0
  49. heurist/validators/exceptions.py +34 -0
  50. heurist/validators/parse_heurist_date.py +71 -0
  51. heurist/validators/record_validator.py +156 -0
  52. heurist/workflows/__init__.py +3 -0
  53. heurist/workflows/etl.py +66 -0
  54. heurist_api-0.1.2.dist-info/METADATA +453 -0
  55. heurist_api-0.1.2.dist-info/RECORD +80 -0
  56. heurist_api-0.1.2.dist-info/WHEEL +4 -0
  57. heurist_api-0.1.2.dist-info/entry_points.txt +2 -0
  58. heurist_api-0.1.2.dist-info/licenses/LICENSE +427 -0
  59. mock_data/__init__.py +22 -0
  60. mock_data/blocktext/__init__.py +0 -0
  61. mock_data/blocktext/single.py +7 -0
  62. mock_data/date/__init__.py +0 -0
  63. mock_data/date/compound_repeated.py +44 -0
  64. mock_data/date/compound_single.py +30 -0
  65. mock_data/date/simple_single.py +16 -0
  66. mock_data/date/timestamp_repeated.py +30 -0
  67. mock_data/enum/__init__.py +0 -0
  68. mock_data/enum/repeated.py +29 -0
  69. mock_data/enum/single.py +18 -0
  70. mock_data/file/__init__.py +0 -0
  71. mock_data/file/single.py +28 -0
  72. mock_data/float/__init__.py +0 -0
  73. mock_data/float/single.py +8 -0
  74. mock_data/freetext/__init__.py +0 -0
  75. mock_data/freetext/single.py +16 -0
  76. mock_data/geo/__init__.py +0 -0
  77. mock_data/geo/single.py +22 -0
  78. mock_data/resource/__init__.py +0 -0
  79. mock_data/resource/repeated.py +35 -0
  80. mock_data/resource/single.py +16 -0
@@ -0,0 +1,36 @@
1
+ from heurist.models.structural.DetailTypes import (
2
+ DetailTypes as DetailTypesModel,
3
+ )
4
+ from heurist.models.structural.RecStructure import (
5
+ RecStructure as RecStructureModel,
6
+ )
7
+ from heurist.models.structural.RecTypeGroups import (
8
+ RecTypeGroups as RecTypeGroupsModel,
9
+ )
10
+ from heurist.models.structural.RecTypes import RecTypes as RecTypesModel
11
+ from heurist.models.structural.Terms import Terms as TermsModel
12
+ from pydantic_xml import BaseXmlModel, element
13
+
14
+
15
+ class HMLStructure(BaseXmlModel, tag="hml_structure", search_mode="unordered"):
16
+ """Parent dataclass forr modeling the entire Heurist database structure.
17
+
18
+ Attributes:
19
+ detail_types (DetailTypes): model for data nested in the DetailTypes tag.
20
+ record_structures (RecStructure): model for data nested in the RecStructure tag.
21
+ record_types (RecTypes): model for data nested in the RecTypes tag.
22
+
23
+ Examples:
24
+ >>> from mock_data import DB_STRUCTURE_XML
25
+ >>>
26
+ >>>
27
+ >>> # Parse structure
28
+ >>> xml = DB_STRUCTURE_XML
29
+ >>> hml = HMLStructure.from_xml(xml)
30
+ """
31
+
32
+ DetailTypes: DetailTypesModel = element(tag="DetailTypes")
33
+ RecStructure: RecStructureModel = element(tag="RecStructure")
34
+ RecTypes: RecTypesModel = element(tag="RecTypes")
35
+ RecTypeGroups: RecTypeGroupsModel = element(tag="RecTypeGroups")
36
+ Terms: TermsModel = element(tag="Terms")
@@ -0,0 +1,141 @@
1
+ from datetime import datetime
2
+ from typing import Literal, Optional
3
+
4
+ from pydantic_xml import BaseXmlModel, element
5
+
6
+
7
+ class RST(BaseXmlModel, tag="rst", search_mode="unordered"):
8
+ """Dataclass to model one of the database's Record Structures. Record Structures
9
+ are the fields of a Record Type.
10
+
11
+ When possible, the attribute descriptions are taken from Heurist's source code.
12
+
13
+ Attributes:
14
+ rst_ID (int): Primary key for the record structures table
15
+ rst_RecTypeID (int): The record type to which this detail is allocated, \
16
+ 0 = all rectypes
17
+ rst_DetailTypeID (int): Detail type for this field or, if MSB set, \
18
+ FieldSet code + 32767
19
+ rst_DisplayName (str): Display name for this dtl type in this rectype, \
20
+ autofill with dty_Name
21
+ rst_DisplayHelpText (Optional[str]): The user help text to be displayed for \
22
+ this detail type for this record type
23
+ rst_DisplayExtendedDescription (Optional[str]): The rollover text to be \
24
+ displayed for this detail type for this record type
25
+ rst_DisplayOrder (int): A sort order for display of this detail type in the \
26
+ record edit form
27
+ rst_DisplayWidth (int): The field width displayed for this detail type in \
28
+ this record type
29
+ rst_DisplayHeight (int): The field height for this detail type in this record \
30
+ type, only relevant for memo fields
31
+ rst_DefaultValue (Optional[str]): The default value for this detail type for \
32
+ this record type
33
+ rst_RecordMatchOrder (int): Indicates order of significance in detecting \
34
+ duplicate records, 1 = highest
35
+ rst_CalcFunctionID (Optional[int]): FK to table of function specifications for \
36
+ calculating string values
37
+ rst_CalcFieldMask (Optional[str]): A mask string along the lines of the title \
38
+ mask allowing a composite field to be generated from other fields in the \
39
+ record
40
+ rst_RequirementType (Literal["required", "recommended", "optional", \
41
+ "forbidden"]):
42
+ rst_NonOwnerVisibility (Literal["hidden", "viewable", "public", "pending"]): \
43
+ Allows restriction of visibility of a particular field in a specified \
44
+ record type
45
+ rst_Status (Literal["reserved", "approved", "pending", "open"]): Reserved \
46
+ Heurist codes, approved/pending by ''Board'', and user additions
47
+ rst_MayModify (Literal["locked", "discouraged", "open"]): Extent to which \
48
+ detail may be modified within this record structure
49
+ rst_OriginatingDBID (int): Database where this record structure element \
50
+ originated, 0 = locally
51
+ rst_IDInOriginatingDB (Optional[int]): ID used in database where this record \
52
+ structure element originated
53
+ rst_MaxValues (int): Maximum number of values per record for this detail, 1 - \
54
+ single, >1 limited, NULL or 0 = no limit
55
+ rst_MinValues (int): If required, minimum number of values per record for this \
56
+ detail
57
+ rst_InitialRepeats (int): Number of repeat values to be displayed for this \
58
+ field when a new record is first displayed
59
+ rst_DisplayDetailTypeGroupID (Optional[int]): If set, places detail in \
60
+ specified group instead of according to dty_DetailTypeGroup
61
+ rst_FilteredJsonTermIDTree (Optional[str]): JSON encoded tree of allowed \
62
+ terms, subset of those defined in defDetailType. This field is no \
63
+ longer used
64
+ rst_PtrFilteredIDs (Optional[str]): Allowed Rectypes (CSV) within list defined \
65
+ by defDetailType (for pointer details) This field is no longer used
66
+ rst_CreateChildIfRecPtr (bool): For pointer fields, flags that new records \
67
+ created from this field should be marked as children of the creating record
68
+ rst_PointerMode (Literal["dropdown_add", "dropdown", "addorbrowse", "addonly", \
69
+ "browseonly"]): When adding record pointer values, default or null = show \
70
+ both add and browse, otherwise only allow add or only allow \
71
+ browse-for-existing
72
+ rst_PointerBrowseFilter (Optional[str]): When adding record pointer values, \
73
+ defines a Heurist filter to restrict the list of target records browsed
74
+ rst_OrderForThumbnailGeneration (Optional[str]): Priority order of fields to \
75
+ use in generating thumbnail, null = do not use
76
+ rst_TermIDTreeNonSelectableIDs (Optional[str]): Term IDs to use as \
77
+ non-selectable headers for this field
78
+ rst_ShowDetailCertainty (bool): When editing the field, allow editng of the \
79
+ dtl_Certainty value (off by default)
80
+ rst_ShowDetailAnnotation (bool): When editing the field, allow editng of the \
81
+ dtl_Annotation value (off by default)
82
+ rst_NumericLargestValueUsed (Optional[int]): For numeric fields, Null = \
83
+ no auto increment, 0 or more indicates largest value used so far. \
84
+ Set to 0 to switch on incrementing
85
+ rst_EntryMask (Optional[str]): Data entry mask, use to control decimals on \
86
+ numeric values, content of text fields etc. for this record type - future \
87
+ implementation Aug 2017
88
+ rst_Modified (datetime): Date of last modification of this record, used to get \
89
+ last updated date for table
90
+ rst_LocallyModified (int): Flags a definition element which has been modified \
91
+ relative to the original source
92
+ rst_SemanticReferenceURL (Optional[str]): The URI to a semantic definition or \
93
+ web page describing this field used within this record type
94
+ rst_TermsAsButtons (bool): If 1, term list fields are represented as buttons \
95
+ (if single value) or checkboxes (if repeat values)
96
+ """
97
+
98
+ rst_ID: int = element()
99
+ rst_RecTypeID: int = element()
100
+ rst_DetailTypeID: int = element()
101
+ rst_DisplayName: str = element()
102
+ rst_DisplayHelpText: Optional[str] = element(default=None)
103
+ rst_DisplayExtendedDescription: Optional[str] = element(default=None)
104
+ rst_DisplayOrder: int = element()
105
+ rst_DisplayWidth: int = element()
106
+ rst_DisplayHeight: int = element()
107
+ rst_DefaultValue: Optional[str] = element(default=None)
108
+ rst_RecordMatchOrder: int = element()
109
+ rst_CalcFunctionID: Optional[int] = element(default=None)
110
+ rst_CalcFieldMask: Optional[str] = element(default=None)
111
+ rst_RequirementType: Literal["required", "recommended", "optional", "forbidden"] = (
112
+ element()
113
+ )
114
+ rst_NonOwnerVisibility: Literal["hidden", "viewable", "public", "pending"] = (
115
+ element()
116
+ )
117
+ rst_Status: Literal["reserved", "approved", "pending", "open"] = element()
118
+ rst_MayModify: Literal["locked", "discouraged", "open"] = element()
119
+ rst_OriginatingDBID: int = element()
120
+ rst_IDInOriginatingDB: Optional[int] = element(default=None)
121
+ rst_MaxValues: int = element()
122
+ rst_MinValues: int = element()
123
+ rst_InitialRepeats: int = element()
124
+ rst_DisplayDetailTypeGroupID: Optional[int] = element(default=None)
125
+ rst_FilteredJsonTermIDTree: Optional[str] = element(default=None)
126
+ rst_PtrFilteredIDs: Optional[str] = element(default=None)
127
+ rst_CreateChildIfRecPtr: bool = element()
128
+ rst_PointerMode: Literal[
129
+ "dropdown_add", "dropdown", "addorbrowse", "addonly", "browseonly"
130
+ ] = element()
131
+ rst_PointerBrowseFilter: Optional[str] = element(default=None)
132
+ rst_OrderForThumbnailGeneration: Optional[str] = element(default=None)
133
+ rst_TermIDTreeNonSelectableIDs: Optional[str] = element(default=None)
134
+ rst_ShowDetailCertainty: bool = element()
135
+ rst_ShowDetailAnnotation: bool = element()
136
+ rst_NumericLargestValueUsed: Optional[int] = element(default=None)
137
+ rst_EntryMask: Optional[str] = element(default=None)
138
+ rst_Modified: datetime = element()
139
+ rst_LocallyModified: int = element()
140
+ rst_SemanticReferenceURL: Optional[str] = element(default=None)
141
+ rst_TermsAsButtons: bool = element()
@@ -0,0 +1,25 @@
1
+ from datetime import datetime
2
+ from typing import Optional
3
+
4
+ from pydantic_xml import BaseXmlModel, element
5
+
6
+
7
+ class RTG(BaseXmlModel, tag="rtg", search_mode="unordered"):
8
+ """Dataclass to model one of the database's Record Type Groups. A Record Type \
9
+ Group categorizes the record types in the database.
10
+
11
+ When possible, the attribute descriptions are taken from Heurist's source code.
12
+
13
+ Attributes:
14
+ rtg_ID (int): __Description__.
15
+ rtg_Name (str): __Description__.
16
+ rtg_Domain (str): __Description__.
17
+ rtg_Description (Optional[str]): __Description__.
18
+ rtg_Modified (datetime): __Description__.
19
+ """
20
+
21
+ rtg_ID: int = element()
22
+ rtg_Name: str = element()
23
+ rtg_Domain: str = element()
24
+ rtg_Description: Optional[str] = element(default=None)
25
+ rtg_Modified: datetime = element()
@@ -0,0 +1,81 @@
1
+ from datetime import datetime
2
+ from typing import Literal, Optional
3
+
4
+ from pydantic_xml import BaseXmlModel, element
5
+
6
+
7
+ class RTY(BaseXmlModel, tag="rty", search_mode="unordered"):
8
+ """Dataclass to model one of the database's Record Types. A Record Type is the \
9
+ schema for an entity in the database.
10
+
11
+ When possible, the attribute descriptions are taken from Heurist's source code.
12
+
13
+ Attributes:
14
+ rty_ID (int): Record type code, widely used to reference record types, primary \
15
+ key
16
+ rty_Name (str): The name which is used to describe this record (object) type
17
+ rty_OrderInGroup (int): Ordering within record type display groups for pulldowns
18
+ rty_Description (str): Description of this record type
19
+ rty_TitleMask (str): Mask to build a composite title by combining field values
20
+ rty_CanonicalTitleMask (str): Version of the mask converted to detail codes \
21
+ for processing
22
+ rty_Plural (Optional[str]): Plural form of the record type name, manually \
23
+ entered
24
+ rty_Status (Literal["reserved", "approved", "pending", "open"]): Reserved \
25
+ Heurist codes, approved/pending by ''Board'', and user additions
26
+ rty_OriginatingDBID (Optional[int]): Database where this record type \
27
+ originated, 0 = locally
28
+ rty_NameInOriginatingDB: (Optional[str]) Name used in database where this \
29
+ record type originated
30
+ rty_IDInOriginatingDB (Optional[int]): ID in database where this record type \
31
+ originated
32
+ rty_NonOwnerVisibility (Literal["hidden", "viewable", "public", "pending"]): \
33
+ Allows blanket restriction of visibility of a particular record type
34
+ rty_ShowInLists (bool): Flags if record type is to be shown in end-user \
35
+ interface, 1=yes
36
+ rty_RecTypeGroupID (int): Record type group to which this record type belongs
37
+ rty_RecTypeModelIDs (str): The model group(s) to which this rectype belongs, \
38
+ comma sep. list
39
+ rty_FlagAsFieldset (bool): 0 = full record type, 1 = Fieldset = set of fields \
40
+ to include in other rectypes
41
+ rty_ReferenceURL (Optional[str]): A semantic reference URI for, or a URL \
42
+ describing, the record type
43
+ rty_AlternativeRecEditor (Optional[str]): Name or URL of alternative record \
44
+ editor function to be used for this rectype
45
+ rty_Type (Literal["normal", "relationship", "dummy"]): Use to flag special \
46
+ record types to trigger special functions
47
+ rty_ShowURLOnEditForm (bool): Determines whether special URL field is shown at \
48
+ the top of the edit form
49
+ rty_ShowDescriptionOnEditForm (bool): Determines whether the record type \
50
+ description field is shown at the top of the edit form
51
+ rty_Modified (datetime): Date of last modification of this record, used to get \
52
+ last updated date for table
53
+ rty_LocallyModified (bool): Flags a definition element which has been modified \
54
+ relative to the original source
55
+ """
56
+
57
+ rty_ID: int = element()
58
+ rty_Name: str = element()
59
+ rty_OrderInGroup: int = element()
60
+ rty_Description: str = element()
61
+ rty_TitleMask: str = element()
62
+ rty_CanonicalTitleMask: Optional[str] = element(default=None)
63
+ rty_Plural: Optional[str] = element(default=None)
64
+ rty_Status: Literal["reserved", "approved", "pending", "open"] = element()
65
+ rty_OriginatingDBID: Optional[int] = element(default=None)
66
+ rty_NameInOriginatingDB: Optional[str] = element(default=None)
67
+ rty_IDInOriginatingDB: Optional[int] = element(default=None)
68
+ rty_NonOwnerVisibility: Literal["hidden", "viewable", "public", "pending"] = (
69
+ element()
70
+ )
71
+ rty_ShowInLists: bool = element()
72
+ rty_RecTypeGroupID: int = element()
73
+ rty_RecTypeModelIDs: str = element(default=None)
74
+ rty_FlagAsFieldset: bool = element(default=None)
75
+ rty_ReferenceURL: Optional[str] = element(default=None)
76
+ rty_AlternativeRecEditor: Optional[str] = element(default=None)
77
+ rty_Type: Literal["normal", "relationship", "dummy"] = element()
78
+ rty_ShowURLOnEditForm: bool = element()
79
+ rty_ShowDescriptionOnEditForm: bool = element()
80
+ rty_Modified: datetime = element()
81
+ rty_LocallyModified: bool = element()
@@ -0,0 +1,34 @@
1
+ from datetime import datetime
2
+ from typing import Literal, Optional
3
+
4
+ from pydantic_xml import BaseXmlModel, element
5
+
6
+
7
+ class TRM(BaseXmlModel, tag="trm", search_mode="unordered"):
8
+ """Dataclass to model one of the database's vocabulary terms.
9
+
10
+ When possible, the attribute descriptions are taken from Heurist's source code.
11
+ """
12
+
13
+ trm_ID: int = element()
14
+ trm_Label: str = element()
15
+ trm_InverseTermId: Optional[int] = element(default=None)
16
+ trm_Description: Optional[str] = element(default=None)
17
+ trm_Status: Literal["open", "approved", "reserved"] = element()
18
+ trm_OriginatingDBID: int = element()
19
+ trm_NameInOriginatingDB: Optional[str] = element(default=None)
20
+ trm_IDInOriginatingDB: Optional[int] = element(default=None)
21
+ trm_AddedByImport: bool = element()
22
+ trm_IsLocalExtension: bool = element()
23
+ trm_Domain: Literal["relation", "enum"] = element()
24
+ trm_OntID: int = element()
25
+ trm_ChildCount: int = element()
26
+ trm_ParentTermID: Optional[int] = element(default=None)
27
+ trm_Depth: int = element()
28
+ trm_Modified: datetime = element()
29
+ trm_LocallyModified: bool = element()
30
+ trm_Code: Optional[str] = element(default=None)
31
+ trm_SemanticReferenceURL: Optional[str] = element(default=None)
32
+ trm_IllustrationURL: Optional[str] = element(default=None)
33
+ trm_VocabularyGroupID: Optional[int] = element(default=None)
34
+ trm_OrderInBranch: Optional[int] = element(default=None)
@@ -0,0 +1,53 @@
1
+ """Module for utilities commonly used by XML parsers in schemas."""
2
+
3
+ import json
4
+
5
+
6
+ def split_ids(input: str) -> list:
7
+ """Function for converting a string representation of a list of quoted integers \
8
+ into a Python list object.
9
+
10
+ Examples:
11
+ >>> s = "3001,3110,3113,3288"
12
+ >>> split_ids(s)
13
+ [3001, 3110, 3113, 3288]
14
+ >>> s = '[\\"3001\\",\\"3110\\",\\"3113\\",\\"3288\\"]'
15
+ >>> split_ids(s)
16
+ [3001, 3110, 3113, 3288]
17
+ >>> l = ['[\\"3001\\",\\"3110\\",\\"3113\\",\\"3288\\"]']
18
+ >>> split_ids(l)
19
+ [3001, 3110, 3113, 3288]
20
+ >>> s = '[]'
21
+ >>> split_ids(s)
22
+ []
23
+
24
+ Args:
25
+ input (str|Any): List of integers.
26
+
27
+ Returns:
28
+ list: _description_
29
+ """
30
+
31
+ # Handle input of potentially malformatted JSON list
32
+ if isinstance(input, list) or "[" in input:
33
+ if not isinstance(input, str):
34
+ s = json.dumps(input)
35
+ if not isinstance(s, str):
36
+ print(input)
37
+ raise TypeError("Integer list was not converted to string.")
38
+ else:
39
+ s = input
40
+
41
+ # Remove the JSON string escape characters
42
+ s = s.replace("\\", "")
43
+ s = s.replace('"', "")
44
+ s = s.replace("[", "")
45
+ s = s.replace("]", "")
46
+
47
+ input_string = s
48
+
49
+ else:
50
+ input_string = input
51
+
52
+ # Split and recast regular string
53
+ return [int(i) for i in input_string.split(",") if i != ""]
@@ -0,0 +1,27 @@
1
+ import json
2
+ from datetime import date
3
+ from pathlib import Path
4
+
5
+ import duckdb
6
+ from heurist.schema.rel_to_dict import convert_rty_description
7
+ from heurist.sql.sql_safety import SafeSQLName
8
+
9
+
10
+ def output_csv(dir: Path, descriptions: list[duckdb.DuckDBPyRelation]) -> None:
11
+ for rel in descriptions:
12
+ name = rel.select("rty_Name").limit(1).fetchone()[0]
13
+ safe_name = SafeSQLName().create_table_name(name)
14
+ fp = dir.joinpath(safe_name).with_suffix(".csv")
15
+ rel.write_csv(file_name=str(fp), header=True)
16
+
17
+
18
+ def output_json(descriptions: list[duckdb.DuckDBPyRelation], fp: Path) -> None:
19
+ date_string = date.today().isoformat()
20
+ data = {"lastModified": date_string, "items": []}
21
+ for desc in descriptions:
22
+ kv_dict = convert_rty_description(description=desc)
23
+ for id, metadata in kv_dict.items():
24
+ d = {"id": id} | metadata
25
+ data["items"].append(d)
26
+ with open(fp, "w", encoding="utf-8") as f:
27
+ json.dump(data, f, indent=4, ensure_ascii=False)
@@ -0,0 +1,70 @@
1
+ from typing import List, Optional
2
+
3
+ from pydantic import BaseModel
4
+ from pydantic.functional_validators import BeforeValidator
5
+ from typing_extensions import Annotated
6
+
7
+
8
+ def convert_vocab_map_to_list(vocab_map: dict | None) -> list[dict]:
9
+ """
10
+ Convert map generated in the SQL query to a list of vocab dictionary objects.
11
+
12
+ See the SQL query in the sql/ directory. The relevant selection is:
13
+ map(list(trm_Label), list(
14
+ {
15
+ "description": trm_Description,
16
+ "url": trm_SemanticReferenceURL,
17
+ "id": trm_ID
18
+ })
19
+ ) AS terms
20
+
21
+ Examples:
22
+ >>> vocab0 = {'perg': {'description': 'Parchment', 'url': None, 'id': 9782}}
23
+ >>> vocab1 = {'chart': {'description': 'Paper', 'url': None, 'id': 9783}}
24
+ >>> vocab2 = {'mixed': {'description': None, 'url': None, 'id': 9785}}
25
+ >>> map = vocab0 | vocab1 | vocab2
26
+ >>> terms = convert_vocab_map_to_list(map)
27
+ >>> len(terms)
28
+ 3
29
+ >>> terms[0]
30
+ {'label': 'perg', 'description': 'Parchment', 'url': None, 'id': 9782}
31
+
32
+ Args:
33
+ vocab_map (dict | None): Map created in SQL query from aggregation function.
34
+
35
+ Returns:
36
+ list[dict]: List of vocabulary term metadata in dictionary objects.
37
+ """
38
+
39
+ vocab_terms = []
40
+ if vocab_map:
41
+ for k, v in vocab_map.items():
42
+ nd = {"label": k} | v
43
+ vocab_terms.append(nd)
44
+ return vocab_terms
45
+
46
+
47
+ VocabTerms = Annotated[list, BeforeValidator(convert_vocab_map_to_list)]
48
+
49
+
50
+ class DTY(BaseModel):
51
+ rst_DisplayName: str
52
+ rst_DisplayHelpText: str
53
+ dty_ID: int
54
+ dty_Type: str
55
+ dty_PtrTargetRectypeIDs: Optional[List[int]]
56
+ dty_SemanticReferenceURL: Optional[str]
57
+ trm_TreeID: Optional[int]
58
+ trm_Label: Optional[str]
59
+ trm_Description: Optional[str]
60
+ rst_RequirementType: str
61
+ rst_MaxValues: int
62
+ vocabTerms: Optional[VocabTerms]
63
+
64
+
65
+ class RTY(BaseModel):
66
+ rty_ID: int
67
+ rty_Name: str
68
+ rty_Description: str
69
+ rty_TitleMask: str
70
+ rty_ReferenceURL: Optional[str]
@@ -0,0 +1,39 @@
1
+ import duckdb
2
+ from heurist.schema.models import DTY, RTY
3
+ from heurist.utils.rel_to_dict_array import rel_to_dict_array
4
+
5
+
6
+ def convert_rty_description(description: duckdb.DuckDBPyRelation) -> dict:
7
+ """
8
+ Convert the SQL result of the joined record type schema description into \
9
+ a Python dictionary.
10
+
11
+ Args:
12
+ description (duckdb.DuckDBPyRelation): Relation from SQL query.
13
+
14
+ Returns:
15
+ dict: Dictionary representation of a record's schema.
16
+ """
17
+
18
+ rel = description.filter("dty_Type not like 'separator'").order(
19
+ "group_id asc, rst_DisplayOrder asc"
20
+ )
21
+
22
+ sections = {}
23
+
24
+ for field in rel_to_dict_array(rel):
25
+ section_id = field["group_id"]
26
+ section_name = field["sec"]
27
+ if not sections.get(section_id):
28
+ sections.update({section_id: {"sectionName": section_name, "fields": []}})
29
+ field_model = DTY.model_validate(field).model_dump()
30
+ sections[section_id]["fields"].append(field_model)
31
+
32
+ section_list = list(sections.values())
33
+
34
+ rty_rel = rel.limit(1)
35
+ rty_data = rel_to_dict_array(rty_rel)[0]
36
+ rty = RTY.model_validate(rty_data)
37
+
38
+ output = {rty.rty_ID: {"metadata": rty.model_dump(), "sections": section_list}}
39
+ return output
@@ -0,0 +1,21 @@
1
+ from pathlib import Path
2
+
3
+ file_path_record_type_schema = Path(__file__).parent.joinpath(
4
+ "selectRecordTypeSchema.sql"
5
+ )
6
+ file_path_record_type_metadata_by_group_type = Path(__file__).parent.joinpath(
7
+ "joinRecordTypeIDNameByGroupType.sql"
8
+ )
9
+ file_path_fully_joined_record_type_metadata = Path(__file__).parent.joinpath(
10
+ "joinRecordTypeMetadata.sql"
11
+ )
12
+
13
+
14
+ with open(file_path_record_type_schema) as f:
15
+ RECORD_TYPE_SCHEMA = f.read()
16
+
17
+ with open(file_path_record_type_metadata_by_group_type) as f:
18
+ RECORD_BY_GROUP_TYPE = f.read()
19
+
20
+ with open(file_path_fully_joined_record_type_metadata) as f:
21
+ RECORD_TYPE_METADATA = f.read()
@@ -0,0 +1,10 @@
1
+ /* Select the ID and name of record types that are in
2
+ targeted record type groups.
3
+
4
+ This query should be appended with 1 or more conditions
5
+ of "rtg.rtg_Name" = '<target group name>'. */
6
+ SELECT
7
+ rty.rty_ID,
8
+ rty.rty_Name
9
+ FROM rty
10
+ INNER JOIN rtg ON rty.rty_RecTypeGroupID = rtg.rtg_ID
@@ -0,0 +1,17 @@
1
+ /* Join the tables Record Structure (rst), Detail Type (dty),
2
+ and Record Type (rty) to get all the relevant information
3
+ about a record type's data fields.
4
+
5
+ This query requires a parameter: the record type ID. */
6
+ SELECT
7
+ dty_ID,
8
+ rst_DisplayName,
9
+ dty_Type,
10
+ rst_MaxValues
11
+ FROM rst
12
+ INNER JOIN rty ON rst.rst_RecTypeID = rty.rty_ID
13
+ INNER JOIN dty ON rst.rst_DetailTypeID = dty.dty_ID
14
+ WHERE rty.rty_ID = ?
15
+ AND dty.dty_Type NOT LIKE 'separator'
16
+ AND dty.dty_Type NOT LIKE 'relmarker'
17
+ ORDER BY rst.rst_DisplayOrder
@@ -0,0 +1,51 @@
1
+ /* Build a selection of data fields for a record type that
2
+ groups the fields by their groups in Heurist's interface,
3
+ as defined by the "separator" field Heurist adds between
4
+ fields of two different groups.
5
+
6
+ This query requires a parameter: the record type ID. */
7
+ SELECT
8
+ CASE
9
+ WHEN group_id != 0 THEN FIRST_VALUE(rst_DisplayName) OVER (PARTITION BY group_id)
10
+ ELSE NULL
11
+ END
12
+ AS sec,
13
+ CASE
14
+ WHEN group_id !=0 THEN FIRST_VALUE(rst_DisplayHelpText) OVER (PARTITION BY group_id)
15
+ ELSE NULL
16
+ END
17
+ AS secHelpText
18
+ , *
19
+ FROM (
20
+ SELECT *
21
+ FROM (
22
+ SELECT
23
+ COUNT(
24
+ CASE WHEN dty_type LIKE 'separator' THEN rst_DisplayName ELSE NULL end
25
+ ) OVER (ORDER BY rst_DisplayOrder) AS group_id,
26
+ *
27
+ FROM rst
28
+ JOIN rty ON rst_RecTypeID = rty.rty_ID
29
+ JOIN dty ON rst_DetailTypeID = dty.dty_ID
30
+ WHERE rty_ID = ?
31
+ )
32
+ LEFT JOIN (
33
+ SELECT
34
+ a.vocab_id as trm_TreeID,
35
+ b.trm_Label,
36
+ b.trm_Description,
37
+ a.term_count as n_vocabTerms,
38
+ a.terms as vocabTerms
39
+ FROM (
40
+ SELECT
41
+ trm_ParentTermID AS vocab_id,
42
+ count(*) AS term_count,
43
+ map(list(trm_Label), list({"description": trm_Description, "url": trm_SemanticReferenceURL, "id": trm_ID})) AS terms
44
+ FROM trm
45
+ GROUP BY trm_ParentTermID
46
+ ) a
47
+ LEFT JOIN trm b ON a.vocab_id = b.trm_ID
48
+ ) c ON c.trm_TreeID = dty_JsonTermIDTree
49
+ ORDER BY rst_DisplayOrder
50
+ )
51
+ ORDER BY rst_DisplayOrder