ViewVC Help
View File | Revision Log | Show Annotations | View Changeset | Root Listing
root/ns_dev/Python/NinoCode/Active_prgs/Redgrave/RelativityAuditFlattenerXml.py
(Generate patch)

Comparing Python/NinoCode/Active_prgs/Redgrave/RelativityAuditFlattenerXml.py (file contents):
Revision 960 by nino.borges, Mon Dec 8 22:03:22 2025 UTC vs.
Revision 961 by nino.borges, Mon Dec 8 22:26:25 2025 UTC

# Line 21 | Line 21 | class RelativityAuditFlattenerXml:
21      Reads a Relativity audit CSV and flattens XML 'Details' so that
22      each <field> modification becomes its own row, with the original
23      columns (Name through Request Origination) repeated.
24 +
25 +    XML schema (from your inspector):
26 +      - Root: <auditElement>
27 +      - Repeated per-field nodes: <field id="..." type="..." name="..." formatstring="...">
28 +      - Inside <field>:
29 +          <setChoice>...</setChoice>
30 +          <unSetChoice>...</unSetChoice>
31 +          <unsetChoice>...</unsetChoice>
32 +          <oldValue>...</oldValue>
33 +          <newValue>...</newValue>
34      """
35  
36      # The base columns we want to carry through from the CSV
# Line 36 | Line 46 | class RelativityAuditFlattenerXml:
46          "Request Origination",
47      ]
48  
49 <    # The new columns we add from the XML
49 >    # New columns derived from the XML
50      DETAIL_COLUMNS = [
51 <        "Field Name",
52 <        "Change Type",
53 <        "Old Value",
54 <        "New Value",
55 <        "Raw Details XML",
51 >        "Field Name",   # from <field name="...">
52 >        "Field Type",   # from <field type="...">
53 >        "unSetChoice",  # combined <unSetChoice> and <unsetChoice>
54 >        "setChoice",    # <setChoice>
55 >        "oldValue",     # <oldValue>
56 >        "newValue",     # <newValue>
57      ]
58  
59      def __init__(self, csv_path: str) -> None:
# Line 70 | Line 81 | class RelativityAuditFlattenerXml:
81                  if not field_change_entries:
82                      out_row = self._copy_base_columns(src_row)
83                      out_row.update({
84 <                        "Field Name": "(unknown)",
85 <                        "Change Type": src_row.get("Action", ""),
86 <                        "Old Value": "",
87 <                        "New Value": "",
88 <                        "Raw Details XML": details_str,
84 >                        "Field Name": "",
85 >                        "Field Type": "",
86 >                        "unSetChoice": "",
87 >                        "setChoice": "",
88 >                        "oldValue": "",
89 >                        "newValue": "",
90                      })
91                      self.rows.append(out_row)
92                      continue
# Line 84 | Line 96 | class RelativityAuditFlattenerXml:
96                      out_row = self._copy_base_columns(src_row)
97                      out_row.update({
98                          "Field Name": fc.get("field_name", ""),
99 <                        "Change Type": fc.get("change_type", src_row.get("Action", "")),
100 <                        "Old Value": fc.get("old_value", ""),
101 <                        "New Value": fc.get("new_value", ""),
102 <                        "Raw Details XML": details_str,
99 >                        "Field Type": fc.get("field_type", ""),
100 >                        "unSetChoice": fc.get("unset_choice", ""),
101 >                        "setChoice": fc.get("set_choice", ""),
102 >                        "oldValue": fc.get("old_value", ""),
103 >                        "newValue": fc.get("new_value", ""),
104                      })
105                      self.rows.append(out_row)
106  
# Line 97 | Line 110 | class RelativityAuditFlattenerXml:
110  
111      def export_to_excel(self, excel_path: str) -> None:
112          """
113 <        Export flattened rows to Excel using win32com.
113 >        Export flattened rows to Excel using win32com (your preferred approach).
114          """
115          from win32com.client import Dispatch
116  
# Line 156 | Line 169 | class RelativityAuditFlattenerXml:
169  
170      def _parse_details_xml(self, xml_str: str) -> List[Dict[str, str]]:
171          """
172 <        Parse the XML in Details and return a list of field-change dicts:
173 <            { "field_name": ..., "change_type": ..., "old_value": ..., "new_value": ... }
174 <
175 <        Based on your schema:
176 <          - <auditElement> is the root.
177 <          - Each <field> element has attributes: id, type, name, formatstring.
178 <          - For choices:
166 <              <setChoice>...values added...</setChoice>
167 <              <unSetChoice>...values removed...</unSetChoice>
168 <              <unsetChoice>...values removed...</unsetChoice>
169 <          - For some fields:
170 <              <oldValue>...</oldValue>
171 <              <newValue>...</newValue>
172 >        Parse the XML in Details and return a list of dicts with keys:
173 >          - field_name
174 >          - field_type
175 >          - unset_choice  (combined unSetChoice/unsetChoice)
176 >          - set_choice
177 >          - old_value
178 >          - new_value
179          """
180          xml_str = (xml_str or "").strip()
181          if not xml_str:
# Line 186 | Line 193 | class RelativityAuditFlattenerXml:
193              attrib = field_elem.attrib
194  
195              field_name = attrib.get("name", "")
196 +            field_type = attrib.get("type", "")
197  
198 <            # Gather choice changes
198 >            # Collect choice changes
199              set_choices = [
200                  (child.text or "").strip()
201                  for child in field_elem.findall("setChoice")
202                  if (child.text or "").strip()
203              ]
204  
205 <            # Note Relativity uses both <unSetChoice> and <unsetChoice> (you saw both)
205 >            # Note: both <unSetChoice> and <unsetChoice> appear in your data
206              unset_choices = [
207                  (child.text or "").strip()
208                  for child in field_elem.findall("unSetChoice")
# Line 206 | Line 214 | class RelativityAuditFlattenerXml:
214                  if (child.text or "").strip()
215              ]
216  
217 <            # Gather old/new value tags (for non-choice or special cases)
217 >            unset_choice_str = "; ".join(unset_choices) if unset_choices else ""
218 >            set_choice_str = "; ".join(set_choices) if set_choices else ""
219 >
220 >            # Gather old/new value tags (for non-choice cases)
221              old_val_node = field_elem.find("oldValue")
222              new_val_node = field_elem.find("newValue")
223 <            old_val_text = (old_val_node.text or "").strip() if old_val_node is not None and old_val_node.text else ""
224 <            new_val_text = (new_val_node.text or "").strip() if new_val_node is not None and new_val_node.text else ""
214 <
215 <            # Decide what to treat as old/new
216 <            old_value = ""
217 <            new_value = ""
218 <            change_type = "Update"
219 <
220 <            if old_val_text or new_val_text:
221 <                # Classic before/after
222 <                old_value = old_val_text
223 <                new_value = new_val_text
224 <                change_type = "Value Update"
225 <            elif set_choices or unset_choices:
226 <                # Choice changes
227 <                # Treat unset choices as old, set choices as new
228 <                if unset_choices:
229 <                    old_value = "; ".join(unset_choices)
230 <                if set_choices:
231 <                    new_value = "; ".join(set_choices)
232 <
233 <                if set_choices and unset_choices:
234 <                    change_type = "Choices Updated"
235 <                elif set_choices:
236 <                    change_type = "Choices Set"
237 <                else:
238 <                    change_type = "Choices Unset"
239 <            else:
240 <                # We saw a <field> but no obvious value info
241 <                # Keep field_name but leave values blank
242 <                change_type = "Field Touched"
223 >            old_val_text = (old_val_node.text or "").strip() if (old_val_node is not None and old_val_node.text) else ""
224 >            new_val_text = (new_val_node.text or "").strip() if (new_val_node is not None and new_val_node.text) else ""
225  
226              results.append(
227                  {
228                      "field_name": field_name,
229 <                    "change_type": change_type,
230 <                    "old_value": old_value,
231 <                    "new_value": new_value,
229 >                    "field_type": field_type,
230 >                    "unset_choice": unset_choice_str,
231 >                    "set_choice": set_choice_str,
232 >                    "old_value": old_val_text,
233 >                    "new_value": new_val_text,
234                  }
235              )
236  
# Line 268 | Line 252 | if __name__ == "__main__":
252  
253      flattener.export_to_excel(output_excel)
254      print(f"Excel report written to: {output_excel}")
255 +

Diff Legend

Removed lines
+ Added lines
< Changed lines (old)
> Changed lines (new)