Coverage for cc_modules/cc_summaryelement.py: 49%

47 statements  

« prev     ^ index     » next       coverage.py v7.9.2, created at 2025-07-15 14:23 +0100

1""" 

2camcops_server/cc_modules/cc_summaryelement.py 

3 

4=============================================================================== 

5 

6 Copyright (C) 2012, University of Cambridge, Department of Psychiatry. 

7 Created by Rudolf Cardinal (rnc1001@cam.ac.uk). 

8 

9 This file is part of CamCOPS. 

10 

11 CamCOPS is free software: you can redistribute it and/or modify 

12 it under the terms of the GNU General Public License as published by 

13 the Free Software Foundation, either version 3 of the License, or 

14 (at your option) any later version. 

15 

16 CamCOPS is distributed in the hope that it will be useful, 

17 but WITHOUT ANY WARRANTY; without even the implied warranty of 

18 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 

19 GNU General Public License for more details. 

20 

21 You should have received a copy of the GNU General Public License 

22 along with CamCOPS. If not, see <https://www.gnu.org/licenses/>. 

23 

24=============================================================================== 

25 

26**Classes to represent summary information created by tasks.** 

27 

28For example, the PHQ9 task calculates a total score; that's part of its summary 

29information. 

30 

31""" 

32 

33from collections import OrderedDict 

34from typing import Any, Dict, List, Optional, Set, Type, TYPE_CHECKING, Union 

35 

36from cardinal_pythonlib.reprfunc import auto_repr 

37from sqlalchemy.sql.schema import Column 

38from sqlalchemy.sql.type_api import TypeEngine 

39 

40from camcops_server.cc_modules.cc_dataclasses import SummarySchemaInfo 

41from camcops_server.cc_modules.cc_db import TaskDescendant 

42from camcops_server.cc_modules.cc_spreadsheet import SpreadsheetPage 

43from camcops_server.cc_modules.cc_xml import XmlElement 

44 

45if TYPE_CHECKING: 

46 from camcops_server.cc_modules.cc_task import Task 

47 

48 

49# ============================================================================= 

50# SummaryElement 

51# ============================================================================= 

52 

53 

54class SummaryElement(object): 

55 """ 

56 Returned by tasks to represent extra summary information that they 

57 calculate. 

58 

59 Use this for extra information that can be added to a row represented by a 

60 task or its ancillary object. 

61 """ 

62 

63 def __init__( 

64 self, name: str, coltype: TypeEngine, value: Any, comment: str = None 

65 ) -> None: 

66 """ 

67 Args: 

68 name: column name 

69 coltype: SQLAlchemy column type; e.g. ``Integer()``, 

70 ``String(length=50)`` 

71 value: value 

72 comment: explanatory comment 

73 """ 

74 self.name = name 

75 self.coltype = coltype 

76 self.value = value 

77 self.comment = comment 

78 

79 @property 

80 def decorated_comment(self) -> Optional[str]: 

81 return "(SUMMARY) " + self.comment if self.comment else None 

82 

83 

84# ============================================================================= 

85# ExtraSummaryTable 

86# ============================================================================= 

87 

88 

89class ExtraSummaryTable(TaskDescendant): 

90 """ 

91 Additional summary information returned by a task. 

92 

93 Use this to represent an entire table that doesn't have a 1:1 relationship 

94 with rows of a task or ancillary object. 

95 """ 

96 

97 def __init__( 

98 self, 

99 tablename: str, 

100 xmlname: str, 

101 columns: List[Column], 

102 rows: List[Union[Dict[str, Any], OrderedDict]], 

103 task: "Task", 

104 ) -> None: 

105 """ 

106 Args: 

107 tablename: name of the additional summary table 

108 xmlname: name of the XML tag to encapsulate this information 

109 columns: list of SQLAlchemy columns 

110 rows: list of rows, where each row is a dictionary mapping 

111 column names to values 

112 task: parent task (for cross-referencing in some kinds of export) 

113 """ 

114 self.tablename = tablename 

115 self.xmlname = xmlname 

116 self.columns = columns 

117 self.rows = rows 

118 self.task = task 

119 

120 def get_xml_element(self) -> XmlElement: 

121 """ 

122 Returns an :class:`camcops_server.cc_modules.cc_xml.XmlElement` 

123 representing this summary table. 

124 """ 

125 itembranches = [] # type: List[XmlElement] 

126 for valuedict in self.rows: 

127 leaves = [] # type: List[XmlElement] 

128 for k, v in valuedict.items(): 

129 leaves.append(XmlElement(name=k, value=v)) 

130 branch = XmlElement(name=self.tablename, value=leaves) 

131 itembranches.append(branch) 

132 return XmlElement(name=self.xmlname, value=itembranches) 

133 

134 def get_spreadsheet_page(self) -> SpreadsheetPage: 

135 """ 

136 Returns an 

137 :class:`camcops_server.cc_modules.cc_spreadsheet.SpreadsheetPage` 

138 representing this summary table. 

139 """ 

140 return SpreadsheetPage(name=self.tablename, rows=self.rows) 

141 

142 def get_spreadsheet_schema_elements(self) -> Set[SummarySchemaInfo]: 

143 """ 

144 Schema equivalent to :func:`get_spreadsheet_page`. 

145 """ 

146 return set( 

147 SummarySchemaInfo.from_column( 

148 c, 

149 table_name=self.tablename, 

150 source=SummarySchemaInfo.SSV_SUMMARY, 

151 ) 

152 for c in self.columns 

153 ) 

154 

155 def __repr__(self) -> str: 

156 return auto_repr(self) 

157 

158 # ------------------------------------------------------------------------- 

159 # TaskDescendant overrides 

160 # ------------------------------------------------------------------------- 

161 

162 @classmethod 

163 def task_ancestor_class(cls) -> Optional[Type["Task"]]: 

164 return None 

165 

166 def task_ancestor(self) -> Optional["Task"]: 

167 return self.task