diff --git a/Core/Entities/CareerField.cs b/Core/Entities/CareerField.cs
new file mode 100644
index 0000000..f1627be
--- /dev/null
+++ b/Core/Entities/CareerField.cs
@@ -0,0 +1,48 @@
+namespace Core.Entities;
+
+///
+/// Represents a career field cluster that groups related careers together.
+///
+public class CareerField
+{
+ ///
+ /// Unique identifier for the career field (1-25)
+ ///
+ public int Id { get; }
+
+ ///
+ /// Display name of the career field
+ ///
+ public string Name { get; }
+
+ ///
+ /// Exact career names that belong to this field
+ ///
+ public IReadOnlyList DirectCareerMatches { get; }
+
+ ///
+ /// Keywords for pattern matching (case-insensitive substring matching)
+ ///
+ public IReadOnlyList PatternKeywords { get; }
+
+ ///
+ /// Creates a new CareerField instance
+ ///
+ /// Unique identifier
+ /// Display name
+ /// Exact career name matches
+ /// Keywords for pattern matching
+ public CareerField(int id, string name, IReadOnlyList directCareerMatches, IReadOnlyList patternKeywords)
+ {
+ Id = id;
+ Name = name;
+ DirectCareerMatches = directCareerMatches ?? Array.Empty();
+ PatternKeywords = patternKeywords ?? Array.Empty();
+ }
+
+ public override string ToString()
+ {
+ return Name;
+ }
+}
+
diff --git a/Core/Utility/CareerFieldDefinitions.cs b/Core/Utility/CareerFieldDefinitions.cs
new file mode 100644
index 0000000..9f82d4c
--- /dev/null
+++ b/Core/Utility/CareerFieldDefinitions.cs
@@ -0,0 +1,281 @@
+using Core.Entities;
+
+namespace Core.Utility;
+
+///
+/// Static class that defines all 25 career field clusters and provides methods to match careers to fields.
+///
+public static class CareerFieldDefinitions
+{
+ private static readonly Lazy> _allFields = new(() => CreateAllCareerFields());
+
+ ///
+ /// Gets all 25 career field definitions
+ ///
+ public static IReadOnlyList GetAllCareerFields() => _allFields.Value;
+
+ ///
+ /// Gets the career fields that are related to the given careers.
+ /// Uses both direct name matching and pattern-based keyword matching.
+ ///
+ /// The careers to match against
+ /// Distinct list of related CareerFields
+ public static IReadOnlyList GetRelatedCareerFields(IEnumerable careers)
+ {
+ if (careers == null)
+ return Array.Empty();
+
+ var careerNames = careers
+ .Where(c => !string.IsNullOrWhiteSpace(c.Name))
+ .Select(c => c.Name.Trim())
+ .ToHashSet(StringComparer.OrdinalIgnoreCase);
+
+ if (!careerNames.Any())
+ return Array.Empty();
+
+ var matchingFields = new HashSet();
+ var allFields = GetAllCareerFields();
+
+ foreach (var field in allFields)
+ {
+ bool matches = false;
+
+ // Check direct matches
+ foreach (var directMatch in field.DirectCareerMatches)
+ {
+ if (careerNames.Contains(directMatch))
+ {
+ matches = true;
+ break;
+ }
+ }
+
+ // Check pattern matches if no direct match found
+ if (!matches)
+ {
+ foreach (var keyword in field.PatternKeywords)
+ {
+ if (careerNames.Any(name => name.Contains(keyword, StringComparison.OrdinalIgnoreCase)))
+ {
+ matches = true;
+ break;
+ }
+ }
+ }
+
+ if (matches)
+ {
+ matchingFields.Add(field);
+ }
+ }
+
+ return matchingFields.OrderBy(f => f.Id).ToList();
+ }
+
+ private static IReadOnlyList CreateAllCareerFields()
+ {
+ return new List
+ {
+ // 1. Aerospace & Automotive Engineering
+ new CareerField(
+ 1,
+ "Aerospace & Automotive Engineering",
+ new[] { "Aeronautical engineer", "Aircraft systems engineer", "Automobile designer", "Automotive designer", "Automotive modeler", "Race car engineer" },
+ new[] { "aeronautical", "aircraft", "automobile", "automotive", "race car" }
+ ),
+
+ // 2. Mechanical & Robotics Engineering
+ new CareerField(
+ 2,
+ "Mechanical & Robotics Engineering",
+ new[] { "Machine designer", "Mechanical drafter", "Mechanical engineer", "Robotics engineer" },
+ new[] { "mechanical", "robotics", "machine" }
+ ),
+
+ // 3. Electrical & Electronics Engineering
+ new CareerField(
+ 3,
+ "Electrical & Electronics Engineering",
+ new[] { "Electrical engineer", "Electrical technician", "Electrician", "Electromechanical engineer", "Electronic analyst", "Electronic designer" },
+ new[] { "electrical", "electronic", "electrician" }
+ ),
+
+ // 4. Civil & Structural Engineering
+ new CareerField(
+ 4,
+ "Civil & Structural Engineering",
+ new[] { "Civil engineer", "Construction analyst", "Construction manager", "General contractor", "Structural engineer", "Structural iron and steel work technician" },
+ new[] { "civil", "construction", "structural", "contractor" }
+ ),
+
+ // 5. Environmental & Energy Engineering
+ new CareerField(
+ 5,
+ "Environmental & Energy Engineering",
+ new[] { "Chemical engineer", "Energy efficiency technician", "Environmental engineer", "Solar engineer", "Solar panel installer", "Solar sales consultant" },
+ new[] { "chemical", "energy", "environmental", "solar" }
+ ),
+
+ // 6. General Engineering & Quality
+ new CareerField(
+ 6,
+ "General Engineering & Quality",
+ new[] { "Engineer", "Engineering manager", "Engineering technician", "Quality assurance engineer", "Quality engineer", "Standards engineer" },
+ new[] { "engineer", "quality", "standards" }
+ ),
+
+ // 7. Architecture & Urban Planning
+ new CareerField(
+ 7,
+ "Architecture & Urban Planning",
+ new[] { "Architect", "Community planner", "Interior designer", "Urban and regional planner" },
+ new[] { "architect", "planner", "interior design", "urban" }
+ ),
+
+ // 8. Software Development
+ new CareerField(
+ 8,
+ "Software Development",
+ new[] { "Computer programmer", "Computer software engineer", "Programming & software development", "Software designer", "Software engineer" },
+ new[] { "programming", "programmer", "software", "developer" }
+ ),
+
+ // 9. IT & Networking
+ new CareerField(
+ 9,
+ "IT & Networking",
+ new[] { "Computer engineer", "Computer network specialist", "Computer technician", "Information support & services", "Network systems", "Technical support specialist", "Telecommunications manager" },
+ new[] { "network", "computer", "technical support", "telecommunications", "IT" }
+ ),
+
+ // 10. Cybersecurity & Digital Forensics
+ new CareerField(
+ 10,
+ "Cybersecurity & Digital Forensics",
+ new[] { "Cryptographer", "Cyber Crime Investigator", "Cyber defense incident responder", "Cyber forensics expert", "Cyber legal advisor", "Cyber operator", "Cybersecurity engineer", "Vulnerability assessor" },
+ new[] { "cyber", "security", "forensics", "cryptography", "vulnerability" }
+ ),
+
+ // 11. Data Science & Analytics
+ new CareerField(
+ 11,
+ "Data Science & Analytics",
+ new[] { "Actuary", "Data analyst", "Data scientist", "Economist", "Mathematician", "Operations research analyst" },
+ new[] { "data", "analyst", "actuary", "economist", "mathematician", "research" }
+ ),
+
+ // 12. CAD, CNC & Manufacturing
+ new CareerField(
+ 12,
+ "CAD, CNC & Manufacturing",
+ new[] { "CAD professional", "CNC programmer", "Manufacturing", "Production planner" },
+ new[] { "CAD", "CNC", "manufacturing", "production" }
+ ),
+
+ // 13. Industrial & Product Design
+ new CareerField(
+ 13,
+ "Industrial & Product Design",
+ new[] { "Appraiser", "Commercial and industrial design", "Designer", "Industrial designer", "Product designer" },
+ new[] { "designer", "design", "industrial", "product", "appraiser" }
+ ),
+
+ // 14. Visual Arts & Animation
+ new CareerField(
+ 14,
+ "Visual Arts & Animation",
+ new[] { "Animator", "Artist", "Computer animator", "Graphic artist", "Illustrator", "Multimedia designer" },
+ new[] { "animator", "artist", "graphic", "illustrator", "multimedia" }
+ ),
+
+ // 15. Game Design & Interactive Media
+ new CareerField(
+ 15,
+ "Game Design & Interactive Media",
+ new[] { "Game designer", "Game Play Tester", "Professional Gamer" },
+ new[] { "game", "gamer", "gaming" }
+ ),
+
+ // 16. Audio & Music Production
+ new CareerField(
+ 16,
+ "Audio & Music Production",
+ new[] { "Audio designer or engineer", "Audio Engineer", "Audio operator or technician", "Broadcast technician", "Music composer" },
+ new[] { "audio", "music", "broadcast", "sound" }
+ ),
+
+ // 17. Video & Film Production
+ new CareerField(
+ 17,
+ "Video & Film Production",
+ new[] { "Audiovisual technician", "Director", "Entertainment/television broadcaster", "Videographer" },
+ new[] { "video", "film", "director", "television", "broadcast", "videographer" }
+ ),
+
+ // 18. Web & Digital Communications
+ new CareerField(
+ 18,
+ "Web & Digital Communications",
+ new[] { "Instructional technologist", "Web & digital communications", "Webmaster", "Website designer" },
+ new[] { "web", "website", "digital", "communications", "webmaster" }
+ ),
+
+ // 19. Writing & Publishing
+ new CareerField(
+ 19,
+ "Writing & Publishing",
+ new[] { "Ad copy writer", "Editor", "Publisher", "Screenplay writer", "Speech writer", "Technical writer", "Writer" },
+ new[] { "writing", "writer", "editor", "publisher", "copy" }
+ ),
+
+ // 20. Journalism & Public Relations
+ new CareerField(
+ 20,
+ "Journalism & Public Relations",
+ new[] { "Internal communications manager", "Motivational speaker", "Photojournalist", "Reporter" },
+ new[] { "journalism", "reporter", "photojournalist", "communications", "speaker" }
+ ),
+
+ // 21. Forensics & Criminal Investigation
+ new CareerField(
+ 21,
+ "Forensics & Criminal Investigation",
+ new[] { "Crime scene investigator", "Detective", "Forensic accountant", "Forensic anthropologist", "Forensic engineering scientist", "Forensic pathologist" },
+ new[] { "forensic", "detective", "investigator", "crime" }
+ ),
+
+ // 22. Healthcare & Medical Technology
+ new CareerField(
+ 22,
+ "Healthcare & Medical Technology",
+ new[] { "Dietitian", "Doctor", "Epidemiologist", "Medical technologist", "Nurse", "Pharmacist", "Prosthetics practitioner" },
+ new[] { "medical", "health", "doctor", "nurse", "pharmacist", "dietitian", "epidemiology" }
+ ),
+
+ // 23. Science & Research
+ new CareerField(
+ 23,
+ "Science & Research",
+ new[] { "Botanist", "Food scientist", "Meteorologist", "Molecular biologist", "Physics instructor", "Plant geneticist", "Research and development scientist", "Research assistant", "Researcher" },
+ new[] { "scientist", "research", "biology", "physics", "botanist", "meteorologist", "geneticist" }
+ ),
+
+ // 24. Education & Training
+ new CareerField(
+ 24,
+ "Education & Training",
+ new[] { "Educator", "Teacher/trainer", "Technology education instructor" },
+ new[] { "educator", "teacher", "trainer", "education", "instructor" }
+ ),
+
+ // 25. Business, Legal & Government
+ new CareerField(
+ 25,
+ "Business, Legal & Government",
+ new[] { "Creative consultant", "Entrepreneur", "Government Official", "Lawyer", "Legal Aide", "Lobbyist", "Management executive", "Market researcher", "Marketing strategist", "Parliamentarian", "Politician", "Project manager", "Public affairs specialist", "Public policy specialist", "Recording Clerk", "Small business owner", "Volunteer manager" },
+ new[] { "business", "legal", "lawyer", "government", "politician", "manager", "marketing", "consultant", "entrepreneur" }
+ )
+ };
+ }
+}
+
diff --git a/WebApp/Components/Features/Events/CareerMapping.razor b/WebApp/Components/Features/Events/CareerMapping.razor
index 108e5bf..41cbd86 100644
--- a/WebApp/Components/Features/Events/CareerMapping.razor
+++ b/WebApp/Components/Features/Events/CareerMapping.razor
@@ -2,6 +2,8 @@
@attribute [Authorize]
@using Microsoft.EntityFrameworkCore
@using WebApp.Components.Shared.Components
+@using Core.Utility
+@using Core.Entities
@inject AppDbContext Context
- No Career Mappings Found
+ No Career Field Mappings Found
- No events have related careers assigned. Edit events to add related careers.
+ No events have related careers assigned that match any career fields. Edit events to add related careers.
}
else
{
- Event-Career Relationships
+ Event-Career Field Relationships
- This diagram shows the connections between events and their related careers.
- Events are shown on the left, careers on the right.
+ This diagram shows the connections between events and their related career fields.
+ Events are shown on the left, career fields on the right. Career fields are clusters of related careers.
+ @_mermaidDefinition
}
@@ -61,9 +64,14 @@ else
.OrderBy(e => e.Name)
.ToListAsync();
- if (events.Any())
+ // Filter to only events that have career fields (after matching)
+ var eventsWithFields = events
+ .Where(e => CareerFieldDefinitions.GetRelatedCareerFields(e.RelatedCareers).Any())
+ .ToList();
+
+ if (eventsWithFields.Any())
{
- _mermaidDefinition = GenerateMermaidDiagram(events);
+ _mermaidDefinition = GenerateMermaidDiagram(eventsWithFields);
}
}
finally
@@ -79,12 +87,15 @@ else
// Dictionary to track node IDs and labels (to avoid duplicates)
var eventNodeIds = new Dictionary();
- var careerNodeIds = new Dictionary();
- var careerCounter = 1;
+ var fieldNodeIds = new Dictionary();
+ var fieldCounter = 1;
var eventCounter = 1;
- // First pass: collect all unique nodes
- foreach (var evt in events)
+ // Dictionary to track which events connect to which career fields
+ var eventToFields = new Dictionary>();
+
+ // First pass: collect all unique nodes and determine relationships
+ foreach (var evt in events.OrderBy(e => e.Name))
{
if (!eventNodeIds.ContainsKey(evt.Id))
{
@@ -93,40 +104,57 @@ else
eventNodeIds[evt.Id] = (eventNodeId, eventLabel);
}
- foreach (var career in evt.RelatedCareers)
+ // Get related career fields for this event's careers
+ var relatedFields = CareerFieldDefinitions.GetRelatedCareerFields(evt.RelatedCareers);
+ if (relatedFields.Any())
{
- if (!careerNodeIds.ContainsKey(career.Id))
+ eventToFields[evt.Id] = new HashSet(relatedFields);
+
+ // Track career field nodes
+ foreach (var field in relatedFields)
{
- var careerNodeId = $"C{careerCounter++}";
- var careerLabel = EscapeMermaidLabel(career.Name);
- careerNodeIds[career.Id] = (careerNodeId, careerLabel);
+ if (!fieldNodeIds.ContainsKey(field.Id))
+ {
+ var fieldNodeId = $"F{fieldCounter++}";
+ var fieldLabel = EscapeMermaidLabel(field.Name);
+ fieldNodeIds[field.Id] = (fieldNodeId, fieldLabel);
+ }
}
}
}
- // Second pass: define all nodes
+ // Second pass: define all event nodes (blue)
foreach (var (id, (nodeId, label)) in eventNodeIds.OrderBy(e => e.Value.Label))
{
- builder.AppendLine($" {nodeId}[\"{label}\"]");
+ builder.AppendLine($" {nodeId}[\"{label}\"]:::eventNode");
}
- foreach (var (id, (nodeId, label)) in careerNodeIds.OrderBy(c => c.Value.Label))
+ // Third pass: define all career field nodes (green)
+ foreach (var (id, (nodeId, label)) in fieldNodeIds.OrderBy(f => f.Value.Label))
{
- builder.AppendLine($" {nodeId}[\"{label}\"]");
+ builder.AppendLine($" {nodeId}[\"{label}\"]:::fieldNode");
}
- // Third pass: define all edges
+ // Fourth pass: define all edges (events on left, fields on right)
foreach (var evt in events.OrderBy(e => e.Name))
{
- var currentEventNodeId = eventNodeIds[evt.Id].Id;
-
- foreach (var career in evt.RelatedCareers.OrderBy(c => c.Name))
+ if (eventToFields.TryGetValue(evt.Id, out var fields))
{
- var currentCareerNodeId = careerNodeIds[career.Id].Id;
- builder.AppendLine($" {currentEventNodeId} --> {currentCareerNodeId}");
+ var currentEventNodeId = eventNodeIds[evt.Id].Id;
+
+ foreach (var field in fields.OrderBy(f => f.Name))
+ {
+ var currentFieldNodeId = fieldNodeIds[field.Id].Id;
+ builder.AppendLine($" {currentEventNodeId} --> {currentFieldNodeId}");
+ }
}
}
+ // Add styling for different colors
+ builder.AppendLine("");
+ builder.AppendLine("classDef eventNode fill:#4A90E2,stroke:#2E5C8A,stroke-width:2px,color:#fff");
+ builder.AppendLine("classDef fieldNode fill:#50C878,stroke:#2D8659,stroke-width:2px,color:#fff");
+
return builder.ToString();
}