|
24 | 24 | ) |
25 | 25 | from log_generator.generation.ground_truth import GroundTruthGenerator |
26 | 26 | from log_generator.generation.state_manager import StateManager |
27 | | -from log_generator.models.scenario import Scenario, User, System |
| 27 | +from log_generator.models.scenario import Persona, Scenario, User, System |
28 | 28 | from log_generator.utils.time import parse_duration, parse_iso8601, resolve_time_window |
29 | 29 |
|
30 | 30 | logger = logging.getLogger(__name__) |
@@ -249,8 +249,13 @@ def _generate_baseline(self) -> None: |
249 | 249 |
|
250 | 250 | # Generate events for each user this hour |
251 | 251 | for user in enabled_users: |
| 252 | + # Resolve persona for work hours and risk modulation |
| 253 | + persona = self._get_user_persona(user) |
| 254 | + |
252 | 255 | # Calculate events for this user this hour |
253 | | - num_events = self._calculate_events_for_hour(user) |
| 256 | + num_events = self._calculate_events_for_hour( |
| 257 | + user, current_hour=current_hour.hour, persona=persona |
| 258 | + ) |
254 | 259 |
|
255 | 260 | if num_events > 0: |
256 | 261 | # Distribute events across the hour |
@@ -280,26 +285,59 @@ def _barrier_flush_all_emitters(self) -> None: |
280 | 285 | emitter.barrier_flush() |
281 | 286 | logger.debug("Barrier flush: all emitters complete") |
282 | 287 |
|
283 | | - def _calculate_events_for_hour(self, user: User) -> int: |
| 288 | + def _get_user_persona(self, user: User) -> Optional[Persona]: |
| 289 | + """Resolve user.persona string to Persona object. |
| 290 | +
|
| 291 | + Args: |
| 292 | + user: User whose persona to resolve |
| 293 | +
|
| 294 | + Returns: |
| 295 | + Persona object or None if not found |
| 296 | + """ |
| 297 | + if not user.persona or not self.scenario.personas: |
| 298 | + return None |
| 299 | + for persona in self.scenario.personas: |
| 300 | + if persona.name == user.persona: |
| 301 | + return persona |
| 302 | + return None |
| 303 | + |
| 304 | + def _calculate_events_for_hour( |
| 305 | + self, |
| 306 | + user: User, |
| 307 | + current_hour: Optional[int] = None, |
| 308 | + persona: Optional[Persona] = None, |
| 309 | + ) -> int: |
284 | 310 | """Calculate number of events for user this hour. |
285 | 311 |
|
286 | | - Applies intensity + variation + persona risk profile to determine |
287 | | - how many events to generate for this user during this hour. |
| 312 | + Applies intensity + variation + persona risk profile + work hours |
| 313 | + to determine how many events to generate for this user during this hour. |
| 314 | +
|
| 315 | + Phase 2.6: Uses persona data for time-of-day modulation and risk scaling. |
288 | 316 |
|
289 | 317 | Args: |
290 | 318 | user: User to calculate events for |
| 319 | + current_hour: Hour of day (0-23) for work hours modulation |
| 320 | + persona: Resolved Persona object for risk/work-hours modulation |
291 | 321 |
|
292 | 322 | Returns: |
293 | 323 | Number of events to generate (>= 0) |
294 | 324 | """ |
295 | | - # Base intensity |
| 325 | + # Base intensity from scenario |
296 | 326 | intensity_map = {'low': 5, 'medium': 15, 'high': 40} |
297 | 327 | base_events = intensity_map[self.scenario.baseline_activity.intensity] |
298 | 328 |
|
299 | | - # Risk profile adjustment (if persona assigned) |
300 | | - # Note: Phase 1 - persona is just a string name, not full Persona object |
301 | | - # Risk adjustments would require full persona definition (Phase 2+) |
302 | | - # For now, skip risk adjustment since we don't have access to risk_profile |
| 329 | + # Phase 2.6: Risk profile multiplier |
| 330 | + if persona and persona.risk_profile: |
| 331 | + risk_mult = {'low': 0.7, 'medium': 1.0, 'high': 1.3} |
| 332 | + base_events = int(base_events * risk_mult.get(persona.risk_profile, 1.0)) |
| 333 | + |
| 334 | + # Phase 2.6: Work hours modulation |
| 335 | + if persona and persona.work_hours_parsed and current_hour is not None: |
| 336 | + whp = persona.work_hours_parsed |
| 337 | + if current_hour not in whp['hours']: |
| 338 | + return 0 # Outside work hours — no activity |
| 339 | + elif current_hour in (whp.get('peak_hours') or []): |
| 340 | + base_events = int(base_events * 1.5) # Peak hours: 150% |
303 | 341 |
|
304 | 342 | # Apply variation (random jitter) |
305 | 343 | variation_map = {'low': 0.10, 'medium': 0.25, 'high': 0.50} |
@@ -357,9 +395,9 @@ def _generate_user_activity(self, user: User, event_time: datetime) -> None: |
357 | 395 | system = random.choice(self.scenario.environment.systems) |
358 | 396 |
|
359 | 397 | # Get baseline pattern for user's persona |
360 | | - # Note: persona is a string (persona name) in Phase 1, not a Persona object |
| 398 | + persona = self._get_user_persona(user) |
361 | 399 | persona_name = user.persona if user.persona else None |
362 | | - pattern = self.activity_generator.get_baseline_pattern(persona_name) |
| 400 | + pattern = self.activity_generator.get_baseline_pattern(persona_name, persona=persona) |
363 | 401 |
|
364 | 402 | # Execute activities based on probabilities |
365 | 403 | for activity_type, probability in pattern: |
|
0 commit comments