Improve company matching and move points to employment table

Company Matching:
- Extract parent company from "Brand (Parent Company)" format
- Handle slash-separated names like "ASDA/WALMART"
- Match against both original name and search query for flexibility
- Add PLC/Plc case variations

Report UI:
- Remove separate Score Breakdown section
- Add Points column to Employment Verification table
- Calculate points per company from matching flags

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
2026-01-20 21:49:26 +01:00
parent f4890b3049
commit 5d2ec4b98e
2 changed files with 132 additions and 91 deletions

View File

@@ -144,70 +144,6 @@
</div>
</div>
<!-- Score Breakdown -->
<div class="card border-0 shadow-sm mb-4">
<div class="card-header bg-white py-3">
<h5 class="mb-0 fw-bold">
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" fill="currentColor" class="bi bi-calculator me-2" viewBox="0 0 16 16">
<path d="M12 1a1 1 0 0 1 1 1v12a1 1 0 0 1-1 1H4a1 1 0 0 1-1-1V2a1 1 0 0 1 1-1h8zM4 0a2 2 0 0 0-2 2v12a2 2 0 0 0 2 2h8a2 2 0 0 0 2-2V2a2 2 0 0 0-2-2H4z"/>
<path d="M4 2.5a.5.5 0 0 1 .5-.5h7a.5.5 0 0 1 .5.5v2a.5.5 0 0 1-.5.5h-7a.5.5 0 0 1-.5-.5v-2zm0 4a.5.5 0 0 1 .5-.5h1a.5.5 0 0 1 .5.5v1a.5.5 0 0 1-.5.5h-1a.5.5 0 0 1-.5-.5v-1zm0 3a.5.5 0 0 1 .5-.5h1a.5.5 0 0 1 .5.5v1a.5.5 0 0 1-.5.5h-1a.5.5 0 0 1-.5-.5v-1zm0 3a.5.5 0 0 1 .5-.5h1a.5.5 0 0 1 .5.5v1a.5.5 0 0 1-.5.5h-1a.5.5 0 0 1-.5-.5v-1zm3-6a.5.5 0 0 1 .5-.5h1a.5.5 0 0 1 .5.5v1a.5.5 0 0 1-.5.5h-1a.5.5 0 0 1-.5-.5v-1zm0 3a.5.5 0 0 1 .5-.5h1a.5.5 0 0 1 .5.5v1a.5.5 0 0 1-.5.5h-1a.5.5 0 0 1-.5-.5v-1zm0 3a.5.5 0 0 1 .5-.5h1a.5.5 0 0 1 .5.5v1a.5.5 0 0 1-.5.5h-1a.5.5 0 0 1-.5-.5v-1zm3-6a.5.5 0 0 1 .5-.5h1a.5.5 0 0 1 .5.5v1a.5.5 0 0 1-.5.5h-1a.5.5 0 0 1-.5-.5v-1zm0 3a.5.5 0 0 1 .5-.5h1a.5.5 0 0 1 .5.5v4a.5.5 0 0 1-.5.5h-1a.5.5 0 0 1-.5-.5v-4z"/>
</svg>
Score Breakdown
</h5>
</div>
<div class="card-body">
@{
var deductedFlags = _report.Flags
.GroupBy(f => (f.Title, f.Description))
.Select(g => g.First())
.Where(f => f.ScoreImpact < 0)
.ToList();
var totalDeductions = deductedFlags.Sum(f => Math.Abs(f.ScoreImpact));
}
<div class="row">
<div class="col-md-6">
<table class="table table-sm">
<tbody>
<tr class="table-success">
<td><strong>Base Score</strong></td>
<td class="text-end"><strong>100 pts</strong></td>
</tr>
@foreach (var flag in deductedFlags.OrderByDescending(f => Math.Abs(f.ScoreImpact)))
{
<tr class="text-danger">
<td>@FormatFlagTitle(flag.Title)</td>
<td class="text-end">-@Math.Abs(flag.ScoreImpact) pts</td>
</tr>
}
<tr class="table-secondary">
<td><strong>Total Deductions</strong></td>
<td class="text-end text-danger"><strong>-@totalDeductions pts</strong></td>
</tr>
<tr class="table-primary">
<td><strong>Final Score</strong></td>
<td class="text-end"><strong>@_report.OverallScore pts</strong></td>
</tr>
</tbody>
</table>
</div>
<div class="col-md-6">
<div class="alert alert-light border">
<h6 class="fw-bold mb-2">How Scoring Works</h6>
<ul class="small mb-0">
<li>Everyone starts with <strong>100 points</strong></li>
<li>Points are deducted for issues found during verification</li>
<li>Unverified companies: <strong>-10 pts</strong> each</li>
<li>Director claims not verified: <strong>-20 pts</strong> each</li>
<li>Employment before company existed: <strong>-20 pts</strong></li>
<li>Employment gaps: <strong>-1 to -10 pts</strong> per gap</li>
<li>Concurrent employment is noted but not penalised</li>
</ul>
</div>
</div>
</div>
</div>
</div>
<!-- Employment Verification -->
<div class="card border-0 shadow-sm mb-4">
<div class="card-header bg-white py-3">
@@ -229,6 +165,7 @@
<th>Matched Company</th>
<th class="text-center">Match Score</th>
<th class="text-center">Status</th>
<th class="text-center">Points</th>
</tr>
</thead>
<tbody>
@@ -284,11 +221,24 @@
<span class="badge bg-warning text-dark">Unverified</span>
}
</td>
<td class="text-center">
@{
var companyPoints = GetPointsForCompany(verification.ClaimedCompany, verification.MatchedCompanyName);
}
@if (companyPoints < 0)
{
<span class="text-danger fw-medium">@companyPoints</span>
}
else
{
<span class="text-success">0</span>
}
</td>
</tr>
@if (!string.IsNullOrEmpty(verification.VerificationNotes))
{
<tr class="table-light">
<td colspan="5" class="small text-muted py-1 ps-4">
<td colspan="6" class="small text-muted py-1 ps-4">
<svg xmlns="http://www.w3.org/2000/svg" width="12" height="12" fill="currentColor" class="bi bi-info-circle me-1" viewBox="0 0 16 16">
<path d="M8 15A7 7 0 1 1 8 1a7 7 0 0 1 0 14zm0 1A8 8 0 1 0 8 0a8 8 0 0 0 0 16z"/>
<path d="m8.93 6.588-2.29.287-.082.38.45.083c.294.07.352.176.288.469l-.738 3.468c-.194.897.105 1.319.808 1.319.545 0 1.178-.252 1.465-.598l.088-.416c-.2.176-.492.246-.686.246-.275 0-.375-.193-.304-.533L8.93 6.588zM9 4.5a1 1 0 1 1-2 0 1 1 0 0 1 2 0z"/>
@@ -752,4 +702,20 @@
_ => title
};
}
private int GetPointsForCompany(string claimedCompany, string? matchedCompany)
{
if (_report?.Flags is null) return 0;
// Sum up all flags that mention this company in their description
var companyFlags = _report.Flags
.Where(f => f.ScoreImpact < 0 &&
((!string.IsNullOrEmpty(f.Description) && f.Description.Contains(claimedCompany, StringComparison.OrdinalIgnoreCase)) ||
(!string.IsNullOrEmpty(matchedCompany) && !string.IsNullOrEmpty(f.Description) && f.Description.Contains(matchedCompany, StringComparison.OrdinalIgnoreCase))))
.GroupBy(f => (f.Title, f.Description))
.Select(g => g.First())
.ToList();
return companyFlags.Sum(f => f.ScoreImpact);
}
}