Normalize a stored profile into the current schema.
(profile: Optional[Dict[str, Any]], user_id: str)
| 149 | |
| 150 | |
| 151 | def ensure_profile_shape(profile: Optional[Dict[str, Any]], user_id: str) -> Dict[str, Any]: |
| 152 | """Normalize a stored profile into the current schema.""" |
| 153 | normalized = copy.deepcopy(profile) if profile else build_empty_profile(user_id) |
| 154 | normalized["user_id"] = user_id |
| 155 | normalized.setdefault("version", "0.1") |
| 156 | normalized.setdefault("created_at", normalized.get("updated_at", datetime.now().isoformat())) |
| 157 | normalized["updated_at"] = datetime.now().isoformat() |
| 158 | |
| 159 | for key in ("core_directions", "methodology_preferences", "topic_weights", "author_heat", "institution_heat", "taste_profile"): |
| 160 | value = normalized.get(key) |
| 161 | normalized[key] = value if isinstance(value, dict) else {} |
| 162 | |
| 163 | must_read = normalized.get("must_read") |
| 164 | normalized["must_read"] = must_read if isinstance(must_read, dict) else {} |
| 165 | for key in ("authors", "institutions", "keywords"): |
| 166 | value = normalized["must_read"].get(key) |
| 167 | normalized["must_read"][key] = value if isinstance(value, list) else [] |
| 168 | |
| 169 | for key in ("interest_vector", "reading_history", "behavior_logs"): |
| 170 | value = normalized.get(key) |
| 171 | normalized[key] = value if isinstance(value, list) else [] |
| 172 | |
| 173 | drift_state = normalized.get("drift_state") |
| 174 | normalized["drift_state"] = drift_state if isinstance(drift_state, dict) else {} |
| 175 | for key, default_value in build_default_drift_state(normalized["updated_at"]).items(): |
| 176 | normalized["drift_state"].setdefault(key, copy.deepcopy(default_value)) |
| 177 | |
| 178 | raw_core_directions = dict(normalized.get("core_directions") or {}) |
| 179 | raw_topic_weights = dict(normalized.get("topic_weights") or {}) |
| 180 | normalized["core_directions"] = _normalize_direction_weight_map(raw_core_directions) |
| 181 | normalized["topic_weights"] = _normalize_direction_weight_map(raw_topic_weights) |
| 182 | |
| 183 | if normalized["core_directions"] and normalized["core_directions"] != raw_core_directions: |
| 184 | normalized["interest_vector"] = generate_interest_vector(normalized["core_directions"]) |
| 185 | |
| 186 | return normalized |
| 187 | |
| 188 | |
| 189 | def _normalize_direction_weight_map(raw_weights: Any) -> Dict[str, float]: |
no test coverage detected