-
Notifications
You must be signed in to change notification settings - Fork 0
new job changes #14
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
new job changes #14
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,81 @@ | ||
| using JobTracker.Models; | ||
|
|
||
| namespace JobTracker.Services; | ||
|
|
||
| public class JsaActivityModeJob | ||
| { | ||
| private readonly IServiceScopeFactory _scopeFactory; | ||
| private readonly ILogger<JsaActivityModeJob> _logger; | ||
|
|
||
| /// <summary>Maximum duration the job will run before stopping.</summary> | ||
| private static readonly TimeSpan MaxRunDuration = TimeSpan.FromHours(4); | ||
|
|
||
| public JsaActivityModeJob(IServiceScopeFactory scopeFactory, ILogger<JsaActivityModeJob> logger) | ||
| { | ||
| _scopeFactory = scopeFactory; | ||
| _logger = logger; | ||
| } | ||
|
|
||
| public async Task RunAsync(CancellationToken ct = default) | ||
| { | ||
| _logger.LogInformation("[JsaActivityMode] Starting — will run for up to {Hours} hours", MaxRunDuration.TotalHours); | ||
|
|
||
| var deadline = DateTime.Now.Add(MaxRunDuration); | ||
| var random = new Random(); | ||
| int totalMarked = 0; | ||
|
|
||
| while (DateTime.Now < deadline && !ct.IsCancellationRequested) | ||
| { | ||
| // Wait a random period between 1 and 3 minutes | ||
| var delayMinutes = 1 + (random.NextDouble() * 2); | ||
| var delay = TimeSpan.FromMinutes(delayMinutes); | ||
| _logger.LogDebug("[JsaActivityMode] Waiting {Delay:F1} minutes before next action", delay.TotalMinutes); | ||
|
|
||
| try | ||
| { | ||
| await Task.Delay(delay, ct); | ||
| } | ||
| catch (OperationCanceledException) | ||
| { | ||
| break; | ||
| } | ||
|
|
||
| if (ct.IsCancellationRequested || DateTime.Now >= deadline) | ||
| break; | ||
|
|
||
| using var scope = _scopeFactory.CreateScope(); | ||
| var jobService = scope.ServiceProvider.GetRequiredService<JobListingService>(); | ||
| var authService = scope.ServiceProvider.GetRequiredService<AuthService>(); | ||
|
|
||
| var users = authService.GetAllUsers(); | ||
|
|
||
| foreach (var user in users) | ||
| { | ||
| var allJobs = jobService.GetAllJobListings(user.Id); | ||
|
|
||
| // "Browse" tab = not applied, not possible, not unsuitable, not archived | ||
| var browseJobs = allJobs | ||
| .Where(j => !j.HasApplied | ||
| && j.Suitability == SuitabilityStatus.NotChecked | ||
| && !j.IsArchived) | ||
| .ToList(); | ||
|
|
||
| if (browseJobs.Count == 0) | ||
| { | ||
| _logger.LogInformation("[JsaActivityMode] No browse jobs left for user {User}, skipping", user.Email); | ||
| continue; | ||
| } | ||
|
|
||
| var pick = browseJobs[random.Next(browseJobs.Count)]; | ||
| jobService.SetSuitabilityStatus(pick.Id, SuitabilityStatus.Unsuitable, HistoryChangeSource.Manual, | ||
| forUserId: user.Id); | ||
| totalMarked++; | ||
|
|
||
| _logger.LogInformation("[JsaActivityMode] Marked \"{Title}\" at {Company} as unsuitable ({Remaining} browse jobs remaining)", | ||
| pick.Title, pick.Company, browseJobs.Count - 1); | ||
| } | ||
| } | ||
|
|
||
| _logger.LogInformation("[JsaActivityMode] Finished — marked {Total} jobs as unsuitable over the session", totalMarked); | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -9,7 +9,8 @@ public enum JobCategory | |||||||||||||||||||||
| JobDiscovery, | ||||||||||||||||||||||
| PipelineAutomation, | ||||||||||||||||||||||
| EmailIntegration, | ||||||||||||||||||||||
| Maintenance | ||||||||||||||||||||||
| Maintenance, | ||||||||||||||||||||||
| JsaActivity | ||||||||||||||||||||||
| } | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| public class BackgroundJobStatus | ||||||||||||||||||||||
|
|
@@ -55,9 +56,13 @@ public class LocalBackgroundService : BackgroundService | |||||||||||||||||||||
|
|
||||||||||||||||||||||
| // Maintenance | ||||||||||||||||||||||
| ["ScheduledBackup"] = ("Scheduled Backup", JobCategory.Maintenance, TimeSpan.FromHours(24)), | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| // JSA Activity (only visible when data path contains "passp") | ||||||||||||||||||||||
| ["JsaActivityMode"] = ("JSA Activity Mode", JobCategory.JsaActivity, TimeSpan.FromHours(12)), | ||||||||||||||||||||||
| }; | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| private readonly Dictionary<string, BackgroundJobStatus> _jobStatuses = new(); | ||||||||||||||||||||||
| private readonly bool _jsaActivityEnabled; | ||||||||||||||||||||||
| private DateTime? _startedAt; | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| public static string GetCategoryDisplayName(JobCategory category) => category switch | ||||||||||||||||||||||
|
|
@@ -67,6 +72,7 @@ public class LocalBackgroundService : BackgroundService | |||||||||||||||||||||
| JobCategory.PipelineAutomation => "Pipeline Automation", | ||||||||||||||||||||||
| JobCategory.EmailIntegration => "Email Integration", | ||||||||||||||||||||||
| JobCategory.Maintenance => "Maintenance", | ||||||||||||||||||||||
| JobCategory.JsaActivity => "JSA Activity", | ||||||||||||||||||||||
| _ => category.ToString() | ||||||||||||||||||||||
| }; | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
|
|
@@ -77,6 +83,7 @@ public class LocalBackgroundService : BackgroundService | |||||||||||||||||||||
| JobCategory.PipelineAutomation => "Automatically update job statuses based on rules", | ||||||||||||||||||||||
| JobCategory.EmailIntegration => "Process incoming emails and send notifications", | ||||||||||||||||||||||
| JobCategory.Maintenance => "System maintenance and data backup tasks", | ||||||||||||||||||||||
| JobCategory.JsaActivity => "Automatically review new jobs to simulate JSA job search activity", | ||||||||||||||||||||||
| _ => "" | ||||||||||||||||||||||
| }; | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
|
|
@@ -86,10 +93,16 @@ public LocalBackgroundService(IServiceScopeFactory scopeFactory, ILogger<LocalBa | |||||||||||||||||||||
| _logger = logger; | ||||||||||||||||||||||
| _configPath = Path.Combine(env.ContentRootPath, "Data", "background-jobs.json"); | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| // JSA Activity Mode is only available when the data directory contains "passp" | ||||||||||||||||||||||
| _jsaActivityEnabled = _configPath.Contains("passp", StringComparison.OrdinalIgnoreCase); | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
|
Comment on lines
94
to
+98
|
||||||||||||||||||||||
| _configPath = Path.Combine(env.ContentRootPath, "Data", "background-jobs.json"); | |
| // JSA Activity Mode is only available when the data directory contains "passp" | |
| _jsaActivityEnabled = _configPath.Contains("passp", StringComparison.OrdinalIgnoreCase); | |
| var dataDirectory = Path.Combine(env.ContentRootPath, "Data"); | |
| _configPath = Path.Combine(dataDirectory, "background-jobs.json"); | |
| // JSA Activity Mode is only available when the Data directory contains the explicit marker file. | |
| _jsaActivityEnabled = File.Exists(Path.Combine(dataDirectory, "jsa-activity.enabled")); |
Copilot
AI
Apr 9, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
JsaActivityModeJob supports cancellation via CancellationToken, but RunLoop/RunJsaActivityMode don’t pass the stoppingToken through, meaning the host may be unable to shut down promptly (this job can run up to 4 hours). Consider updating RunLoop to accept a token-aware delegate (e.g., Func<CancellationToken, Task>) and call job.RunAsync(ct) so shutdown cancels the session.
Copilot
AI
Apr 9, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
RunJsaActivityMode resolves JsaActivityModeJob from DI, but the job is not registered in Program.cs (no AddTransient/AddScoped entry). This will throw at runtime the first time the loop runs. Register JsaActivityModeJob in the service container the same way other background jobs are registered.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This job changes suitability automatically but records the history source as
Manual, which can make the audit trail misleading (it will look like a user action). Consider usingHistoryChangeSource.System(or a dedicated source) so downstream views/reports can distinguish automated activity from real user clicks.