Spaces:
Build error
Build error
File size: 14,347 Bytes
441d880 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 | import re
import datetime
import textwrap
from Config import Config
from DataDictionary import *
from FastFacts import *
from PersonalityValues import *
import pandas as pd
import numpy as np
class AttributeGroup:
"""
Represents an attribute group (type) in the user profile.
Fields are dynamically populated based on the group's parameters.
"""
def __init__(self, group_name, fields):
self.group_name = group_name
self.fields = {field: None for field in fields}
def set_field(self, field_name, value):
"""
Set a value for a specific field in the attribute group.
If the field does not exist, it is added dynamically.
"""
if field_name not in self.fields:
print(f"Warning: Field '{field_name}' not found in '{self.group_name}'. Adding dynamically.")
self.fields[field_name] = None # Add the field dynamically
self.fields[field_name] = value # Assign the provided value
def get_field(self, field_name):
"""
Get a value for a specific field in the attribute group.
"""
if field_name in self.fields:
return self.fields[field_name]
else:
print(f"Field '{field_name}' does not exist in the '{self.group_name}' attribute group.")
def to_dict(self):
"""
Convert the attribute group to a dictionary with non-null values.
"""
return {field: value for field, value in self.fields.items() if value is not None}
def __repr__(self):
"""
String representation of the attribute group with non-null fields.
"""
fields_repr = ", ".join(f"{k}={v}" for k, v in self.fields.items() if v is not None)
return f"{self.group_name}({fields_repr})"
class UserProfile:
"""
Represents a user profile, dynamically initialised with attribute groups based on the DataDictionary.
Includes a lazily initialised FastFacts section for storing additional facts about the user.
"""
def __init__(self, data_dictionary):
self.data_dictionary = data_dictionary # Store the data dictionary for dynamic group creation
self.attribute_groups = {} # Dictionary to hold created attribute groups
self.ID = None # Unique identifier for the user profile
self.fast_facts = None # Lazily initialised FastFacts attribute
def set_ID(self, ID):
"""
Set the ID for the user profile.
"""
self.ID = ID
def set_field(self, group_name, field_name, value):
"""
Set a value for a field in a specific attribute group.
If the group does not already exist, it will be created dynamically.
"""
if group_name not in self.attribute_groups:
# Create the AttributeGroup only when needed
if group_name in self.data_dictionary.get_types():
self.attribute_groups[group_name] = AttributeGroup(
group_name,
self.data_dictionary.get_parameters(type=group_name)
)
else:
print(f"Attribute group '{group_name}' is not defined in the DataDictionary.")
return
self.attribute_groups[group_name].set_field(field_name, value)
def get_field(self, group_name, field_name):
"""
Get a value for a field in a specific attribute group.
"""
if group_name not in self.attribute_groups:
print(f"Attribute group '{group_name}' is not found.")
return None
return self.attribute_groups[group_name].get_field(field_name)
def set_fields_from_list(self, attribute_type, fields, field_key="field_name", value_key="value"):
"""
Sets fields in a UserProfile from a list of field-value pairs.
Args:
attribute_type (str): The name of the attribute group (e.g., "Values").
fields (list): A list of dictionaries with field names and values to set.
field_key (str): The key in the dictionary that corresponds to the field name.
value_key (str): The key in the dictionary that corresponds to the value.
"""
if not isinstance(fields, list) or not all(isinstance(field, dict) for field in fields):
print("Fields must be a list of dictionaries.")
return
for field in fields:
field_name = field.get(field_key) # Use the specified key for field names
value = field.get(value_key) # Use the specified key for values
if field_name is not None and value is not None:
self.set_field(attribute_type, field_name, value)
else:
print(f"Skipping invalid field: {field}")
def get_attributes(self, attribute_type=None):
"""
Retrieve attributes for a specific attribute type or all attributes if no type is specified.
Args:
attribute_type (str, optional): The name of the attribute group to retrieve.
If None, retrieves all attributes.
Returns:
dict: A dictionary of non-null attributes for the specified type or all types.
"""
if attribute_type:
if attribute_type in self.attribute_groups:
return self.attribute_groups[attribute_type].to_dict()
else:
print(f"Attribute type '{attribute_type}' does not exist in this user profile.")
return {}
else:
# Combine all attributes if no specific type is specified
all_attributes = {}
for group_name, group in self.attribute_groups.items():
all_attributes.update({f"{group_name}_{k}": v for k, v in group.to_dict().items()})
return all_attributes
def add_fast_facts(self, facts):
"""
Lazily initialise and add a set of facts to the FastFacts attribute.
Args:
facts (iterable): A collection of facts to add to FastFacts.
"""
if not isinstance(facts, (set, list)):
print("Facts must be provided as a set or list.")
return
if self.fast_facts is None:
self.fast_facts = FastFacts()
self.fast_facts.add_facts(facts)
def to_dict(self, data_dictionary):
"""
Convert the entire user profile to a dictionary.
"""
profile_dict = {'ID': self.ID}
# Iterate over all types in the data dictionary
for attribute_type in data_dictionary.get_types():
group_attributes = self.get_attributes(attribute_type)
for field_name in data_dictionary.get_parameters(type=attribute_type):
full_field_name = f"{attribute_type}_{field_name}"
# Access the value directly from the dictionary
value = group_attributes.get(field_name)
# Ensure value exists before updating the profile_dict
if value is not None:
profile_dict[full_field_name] = value
else:
print(f"Warning: {field_name} not found in type {attribute_type}")
return profile_dict
def __repr__(self):
"""
String representation of the user profile with attribute groups.
"""
groups_repr = ", ".join(str(group) for group in self.attribute_groups.values())
return f"UserProfile(ID={self.ID}, {groups_repr})"
@staticmethod
def write_user_profiles_to_excel(user_profiles, filename, data_dictionary):
"""
Writes a list of UserProfile objects to an Excel file with columns ordered by the data dictionary.
Args:
user_profiles (list): List of UserProfile objects.
filename (str): Path to the Excel file.
data_dictionary (object): Data dictionary containing column order and metadata.
"""
if not user_profiles:
print("No user profiles to write.")
return
# Convert user profiles to a list of dictionaries
# profiles_data = [user_profile.to_dict(data_dictionary) for user_profile in user_profiles]
profiles_data = []
for i, user_profile in enumerate(user_profiles):
print(f"Processing profile {i+1}: {user_profile}")
profile_dict = user_profile.to_dict(data_dictionary)
print(f"Dict output: {profile_dict}")
profiles_data.append(profile_dict)
# Get the column order from the data dictionary
column_order = ['ID'] + data_dictionary.get_columns() # Ensure this method exists and returns the column names in the desired order
# Create a DataFrame from the profiles data
df = pd.DataFrame(profiles_data)
# Ensure all columns in the data dictionary are present in the DataFrame
for column in column_order:
if column not in df.columns:
print(f"Column {column} is missing")
df[column] = None # Add missing columns with NaN/None
# Reorder columns based on the data dictionary
df = df[column_order]
# Write the DataFrame to an Excel file
df.to_excel(filename, index=False)
print(f"User profiles successfully written to {filename}")
@staticmethod
def read_user_profiles_from_excel(respondent_details_file, data_dictionary, pv_criteria):
"""
Reads a list of UserProfile objects from an Excel file and converts scores into definitions.
Args:
respondent_details_file (str): Path to the Excel file.
data_dictionary (DataDictionary): Instance of DataDictionary containing valid fields.
pv_criteria (PVAssessment): Instance of PVAssessment to retrieve text descriptions of values.
Returns:
list: List of UserProfile objects.
"""
user_profiles = []
# Read the Excel file into a DataFrame
df = pd.read_excel(respondent_details_file)
# Iterate over the rows in the DataFrame
for _, row in df.iterrows():
user_profile = UserProfile(data_dictionary)
# Set basic fields for UserProfile if they are present
if pd.notna(row.get('ID')):
user_profile.set_ID(row.get('ID'))
# Iterate over all types in the data dictionary
for attribute_type in data_dictionary.get_types():
for field in data_dictionary.get_parameters(type=attribute_type):
full_field_name = f"{attribute_type}_{field}"
raw_value = row.get(full_field_name)
if pd.notna(raw_value):
# Special handling for Values type
if attribute_type.lower() == "values":
try:
score = int(raw_value) # Convert to integer score
# Get description from PVAssessment
description = PVAssessment.get_score_definition(field, score, pv_criteria)
# Set score field
#user_profile.set_field(attribute_type, field, score)
# Set description field
description_field_name = f"{attribute_type}_{field}_Description"
user_profile.set_field(attribute_type, description_field_name, description)
except ValueError:
print(f"Warning: Could not convert '{raw_value}' to an integer for field '{full_field_name}'.")
else:
# Generic field setting for non-Values types
user_profile.set_field(attribute_type, field, raw_value)
user_profiles.append(user_profile)
print(f"User profiles successfully read from {respondent_details_file}")
return user_profiles
class UserProfileDetail:
def __init__(self, key, original_value, qa_check, value):
"""
Initialize a UserProfileDetail entry.
"""
self.key = key
self.original_value = original_value
self.qa_check = qa_check
self.value = value
def __repr__(self):
fields = {k: v for k, v in self.__dict__.items() if v and v != "Unable to map"}
formatted_fields = [f"{k}='{v}'" for k, v in fields.items()]
return f"{self.__class__.__name__}: " + ", ".join(formatted_fields) + ")"
@staticmethod
def filter_profiles(profiles, key=None, qa_check=None, value=None):
"""
Static method to filter user profiles by key, QA check status, or value.
Args:
profiles (list): List of UserProfileDetail objects.
key (str, optional): The key to filter by.
qa_check (str, optional): The QA check status to filter by.
value (str, optional): The value to filter by.
Returns:
list: A list of UserProfileDetail entries that match the criteria.
"""
return [
profile for profile in profiles
if (key is None or profile.key == key) and
(qa_check is None or profile.qa_check == qa_check) and
(value is None or profile.value == value)
]
@staticmethod
def generate_user_profiles(file_path):
"""
Static method to generate a list of UserProfileDetail entries from an Excel (.xlsx) file.
Args:
file_path (str): The path to the Excel file containing user profile entries.
Returns:
list: A list of UserProfileDetail objects generated from the file.
"""
# Read the Excel file
df = pd.read_excel(file_path)
profiles = []
for _, row in df.iterrows():
profile = UserProfileDetail(
key=row['Key'],
original_value=row['Value'],
qa_check=row['QA Check'],
value=row['Revised Value']
)
profiles.append(profile)
return profiles
|