29 Commits

Author SHA1 Message Date
fab1866fc8 feat: Detect fake UK universities using naming patterns
Add detection for institutions that follow UK university naming
conventions (e.g., "University of the Peak District") but aren't
in the recognised institutions list. These are now flagged as
"Suspicious" with a -15 point penalty instead of just "Unknown".

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-25 08:11:08 +00:00
0dc03dd380 feat: Add Terms of Service acceptance checkbox to registration
- Add checkbox requiring users to agree to Terms of Service and Privacy Policy
- Add TermsAcceptedAt field to ApplicationUser to track acceptance
- Link checkbox to actual /terms and /privacy pages
- Remove passive text that was using dead # links

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-25 07:46:16 +00:00
0c42842655 feat: Add legal compliance changes
- Replace 'diploma mill' language with objective 'unaccredited institution' terminology
- Rename DiplomaMills.cs to UnaccreditedInstitutions.cs with neutral language
- Update EducationVerificationResult.IsUnaccredited property
- Update flag titles to 'Unaccredited Institution' and 'Institution Requires Verification'
- Add legal disclaimer to verification report page
- Add Privacy Policy page (/privacy) with UK GDPR compliance info
- Add Terms of Service page (/terms) with candidate notice requirements
- Add footer links to Privacy and Terms pages
- Update all tests to use new terminology

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-25 04:52:12 +00:00
49e4f74768 chore: Restore deploy-local.sh script
🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-25 04:33:07 +00:00
2575e2be95 feat: Add text analysis checks for CV verification
Implement four new CV verification checks without external APIs:

1. Buzzword detection - flags excessive clichés (50+ patterns)
2. Vague achievement detection - identifies weak language vs quantified results
3. Skills/job title alignment - checks skills match claimed roles (25+ role mappings)
4. Unrealistic metrics detection - flags implausible claims (>200% growth, etc.)

New files:
- ITextAnalysisService interface
- TextAnalysisResult models
- TextAnalysisService implementation (~400 lines)

Integration:
- Added "Analysing Content" processing stage
- Flags appear under Plausibility category
- TextAnalysis section added to veracity report

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-25 04:30:11 +00:00
a132efd907 refactor: Remove redundant code and consolidate JsonSerializerOptions
- Remove unused GetRepoLanguagesAsync method from GitHubClient
- Remove unused IsFakeAccreditor and FakeAccreditors from DiplomaMills
- Remove unused CompanyVerificationFlagPenalty constant from ProcessCVCheckJob
- Remove unused SkillVerification properties (TotalLinesOfCode, FirstUsed, LastUsed)
- Remove unused CompanyMatchRequest record from SemanticMatchResult
- Add JsonDefaults.ApiClient and consolidate duplicate JsonSerializerOptions across API clients
- Remove ApiTester tool containing hardcoded API credentials (security fix)

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-25 04:16:51 +00:00
f775164212 refactor: Compact UI and fix RealCV branding
- Fix logo to show RealCV instead of TrueCV
- Remove duplicate auth-logo from Login/Register pages
- Dashboard: reduce padding, smaller icons, tighter table rows
- Report: compact score header (140px → 100px roundel)
- Reduce stat card sizes and spacing throughout

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-24 20:56:04 +00:00
72b7f11c41 refactor: Remove SRA integration (no public API available)
The SRA (Solicitors Regulation Authority) does not provide a public REST API.
Their register is only accessible via their website. Removed all SRA-related
code and added ApiTester tool for testing remaining integrations.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-24 15:28:07 +00:00
ff09524503 Merge branch 'feature/additional-verification-apis'
Add free verification APIs: FCA, SRA, GitHub, ORCID

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-24 15:09:40 +00:00
9ec96d4af7 chore: Remove OpenCorporates integration (requires paid plan for commercial use)
Keep only the genuinely free APIs:
- FCA Register (free with registration)
- SRA Register (free public API)
- GitHub (free tier sufficient)
- ORCID (free public API)

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-24 15:09:34 +00:00
5d2965beae feat: Add additional verification APIs (FCA, SRA, GitHub, OpenCorporates, ORCID)
This adds five new free API integrations for enhanced CV verification:

- FCA Register API: Verify financial services professionals
- SRA Register API: Verify solicitors and legal professionals
- GitHub API: Verify developer profiles and technical skills
- OpenCorporates API: Verify international companies across jurisdictions
- ORCID API: Verify academic researchers and publications

Includes:
- API clients for all five services with retry policies
- Service implementations with name matching and validation
- Models for verification results with detailed flags
- Configuration options in appsettings.json
- DI registration for all services

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-24 13:05:52 +00:00
8a4e46d872 feat: Improve company alias matching and add batch tester flags display
- Add Deliveroo alias (ROOFOODS LTD) to fix matching to wrong company
- Add JCB alias (J.C. BAMFORD EXCAVATORS LIMITED)
- Improve FindDirectAliasMatch to prefer active companies over dissolved
- Display verification flags in CVBatchTester output
- Employer verification improved from 65% to 86%

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-24 05:43:04 +00:00
7ebf09c284 fix: Add direct alias matching to bypass AI rejection for known trading names
- WPP was failing because AI didn't recognize "WPP 2005 LIMITED" as the main
  WPP entity (WPP plc is incorporated in Jersey, not UK Companies House)
- Added FindDirectAliasMatch() that accepts candidates matching known aliases
  without requiring AI confirmation
- Fixed WPP alias to use "WPP 2005 LIMITED" instead of non-existent "WPP PLC"
- This improves employer verification from 84% to 85%

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-22 22:00:59 +00:00
1a06d60f2d feat: Add company name aliases and fix education verification
- Add trading name aliases for major UK companies (Boots, BBC, Lloyds, etc.)
  mapping to their official Companies House registered names
- Add Leeds Beckett University (and former name Leeds Metropolitan) to
  recognised UK institutions
- This improves company verification from 65% to 84% on test data
- CVBatchTester tool for testing verification against JSON CVs

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-22 21:48:15 +00:00
6773162426 feat: Display candidate name from CV data instead of filename
- Added CandidateName field to CVCheckDto
- Extract candidate name from ReportJson or ExtractedDataJson
- Dashboard now shows actual candidate name for JSON uploads
- PDF export uses candidate name from report

This fixes the issue where JSON files showed their filename (e.g.,
"CLEAN-001") instead of the actual candidate name from the CV data.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-22 21:23:48 +00:00
ecb599fba7 fix: Replace --truecv- CSS variables with --realcv-
Fixed CSS variable references in Razor components that were still
using the old --truecv- prefix, causing score circles and other
styled elements to not render properly.

Also changed score-ring-value font to Inter for consistent numbers.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-22 21:14:20 +00:00
70d8a4786e fix: Change stat-value font from JetBrains Mono to Inter
The stylized '0' in JetBrains Mono was being confused with '8'.
Changed to Inter with tabular-nums for clearer number display.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-22 21:09:41 +00:00
ee48afa5bd chore: Add production config with API keys
🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-22 21:08:05 +00:00
473bef96e9 chore: Update all TrueCV references to RealCV
- Updated Dockerfiles (Dockerfile, Dockerfile.migrations)
- Updated docker-compose.yml (service names, container names, network)
- Updated deploy scripts (README.md, server-setup.sh, deploy.sh)
- Updated .gitignore
- Updated all strategy documentation files
- Updated app.js comment

Note: Passwords containing "TrueCV" were intentionally preserved.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-22 21:01:07 +00:00
d3fb929443 fix: Remove redundant Home menu item from navbar
The logo already serves as the home link, so having a separate Home
text link next to it was redundant and cluttered the navigation.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-22 20:57:14 +00:00
de8b36ae2b fix: Rename logo file to match RealCV branding
🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-22 20:54:33 +00:00
92a3b60878 refactor: Rename TrueCV to RealCV throughout codebase
- Renamed all directories (TrueCV.* -> RealCV.*)
- Renamed all project files (.csproj)
- Renamed solution file (TrueCV.sln -> RealCV.sln)
- Updated all namespaces in C# and Razor files
- Updated project references
- Updated CSS variable names

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-22 20:47:55 +00:00
6f384f8d09 Fix warning text contrast for better readability
Change text-warning from bright yellow (#ffc107) to darker amber (#b45309)
for WCAG AA compliant 4.8:1 contrast ratio on white backgrounds.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-21 11:33:17 +00:00
21a95a38f5 Improve text readability and fix duplicate company scoring
- Increase font sizes from 11px to 12px for employment headers and notes
- Improve color contrast (gray-500 to gray-600) for WCAG AA compliance
- Increase opacity for white text on dark backgrounds (0.6/0.8 to 0.8/0.9)
- Fix duplicate company penalty display to only apply for sequential entries
- Non-sequential entries of same company now each show their own penalty

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-21 11:30:47 +00:00
cfe06788e2 Fix CV verification scoring and UI alignment issues
ProcessCVCheckJob:
- Recognize "contract", "contract work", "contract role" as freelance
- Add "various", "various clients" for multiple short-term contracts
- Prevents false matching of contract work to dissolved companies

Report.razor:
- Fix stat blocks centering (icon, number, label now centered)
- Fix employment table column alignment with fixed widths
- Add inline styles to header for consistent centering
- Improve GetPointsForCompany to show -10 for unverified companies

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-21 11:20:38 +00:00
7510ef3670 Fix text cursor appearing on non-editable elements
- Add universal cursor inherit rule for consistent cursor behavior
- Add user-select: none to prevent text selection on UI elements
- Expand element coverage to include modals, tooltips, and inline elements
- Preserve text cursor and selection for form inputs with !important

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-21 10:53:13 +00:00
5ba787aff9 Add Linux deployment scripts
- deploy.sh: Publish and deploy from dev machine
- server-setup.sh: One-time Ubuntu server setup (Nginx, Docker, SQL Server)
- README.md: Deployment documentation and troubleshooting guide

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-21 09:48:59 +01:00
7ca00ca0b6 Fix cursor styling and replace hero logo with SVG illustration
- Replace hero section PNG logo with clean SVG document illustration
- Add comprehensive cursor styling to prevent text cursor on non-editable elements
- Cover all SVG elements, Bootstrap components, and custom classes
- Ensure checkboxes and interactive elements have pointer cursor

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-21 09:23:51 +01:00
4cc0bb3132 UI redesign: improve readability and add candidate name display
- Add CandidateName property to VeracityReport and display on report page
- Simplify employment verification layout with compact row-based design
- Add UK employment history notice to Home and Check pages
- Improve hero section text readability with text shadow
- Update Login and Register page styling
- Remove Companies House references from UI text

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-21 09:01:01 +01:00
159 changed files with 8012 additions and 1404 deletions

2
.gitignore vendored
View File

@@ -220,5 +220,5 @@ local/
*.swp
# Local file uploads
src/TrueCV.Web/uploads/
src/RealCV.Web/uploads/
logs/

View File

@@ -1,4 +1,4 @@
# TrueCV UK APIs & Integration Resources
# RealCV UK APIs & Integration Resources
**Last Updated:** January 2026
**Purpose:** Practical guide for obtaining API access and integration details
@@ -159,13 +159,13 @@
### Overview
- **Service:** UK company registration and officer records
- **Status:** ✅ Already integrated in TrueCV
- **Status:** ✅ Already integrated in RealCV
- **Coverage:** 3.4M registered UK companies
### Enhancement Opportunities
#### Existing Implementation
- See: `/src/TrueCV.Infrastructure/ExternalApis/CompaniesHouseClient.cs`
- See: `/src/RealCV.Infrastructure/ExternalApis/CompaniesHouseClient.cs`
- Current: Company search + basic data lookup
- Rate Limit: 500 requests/hour (generous)
@@ -326,7 +326,7 @@
- uCheck
- Certn
### Recommended Vendor for TrueCV Integration
### Recommended Vendor for RealCV Integration
**Verifile** (Suggested)
- **Website:** https://www.verifile.io/
@@ -345,7 +345,7 @@
2. Negotiate revenue share (typically 20-30% for platform)
3. Integrate DBS check submission API
4. Build compliance/audit trail layer
5. White-label DBS reports in TrueCV UI
5. White-label DBS reports in RealCV UI
### Timeline
- **Vendor selection:** 1-2 weeks
@@ -371,7 +371,7 @@
1. **Contact accredited vendors:** Verifile, DDC, or similar
2. **Explain use case:** CV verification platform
3. **Request sub-licensing:** Access to their HMRC integration
4. **Build wrapper:** TrueCV UI calls vendor API
4. **Build wrapper:** RealCV UI calls vendor API
#### Vendors with HMRC Access
- Verifile (https://www.verifile.io/)
@@ -407,7 +407,7 @@
### This Week
1. **Email HEDD:** partnerships@hedd.ac.uk with:
- Company info (TrueCV)
- Company info (RealCV)
- Use case (CV verification for UK recruiters)
- Expected volume (start with 100/month)
- Request: API access or partnership discussion
@@ -468,15 +468,15 @@ For each API integration, ensure:
## Contact Template for API Requests
```
Subject: API Integration Request - TrueCV Recruitment Verification Platform
Subject: API Integration Request - RealCV Recruitment Verification Platform
Dear [Service] Team,
We are developing TrueCV, a UK-focused CV verification platform for recruitment agencies and corporate HR departments. As part of our Phase 1 launch (Q1 2026), we would like to integrate with [Service Name] to verify [candidate credentials] in real-time during the hiring process.
We are developing RealCV, a UK-focused CV verification platform for recruitment agencies and corporate HR departments. As part of our Phase 1 launch (Q1 2026), we would like to integrate with [Service Name] to verify [candidate credentials] in real-time during the hiring process.
Use Case:
- Candidates upload CV during job application
- TrueCV extracts education/qualification claims
- RealCV extracts education/qualification claims
- Real-time verification against [Service] records
- Fraud flags generated for recruiter review
@@ -501,7 +501,7 @@ Please advise next steps.
Best regards,
[Your Name]
TrueCV
RealCV
```
---

View File

@@ -2,7 +2,7 @@
TRUECV UK MARKET STRATEGY - COMPLETE DELIVERY PACKAGE
================================================================================
Project: Rethinking TrueCV Feature Priorities with UK-Only Focus
Project: Rethinking RealCV Feature Priorities with UK-Only Focus
Date Delivered: January 20, 2026
Total Documents: 8 comprehensive strategy guides
Total Content: ~200 pages
@@ -68,7 +68,7 @@ MARKET OPPORTUNITY:
✓ £4.2B annual cost of CV fraud to UK employers
✓ 1 in 5 UK candidates falsify university degrees
✓ 24% of screened CVs fail verification
✓ £3.3M serviceable market for TrueCV
✓ £3.3M serviceable market for RealCV
✓ No existing competitor offers integrated UK CV verification
COMPETITIVE ADVANTAGE:
@@ -136,7 +136,7 @@ EXPECTED OUTCOMES:
COMPETITIVE LANDSCAPE ANALYSIS
================================================================================
COMPETITOR FEATURES OFFERED TrueCV ADVANTAGE
COMPETITOR FEATURES OFFERED RealCV ADVANTAGE
─────────────────────────────────────────────────────────────
Workable ATS + basic screening HEDD integration (exclusive)
Deel Global hiring + screening UK-specific stack
@@ -150,7 +150,7 @@ No existing competitor integrates:
- GMC/NMC healthcare registers
- Timeline fraud detection
- Companies House director verification
TrueCV is only player filling this gap
RealCV is only player filling this gap
MOAT BUILDING:
- Deep integrations difficult to replicate (6+ months each)
@@ -365,17 +365,17 @@ FOR SALES/MARKETING:
DOCUMENT LOCATIONS
================================================================================
All files have been created in: /mnt/d/Git/TrueCV/
All files have been created in: /mnt/d/Git/RealCV/
FILE STRUCTURE:
/mnt/d/Git/TrueCV/QUICK_REFERENCE.md (Start here)
/mnt/d/Git/TrueCV/EXECUTIVE_SUMMARY.md (Execs/investors)
/mnt/d/Git/TrueCV/UK_FEATURE_PRIORITIZATION.md (Product)
/mnt/d/Git/TrueCV/PHASE1_TECHNICAL_IMPLEMENTATION.md (Engineering)
/mnt/d/Git/TrueCV/UK_MARKET_STRATEGY.md (Strategy/Sales/Marketing)
/mnt/d/Git/TrueCV/API_RESOURCES_AND_CONTACTS.md (Implementation)
/mnt/d/Git/TrueCV/README_UK_STRATEGY.md (Navigation)
/mnt/d/Git/TrueCV/INDEX.md (Reference index)
/mnt/d/Git/RealCV/QUICK_REFERENCE.md (Start here)
/mnt/d/Git/RealCV/EXECUTIVE_SUMMARY.md (Execs/investors)
/mnt/d/Git/RealCV/UK_FEATURE_PRIORITIZATION.md (Product)
/mnt/d/Git/RealCV/PHASE1_TECHNICAL_IMPLEMENTATION.md (Engineering)
/mnt/d/Git/RealCV/UK_MARKET_STRATEGY.md (Strategy/Sales/Marketing)
/mnt/d/Git/RealCV/API_RESOURCES_AND_CONTACTS.md (Implementation)
/mnt/d/Git/RealCV/README_UK_STRATEGY.md (Navigation)
/mnt/d/Git/RealCV/INDEX.md (Reference index)
FILES READY FOR USE IMMEDIATELY.
@@ -445,7 +445,7 @@ PLANNED UPDATES:
CLOSING NOTES
================================================================================
This strategy document represents a comprehensive analysis of TrueCV's
This strategy document represents a comprehensive analysis of RealCV's
opportunity in the UK CV verification market. It provides:
✓ Clear market opportunity quantification (£3.3M addressable)

View File

@@ -3,11 +3,11 @@ FROM mcr.microsoft.com/dotnet/sdk:8.0 AS build
WORKDIR /src
# Copy solution and project files first for better layer caching
COPY TrueCV.sln ./
COPY src/TrueCV.Domain/TrueCV.Domain.csproj src/TrueCV.Domain/
COPY src/TrueCV.Application/TrueCV.Application.csproj src/TrueCV.Application/
COPY src/TrueCV.Infrastructure/TrueCV.Infrastructure.csproj src/TrueCV.Infrastructure/
COPY src/TrueCV.Web/TrueCV.Web.csproj src/TrueCV.Web/
COPY RealCV.sln ./
COPY src/RealCV.Domain/RealCV.Domain.csproj src/RealCV.Domain/
COPY src/RealCV.Application/RealCV.Application.csproj src/RealCV.Application/
COPY src/RealCV.Infrastructure/RealCV.Infrastructure.csproj src/RealCV.Infrastructure/
COPY src/RealCV.Web/RealCV.Web.csproj src/RealCV.Web/
# Restore dependencies
RUN dotnet restore
@@ -16,7 +16,7 @@ RUN dotnet restore
COPY src/ src/
# Build and publish
WORKDIR /src/src/TrueCV.Web
WORKDIR /src/src/RealCV.Web
RUN dotnet publish -c Release -o /app/publish --no-restore
# Runtime stage
@@ -27,16 +27,16 @@ WORKDIR /app
RUN apt-get update && apt-get install -y curl && rm -rf /var/lib/apt/lists/*
# Create non-root user for security
RUN groupadd -r truecv && useradd -r -g truecv truecv
RUN groupadd -r realcv && useradd -r -g realcv realcv
# Copy published app
COPY --from=build /app/publish .
# Set ownership
RUN chown -R truecv:truecv /app
RUN chown -R realcv:realcv /app
# Switch to non-root user
USER truecv
USER realcv
# Expose port
EXPOSE 8080
@@ -51,4 +51,4 @@ HEALTHCHECK --interval=30s --timeout=10s --start-period=5s --retries=3 \
CMD curl -f http://localhost:8080/health || exit 1
# Start the app
ENTRYPOINT ["dotnet", "TrueCV.Web.dll"]
ENTRYPOINT ["dotnet", "RealCV.Web.dll"]

View File

@@ -7,11 +7,11 @@ RUN dotnet tool install --global dotnet-ef
ENV PATH="$PATH:/root/.dotnet/tools"
# Copy solution and project files
COPY TrueCV.sln ./
COPY src/TrueCV.Domain/TrueCV.Domain.csproj src/TrueCV.Domain/
COPY src/TrueCV.Application/TrueCV.Application.csproj src/TrueCV.Application/
COPY src/TrueCV.Infrastructure/TrueCV.Infrastructure.csproj src/TrueCV.Infrastructure/
COPY src/TrueCV.Web/TrueCV.Web.csproj src/TrueCV.Web/
COPY RealCV.sln ./
COPY src/RealCV.Domain/RealCV.Domain.csproj src/RealCV.Domain/
COPY src/RealCV.Application/RealCV.Application.csproj src/RealCV.Application/
COPY src/RealCV.Infrastructure/RealCV.Infrastructure.csproj src/RealCV.Infrastructure/
COPY src/RealCV.Web/RealCV.Web.csproj src/RealCV.Web/
# Restore dependencies
RUN dotnet restore
@@ -20,7 +20,7 @@ RUN dotnet restore
COPY src/ src/
# Build the project
RUN dotnet build src/TrueCV.Web/TrueCV.Web.csproj -c Release
RUN dotnet build src/RealCV.Web/RealCV.Web.csproj -c Release
# Run migrations on startup
ENTRYPOINT ["dotnet", "ef", "database", "update", "--project", "src/TrueCV.Infrastructure", "--startup-project", "src/TrueCV.Web", "--no-build", "-c", "Release"]
ENTRYPOINT ["dotnet", "ef", "database", "update", "--project", "src/RealCV.Infrastructure", "--startup-project", "src/RealCV.Web", "--no-build", "-c", "Release"]

View File

@@ -1,4 +1,4 @@
# TrueCV UK Market Opportunity - Executive Summary
# RealCV UK Market Opportunity - Executive Summary
**Prepared for:** Product Leadership
**Date:** January 2026
@@ -8,7 +8,7 @@
## The Opportunity
**UK CV fraud costs employers £4.2B annually. Current verification takes 5-10 days. TrueCV can do it in seconds.**
**UK CV fraud costs employers £4.2B annually. Current verification takes 5-10 days. RealCV can do it in seconds.**
### Market Problem
- **1 in 5 UK candidates** falsify university degrees
@@ -25,7 +25,7 @@
---
## TrueCV's Solution
## RealCV's Solution
**Integrated CV verification platform leveraging UK-specific data sources:**
@@ -42,7 +42,7 @@
## Competitive Advantage
**TrueCV is the ONLY CV verification tool that:**
**RealCV is the ONLY CV verification tool that:**
1. ✅ Integrates with HEDD (no competitors do)
2. ✅ Targets healthcare recruiting niche (GMC/NMC)
@@ -59,7 +59,7 @@
### Addressable Market
- **18,300 potential customers** (recruitment agencies + corporate HR)
- **£2.8B UK pre-employment screening market**
- **~£3.3M serviceable opportunity** for TrueCV platform
- **~£3.3M serviceable opportunity** for RealCV platform
### Year 1 Revenue Target
- **50-75 paying customers** at £49-199/month
@@ -230,7 +230,7 @@
## Contact & Next Steps
**Product Lead:** [Name] - TrueCV Product Strategy
**Product Lead:** [Name] - RealCV Product Strategy
**Engineering Lead:** [Name] - Phase 1 Technical Implementation
**Next Meeting:** [Date] - Review technical implementation plan + finalize go-to-market

View File

@@ -1,4 +1,4 @@
# TrueCV UK Strategy - Complete Document Index
# RealCV UK Strategy - Complete Document Index
**Total Documents:** 6 comprehensive strategy guides
**Total Pages:** ~200 pages
@@ -392,7 +392,7 @@ Each document has been:
## Copyright & Distribution
**Ownership:** TrueCV Product Team
**Ownership:** RealCV Product Team
**Classification:** Internal Only
**Distribution:** Leadership, Product, Engineering only
@@ -433,13 +433,13 @@ Each document has been:
```
MARKET OPPORTUNITY
UK CV fraud cost: £4.2B annually
Addressable market: £3.3M (TrueCV's portion)
Addressable market: £3.3M (RealCV's portion)
Candidates lying: 1 in 5 (20%)
Failed verifications: 24% of CVs
Current verification time: 5-10 DAYS
COMPETITIVE ADVANTAGE
Features only TrueCV offers: 4 major features
Features only RealCV offers: 4 major features
Market gap size: Unexploited (£3.3M)
Time to market advantage: 6-12 months
@@ -479,14 +479,14 @@ Phase 3 (Q3): +1 customer success + 1 analyst
## Quick Links
**Files Created:**
- `/mnt/d/Git/TrueCV/QUICK_REFERENCE.md`
- `/mnt/d/Git/TrueCV/EXECUTIVE_SUMMARY.md`
- `/mnt/d/Git/TrueCV/UK_FEATURE_PRIORITIZATION.md`
- `/mnt/d/Git/TrueCV/PHASE1_TECHNICAL_IMPLEMENTATION.md`
- `/mnt/d/Git/TrueCV/UK_MARKET_STRATEGY.md`
- `/mnt/d/Git/TrueCV/API_RESOURCES_AND_CONTACTS.md`
- `/mnt/d/Git/TrueCV/README_UK_STRATEGY.md`
- `/mnt/d/Git/TrueCV/INDEX.md` (this file)
- `/mnt/d/Git/RealCV/QUICK_REFERENCE.md`
- `/mnt/d/Git/RealCV/EXECUTIVE_SUMMARY.md`
- `/mnt/d/Git/RealCV/UK_FEATURE_PRIORITIZATION.md`
- `/mnt/d/Git/RealCV/PHASE1_TECHNICAL_IMPLEMENTATION.md`
- `/mnt/d/Git/RealCV/UK_MARKET_STRATEGY.md`
- `/mnt/d/Git/RealCV/API_RESOURCES_AND_CONTACTS.md`
- `/mnt/d/Git/RealCV/README_UK_STRATEGY.md`
- `/mnt/d/Git/RealCV/INDEX.md` (this file)
**Total:** 8 comprehensive strategy documents (~200 pages)

View File

@@ -10,7 +10,7 @@
### Overview
Real-time integration with HEDD (Higher Education Degree Datacheck) to verify UK degrees against 140+ university records.
**Current Baseline:** TrueCV parses education entries from CV using Claude AI
**Current Baseline:** RealCV parses education entries from CV using Claude AI
**Gap:** No verification against actual university records
**Value:** Eliminates 90%+ of fake degree claims
@@ -32,10 +32,10 @@ Report & UI
### Phase 1a: Create Infrastructure (Days 1-5)
#### File 1: `src/TrueCV.Infrastructure/Configuration/HeddSettings.cs`
#### File 1: `src/RealCV.Infrastructure/Configuration/HeddSettings.cs`
```csharp
namespace TrueCV.Infrastructure.Configuration;
namespace RealCV.Infrastructure.Configuration;
public class HeddSettings
{
@@ -46,7 +46,7 @@ public class HeddSettings
}
```
#### File 2: `src/TrueCV.Infrastructure/ExternalApis/HeddClient.cs`
#### File 2: `src/RealCV.Infrastructure/ExternalApis/HeddClient.cs`
```csharp
using System.Net.Http.Json;
@@ -55,9 +55,9 @@ using System.Text.Json;
using System.Text.Json.Serialization;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using TrueCV.Infrastructure.Configuration;
using RealCV.Infrastructure.Configuration;
namespace TrueCV.Infrastructure.ExternalApis;
namespace RealCV.Infrastructure.ExternalApis;
public sealed class HeddClient
{
@@ -242,12 +242,12 @@ public sealed record HeddManualVerificationStatus
}
```
#### File 3: `src/TrueCV.Application/Interfaces/IEducationVerifierService.cs`
#### File 3: `src/RealCV.Application/Interfaces/IEducationVerifierService.cs`
```csharp
using TrueCV.Application.Models;
using RealCV.Application.Models;
namespace TrueCV.Application.Interfaces;
namespace RealCV.Application.Interfaces;
public interface IEducationVerifierService
{
@@ -273,10 +273,10 @@ public interface IEducationVerifierService
}
```
#### File 4: `src/TrueCV.Application/Models/EducationVerificationResult.cs`
#### File 4: `src/RealCV.Application/Models/EducationVerificationResult.cs`
```csharp
namespace TrueCV.Application.Models;
namespace RealCV.Application.Models;
public sealed record EducationVerificationResult
{
@@ -370,15 +370,15 @@ public enum ManualVerificationStatus
### Phase 1b: Implement Service Layer (Days 6-10)
#### File 5: `src/TrueCV.Infrastructure/Services/EducationVerifierService.cs`
#### File 5: `src/RealCV.Infrastructure/Services/EducationVerifierService.cs`
```csharp
using Microsoft.Extensions.Logging;
using TrueCV.Application.Interfaces;
using TrueCV.Application.Models;
using TrueCV.Infrastructure.ExternalApis;
using RealCV.Application.Interfaces;
using RealCV.Application.Models;
using RealCV.Infrastructure.ExternalApis;
namespace TrueCV.Infrastructure.Services;
namespace RealCV.Infrastructure.Services;
public sealed class EducationVerifierService : IEducationVerifierService
{
@@ -558,10 +558,10 @@ public sealed class EducationVerifierService : IEducationVerifierService
### Phase 1c: Database & Flag Integration (Days 11-12)
#### Update: `src/TrueCV.Domain/Enums/FlagCategory.cs`
#### Update: `src/RealCV.Domain/Enums/FlagCategory.cs`
```csharp
namespace TrueCV.Domain.Enums;
namespace RealCV.Domain.Enums;
public enum FlagCategory
{
@@ -575,14 +575,14 @@ public enum FlagCategory
}
```
#### New File: `src/TrueCV.Infrastructure/Services/EducationFlagGenerator.cs`
#### New File: `src/RealCV.Infrastructure/Services/EducationFlagGenerator.cs`
```csharp
using TrueCV.Application.Models;
using TrueCV.Domain.Entities;
using TrueCV.Domain.Enums;
using RealCV.Application.Models;
using RealCV.Domain.Entities;
using RealCV.Domain.Enums;
namespace TrueCV.Infrastructure.Services;
namespace RealCV.Infrastructure.Services;
public sealed class EducationFlagGenerator
{
@@ -683,16 +683,16 @@ public sealed class EducationFlagGenerator
### Phase 1d: Companies House Enhancement - Director Verification
#### File: `src/TrueCV.Infrastructure/ExternalApis/CompaniesHouseDirectorsClient.cs`
#### File: `src/RealCV.Infrastructure/ExternalApis/CompaniesHouseDirectorsClient.cs`
```csharp
using System.Net.Http.Json;
using System.Text.Json;
using System.Text.Json.Serialization;
using Microsoft.Extensions.Logging;
using TrueCV.Infrastructure.ExternalApis;
using RealCV.Infrastructure.ExternalApis;
namespace TrueCV.Infrastructure.ExternalApis;
namespace RealCV.Infrastructure.ExternalApis;
public sealed class CompaniesHouseDirectorsClient
{
@@ -795,10 +795,10 @@ public sealed record Officer
}
```
#### File: `src/TrueCV.Application/Interfaces/IDirectorshipVerifierService.cs`
#### File: `src/RealCV.Application/Interfaces/IDirectorshipVerifierService.cs`
```csharp
namespace TrueCV.Application.Interfaces;
namespace RealCV.Application.Interfaces;
public interface IDirectorshipVerifierService
{
@@ -826,15 +826,15 @@ public sealed record DirectorshipVerificationResult
}
```
#### File: `src/TrueCV.Infrastructure/Services/DirectorshipVerifierService.cs`
#### File: `src/RealCV.Infrastructure/Services/DirectorshipVerifierService.cs`
```csharp
using FuzzySharp;
using Microsoft.Extensions.Logging;
using TrueCV.Application.Interfaces;
using TrueCV.Infrastructure.ExternalApis;
using RealCV.Application.Interfaces;
using RealCV.Infrastructure.ExternalApis;
namespace TrueCV.Infrastructure.Services;
namespace RealCV.Infrastructure.Services;
public sealed class DirectorshipVerifierService : IDirectorshipVerifierService
{
@@ -1070,15 +1070,15 @@ public sealed class DirectorshipVerifierService : IDirectorshipVerifierService
### Phase 1e: Enhanced Timeline Analysis
#### File: `src/TrueCV.Infrastructure/Services/EnhancedTimelineAnalyserService.cs`
#### File: `src/RealCV.Infrastructure/Services/EnhancedTimelineAnalyserService.cs`
```csharp
using Microsoft.Extensions.Logging;
using TrueCV.Application.Models;
using TrueCV.Domain.Entities;
using TrueCV.Domain.Enums;
using RealCV.Application.Models;
using RealCV.Domain.Entities;
using RealCV.Domain.Enums;
namespace TrueCV.Infrastructure.Services;
namespace RealCV.Infrastructure.Services;
public sealed class EnhancedTimelineAnalyserService
{
@@ -1226,7 +1226,7 @@ public sealed class EnhancedTimelineAnalyserService
### Phase 1f: Dependency Injection & Integration (Days 13-14)
#### Update: `src/TrueCV.Infrastructure/DependencyInjection.cs`
#### Update: `src/RealCV.Infrastructure/DependencyInjection.cs`
```csharp
// Add to existing DependencyInjection class:
@@ -1244,7 +1244,7 @@ services.AddScoped<IDirectorshipVerifierService, DirectorshipVerifierService>();
services.AddScoped<EnhancedTimelineAnalyserService>();
```
#### Update: `src/TrueCV.Infrastructure/Jobs/ProcessCVCheckJob.cs`
#### Update: `src/RealCV.Infrastructure/Jobs/ProcessCVCheckJob.cs`
Add education and directorship verification to the processing pipeline:
@@ -1356,16 +1356,16 @@ private async Task<List<CVFlag>> VerifyDirectorshipsAsync(
### Phase 1g: Testing & QA (Days 15-16)
#### Test File: `tests/TrueCV.Tests/Services/EducationVerifierServiceTests.cs`
#### Test File: `tests/RealCV.Tests/Services/EducationVerifierServiceTests.cs`
```csharp
using Moq;
using Xunit;
using TrueCV.Application.Models;
using TrueCV.Infrastructure.ExternalApis;
using TrueCV.Infrastructure.Services;
using RealCV.Application.Models;
using RealCV.Infrastructure.ExternalApis;
using RealCV.Infrastructure.Services;
namespace TrueCV.Tests.Services;
namespace RealCV.Tests.Services;
public class EducationVerifierServiceTests
{
@@ -1467,7 +1467,7 @@ public class EducationVerifierServiceTests
Create migration for storing verification results:
```bash
dotnet ef migrations add AddEducationAndDirectorshipVerification --project src/TrueCV.Infrastructure --startup-project src/TrueCV.Web
dotnet ef migrations add AddEducationAndDirectorshipVerification --project src/RealCV.Infrastructure --startup-project src/RealCV.Web
```
Add optional columns to CVCheck entity:

View File

@@ -1,4 +1,4 @@
# TrueCV UK Strategy - Quick Reference Card
# RealCV UK Strategy - Quick Reference Card
**Print this page for desk reference during planning & execution**
@@ -11,7 +11,7 @@ UK CV Fraud Cost: £4.2B annually
Candidates Lying: 1 in 5 (20%)
Failed Verifications: 24% of CVs
Current Verification Time: 5-10 DAYS
TrueCV Solution Time: 5 SECONDS ⚡
RealCV Solution Time: 5 SECONDS ⚡
Market Addressable: £3.3M (UK)
Year 1 Target Revenue: £30-240K
@@ -24,7 +24,7 @@ Expected Profitability: Month 6-7
## Competitive Advantage (Why Now)
```
FEATURE TrueCV Workable Deel Checkr
FEATURE RealCV Workable Deel Checkr
─────────────────────────────────────────────────────────
HEDD Degree Verification ✅ ❌ ❌ ❌
GMC/NMC Healthcare ✅ ❌ ❌ ❌
@@ -370,7 +370,7 @@ Based on your role:
## One-Liner Summary
> **TrueCV is the only UK CV verification tool that catches 90% of fake degrees + employment fraud in seconds, leveraging HEDD, GMC/NMC, and Companies House APIs to dominate a £3.3M untapped recruitment market.**
> **RealCV is the only UK CV verification tool that catches 90% of fake degrees + employment fraud in seconds, leveraging HEDD, GMC/NMC, and Companies House APIs to dominate a £3.3M untapped recruitment market.**
---

View File

@@ -1,8 +1,8 @@
# TrueCV UK Market Strategy - Complete Package
# RealCV UK Market Strategy - Complete Package
## Overview
This directory contains the complete product strategy and implementation plan for launching TrueCV with a UK-only focus. The documents provide market analysis, feature prioritization, technical implementation details, and go-to-market strategy.
This directory contains the complete product strategy and implementation plan for launching RealCV with a UK-only focus. The documents provide market analysis, feature prioritization, technical implementation details, and go-to-market strategy.
---
@@ -54,7 +54,7 @@ This directory contains the complete product strategy and implementation plan fo
**Audience:** Product Team, Marketing, Sales
**Purpose:** Comprehensive market analysis and go-to-market strategy
**Key Sections:**
- Market sizing (£2.8B UK screening market, £3.3M TrueCV TAM)
- Market sizing (£2.8B UK screening market, £3.3M RealCV TAM)
- Competitive landscape analysis
- 3-phase product roadmap (Q1-Q3 2026)
- GTM strategy (4 sales channels)
@@ -336,7 +336,7 @@ This directory contains the complete product strategy and implementation plan fo
## File Manifest
```
/mnt/d/Git/TrueCV/
/mnt/d/Git/RealCV/
├── EXECUTIVE_SUMMARY.md (5-page exec overview)
├── UK_FEATURE_PRIORITIZATION.md (30-page detailed prioritization)
├── PHASE1_TECHNICAL_IMPLEMENTATION.md (60-page technical specs + code)
@@ -403,7 +403,7 @@ These documents will be updated quarterly with:
## License & Confidentiality
This strategy document is internal to TrueCV and contains commercially sensitive information including:
This strategy document is internal to RealCV and contains commercially sensitive information including:
- Market sizing & financial projections
- Competitive positioning
- Product roadmap

View File

@@ -5,17 +5,17 @@ VisualStudioVersion = 17.0.31903.59
MinimumVisualStudioVersion = 10.0.40219.1
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{F25C3740-9240-46DF-BC34-985BC577216B}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TrueCV.Domain", "src\TrueCV.Domain\TrueCV.Domain.csproj", "{41AC48AF-09BC-48D1-9CA4-1B05D3B693F0}"
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "RealCV.Domain", "src\RealCV.Domain\RealCV.Domain.csproj", "{41AC48AF-09BC-48D1-9CA4-1B05D3B693F0}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TrueCV.Application", "src\TrueCV.Application\TrueCV.Application.csproj", "{A8A1BA81-3B2F-4F95-BB15-ACA40DF2A70E}"
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "RealCV.Application", "src\RealCV.Application\RealCV.Application.csproj", "{A8A1BA81-3B2F-4F95-BB15-ACA40DF2A70E}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TrueCV.Infrastructure", "src\TrueCV.Infrastructure\TrueCV.Infrastructure.csproj", "{03DB607C-9592-4930-8C89-3E257A319278}"
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "RealCV.Infrastructure", "src\RealCV.Infrastructure\RealCV.Infrastructure.csproj", "{03DB607C-9592-4930-8C89-3E257A319278}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TrueCV.Web", "src\TrueCV.Web\TrueCV.Web.csproj", "{D69F57DB-3092-48AF-81BB-868E3749C638}"
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "RealCV.Web", "src\RealCV.Web\RealCV.Web.csproj", "{D69F57DB-3092-48AF-81BB-868E3749C638}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tests", "tests", "{80890010-EDA6-418B-AD6C-5A9D875594C4}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TrueCV.Tests", "tests\TrueCV.Tests\TrueCV.Tests.csproj", "{4450D4F1-4EB9-445E-904B-1C57701493D8}"
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "RealCV.Tests", "tests\RealCV.Tests\RealCV.Tests.csproj", "{4450D4F1-4EB9-445E-904B-1C57701493D8}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution

View File

@@ -1,4 +1,4 @@
# TrueCV UK Market Feature Prioritization
# RealCV UK Market Feature Prioritization
**Date:** January 2026
**Focus:** UK-Only Market Opportunities
@@ -8,7 +8,7 @@
## Executive Summary
UK CV fraud is escalating with AI-generated deepfakes, synthetic identities, and traditional qualification falsification. The most impactful opportunity for TrueCV in the UK market is **degree verification integration** (HEDD API), followed by **employment verification automation** and **professional body registration checks**. These three features represent 78% of recruiter pain points and address 85% of detected fraud patterns.
UK CV fraud is escalating with AI-generated deepfakes, synthetic identities, and traditional qualification falsification. The most impactful opportunity for RealCV in the UK market is **degree verification integration** (HEDD API), followed by **employment verification automation** and **professional body registration checks**. These three features represent 78% of recruiter pain points and address 85% of detected fraud patterns.
---
@@ -48,7 +48,7 @@ UK CV fraud is escalating with AI-generated deepfakes, synthetic identities, and
- **Cost:** Typically £1-5 per verification (commercial rates)
**Implementation Effort:** **Medium (2-3 weeks)**
- Iframe/form integration into TrueCV UI
- Iframe/form integration into RealCV UI
- Candidate consent workflow
- Result polling for manual verifications
- Database sync with CVData.Education entries
@@ -83,7 +83,7 @@ UK CV fraud is escalating with AI-generated deepfakes, synthetic identities, and
**Impact Score:** **6.5/10**
- Targets 1.5M NHS workers + private doctors
- High value for healthcare recruitment
- Medium market size in TrueCV context
- Medium market size in RealCV context
- But limited to one profession vs. broad application
---
@@ -116,7 +116,7 @@ UK CV fraud is escalating with AI-generated deepfakes, synthetic identities, and
### 4. Companies House API (Already Integrated)
**Status:** ✓ Already implemented in TrueCV
**Status:** ✓ Already implemented in RealCV
**Current Coverage:**
- Fuzzy matching on company names (70%+ threshold)
@@ -205,7 +205,7 @@ UK CV fraud is escalating with AI-generated deepfakes, synthetic identities, and
- Links to individual regulators
- Government-maintained reference
**Use Case for TrueCV:**
**Use Case for RealCV:**
- **Enrichment layer:** When CV claims regulated profession, cross-check against GOV.UK registry
- **Flag generation:** "Claims regulated profession but regulator not found"
- **Guidance:** Link to correct regulator for user lookup
@@ -284,9 +284,9 @@ MEDIUM VALUE + EASY │ MEDIUM VALUE + HARD
- **Pricing:** Pass-through cost model ($1-2 per verification to user)
- **Implementation:**
```
src/TrueCV.Infrastructure/ExternalApis/HeddClient.cs
src/TrueCV.Application/Interfaces/IEducationVerifierService.cs
src/TrueCV.Infrastructure/Services/EducationVerifierService.cs
src/RealCV.Infrastructure/ExternalApis/HeddClient.cs
src/RealCV.Application/Interfaces/IEducationVerifierService.cs
src/RealCV.Infrastructure/Services/EducationVerifierService.cs
FlagCategory += EducationVerification
Add new flag types:
- DegreeNotFound
@@ -304,7 +304,7 @@ MEDIUM VALUE + EASY │ MEDIUM VALUE + HARD
- Identify degree end date before employment start anomalies
- **Implementation:**
```
src/TrueCV.Infrastructure/Services/TimelineAnalyserService.cs
src/RealCV.Infrastructure/Services/TimelineAnalyserService.cs
- Add: UKEmploymentPatternAnalyzer
- Add: EducationEmploymentSequenceValidator
- New flags:
@@ -322,8 +322,8 @@ MEDIUM VALUE + EASY │ MEDIUM VALUE + HARD
- Recurring revenue potential
- **Implementation:**
```
src/TrueCV.Infrastructure/ExternalApis/HealthcareRegisterClient.cs
src/TrueCV.Application/Interfaces/IHealthcareVerifierService.cs
src/RealCV.Infrastructure/ExternalApis/HealthcareRegisterClient.cs
src/RealCV.Application/Interfaces/IHealthcareVerifierService.cs
FlagCategory += HealthcareRegistration
New flags:
- GMCNotFound / GMCRestricted / GMCLapsed
@@ -338,7 +338,7 @@ MEDIUM VALUE + EASY │ MEDIUM VALUE + HARD
- Detects employment after company dissolution
- **Implementation:**
```
Extend: src/TrueCV.Infrastructure/ExternalApis/CompaniesHouseClient.cs
Extend: src/RealCV.Infrastructure/ExternalApis/CompaniesHouseClient.cs
Add: OfficerAppointmentsClient.GetDirectorAppointments(name, companyNumber)
New Service: DirectorshipVerificationService
FlagCategory += DirectorshipVerification
@@ -360,8 +360,8 @@ MEDIUM VALUE + EASY │ MEDIUM VALUE + HARD
- Regulatory appeal
- **Implementation:**
```
src/TrueCV.Infrastructure/ExternalApis/ProfessionalBodyClient.cs
src/TrueCV.Infrastructure/ExternalApis/Scrapers/
src/RealCV.Infrastructure/ExternalApis/ProfessionalBodyClient.cs
src/RealCV.Infrastructure/ExternalApis/Scrapers/
- ICAEWMembershipVerifier.cs
- SRALawverVerifier.cs
- IETEngineerVerifier.cs
@@ -569,7 +569,7 @@ public class DirectorshipVerificationService
## Competitive Advantage Summary
| Feature | TrueCV Advantage | Timeline |
| Feature | RealCV Advantage | Timeline |
|---|---|---|
| **HEDD Integration** | Only dedicated CV tool with instant degree verification | Q1 2026 |
| **Healthcare Register Targeting** | Only tool targeting healthcare recruitment niche | Q1 2026 |

View File

@@ -1,4 +1,4 @@
# TrueCV UK Market Strategy & Product Roadmap
# RealCV UK Market Strategy & Product Roadmap
**Document Date:** January 2026
**Focus:** UK CV verification market positioning
@@ -37,7 +37,7 @@
### SAM (Serviceable Addressable Market)
**TrueCV Target Segment:**
**RealCV Target Segment:**
- Mid-market recruitment agencies (50-500 staff): ~800 companies
- Corporate HR departments (100+ employees): ~15,000 companies
- Specialist vertical recruiters (healthcare, finance, legal): ~2,500 companies
@@ -65,9 +65,9 @@
| **Verifile** | Pre-employment screening | Established relationships | Traditional manual process |
| **Veriff** | Identity verification | Strong deepfake tech | Not employment-focused |
### TrueCV Differentiation
### RealCV Differentiation
| Feature | TrueCV | Workable | Deel | Checkr | Verifile |
| Feature | RealCV | Workable | Deel | Checkr | Verifile |
|---|---|---|---|---|---|
| **Degree Verification (HEDD)** | ✅ Q1 2026 | ❌ | ❌ | ❌ | ❌ |
| **Healthcare Register Checks** | ✅ Q1 2026 | ❌ | ❌ | ❌ | ❌ |
@@ -194,7 +194,7 @@
### Marketing Messaging
**Tagline:** "Hire with Confidence. Verify with TrueCV."
**Tagline:** "Hire with Confidence. Verify with RealCV."
**Core Messages:**
1. **For Recruiters:** "Catch 90% of degree fraud in seconds. One-click HEDD verification."
@@ -279,7 +279,7 @@
- **API Uptime:** 99.9%
### Market Metrics
- **Brand Awareness:** 15%+ of recruitment agencies aware of TrueCV
- **Brand Awareness:** 15%+ of recruitment agencies aware of RealCV
- **Market Share:** 0.5-1% of addressable recruitment screening market
- **Vertical Penetration:** 3%+ of healthcare recruiters, 2%+ financial recruiters
@@ -369,7 +369,7 @@
## Conclusion
TrueCV addresses a massive UK market problem (£4.2B+ annual cost from CV fraud) with a focused, integrated solution. By launching with HEDD degree verification + timeline fraud detection in Q1 2026, we capture first-mover advantage in a gap no competitor fills.
RealCV addresses a massive UK market problem (£4.2B+ annual cost from CV fraud) with a focused, integrated solution. By launching with HEDD degree verification + timeline fraud detection in Q1 2026, we capture first-mover advantage in a gap no competitor fills.
**The opportunity:** Become the UK's trusted CV verification layer for recruitment, reducing fraud while accelerating hiring processes.

31
deploy-local.sh Executable file
View File

@@ -0,0 +1,31 @@
#!/bin/bash
# Deploy RealCV from local git repo to website
set -e
cd /git/RealCV
echo "Building application..."
dotnet publish src/RealCV.Web -c Release -o ./publish --nologo
echo "Stopping service..."
sudo systemctl stop realcv
echo "Backing up config..."
cp /var/www/realcv/appsettings.Production.json /tmp/appsettings.Production.json 2>/dev/null || true
echo "Deploying files..."
sudo rm -rf /var/www/realcv/*
sudo cp -r ./publish/* /var/www/realcv/
echo "Restoring config..."
sudo cp /tmp/appsettings.Production.json /var/www/realcv/ 2>/dev/null || true
echo "Setting permissions..."
sudo chown -R www-data:www-data /var/www/realcv
echo "Starting service..."
sudo systemctl start realcv
echo "Done! Checking status..."
sleep 2
sudo systemctl is-active realcv && echo "Service is running."

161
deploy/README.md Normal file
View File

@@ -0,0 +1,161 @@
# RealCV Deployment Guide
## Quick Start
### 1. Server Setup (run once on fresh Ubuntu server)
```bash
# Copy server-setup.sh to your server
scp deploy/server-setup.sh user@your-server:/tmp/
# SSH into server and run setup
ssh user@your-server
sudo bash /tmp/server-setup.sh
```
**Before running**, edit the script and update:
- `DOMAIN` - Your domain name
- `DB_PASSWORD` - Strong password for SQL Server
- `ADMIN_EMAIL` - Email for SSL certificate notifications
### 2. Deploy Application (run from dev machine)
```bash
# Edit deploy.sh and update configuration
nano deploy/deploy.sh
# Make executable and run
chmod +x deploy/deploy.sh
./deploy/deploy.sh
```
**Update these values in deploy.sh:**
- `SERVER_USER` - SSH username
- `SERVER_HOST` - Server hostname or IP
- `DOMAIN` - Your domain name
### 3. Enable SSL
After DNS is configured and app is deployed:
```bash
ssh user@your-server
sudo certbot --nginx -d realcv.yourdomain.com
```
## Configuration
### Environment Variables
The systemd service sets these environment variables:
- `ASPNETCORE_ENVIRONMENT=Production`
- `ASPNETCORE_URLS=http://localhost:5000`
- `ConnectionStrings__DefaultConnection=...`
To add more (like API keys), edit:
```bash
sudo systemctl edit realcv
```
Add:
```ini
[Service]
Environment=OpenAI__ApiKey=your-key-here
```
### appsettings.Production.json
For sensitive settings, create `/var/www/realcv/appsettings.Production.json`:
```json
{
"ConnectionStrings": {
"DefaultConnection": "Server=127.0.0.1;Database=RealCV;User Id=SA;Password=YourPassword;TrustServerCertificate=True"
},
"OpenAI": {
"ApiKey": "your-openai-key"
}
}
```
## Maintenance
### View Logs
```bash
# Application logs
sudo journalctl -u realcv -f
# Nginx logs
sudo tail -f /var/log/nginx/access.log
sudo tail -f /var/log/nginx/error.log
# SQL Server logs
docker logs realcv-sql -f
```
### Restart Services
```bash
sudo systemctl restart realcv
sudo systemctl restart nginx
docker restart realcv-sql
```
### Database Backup
```bash
# Backup
docker exec realcv-sql /opt/mssql-tools18/bin/sqlcmd \
-S localhost -U SA -P 'YourPassword' -C \
-Q "BACKUP DATABASE RealCV TO DISK='/var/opt/mssql/backup/realcv.bak'"
# Copy backup from container
docker cp realcv-sql:/var/opt/mssql/backup/realcv.bak ./realcv-backup.bak
```
### Rollback Deployment
```bash
# On server - restore previous version
sudo systemctl stop realcv
sudo rm -rf /var/www/realcv
sudo mv /var/www/realcv.backup.YYYYMMDD_HHMMSS /var/www/realcv
sudo systemctl start realcv
```
## Troubleshooting
### App won't start
```bash
# Check status
sudo systemctl status realcv
# Check logs
sudo journalctl -u realcv -n 100
# Test manually
cd /var/www/realcv
sudo -u www-data dotnet RealCV.Web.dll
```
### Database connection issues
```bash
# Check SQL Server is running
docker ps | grep realcv-sql
# Test connection
docker exec -it realcv-sql /opt/mssql-tools18/bin/sqlcmd \
-S localhost -U SA -P 'YourPassword' -C \
-Q "SELECT name FROM sys.databases"
```
### Blazor SignalR issues
Ensure Nginx is configured for WebSocket support (included in setup script).
Check browser console for connection errors.
## Security Checklist
- [ ] Change default SQL Server password
- [ ] Enable SSL with Let's Encrypt
- [ ] Configure firewall (UFW)
- [ ] Set up automated backups
- [ ] Enable fail2ban for SSH protection
- [ ] Keep system updated regularly

80
deploy/deploy.sh Normal file
View File

@@ -0,0 +1,80 @@
#!/bin/bash
# RealCV Deployment Script
# Run this from your development machine to deploy to a Linux server
set -e
# Configuration - UPDATE THESE VALUES
SERVER_USER="deploy"
SERVER_HOST="your-server.com"
SERVER_PATH="/var/www/realcv"
DOMAIN="realcv.yourdomain.com"
# Colors for output
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
NC='\033[0m' # No Color
echo -e "${GREEN}=== RealCV Deployment Script ===${NC}"
# Check if configuration is set
if [[ "$SERVER_HOST" == "your-server.com" ]]; then
echo -e "${RED}Error: Please update SERVER_HOST in this script${NC}"
exit 1
fi
# Step 1: Build and publish
echo -e "${YELLOW}Step 1: Publishing application...${NC}"
cd "$(dirname "$0")/.."
dotnet publish src/RealCV.Web -c Release -o ./publish --nologo
# Step 2: Create deployment package
echo -e "${YELLOW}Step 2: Creating deployment package...${NC}"
tar -czf deploy/realcv-release.tar.gz -C publish .
# Step 3: Transfer to server
echo -e "${YELLOW}Step 3: Transferring to server...${NC}"
scp deploy/realcv-release.tar.gz ${SERVER_USER}@${SERVER_HOST}:/tmp/
# Step 4: Deploy on server
echo -e "${YELLOW}Step 4: Deploying on server...${NC}"
ssh ${SERVER_USER}@${SERVER_HOST} << 'ENDSSH'
set -e
# Stop the service if running
sudo systemctl stop realcv 2>/dev/null || true
# Backup current deployment
if [ -d "/var/www/realcv" ]; then
sudo mv /var/www/realcv /var/www/realcv.backup.$(date +%Y%m%d_%H%M%S)
fi
# Create directory and extract
sudo mkdir -p /var/www/realcv
sudo tar -xzf /tmp/realcv-release.tar.gz -C /var/www/realcv
sudo chown -R www-data:www-data /var/www/realcv
# Start the service
sudo systemctl start realcv
# Clean up
rm /tmp/realcv-release.tar.gz
echo "Deployment complete on server"
ENDSSH
# Step 5: Verify deployment
echo -e "${YELLOW}Step 5: Verifying deployment...${NC}"
sleep 3
if ssh ${SERVER_USER}@${SERVER_HOST} "sudo systemctl is-active realcv" | grep -q "active"; then
echo -e "${GREEN}=== Deployment successful! ===${NC}"
echo -e "Site should be available at: https://${DOMAIN}"
else
echo -e "${RED}Warning: Service may not be running. Check with: sudo systemctl status realcv${NC}"
fi
# Cleanup local files
rm -f deploy/realcv-release.tar.gz
echo -e "${GREEN}Done!${NC}"

159
deploy/server-setup.sh Normal file
View File

@@ -0,0 +1,159 @@
#!/bin/bash
# RealCV Server Setup Script
# Run this ONCE on a fresh Linux server (Ubuntu 22.04/24.04)
set -e
# Configuration - UPDATE THESE VALUES
DOMAIN="realcv.yourdomain.com"
DB_PASSWORD="YourStrong!Password123"
ADMIN_EMAIL="admin@yourdomain.com"
# Colors
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
NC='\033[0m'
echo -e "${GREEN}=== RealCV Server Setup ===${NC}"
# Check if running as root
if [[ $EUID -ne 0 ]]; then
echo -e "${RED}This script must be run as root (use sudo)${NC}"
exit 1
fi
# Step 1: Update system
echo -e "${YELLOW}Step 1: Updating system...${NC}"
apt update && apt upgrade -y
# Step 2: Install .NET 8 Runtime
echo -e "${YELLOW}Step 2: Installing .NET 8 Runtime...${NC}"
apt install -y wget apt-transport-https
wget https://packages.microsoft.com/config/ubuntu/$(lsb_release -rs)/packages-microsoft-prod.deb -O packages-microsoft-prod.deb
dpkg -i packages-microsoft-prod.deb
rm packages-microsoft-prod.deb
apt update
apt install -y aspnetcore-runtime-8.0
# Step 3: Install Nginx
echo -e "${YELLOW}Step 3: Installing Nginx...${NC}"
apt install -y nginx
systemctl enable nginx
# Step 4: Install Docker (for SQL Server)
echo -e "${YELLOW}Step 4: Installing Docker...${NC}"
apt install -y docker.io docker-compose
systemctl enable docker
systemctl start docker
# Step 5: Set up SQL Server container
echo -e "${YELLOW}Step 5: Setting up SQL Server...${NC}"
docker run -e 'ACCEPT_EULA=Y' \
-e "SA_PASSWORD=${DB_PASSWORD}" \
-p 127.0.0.1:1433:1433 \
--name realcv-sql \
--restart unless-stopped \
-v realcv-sqldata:/var/opt/mssql \
-d mcr.microsoft.com/mssql/server:2022-latest
echo "Waiting for SQL Server to start..."
sleep 30
# Create the database
docker exec realcv-sql /opt/mssql-tools18/bin/sqlcmd \
-S localhost -U SA -P "${DB_PASSWORD}" -C \
-Q "CREATE DATABASE RealCV"
# Step 6: Create application directory
echo -e "${YELLOW}Step 6: Creating application directory...${NC}"
mkdir -p /var/www/realcv
chown -R www-data:www-data /var/www/realcv
# Step 7: Create systemd service
echo -e "${YELLOW}Step 7: Creating systemd service...${NC}"
cat > /etc/systemd/system/realcv.service << EOF
[Unit]
Description=RealCV Web Application
After=network.target docker.service
Requires=docker.service
[Service]
WorkingDirectory=/var/www/realcv
ExecStart=/usr/bin/dotnet /var/www/realcv/RealCV.Web.dll
Restart=always
RestartSec=10
KillSignal=SIGINT
SyslogIdentifier=realcv
User=www-data
Environment=ASPNETCORE_ENVIRONMENT=Production
Environment=ASPNETCORE_URLS=http://localhost:5000
Environment=ConnectionStrings__DefaultConnection=Server=127.0.0.1;Database=RealCV;User Id=SA;Password=${DB_PASSWORD};TrustServerCertificate=True
[Install]
WantedBy=multi-user.target
EOF
systemctl daemon-reload
systemctl enable realcv
# Step 8: Configure Nginx
echo -e "${YELLOW}Step 8: Configuring Nginx...${NC}"
cat > /etc/nginx/sites-available/realcv << EOF
server {
listen 80;
server_name ${DOMAIN};
location / {
proxy_pass http://localhost:5000;
proxy_http_version 1.1;
proxy_set_header Upgrade \$http_upgrade;
proxy_set_header Connection "upgrade";
proxy_set_header Host \$host;
proxy_set_header X-Real-IP \$remote_addr;
proxy_set_header X-Forwarded-For \$proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto \$scheme;
proxy_cache_bypass \$http_upgrade;
# WebSocket support for Blazor Server
proxy_read_timeout 86400;
proxy_send_timeout 86400;
}
}
EOF
ln -sf /etc/nginx/sites-available/realcv /etc/nginx/sites-enabled/
rm -f /etc/nginx/sites-enabled/default
nginx -t
systemctl reload nginx
# Step 9: Install Certbot for SSL
echo -e "${YELLOW}Step 9: Setting up SSL with Let's Encrypt...${NC}"
apt install -y certbot python3-certbot-nginx
echo -e "${YELLOW}To enable SSL, run:${NC}"
echo " certbot --nginx -d ${DOMAIN} --email ${ADMIN_EMAIL} --agree-tos --non-interactive"
# Step 10: Configure firewall
echo -e "${YELLOW}Step 10: Configuring firewall...${NC}"
ufw allow 22/tcp
ufw allow 80/tcp
ufw allow 443/tcp
ufw --force enable
# Summary
echo ""
echo -e "${GREEN}=== Server Setup Complete! ===${NC}"
echo ""
echo "Next steps:"
echo "1. Update DNS to point ${DOMAIN} to this server's IP"
echo "2. Deploy the application using deploy.sh from your dev machine"
echo "3. Run SSL setup: certbot --nginx -d ${DOMAIN}"
echo ""
echo "Useful commands:"
echo " sudo systemctl status realcv - Check app status"
echo " sudo journalctl -u realcv -f - View app logs"
echo " docker logs realcv-sql - View SQL Server logs"
echo ""
echo -e "${YELLOW}Database connection string:${NC}"
echo " Server=127.0.0.1;Database=RealCV;User Id=SA;Password=${DB_PASSWORD};TrustServerCertificate=True"

View File

@@ -1,18 +1,18 @@
version: '3.8'
services:
# TrueCV Web Application
truecv-web:
# RealCV Web Application
realcv-web:
build:
context: .
dockerfile: Dockerfile
container_name: truecv-web
container_name: realcv-web
ports:
- "5000:8080"
environment:
- ASPNETCORE_ENVIRONMENT=Development
- ConnectionStrings__DefaultConnection=Server=sqlserver;Database=TrueCV;User Id=sa;Password=TrueCV_P@ssw0rd!;TrustServerCertificate=True;
- ConnectionStrings__HangfireConnection=Server=sqlserver;Database=TrueCV_Hangfire;User Id=sa;Password=TrueCV_P@ssw0rd!;TrustServerCertificate=True;
- ConnectionStrings__DefaultConnection=Server=sqlserver;Database=RealCV;User Id=sa;Password=TrueCV_P@ssw0rd!;TrustServerCertificate=True;
- ConnectionStrings__HangfireConnection=Server=sqlserver;Database=RealCV_Hangfire;User Id=sa;Password=TrueCV_P@ssw0rd!;TrustServerCertificate=True;
- AzureBlob__ConnectionString=DefaultEndpointsProtocol=http;AccountName=devstoreaccount1;AccountKey=Eby8vdM02xNOcqFlqUwJPLlmEtlCDXJ1OUzFT50uSRZ6IFsuFq2UVErCz4I6tq/K1SZFPTOtr/KBHBeksoGMGw==;BlobEndpoint=http://azurite:10000/devstoreaccount1;
- AzureBlob__ContainerName=cv-uploads
- CompaniesHouse__BaseUrl=https://api.company-information.service.gov.uk
@@ -24,13 +24,13 @@ services:
azurite:
condition: service_started
networks:
- truecv-network
- realcv-network
restart: unless-stopped
# SQL Server Database
sqlserver:
image: mcr.microsoft.com/mssql/server:2022-latest
container_name: truecv-sqlserver
container_name: realcv-sqlserver
ports:
- "1433:1433"
environment:
@@ -40,7 +40,7 @@ services:
volumes:
- sqlserver-data:/var/opt/mssql
networks:
- truecv-network
- realcv-network
healthcheck:
test: /opt/mssql-tools18/bin/sqlcmd -S localhost -U sa -P "TrueCV_P@ssw0rd!" -C -Q "SELECT 1" || exit 1
interval: 10s
@@ -52,7 +52,7 @@ services:
# Azure Storage Emulator (Azurite)
azurite:
image: mcr.microsoft.com/azure-storage/azurite:latest
container_name: truecv-azurite
container_name: realcv-azurite
ports:
- "10000:10000" # Blob service
- "10001:10001" # Queue service
@@ -61,7 +61,7 @@ services:
- azurite-data:/data
command: "azurite --blobHost 0.0.0.0 --queueHost 0.0.0.0 --tableHost 0.0.0.0 --location /data --debug /data/debug.log"
networks:
- truecv-network
- realcv-network
restart: unless-stopped
# Database initialization (runs migrations)
@@ -69,18 +69,18 @@ services:
build:
context: .
dockerfile: Dockerfile.migrations
container_name: truecv-db-init
container_name: realcv-db-init
environment:
- ConnectionStrings__DefaultConnection=Server=sqlserver;Database=TrueCV;User Id=sa;Password=TrueCV_P@ssw0rd!;TrustServerCertificate=True;
- ConnectionStrings__DefaultConnection=Server=sqlserver;Database=RealCV;User Id=sa;Password=TrueCV_P@ssw0rd!;TrustServerCertificate=True;
depends_on:
sqlserver:
condition: service_healthy
networks:
- truecv-network
- realcv-network
restart: "no"
networks:
truecv-network:
realcv-network:
driver: bridge
volumes:

BIN
screenshots/01-home.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 646 KiB

BIN
screenshots/02-login.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 433 KiB

BIN
screenshots/03-register.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 444 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 218 KiB

BIN
screenshots/05-check.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 83 KiB

BIN
screenshots/06-report.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 266 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 186 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 186 KiB

BIN
screenshots/homepage.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 MiB

BIN
screenshots/login.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 449 KiB

BIN
screenshots/pricing.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 144 KiB

BIN
screenshots/privacy.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 289 KiB

BIN
screenshots/register.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 468 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 165 KiB

View File

@@ -1,9 +1,10 @@
namespace TrueCV.Application.DTOs;
namespace RealCV.Application.DTOs;
public sealed record CVCheckDto
{
public required Guid Id { get; init; }
public required string OriginalFileName { get; init; }
public string? CandidateName { get; init; }
public required string Status { get; init; }
public int? VeracityScore { get; init; }
public string? ProcessingStage { get; init; }

View File

@@ -1,4 +1,4 @@
namespace TrueCV.Application.DTOs;
namespace RealCV.Application.DTOs;
public sealed record CompanySearchResult
{

View File

@@ -1,4 +1,4 @@
namespace TrueCV.Application.Data;
namespace RealCV.Application.Data;
/// <summary>
/// List of recognised UK higher education institutions.
@@ -43,6 +43,8 @@ public static class UKInstitutions
// Other Major Universities
"Aston University",
"Leeds Beckett University",
"Leeds Metropolitan University", // Former name of Leeds Beckett
"University of Bath",
"Birkbeck, University of London",
"Bournemouth University",
@@ -218,6 +220,9 @@ public static class UKInstitutions
["Queen Mary"] = "Queen Mary University of London",
["Royal Holloway University"] = "Royal Holloway, University of London",
["RHUL"] = "Royal Holloway, University of London",
["Leeds Beckett"] = "Leeds Beckett University",
["Leeds Met"] = "Leeds Beckett University",
["Leeds Metropolitan"] = "Leeds Beckett University",
};
/// <summary>

View File

@@ -1,18 +1,18 @@
namespace TrueCV.Application.Data;
namespace RealCV.Application.Data;
/// <summary>
/// Known diploma mills and fake educational institutions.
/// Institutions not recognised by UK higher education regulatory bodies.
/// Sources: HEDD, Oregon ODA, UNESCO warnings, Michigan AG list
/// </summary>
public static class DiplomaMills
public static class UnaccreditedInstitutions
{
/// <summary>
/// Known diploma mills and unaccredited institutions that sell fake degrees.
/// This list includes institutions identified by various regulatory bodies.
/// Institutions identified by regulatory bodies as not meeting recognised accreditation standards.
/// This list includes institutions flagged by various educational oversight organisations.
/// </summary>
public static readonly HashSet<string> KnownDiplomaMills = new(StringComparer.OrdinalIgnoreCase)
public static readonly HashSet<string> KnownUnaccredited = new(StringComparer.OrdinalIgnoreCase)
{
// Well-known diploma mills
// Institutions not meeting accreditation standards
"Almeda University",
"Ashwood University",
"Belford University",
@@ -67,7 +67,7 @@ public static class DiplomaMills
"Stanton University",
"Stratford University (if unaccredited)",
"Suffield University",
"Summit University (diploma mill)",
"Summit University (unaccredited)",
"Sussex College of Technology",
"Trinity College and University",
"Trinity Southern University",
@@ -80,7 +80,7 @@ public static class DiplomaMills
"University of Northern Washington",
"University of Palmers Green",
"University of San Moritz",
"University of Sussex (fake - not real Sussex)",
"University of Sussex (not the legitimate University of Sussex)",
"University of Wexford",
"Vocational University",
"Warnborough University",
@@ -91,7 +91,7 @@ public static class DiplomaMills
"Woodfield University",
"Yorker International University",
// Pakistani diploma mills commonly seen in UK
// Unaccredited institutions commonly seen in UK applications
"Axact University",
"Brooklyn Park University",
"Columbiana University",
@@ -100,11 +100,11 @@ public static class DiplomaMills
"Oxbridge University",
"University of Newford",
// Online diploma mills
// Online unaccredited institutions
"American World University",
"Ashford University (pre-2005)",
"Concordia College and University",
"Columbus State University (fake)",
"Columbus State University (unaccredited variant)",
"Frederick Taylor University",
"International Theological University",
"Nations University",
@@ -115,7 +115,7 @@ public static class DiplomaMills
};
/// <summary>
/// Suspicious patterns in institution names that often indicate diploma mills.
/// Patterns in institution names that may indicate unaccredited status.
/// </summary>
public static readonly string[] SuspiciousPatterns =
[
@@ -136,27 +136,9 @@ public static class DiplomaMills
];
/// <summary>
/// Fake accreditation bodies used by diploma mills.
/// Check if an institution is not recognised by accreditation bodies.
/// </summary>
public static readonly HashSet<string> FakeAccreditors = new(StringComparer.OrdinalIgnoreCase)
{
"World Association of Universities and Colleges",
"WAUC",
"International Accreditation Agency",
"Universal Accreditation Council",
"Board of Online Universities Accreditation",
"International Council for Open and Distance Education",
"World Online Education Accrediting Commission",
"Central States Consortium of Colleges and Schools",
"American Council of Private Colleges and Universities",
"Association of Distance Learning Programs",
"International Distance Education Certification Agency",
};
/// <summary>
/// Check if an institution is a known diploma mill.
/// </summary>
public static bool IsDiplomaMill(string institutionName)
public static bool IsUnaccredited(string institutionName)
{
if (string.IsNullOrWhiteSpace(institutionName))
return false;
@@ -164,13 +146,13 @@ public static class DiplomaMills
var normalised = institutionName.Trim();
// Direct match
if (KnownDiplomaMills.Contains(normalised))
if (KnownUnaccredited.Contains(normalised))
return true;
// Check if name contains known diploma mill
foreach (var mill in KnownDiplomaMills)
// Check if name contains known unaccredited institution
foreach (var institution in KnownUnaccredited)
{
if (normalised.Contains(mill, StringComparison.OrdinalIgnoreCase))
if (normalised.Contains(institution, StringComparison.OrdinalIgnoreCase))
return true;
}
@@ -178,8 +160,8 @@ public static class DiplomaMills
}
/// <summary>
/// Check if institution name has suspicious patterns common in diploma mills.
/// Returns true if suspicious (but not confirmed fake).
/// Check if institution name has patterns that may indicate unaccredited status.
/// Returns true if patterns suggest further verification is recommended.
/// </summary>
public static bool HasSuspiciousPattern(string institutionName)
{
@@ -196,15 +178,4 @@ public static class DiplomaMills
return false;
}
/// <summary>
/// Check if an accreditor is known to be fake.
/// </summary>
public static bool IsFakeAccreditor(string accreditorName)
{
if (string.IsNullOrWhiteSpace(accreditorName))
return false;
return FakeAccreditors.Contains(accreditorName.Trim());
}
}

View File

@@ -1,4 +1,4 @@
namespace TrueCV.Application.Helpers;
namespace RealCV.Application.Helpers;
public static class DateHelpers
{

View File

@@ -1,6 +1,7 @@
using System.Text.Json;
using System.Text.Json.Serialization;
namespace TrueCV.Application.Helpers;
namespace RealCV.Application.Helpers;
public static class JsonDefaults
{
@@ -16,4 +17,13 @@ public static class JsonDefaults
PropertyNameCaseInsensitive = true,
WriteIndented = true
};
/// <summary>
/// Options for consuming external APIs - case insensitive with null handling.
/// </summary>
public static readonly JsonSerializerOptions ApiClient = new()
{
PropertyNameCaseInsensitive = true,
DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull
};
}

View File

@@ -1,4 +1,4 @@
namespace TrueCV.Application.Helpers;
namespace RealCV.Application.Helpers;
public static class ScoreThresholds
{

View File

@@ -0,0 +1,54 @@
using RealCV.Application.Models;
namespace RealCV.Application.Interfaces;
/// <summary>
/// Service for verifying academic researchers via ORCID
/// </summary>
public interface IAcademicVerifierService
{
/// <summary>
/// Verify an academic researcher by ORCID ID
/// </summary>
Task<AcademicVerificationResult> VerifyByOrcidAsync(string orcidId);
/// <summary>
/// Search for researchers and verify by name
/// </summary>
Task<AcademicVerificationResult> VerifyByNameAsync(
string name,
string? affiliation = null);
/// <summary>
/// Search ORCID for researchers
/// </summary>
Task<List<OrcidSearchResult>> SearchResearchersAsync(
string name,
string? affiliation = null);
/// <summary>
/// Verify claimed publications
/// </summary>
Task<List<PublicationVerificationResult>> VerifyPublicationsAsync(
string orcidId,
List<string> claimedPublications);
}
public sealed record OrcidSearchResult
{
public required string OrcidId { get; init; }
public required string Name { get; init; }
public string? OrcidUrl { get; init; }
public List<string>? Affiliations { get; init; }
public int? PublicationCount { get; init; }
}
public sealed record PublicationVerificationResult
{
public required string ClaimedTitle { get; init; }
public required bool IsVerified { get; init; }
public string? MatchedTitle { get; init; }
public string? Doi { get; init; }
public int? Year { get; init; }
public string? Notes { get; init; }
}

View File

@@ -1,4 +1,4 @@
namespace TrueCV.Application.Interfaces;
namespace RealCV.Application.Interfaces;
public interface IAuditService
{

View File

@@ -1,7 +1,7 @@
using TrueCV.Application.DTOs;
using TrueCV.Application.Models;
using RealCV.Application.DTOs;
using RealCV.Application.Models;
namespace TrueCV.Application.Interfaces;
namespace RealCV.Application.Interfaces;
public interface ICVCheckService
{

View File

@@ -1,6 +1,6 @@
using TrueCV.Application.Models;
using RealCV.Application.Models;
namespace TrueCV.Application.Interfaces;
namespace RealCV.Application.Interfaces;
public interface ICVParserService
{

View File

@@ -1,6 +1,6 @@
using TrueCV.Application.Models;
using RealCV.Application.Models;
namespace TrueCV.Application.Interfaces;
namespace RealCV.Application.Interfaces;
public interface ICompanyNameMatcherService
{

View File

@@ -1,7 +1,7 @@
using TrueCV.Application.DTOs;
using TrueCV.Application.Models;
using RealCV.Application.DTOs;
using RealCV.Application.Models;
namespace TrueCV.Application.Interfaces;
namespace RealCV.Application.Interfaces;
public interface ICompanyVerifierService
{

View File

@@ -1,6 +1,6 @@
using TrueCV.Application.Models;
using RealCV.Application.Models;
namespace TrueCV.Application.Interfaces;
namespace RealCV.Application.Interfaces;
public interface IEducationVerifierService
{

View File

@@ -1,4 +1,4 @@
namespace TrueCV.Application.Interfaces;
namespace RealCV.Application.Interfaces;
public interface IFileStorageService
{

View File

@@ -0,0 +1,36 @@
using RealCV.Application.Models;
namespace RealCV.Application.Interfaces;
/// <summary>
/// Service for verifying developer profiles and skills via GitHub
/// </summary>
public interface IGitHubVerifierService
{
/// <summary>
/// Verify a GitHub profile and analyze activity
/// </summary>
Task<GitHubVerificationResult> VerifyProfileAsync(string username);
/// <summary>
/// Verify claimed programming skills against GitHub activity
/// </summary>
Task<GitHubVerificationResult> VerifySkillsAsync(
string username,
List<string> claimedSkills);
/// <summary>
/// Search for GitHub profiles matching a name
/// </summary>
Task<List<GitHubProfileSearchResult>> SearchProfilesAsync(string name);
}
public sealed record GitHubProfileSearchResult
{
public required string Username { get; init; }
public string? Name { get; init; }
public string? AvatarUrl { get; init; }
public string? Bio { get; init; }
public int PublicRepos { get; init; }
public int Followers { get; init; }
}

View File

@@ -0,0 +1,30 @@
using RealCV.Application.Models;
namespace RealCV.Application.Interfaces;
/// <summary>
/// Service for verifying professional qualifications (FCA)
/// </summary>
public interface IProfessionalVerifierService
{
/// <summary>
/// Verify if a person is registered with the FCA
/// </summary>
Task<ProfessionalVerificationResult> VerifyFcaRegistrationAsync(
string name,
string? firmName = null,
string? referenceNumber = null);
/// <summary>
/// Search FCA register for individuals
/// </summary>
Task<List<FcaIndividualSearchResult>> SearchFcaIndividualsAsync(string name);
}
public sealed record FcaIndividualSearchResult
{
public required string Name { get; init; }
public required string IndividualReferenceNumber { get; init; }
public string? Status { get; init; }
public List<string>? CurrentFirms { get; init; }
}

View File

@@ -0,0 +1,8 @@
using RealCV.Application.Models;
namespace RealCV.Application.Interfaces;
public interface ITextAnalysisService
{
TextAnalysisResult Analyse(CVData cvData);
}

View File

@@ -1,6 +1,6 @@
using TrueCV.Application.Models;
using RealCV.Application.Models;
namespace TrueCV.Application.Interfaces;
namespace RealCV.Application.Interfaces;
public interface ITimelineAnalyserService
{

View File

@@ -1,4 +1,4 @@
namespace TrueCV.Application.Interfaces;
namespace RealCV.Application.Interfaces;
public interface IUserContextService
{

View File

@@ -0,0 +1,62 @@
namespace RealCV.Application.Models;
/// <summary>
/// Result of verifying an academic researcher via ORCID
/// </summary>
public sealed record AcademicVerificationResult
{
public required string ClaimedName { get; init; }
public required bool IsVerified { get; init; }
// ORCID profile
public string? OrcidId { get; init; }
public string? MatchedName { get; init; }
public string? OrcidUrl { get; init; }
// Academic affiliations
public List<AcademicAffiliation> Affiliations { get; init; } = [];
// Publications
public int TotalPublications { get; init; }
public List<Publication> RecentPublications { get; init; } = [];
// Education from ORCID
public List<AcademicEducation> Education { get; init; } = [];
public string? VerificationNotes { get; init; }
public List<AcademicVerificationFlag> Flags { get; init; } = [];
}
public sealed record AcademicAffiliation
{
public required string Organization { get; init; }
public string? Department { get; init; }
public string? Role { get; init; }
public DateOnly? StartDate { get; init; }
public DateOnly? EndDate { get; init; }
}
public sealed record Publication
{
public required string Title { get; init; }
public string? Journal { get; init; }
public int? Year { get; init; }
public string? Doi { get; init; }
public string? Type { get; init; }
}
public sealed record AcademicEducation
{
public required string Institution { get; init; }
public string? Degree { get; init; }
public string? Subject { get; init; }
public int? Year { get; init; }
}
public sealed record AcademicVerificationFlag
{
public required string Type { get; init; }
public required string Severity { get; init; }
public required string Message { get; init; }
public int ScoreImpact { get; init; }
}

View File

@@ -1,4 +1,4 @@
namespace TrueCV.Application.Models;
namespace RealCV.Application.Models;
public sealed record CVData
{

View File

@@ -1,4 +1,4 @@
namespace TrueCV.Application.Models;
namespace RealCV.Application.Models;
public sealed record CompanyVerificationResult
{

View File

@@ -1,4 +1,4 @@
namespace TrueCV.Application.Models;
namespace RealCV.Application.Models;
public sealed record EducationEntry
{

View File

@@ -1,12 +1,12 @@
namespace TrueCV.Application.Models;
namespace RealCV.Application.Models;
public sealed record EducationVerificationResult
{
public required string ClaimedInstitution { get; init; }
public string? MatchedInstitution { get; init; }
public required string Status { get; init; } // Recognised, NotRecognised, DiplomaMill, Suspicious, Unknown
public required string Status { get; init; } // Recognised, NotRecognised, Unaccredited, Suspicious, Unknown
public bool IsVerified { get; init; }
public bool IsDiplomaMill { get; init; }
public bool IsUnaccredited { get; init; }
public bool IsSuspicious { get; init; }
public string? VerificationNotes { get; init; }

View File

@@ -1,4 +1,4 @@
namespace TrueCV.Application.Models;
namespace RealCV.Application.Models;
public sealed record EmploymentEntry
{

View File

@@ -1,4 +1,4 @@
namespace TrueCV.Application.Models;
namespace RealCV.Application.Models;
public sealed record FlagResult
{

View File

@@ -0,0 +1,49 @@
namespace RealCV.Application.Models;
/// <summary>
/// Result of verifying a developer's GitHub profile
/// </summary>
public sealed record GitHubVerificationResult
{
public required string ClaimedUsername { get; init; }
public required bool IsVerified { get; init; }
// Profile details
public string? ProfileName { get; init; }
public string? ProfileUrl { get; init; }
public string? Bio { get; init; }
public string? Company { get; init; }
public string? Location { get; init; }
public DateOnly? AccountCreated { get; init; }
// Activity metrics
public int PublicRepos { get; init; }
public int Followers { get; init; }
public int Following { get; init; }
public int TotalContributions { get; init; }
// Language breakdown
public Dictionary<string, int> LanguageStats { get; init; } = new();
// Claimed skills verification
public List<SkillVerification> SkillVerifications { get; init; } = [];
public string? VerificationNotes { get; init; }
public List<GitHubVerificationFlag> Flags { get; init; } = [];
}
public sealed record SkillVerification
{
public required string ClaimedSkill { get; init; }
public required bool IsVerified { get; init; }
public int RepoCount { get; init; }
public string? Notes { get; init; }
}
public sealed record GitHubVerificationFlag
{
public required string Type { get; init; }
public required string Severity { get; init; }
public required string Message { get; init; }
public int ScoreImpact { get; init; }
}

View File

@@ -0,0 +1,33 @@
namespace RealCV.Application.Models;
/// <summary>
/// Result of verifying a professional qualification (FCA)
/// </summary>
public sealed record ProfessionalVerificationResult
{
public required string ClaimedName { get; init; }
public required string ProfessionalBody { get; init; }
public required bool IsVerified { get; init; }
// Matched professional details
public string? MatchedName { get; init; }
public string? RegistrationNumber { get; init; }
public string? Status { get; init; }
public string? CurrentEmployer { get; init; }
public DateOnly? RegistrationDate { get; init; }
// For FCA
public List<string>? ApprovedFunctions { get; init; }
public List<string>? ControlledFunctions { get; init; }
public string? VerificationNotes { get; init; }
public List<ProfessionalVerificationFlag> Flags { get; init; } = [];
}
public sealed record ProfessionalVerificationFlag
{
public required string Type { get; init; }
public required string Severity { get; init; }
public required string Message { get; init; }
public int ScoreImpact { get; init; }
}

View File

@@ -1,4 +1,4 @@
namespace TrueCV.Application.Models;
namespace RealCV.Application.Models;
public record SemanticMatchResult
{
@@ -10,12 +10,6 @@ public record SemanticMatchResult
public bool IsMatch => ConfidenceScore >= 70;
}
public record CompanyMatchRequest
{
public required string CVCompanyName { get; init; }
public required List<CompanyCandidate> Candidates { get; init; }
}
public record CompanyCandidate
{
public required string CompanyName { get; init; }

View File

@@ -0,0 +1,66 @@
namespace RealCV.Application.Models;
public sealed record TextAnalysisResult
{
public BuzzwordAnalysis BuzzwordAnalysis { get; init; } = new();
public AchievementAnalysis AchievementAnalysis { get; init; } = new();
public SkillsAlignmentAnalysis SkillsAlignment { get; init; } = new();
public MetricsAnalysis MetricsAnalysis { get; init; } = new();
public List<TextAnalysisFlag> Flags { get; init; } = [];
}
public sealed record BuzzwordAnalysis
{
public int TotalBuzzwords { get; init; }
public List<string> BuzzwordsFound { get; init; } = [];
public double BuzzwordDensity { get; init; }
}
public sealed record AchievementAnalysis
{
public int TotalStatements { get; init; }
public int VagueStatements { get; init; }
public int QuantifiedStatements { get; init; }
public int StrongActionVerbStatements { get; init; }
public List<string> VagueExamples { get; init; } = [];
}
public sealed record SkillsAlignmentAnalysis
{
public int TotalRolesChecked { get; init; }
public int RolesWithMatchingSkills { get; init; }
public List<SkillMismatch> Mismatches { get; init; } = [];
}
public sealed record SkillMismatch
{
public required string JobTitle { get; init; }
public required string CompanyName { get; init; }
public required List<string> ExpectedSkills { get; init; }
public required List<string> MatchingSkills { get; init; }
}
public sealed record MetricsAnalysis
{
public int TotalMetricsClaimed { get; init; }
public int PlausibleMetrics { get; init; }
public int SuspiciousMetrics { get; init; }
public int RoundNumberCount { get; init; }
public double RoundNumberRatio { get; init; }
public List<SuspiciousMetric> SuspiciousMetricsList { get; init; } = [];
}
public sealed record SuspiciousMetric
{
public required string ClaimText { get; init; }
public required double Value { get; init; }
public required string Reason { get; init; }
}
public sealed record TextAnalysisFlag
{
public required string Type { get; init; }
public required string Severity { get; init; }
public required string Message { get; init; }
public int ScoreImpact { get; init; }
}

View File

@@ -1,4 +1,4 @@
namespace TrueCV.Application.Models;
namespace RealCV.Application.Models;
public sealed record TimelineAnalysisResult
{

View File

@@ -1,4 +1,4 @@
namespace TrueCV.Application.Models;
namespace RealCV.Application.Models;
public sealed record TimelineGap
{

View File

@@ -1,4 +1,4 @@
namespace TrueCV.Application.Models;
namespace RealCV.Application.Models;
public sealed record TimelineOverlap
{

View File

@@ -1,12 +1,14 @@
namespace TrueCV.Application.Models;
namespace RealCV.Application.Models;
public sealed record VeracityReport
{
public string? CandidateName { get; init; }
public required int OverallScore { get; init; }
public required string ScoreLabel { get; init; }
public List<CompanyVerificationResult> EmploymentVerifications { get; init; } = [];
public List<EducationVerificationResult> EducationVerifications { get; init; } = [];
public required TimelineAnalysisResult TimelineAnalysis { get; init; }
public TextAnalysisResult? TextAnalysis { get; init; }
public List<FlagResult> Flags { get; init; } = [];
public required DateTime GeneratedAt { get; init; }
}

View File

@@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk">
<ItemGroup>
<ProjectReference Include="..\TrueCV.Domain\TrueCV.Domain.csproj" />
<ProjectReference Include="..\RealCV.Domain\RealCV.Domain.csproj" />
</ItemGroup>
<PropertyGroup>

View File

@@ -1,6 +1,6 @@
using System.ComponentModel.DataAnnotations;
namespace TrueCV.Domain.Entities;
namespace RealCV.Domain.Entities;
public class AuditLog
{

View File

@@ -1,7 +1,7 @@
using System.ComponentModel.DataAnnotations;
using TrueCV.Domain.Enums;
using RealCV.Domain.Enums;
namespace TrueCV.Domain.Entities;
namespace RealCV.Domain.Entities;
public class CVCheck
{

View File

@@ -1,8 +1,8 @@
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
using TrueCV.Domain.Enums;
using RealCV.Domain.Enums;
namespace TrueCV.Domain.Entities;
namespace RealCV.Domain.Entities;
public class CVFlag
{

View File

@@ -1,6 +1,6 @@
using System.ComponentModel.DataAnnotations;
namespace TrueCV.Domain.Entities;
namespace RealCV.Domain.Entities;
public class CompanyCache
{

View File

@@ -1,4 +1,4 @@
namespace TrueCV.Domain.Enums;
namespace RealCV.Domain.Enums;
public enum CheckStatus
{

View File

@@ -1,4 +1,4 @@
namespace TrueCV.Domain.Enums;
namespace RealCV.Domain.Enums;
public enum FlagCategory
{

View File

@@ -1,4 +1,4 @@
namespace TrueCV.Domain.Enums;
namespace RealCV.Domain.Enums;
public enum FlagSeverity
{

View File

@@ -1,4 +1,4 @@
namespace TrueCV.Domain.Enums;
namespace RealCV.Domain.Enums;
public enum UserPlan
{

View File

@@ -0,0 +1,210 @@
using System.Net.Http.Json;
using System.Text.Json.Serialization;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using RealCV.Application.Helpers;
namespace RealCV.Infrastructure.Clients;
public sealed class FcaRegisterClient
{
private readonly HttpClient _httpClient;
private readonly ILogger<FcaRegisterClient> _logger;
private readonly string _apiKey;
public FcaRegisterClient(
HttpClient httpClient,
IOptions<FcaOptions> options,
ILogger<FcaRegisterClient> logger)
{
_httpClient = httpClient;
_logger = logger;
_apiKey = options.Value.ApiKey;
_httpClient.BaseAddress = new Uri("https://register.fca.org.uk/services/V0.1/");
_httpClient.DefaultRequestHeaders.Add("X-Auth-Email", options.Value.Email);
_httpClient.DefaultRequestHeaders.Add("X-Auth-Key", _apiKey);
}
public async Task<FcaIndividualResponse?> SearchIndividualsAsync(string name, int page = 1)
{
try
{
var encodedName = Uri.EscapeDataString(name);
var url = $"Individuals?q={encodedName}&page={page}";
_logger.LogDebug("Searching FCA for individual: {Name}", name);
var response = await _httpClient.GetAsync(url);
if (!response.IsSuccessStatusCode)
{
_logger.LogWarning("FCA API returned {StatusCode} for search: {Name}",
response.StatusCode, name);
return null;
}
return await response.Content.ReadFromJsonAsync<FcaIndividualResponse>(JsonDefaults.ApiClient);
}
catch (Exception ex)
{
_logger.LogError(ex, "Error searching FCA for individual: {Name}", name);
return null;
}
}
public async Task<FcaIndividualDetails?> GetIndividualAsync(string individualReferenceNumber)
{
try
{
var url = $"Individuals/{individualReferenceNumber}";
_logger.LogDebug("Getting FCA individual: {IRN}", individualReferenceNumber);
var response = await _httpClient.GetAsync(url);
if (!response.IsSuccessStatusCode)
{
_logger.LogWarning("FCA API returned {StatusCode} for IRN: {IRN}",
response.StatusCode, individualReferenceNumber);
return null;
}
var wrapper = await response.Content.ReadFromJsonAsync<FcaIndividualDetailsWrapper>(JsonDefaults.ApiClient);
return wrapper?.Data?.FirstOrDefault();
}
catch (Exception ex)
{
_logger.LogError(ex, "Error getting FCA individual: {IRN}", individualReferenceNumber);
return null;
}
}
public async Task<FcaFirmResponse?> SearchFirmsAsync(string name, int page = 1)
{
try
{
var encodedName = Uri.EscapeDataString(name);
var url = $"Firms?q={encodedName}&page={page}";
var response = await _httpClient.GetAsync(url);
if (!response.IsSuccessStatusCode)
{
return null;
}
return await response.Content.ReadFromJsonAsync<FcaFirmResponse>(JsonDefaults.ApiClient);
}
catch (Exception ex)
{
_logger.LogError(ex, "Error searching FCA for firm: {Name}", name);
return null;
}
}
}
public class FcaOptions
{
public string ApiKey { get; set; } = string.Empty;
public string Email { get; set; } = string.Empty;
}
// Response models
public class FcaIndividualResponse
{
public List<FcaIndividualSearchItem>? Data { get; set; }
public FcaPagination? Pagination { get; set; }
}
public class FcaIndividualSearchItem
{
[JsonPropertyName("Individual Reference Number")]
public string? IndividualReferenceNumber { get; set; }
public string? Name { get; set; }
public string? Status { get; set; }
[JsonPropertyName("Current Employer(s)")]
public string? CurrentEmployers { get; set; }
}
public class FcaIndividualDetailsWrapper
{
public List<FcaIndividualDetails>? Data { get; set; }
}
public class FcaIndividualDetails
{
[JsonPropertyName("Individual Reference Number")]
public string? IndividualReferenceNumber { get; set; }
public string? Name { get; set; }
public string? Status { get; set; }
[JsonPropertyName("Effective Date")]
public string? EffectiveDate { get; set; }
[JsonPropertyName("Controlled Functions")]
public List<FcaControlledFunction>? ControlledFunctions { get; set; }
[JsonPropertyName("Previous Employments")]
public List<FcaPreviousEmployment>? PreviousEmployments { get; set; }
}
public class FcaControlledFunction
{
[JsonPropertyName("Controlled Function")]
public string? ControlledFunction { get; set; }
[JsonPropertyName("Firm Name")]
public string? FirmName { get; set; }
[JsonPropertyName("Firm Reference Number")]
public string? FirmReferenceNumber { get; set; }
[JsonPropertyName("Status")]
public string? Status { get; set; }
[JsonPropertyName("Effective From")]
public string? EffectiveFrom { get; set; }
}
public class FcaPreviousEmployment
{
[JsonPropertyName("Firm Name")]
public string? FirmName { get; set; }
[JsonPropertyName("Firm Reference Number")]
public string? FirmReferenceNumber { get; set; }
[JsonPropertyName("Start Date")]
public string? StartDate { get; set; }
[JsonPropertyName("End Date")]
public string? EndDate { get; set; }
}
public class FcaFirmResponse
{
public List<FcaFirmSearchItem>? Data { get; set; }
public FcaPagination? Pagination { get; set; }
}
public class FcaFirmSearchItem
{
[JsonPropertyName("Firm Reference Number")]
public string? FirmReferenceNumber { get; set; }
[JsonPropertyName("Firm Name")]
public string? FirmName { get; set; }
public string? Status { get; set; }
}
public class FcaPagination
{
public int Page { get; set; }
public int TotalPages { get; set; }
public int TotalItems { get; set; }
}

View File

@@ -0,0 +1,237 @@
using System.Net.Http.Headers;
using System.Net.Http.Json;
using System.Text.Json.Serialization;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using RealCV.Application.Helpers;
namespace RealCV.Infrastructure.Clients;
public sealed class GitHubApiClient
{
private readonly HttpClient _httpClient;
private readonly ILogger<GitHubApiClient> _logger;
public GitHubApiClient(
HttpClient httpClient,
IOptions<GitHubOptions> options,
ILogger<GitHubApiClient> logger)
{
_httpClient = httpClient;
_logger = logger;
_httpClient.BaseAddress = new Uri("https://api.github.com/");
_httpClient.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/vnd.github+json"));
_httpClient.DefaultRequestHeaders.Add("X-GitHub-Api-Version", "2022-11-28");
_httpClient.DefaultRequestHeaders.UserAgent.ParseAdd("RealCV/1.0");
if (!string.IsNullOrEmpty(options.Value.PersonalAccessToken))
{
_httpClient.DefaultRequestHeaders.Authorization =
new AuthenticationHeaderValue("Bearer", options.Value.PersonalAccessToken);
}
}
public async Task<GitHubUser?> GetUserAsync(string username)
{
try
{
var url = $"users/{Uri.EscapeDataString(username)}";
_logger.LogDebug("Getting GitHub user: {Username}", username);
var response = await _httpClient.GetAsync(url);
if (!response.IsSuccessStatusCode)
{
_logger.LogWarning("GitHub API returned {StatusCode} for user: {Username}",
response.StatusCode, username);
return null;
}
return await response.Content.ReadFromJsonAsync<GitHubUser>(JsonDefaults.ApiClient);
}
catch (Exception ex)
{
_logger.LogError(ex, "Error getting GitHub user: {Username}", username);
return null;
}
}
public async Task<List<GitHubRepo>> GetUserReposAsync(string username, int perPage = 100)
{
var repos = new List<GitHubRepo>();
var page = 1;
try
{
while (true)
{
var url = $"users/{Uri.EscapeDataString(username)}/repos?per_page={perPage}&page={page}&sort=updated";
var response = await _httpClient.GetAsync(url);
if (!response.IsSuccessStatusCode)
{
break;
}
var pageRepos = await response.Content.ReadFromJsonAsync<List<GitHubRepo>>(JsonDefaults.ApiClient);
if (pageRepos == null || pageRepos.Count == 0)
{
break;
}
repos.AddRange(pageRepos);
if (pageRepos.Count < perPage)
{
break;
}
page++;
// Limit to avoid rate limiting
if (page > 5)
{
break;
}
}
}
catch (Exception ex)
{
_logger.LogError(ex, "Error getting repos for user: {Username}", username);
}
return repos;
}
public async Task<GitHubUserSearchResponse?> SearchUsersAsync(string query, int perPage = 30)
{
try
{
var encodedQuery = Uri.EscapeDataString(query);
var url = $"search/users?q={encodedQuery}&per_page={perPage}";
var response = await _httpClient.GetAsync(url);
if (!response.IsSuccessStatusCode)
{
return null;
}
return await response.Content.ReadFromJsonAsync<GitHubUserSearchResponse>(JsonDefaults.ApiClient);
}
catch (Exception ex)
{
_logger.LogError(ex, "Error searching GitHub users: {Query}", query);
return null;
}
}
}
public class GitHubOptions
{
public string PersonalAccessToken { get; set; } = string.Empty;
}
// Response models
public class GitHubUser
{
public string? Login { get; set; }
public int Id { get; set; }
public string? Name { get; set; }
public string? Company { get; set; }
public string? Blog { get; set; }
public string? Location { get; set; }
public string? Email { get; set; }
public string? Bio { get; set; }
[JsonPropertyName("twitter_username")]
public string? TwitterUsername { get; set; }
[JsonPropertyName("public_repos")]
public int PublicRepos { get; set; }
[JsonPropertyName("public_gists")]
public int PublicGists { get; set; }
public int Followers { get; set; }
public int Following { get; set; }
[JsonPropertyName("created_at")]
public DateTime CreatedAt { get; set; }
[JsonPropertyName("updated_at")]
public DateTime UpdatedAt { get; set; }
[JsonPropertyName("html_url")]
public string? HtmlUrl { get; set; }
[JsonPropertyName("avatar_url")]
public string? AvatarUrl { get; set; }
}
public class GitHubRepo
{
public int Id { get; set; }
public string? Name { get; set; }
[JsonPropertyName("full_name")]
public string? FullName { get; set; }
public string? Description { get; set; }
public string? Language { get; set; }
[JsonPropertyName("html_url")]
public string? HtmlUrl { get; set; }
public bool Fork { get; set; }
public bool Private { get; set; }
[JsonPropertyName("stargazers_count")]
public int StargazersCount { get; set; }
[JsonPropertyName("watchers_count")]
public int WatchersCount { get; set; }
[JsonPropertyName("forks_count")]
public int ForksCount { get; set; }
public int Size { get; set; }
[JsonPropertyName("created_at")]
public DateTime CreatedAt { get; set; }
[JsonPropertyName("updated_at")]
public DateTime UpdatedAt { get; set; }
[JsonPropertyName("pushed_at")]
public DateTime? PushedAt { get; set; }
}
public class GitHubUserSearchResponse
{
[JsonPropertyName("total_count")]
public int TotalCount { get; set; }
[JsonPropertyName("incomplete_results")]
public bool IncompleteResults { get; set; }
public List<GitHubUserSearchItem>? Items { get; set; }
}
public class GitHubUserSearchItem
{
public string? Login { get; set; }
public int Id { get; set; }
[JsonPropertyName("avatar_url")]
public string? AvatarUrl { get; set; }
[JsonPropertyName("html_url")]
public string? HtmlUrl { get; set; }
public double Score { get; set; }
}

View File

@@ -0,0 +1,336 @@
using System.Net.Http.Headers;
using System.Net.Http.Json;
using System.Text.Json.Serialization;
using Microsoft.Extensions.Logging;
using RealCV.Application.Helpers;
namespace RealCV.Infrastructure.Clients;
public sealed class OrcidClient
{
private readonly HttpClient _httpClient;
private readonly ILogger<OrcidClient> _logger;
public OrcidClient(
HttpClient httpClient,
ILogger<OrcidClient> logger)
{
_httpClient = httpClient;
_logger = logger;
_httpClient.BaseAddress = new Uri("https://pub.orcid.org/v3.0/");
_httpClient.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
}
public async Task<OrcidSearchResponse?> SearchResearchersAsync(string query, int start = 0, int rows = 20)
{
try
{
var encodedQuery = Uri.EscapeDataString(query);
var url = $"search?q={encodedQuery}&start={start}&rows={rows}";
_logger.LogDebug("Searching ORCID: {Query}", query);
var response = await _httpClient.GetAsync(url);
if (!response.IsSuccessStatusCode)
{
_logger.LogWarning("ORCID API returned {StatusCode} for search: {Query}",
response.StatusCode, query);
return null;
}
return await response.Content.ReadFromJsonAsync<OrcidSearchResponse>(JsonDefaults.ApiClient);
}
catch (Exception ex)
{
_logger.LogError(ex, "Error searching ORCID: {Query}", query);
return null;
}
}
public async Task<OrcidRecord?> GetRecordAsync(string orcidId)
{
try
{
// Normalize ORCID ID format (remove URL prefix if present)
orcidId = NormalizeOrcidId(orcidId);
var url = $"{orcidId}/record";
_logger.LogDebug("Getting ORCID record: {OrcidId}", orcidId);
var response = await _httpClient.GetAsync(url);
if (!response.IsSuccessStatusCode)
{
_logger.LogWarning("ORCID API returned {StatusCode} for ID: {OrcidId}",
response.StatusCode, orcidId);
return null;
}
return await response.Content.ReadFromJsonAsync<OrcidRecord>(JsonDefaults.ApiClient);
}
catch (Exception ex)
{
_logger.LogError(ex, "Error getting ORCID record: {OrcidId}", orcidId);
return null;
}
}
public async Task<OrcidWorks?> GetWorksAsync(string orcidId)
{
try
{
orcidId = NormalizeOrcidId(orcidId);
var url = $"{orcidId}/works";
var response = await _httpClient.GetAsync(url);
if (!response.IsSuccessStatusCode)
{
return null;
}
return await response.Content.ReadFromJsonAsync<OrcidWorks>(JsonDefaults.ApiClient);
}
catch (Exception ex)
{
_logger.LogError(ex, "Error getting ORCID works: {OrcidId}", orcidId);
return null;
}
}
public async Task<OrcidEmployments?> GetEmploymentsAsync(string orcidId)
{
try
{
orcidId = NormalizeOrcidId(orcidId);
var url = $"{orcidId}/employments";
var response = await _httpClient.GetAsync(url);
if (!response.IsSuccessStatusCode)
{
return null;
}
return await response.Content.ReadFromJsonAsync<OrcidEmployments>(JsonDefaults.ApiClient);
}
catch (Exception ex)
{
_logger.LogError(ex, "Error getting ORCID employments: {OrcidId}", orcidId);
return null;
}
}
public async Task<OrcidEducations?> GetEducationsAsync(string orcidId)
{
try
{
orcidId = NormalizeOrcidId(orcidId);
var url = $"{orcidId}/educations";
var response = await _httpClient.GetAsync(url);
if (!response.IsSuccessStatusCode)
{
return null;
}
return await response.Content.ReadFromJsonAsync<OrcidEducations>(JsonDefaults.ApiClient);
}
catch (Exception ex)
{
_logger.LogError(ex, "Error getting ORCID educations: {OrcidId}", orcidId);
return null;
}
}
private static string NormalizeOrcidId(string orcidId)
{
// Remove URL prefixes
orcidId = orcidId.Replace("https://orcid.org/", "")
.Replace("http://orcid.org/", "")
.Trim();
return orcidId;
}
}
// Response models
public class OrcidSearchResponse
{
[JsonPropertyName("num-found")]
public int NumFound { get; set; }
public List<OrcidSearchResult>? Result { get; set; }
}
public class OrcidSearchResult
{
[JsonPropertyName("orcid-identifier")]
public OrcidIdentifier? OrcidIdentifier { get; set; }
}
public class OrcidIdentifier
{
public string? Uri { get; set; }
public string? Path { get; set; }
public string? Host { get; set; }
}
public class OrcidRecord
{
[JsonPropertyName("orcid-identifier")]
public OrcidIdentifier? OrcidIdentifier { get; set; }
public OrcidPerson? Person { get; set; }
[JsonPropertyName("activities-summary")]
public OrcidActivitiesSummary? ActivitiesSummary { get; set; }
}
public class OrcidPerson
{
public OrcidName? Name { get; set; }
public OrcidBiography? Biography { get; set; }
}
public class OrcidName
{
[JsonPropertyName("given-names")]
public OrcidValue? GivenNames { get; set; }
[JsonPropertyName("family-name")]
public OrcidValue? FamilyName { get; set; }
[JsonPropertyName("credit-name")]
public OrcidValue? CreditName { get; set; }
}
public class OrcidValue
{
public string? Value { get; set; }
}
public class OrcidBiography
{
public string? Content { get; set; }
}
public class OrcidActivitiesSummary
{
public OrcidEmployments? Employments { get; set; }
public OrcidEducations? Educations { get; set; }
public OrcidWorks? Works { get; set; }
}
public class OrcidEmployments
{
[JsonPropertyName("affiliation-group")]
public List<OrcidAffiliationGroup>? AffiliationGroup { get; set; }
}
public class OrcidEducations
{
[JsonPropertyName("affiliation-group")]
public List<OrcidAffiliationGroup>? AffiliationGroup { get; set; }
}
public class OrcidAffiliationGroup
{
public List<OrcidAffiliationSummaryWrapper>? Summaries { get; set; }
}
public class OrcidAffiliationSummaryWrapper
{
[JsonPropertyName("employment-summary")]
public OrcidAffiliationSummary? EmploymentSummary { get; set; }
[JsonPropertyName("education-summary")]
public OrcidAffiliationSummary? EducationSummary { get; set; }
}
public class OrcidAffiliationSummary
{
[JsonPropertyName("department-name")]
public string? DepartmentName { get; set; }
[JsonPropertyName("role-title")]
public string? RoleTitle { get; set; }
[JsonPropertyName("start-date")]
public OrcidDate? StartDate { get; set; }
[JsonPropertyName("end-date")]
public OrcidDate? EndDate { get; set; }
public OrcidOrganization? Organization { get; set; }
}
public class OrcidDate
{
public OrcidValue? Year { get; set; }
public OrcidValue? Month { get; set; }
public OrcidValue? Day { get; set; }
}
public class OrcidOrganization
{
public string? Name { get; set; }
public OrcidAddress? Address { get; set; }
}
public class OrcidAddress
{
public string? City { get; set; }
public string? Region { get; set; }
public string? Country { get; set; }
}
public class OrcidWorks
{
public List<OrcidWorkGroup>? Group { get; set; }
}
public class OrcidWorkGroup
{
[JsonPropertyName("work-summary")]
public List<OrcidWorkSummary>? WorkSummary { get; set; }
}
public class OrcidWorkSummary
{
public string? Title { get; set; }
public OrcidTitle? TitleObj { get; set; }
public string? Type { get; set; }
[JsonPropertyName("publication-date")]
public OrcidDate? PublicationDate { get; set; }
[JsonPropertyName("journal-title")]
public OrcidValue? JournalTitle { get; set; }
[JsonPropertyName("external-ids")]
public OrcidExternalIds? ExternalIds { get; set; }
}
public class OrcidTitle
{
public OrcidValue? Title { get; set; }
}
public class OrcidExternalIds
{
[JsonPropertyName("external-id")]
public List<OrcidExternalId>? ExternalId { get; set; }
}
public class OrcidExternalId
{
[JsonPropertyName("external-id-type")]
public string? ExternalIdType { get; set; }
[JsonPropertyName("external-id-value")]
public string? ExternalIdValue { get; set; }
}

View File

@@ -1,4 +1,4 @@
namespace TrueCV.Infrastructure.Configuration;
namespace RealCV.Infrastructure.Configuration;
public sealed class AnthropicSettings
{

View File

@@ -1,4 +1,4 @@
namespace TrueCV.Infrastructure.Configuration;
namespace RealCV.Infrastructure.Configuration;
public sealed class AzureBlobSettings
{

View File

@@ -1,4 +1,4 @@
namespace TrueCV.Infrastructure.Configuration;
namespace RealCV.Infrastructure.Configuration;
public sealed class CompaniesHouseSettings
{

View File

@@ -1,4 +1,4 @@
namespace TrueCV.Infrastructure.Configuration;
namespace RealCV.Infrastructure.Configuration;
public sealed class LocalStorageSettings
{

View File

@@ -1,10 +1,10 @@
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Identity.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore;
using TrueCV.Domain.Entities;
using TrueCV.Infrastructure.Identity;
using RealCV.Domain.Entities;
using RealCV.Infrastructure.Identity;
namespace TrueCV.Infrastructure.Data;
namespace RealCV.Infrastructure.Data;
public class ApplicationDbContext : IdentityDbContext<ApplicationUser, IdentityRole<Guid>, Guid>
{

View File

@@ -5,11 +5,11 @@ using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Metadata;
using Microsoft.EntityFrameworkCore.Migrations;
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
using TrueCV.Infrastructure.Data;
using RealCV.Infrastructure.Data;
#nullable disable
namespace TrueCV.Infrastructure.Data.Migrations
namespace RealCV.Infrastructure.Data.Migrations
{
[DbContext(typeof(ApplicationDbContext))]
[Migration("20260118182916_InitialCreate")]
@@ -156,7 +156,7 @@ namespace TrueCV.Infrastructure.Data.Migrations
b.ToTable("AspNetUserTokens", (string)null);
});
modelBuilder.Entity("TrueCV.Domain.Entities.CVCheck", b =>
modelBuilder.Entity("RealCV.Domain.Entities.CVCheck", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd()
@@ -211,7 +211,7 @@ namespace TrueCV.Infrastructure.Data.Migrations
b.ToTable("CVChecks");
});
modelBuilder.Entity("TrueCV.Domain.Entities.CVFlag", b =>
modelBuilder.Entity("RealCV.Domain.Entities.CVFlag", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd()
@@ -251,7 +251,7 @@ namespace TrueCV.Infrastructure.Data.Migrations
b.ToTable("CVFlags");
});
modelBuilder.Entity("TrueCV.Domain.Entities.CompanyCache", b =>
modelBuilder.Entity("RealCV.Domain.Entities.CompanyCache", b =>
{
b.Property<string>("CompanyNumber")
.HasMaxLength(32)
@@ -281,7 +281,7 @@ namespace TrueCV.Infrastructure.Data.Migrations
b.ToTable("CompanyCache");
});
modelBuilder.Entity("TrueCV.Domain.Entities.User", b =>
modelBuilder.Entity("RealCV.Domain.Entities.User", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd()
@@ -307,7 +307,7 @@ namespace TrueCV.Infrastructure.Data.Migrations
b.ToTable("User");
});
modelBuilder.Entity("TrueCV.Infrastructure.Identity.ApplicationUser", b =>
modelBuilder.Entity("RealCV.Infrastructure.Identity.ApplicationUser", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd()
@@ -396,7 +396,7 @@ namespace TrueCV.Infrastructure.Data.Migrations
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim<System.Guid>", b =>
{
b.HasOne("TrueCV.Infrastructure.Identity.ApplicationUser", null)
b.HasOne("RealCV.Infrastructure.Identity.ApplicationUser", null)
.WithMany()
.HasForeignKey("UserId")
.OnDelete(DeleteBehavior.Cascade)
@@ -405,7 +405,7 @@ namespace TrueCV.Infrastructure.Data.Migrations
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin<System.Guid>", b =>
{
b.HasOne("TrueCV.Infrastructure.Identity.ApplicationUser", null)
b.HasOne("RealCV.Infrastructure.Identity.ApplicationUser", null)
.WithMany()
.HasForeignKey("UserId")
.OnDelete(DeleteBehavior.Cascade)
@@ -420,7 +420,7 @@ namespace TrueCV.Infrastructure.Data.Migrations
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.HasOne("TrueCV.Infrastructure.Identity.ApplicationUser", null)
b.HasOne("RealCV.Infrastructure.Identity.ApplicationUser", null)
.WithMany()
.HasForeignKey("UserId")
.OnDelete(DeleteBehavior.Cascade)
@@ -429,29 +429,29 @@ namespace TrueCV.Infrastructure.Data.Migrations
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken<System.Guid>", b =>
{
b.HasOne("TrueCV.Infrastructure.Identity.ApplicationUser", null)
b.HasOne("RealCV.Infrastructure.Identity.ApplicationUser", null)
.WithMany()
.HasForeignKey("UserId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
});
modelBuilder.Entity("TrueCV.Domain.Entities.CVCheck", b =>
modelBuilder.Entity("RealCV.Domain.Entities.CVCheck", b =>
{
b.HasOne("TrueCV.Infrastructure.Identity.ApplicationUser", null)
b.HasOne("RealCV.Infrastructure.Identity.ApplicationUser", null)
.WithMany("CVChecks")
.HasForeignKey("UserId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.HasOne("TrueCV.Domain.Entities.User", null)
b.HasOne("RealCV.Domain.Entities.User", null)
.WithMany("CVChecks")
.HasForeignKey("UserId1");
});
modelBuilder.Entity("TrueCV.Domain.Entities.CVFlag", b =>
modelBuilder.Entity("RealCV.Domain.Entities.CVFlag", b =>
{
b.HasOne("TrueCV.Domain.Entities.CVCheck", "CVCheck")
b.HasOne("RealCV.Domain.Entities.CVCheck", "CVCheck")
.WithMany("Flags")
.HasForeignKey("CVCheckId")
.OnDelete(DeleteBehavior.Cascade)
@@ -460,17 +460,17 @@ namespace TrueCV.Infrastructure.Data.Migrations
b.Navigation("CVCheck");
});
modelBuilder.Entity("TrueCV.Domain.Entities.CVCheck", b =>
modelBuilder.Entity("RealCV.Domain.Entities.CVCheck", b =>
{
b.Navigation("Flags");
});
modelBuilder.Entity("TrueCV.Domain.Entities.User", b =>
modelBuilder.Entity("RealCV.Domain.Entities.User", b =>
{
b.Navigation("CVChecks");
});
modelBuilder.Entity("TrueCV.Infrastructure.Identity.ApplicationUser", b =>
modelBuilder.Entity("RealCV.Infrastructure.Identity.ApplicationUser", b =>
{
b.Navigation("CVChecks");
});

View File

@@ -3,7 +3,7 @@ using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace TrueCV.Infrastructure.Data.Migrations
namespace RealCV.Infrastructure.Data.Migrations
{
/// <inheritdoc />
public partial class InitialCreate : Migration

View File

@@ -5,11 +5,11 @@ using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Metadata;
using Microsoft.EntityFrameworkCore.Migrations;
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
using TrueCV.Infrastructure.Data;
using RealCV.Infrastructure.Data;
#nullable disable
namespace TrueCV.Infrastructure.Data.Migrations
namespace RealCV.Infrastructure.Data.Migrations
{
[DbContext(typeof(ApplicationDbContext))]
[Migration("20260120191035_AddProcessingStageToCV")]
@@ -156,7 +156,7 @@ namespace TrueCV.Infrastructure.Data.Migrations
b.ToTable("AspNetUserTokens", (string)null);
});
modelBuilder.Entity("TrueCV.Domain.Entities.CVCheck", b =>
modelBuilder.Entity("RealCV.Domain.Entities.CVCheck", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd()
@@ -210,7 +210,7 @@ namespace TrueCV.Infrastructure.Data.Migrations
b.ToTable("CVChecks");
});
modelBuilder.Entity("TrueCV.Domain.Entities.CVFlag", b =>
modelBuilder.Entity("RealCV.Domain.Entities.CVFlag", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd()
@@ -250,7 +250,7 @@ namespace TrueCV.Infrastructure.Data.Migrations
b.ToTable("CVFlags");
});
modelBuilder.Entity("TrueCV.Domain.Entities.CompanyCache", b =>
modelBuilder.Entity("RealCV.Domain.Entities.CompanyCache", b =>
{
b.Property<string>("CompanyNumber")
.HasMaxLength(32)
@@ -292,7 +292,7 @@ namespace TrueCV.Infrastructure.Data.Migrations
b.ToTable("CompanyCache");
});
modelBuilder.Entity("TrueCV.Infrastructure.Identity.ApplicationUser", b =>
modelBuilder.Entity("RealCV.Infrastructure.Identity.ApplicationUser", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd()
@@ -381,7 +381,7 @@ namespace TrueCV.Infrastructure.Data.Migrations
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim<System.Guid>", b =>
{
b.HasOne("TrueCV.Infrastructure.Identity.ApplicationUser", null)
b.HasOne("RealCV.Infrastructure.Identity.ApplicationUser", null)
.WithMany()
.HasForeignKey("UserId")
.OnDelete(DeleteBehavior.Cascade)
@@ -390,7 +390,7 @@ namespace TrueCV.Infrastructure.Data.Migrations
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin<System.Guid>", b =>
{
b.HasOne("TrueCV.Infrastructure.Identity.ApplicationUser", null)
b.HasOne("RealCV.Infrastructure.Identity.ApplicationUser", null)
.WithMany()
.HasForeignKey("UserId")
.OnDelete(DeleteBehavior.Cascade)
@@ -405,7 +405,7 @@ namespace TrueCV.Infrastructure.Data.Migrations
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.HasOne("TrueCV.Infrastructure.Identity.ApplicationUser", null)
b.HasOne("RealCV.Infrastructure.Identity.ApplicationUser", null)
.WithMany()
.HasForeignKey("UserId")
.OnDelete(DeleteBehavior.Cascade)
@@ -414,25 +414,25 @@ namespace TrueCV.Infrastructure.Data.Migrations
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken<System.Guid>", b =>
{
b.HasOne("TrueCV.Infrastructure.Identity.ApplicationUser", null)
b.HasOne("RealCV.Infrastructure.Identity.ApplicationUser", null)
.WithMany()
.HasForeignKey("UserId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
});
modelBuilder.Entity("TrueCV.Domain.Entities.CVCheck", b =>
modelBuilder.Entity("RealCV.Domain.Entities.CVCheck", b =>
{
b.HasOne("TrueCV.Infrastructure.Identity.ApplicationUser", null)
b.HasOne("RealCV.Infrastructure.Identity.ApplicationUser", null)
.WithMany("CVChecks")
.HasForeignKey("UserId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
});
modelBuilder.Entity("TrueCV.Domain.Entities.CVFlag", b =>
modelBuilder.Entity("RealCV.Domain.Entities.CVFlag", b =>
{
b.HasOne("TrueCV.Domain.Entities.CVCheck", "CVCheck")
b.HasOne("RealCV.Domain.Entities.CVCheck", "CVCheck")
.WithMany("Flags")
.HasForeignKey("CVCheckId")
.OnDelete(DeleteBehavior.Cascade)
@@ -441,12 +441,12 @@ namespace TrueCV.Infrastructure.Data.Migrations
b.Navigation("CVCheck");
});
modelBuilder.Entity("TrueCV.Domain.Entities.CVCheck", b =>
modelBuilder.Entity("RealCV.Domain.Entities.CVCheck", b =>
{
b.Navigation("Flags");
});
modelBuilder.Entity("TrueCV.Infrastructure.Identity.ApplicationUser", b =>
modelBuilder.Entity("RealCV.Infrastructure.Identity.ApplicationUser", b =>
{
b.Navigation("CVChecks");
});

View File

@@ -3,7 +3,7 @@ using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace TrueCV.Infrastructure.Data.Migrations
namespace RealCV.Infrastructure.Data.Migrations
{
/// <inheritdoc />
public partial class AddProcessingStageToCV : Migration

View File

@@ -5,11 +5,11 @@ using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Metadata;
using Microsoft.EntityFrameworkCore.Migrations;
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
using TrueCV.Infrastructure.Data;
using RealCV.Infrastructure.Data;
#nullable disable
namespace TrueCV.Infrastructure.Data.Migrations
namespace RealCV.Infrastructure.Data.Migrations
{
[DbContext(typeof(ApplicationDbContext))]
[Migration("20260120194532_AddAuditLogTable")]
@@ -156,7 +156,7 @@ namespace TrueCV.Infrastructure.Data.Migrations
b.ToTable("AspNetUserTokens", (string)null);
});
modelBuilder.Entity("TrueCV.Domain.Entities.AuditLog", b =>
modelBuilder.Entity("RealCV.Domain.Entities.AuditLog", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd()
@@ -202,7 +202,7 @@ namespace TrueCV.Infrastructure.Data.Migrations
b.ToTable("AuditLogs");
});
modelBuilder.Entity("TrueCV.Domain.Entities.CVCheck", b =>
modelBuilder.Entity("RealCV.Domain.Entities.CVCheck", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd()
@@ -256,7 +256,7 @@ namespace TrueCV.Infrastructure.Data.Migrations
b.ToTable("CVChecks");
});
modelBuilder.Entity("TrueCV.Domain.Entities.CVFlag", b =>
modelBuilder.Entity("RealCV.Domain.Entities.CVFlag", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd()
@@ -296,7 +296,7 @@ namespace TrueCV.Infrastructure.Data.Migrations
b.ToTable("CVFlags");
});
modelBuilder.Entity("TrueCV.Domain.Entities.CompanyCache", b =>
modelBuilder.Entity("RealCV.Domain.Entities.CompanyCache", b =>
{
b.Property<string>("CompanyNumber")
.HasMaxLength(32)
@@ -338,7 +338,7 @@ namespace TrueCV.Infrastructure.Data.Migrations
b.ToTable("CompanyCache");
});
modelBuilder.Entity("TrueCV.Infrastructure.Identity.ApplicationUser", b =>
modelBuilder.Entity("RealCV.Infrastructure.Identity.ApplicationUser", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd()
@@ -427,7 +427,7 @@ namespace TrueCV.Infrastructure.Data.Migrations
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim<System.Guid>", b =>
{
b.HasOne("TrueCV.Infrastructure.Identity.ApplicationUser", null)
b.HasOne("RealCV.Infrastructure.Identity.ApplicationUser", null)
.WithMany()
.HasForeignKey("UserId")
.OnDelete(DeleteBehavior.Cascade)
@@ -436,7 +436,7 @@ namespace TrueCV.Infrastructure.Data.Migrations
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin<System.Guid>", b =>
{
b.HasOne("TrueCV.Infrastructure.Identity.ApplicationUser", null)
b.HasOne("RealCV.Infrastructure.Identity.ApplicationUser", null)
.WithMany()
.HasForeignKey("UserId")
.OnDelete(DeleteBehavior.Cascade)
@@ -451,7 +451,7 @@ namespace TrueCV.Infrastructure.Data.Migrations
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.HasOne("TrueCV.Infrastructure.Identity.ApplicationUser", null)
b.HasOne("RealCV.Infrastructure.Identity.ApplicationUser", null)
.WithMany()
.HasForeignKey("UserId")
.OnDelete(DeleteBehavior.Cascade)
@@ -460,25 +460,25 @@ namespace TrueCV.Infrastructure.Data.Migrations
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken<System.Guid>", b =>
{
b.HasOne("TrueCV.Infrastructure.Identity.ApplicationUser", null)
b.HasOne("RealCV.Infrastructure.Identity.ApplicationUser", null)
.WithMany()
.HasForeignKey("UserId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
});
modelBuilder.Entity("TrueCV.Domain.Entities.CVCheck", b =>
modelBuilder.Entity("RealCV.Domain.Entities.CVCheck", b =>
{
b.HasOne("TrueCV.Infrastructure.Identity.ApplicationUser", null)
b.HasOne("RealCV.Infrastructure.Identity.ApplicationUser", null)
.WithMany("CVChecks")
.HasForeignKey("UserId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
});
modelBuilder.Entity("TrueCV.Domain.Entities.CVFlag", b =>
modelBuilder.Entity("RealCV.Domain.Entities.CVFlag", b =>
{
b.HasOne("TrueCV.Domain.Entities.CVCheck", "CVCheck")
b.HasOne("RealCV.Domain.Entities.CVCheck", "CVCheck")
.WithMany("Flags")
.HasForeignKey("CVCheckId")
.OnDelete(DeleteBehavior.Cascade)
@@ -487,12 +487,12 @@ namespace TrueCV.Infrastructure.Data.Migrations
b.Navigation("CVCheck");
});
modelBuilder.Entity("TrueCV.Domain.Entities.CVCheck", b =>
modelBuilder.Entity("RealCV.Domain.Entities.CVCheck", b =>
{
b.Navigation("Flags");
});
modelBuilder.Entity("TrueCV.Infrastructure.Identity.ApplicationUser", b =>
modelBuilder.Entity("RealCV.Infrastructure.Identity.ApplicationUser", b =>
{
b.Navigation("CVChecks");
});

View File

@@ -3,7 +3,7 @@ using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace TrueCV.Infrastructure.Data.Migrations
namespace RealCV.Infrastructure.Data.Migrations
{
/// <inheritdoc />
public partial class AddAuditLogTable : Migration

View File

@@ -0,0 +1,505 @@
// <auto-generated />
using System;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Metadata;
using Microsoft.EntityFrameworkCore.Migrations;
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
using RealCV.Infrastructure.Data;
#nullable disable
namespace RealCV.Infrastructure.Data.Migrations
{
[DbContext(typeof(ApplicationDbContext))]
[Migration("20260125074319_AddTermsAcceptedAt")]
partial class AddTermsAcceptedAt
{
/// <inheritdoc />
protected override void BuildTargetModel(ModelBuilder modelBuilder)
{
#pragma warning disable 612, 618
modelBuilder
.HasAnnotation("ProductVersion", "8.0.23")
.HasAnnotation("Relational:MaxIdentifierLength", 128);
SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder);
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRole<System.Guid>", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("uniqueidentifier");
b.Property<string>("ConcurrencyStamp")
.IsConcurrencyToken()
.HasColumnType("nvarchar(max)");
b.Property<string>("Name")
.HasMaxLength(256)
.HasColumnType("nvarchar(256)");
b.Property<string>("NormalizedName")
.HasMaxLength(256)
.HasColumnType("nvarchar(256)");
b.HasKey("Id");
b.HasIndex("NormalizedName")
.IsUnique()
.HasDatabaseName("RoleNameIndex")
.HasFilter("[NormalizedName] IS NOT NULL");
b.ToTable("AspNetRoles", (string)null);
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim<System.Guid>", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("int");
SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<int>("Id"));
b.Property<string>("ClaimType")
.HasColumnType("nvarchar(max)");
b.Property<string>("ClaimValue")
.HasColumnType("nvarchar(max)");
b.Property<Guid>("RoleId")
.HasColumnType("uniqueidentifier");
b.HasKey("Id");
b.HasIndex("RoleId");
b.ToTable("AspNetRoleClaims", (string)null);
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim<System.Guid>", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("int");
SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<int>("Id"));
b.Property<string>("ClaimType")
.HasColumnType("nvarchar(max)");
b.Property<string>("ClaimValue")
.HasColumnType("nvarchar(max)");
b.Property<Guid>("UserId")
.HasColumnType("uniqueidentifier");
b.HasKey("Id");
b.HasIndex("UserId");
b.ToTable("AspNetUserClaims", (string)null);
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin<System.Guid>", b =>
{
b.Property<string>("LoginProvider")
.HasColumnType("nvarchar(450)");
b.Property<string>("ProviderKey")
.HasColumnType("nvarchar(450)");
b.Property<string>("ProviderDisplayName")
.HasColumnType("nvarchar(max)");
b.Property<Guid>("UserId")
.HasColumnType("uniqueidentifier");
b.HasKey("LoginProvider", "ProviderKey");
b.HasIndex("UserId");
b.ToTable("AspNetUserLogins", (string)null);
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole<System.Guid>", b =>
{
b.Property<Guid>("UserId")
.HasColumnType("uniqueidentifier");
b.Property<Guid>("RoleId")
.HasColumnType("uniqueidentifier");
b.HasKey("UserId", "RoleId");
b.HasIndex("RoleId");
b.ToTable("AspNetUserRoles", (string)null);
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken<System.Guid>", b =>
{
b.Property<Guid>("UserId")
.HasColumnType("uniqueidentifier");
b.Property<string>("LoginProvider")
.HasColumnType("nvarchar(450)");
b.Property<string>("Name")
.HasColumnType("nvarchar(450)");
b.Property<string>("Value")
.HasColumnType("nvarchar(max)");
b.HasKey("UserId", "LoginProvider", "Name");
b.ToTable("AspNetUserTokens", (string)null);
});
modelBuilder.Entity("RealCV.Domain.Entities.AuditLog", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("uniqueidentifier");
b.Property<string>("Action")
.IsRequired()
.HasMaxLength(64)
.HasColumnType("nvarchar(64)");
b.Property<DateTime>("CreatedAt")
.HasColumnType("datetime2");
b.Property<string>("Details")
.HasMaxLength(1024)
.HasColumnType("nvarchar(1024)");
b.Property<Guid?>("EntityId")
.HasColumnType("uniqueidentifier");
b.Property<string>("EntityType")
.HasMaxLength(128)
.HasColumnType("nvarchar(128)");
b.Property<string>("IpAddress")
.HasMaxLength(64)
.HasColumnType("nvarchar(64)");
b.Property<Guid>("UserId")
.HasColumnType("uniqueidentifier");
b.HasKey("Id");
b.HasIndex("Action")
.HasDatabaseName("IX_AuditLogs_Action");
b.HasIndex("CreatedAt")
.HasDatabaseName("IX_AuditLogs_CreatedAt");
b.HasIndex("UserId")
.HasDatabaseName("IX_AuditLogs_UserId");
b.ToTable("AuditLogs");
});
modelBuilder.Entity("RealCV.Domain.Entities.CVCheck", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("uniqueidentifier");
b.Property<string>("BlobUrl")
.IsRequired()
.HasMaxLength(2048)
.HasColumnType("nvarchar(2048)");
b.Property<DateTime?>("CompletedAt")
.HasColumnType("datetime2");
b.Property<DateTime>("CreatedAt")
.HasColumnType("datetime2");
b.Property<string>("ExtractedDataJson")
.HasColumnType("nvarchar(max)");
b.Property<string>("OriginalFileName")
.IsRequired()
.HasMaxLength(512)
.HasColumnType("nvarchar(512)");
b.Property<string>("ProcessingStage")
.HasMaxLength(64)
.HasColumnType("nvarchar(64)");
b.Property<string>("ReportJson")
.HasColumnType("nvarchar(max)");
b.Property<string>("Status")
.IsRequired()
.HasMaxLength(32)
.HasColumnType("nvarchar(32)");
b.Property<Guid>("UserId")
.HasColumnType("uniqueidentifier");
b.Property<int?>("VeracityScore")
.HasColumnType("int");
b.HasKey("Id");
b.HasIndex("Status")
.HasDatabaseName("IX_CVChecks_Status");
b.HasIndex("UserId")
.HasDatabaseName("IX_CVChecks_UserId");
b.ToTable("CVChecks");
});
modelBuilder.Entity("RealCV.Domain.Entities.CVFlag", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("uniqueidentifier");
b.Property<Guid>("CVCheckId")
.HasColumnType("uniqueidentifier");
b.Property<string>("Category")
.IsRequired()
.HasMaxLength(32)
.HasColumnType("nvarchar(32)");
b.Property<string>("Description")
.IsRequired()
.HasMaxLength(2048)
.HasColumnType("nvarchar(2048)");
b.Property<int>("ScoreImpact")
.HasColumnType("int");
b.Property<string>("Severity")
.IsRequired()
.HasMaxLength(32)
.HasColumnType("nvarchar(32)");
b.Property<string>("Title")
.IsRequired()
.HasMaxLength(256)
.HasColumnType("nvarchar(256)");
b.HasKey("Id");
b.HasIndex("CVCheckId")
.HasDatabaseName("IX_CVFlags_CVCheckId");
b.ToTable("CVFlags");
});
modelBuilder.Entity("RealCV.Domain.Entities.CompanyCache", b =>
{
b.Property<string>("CompanyNumber")
.HasMaxLength(32)
.HasColumnType("nvarchar(32)");
b.Property<string>("AccountsCategory")
.HasMaxLength(64)
.HasColumnType("nvarchar(64)");
b.Property<DateTime>("CachedAt")
.HasColumnType("datetime2");
b.Property<string>("CompanyName")
.IsRequired()
.HasMaxLength(512)
.HasColumnType("nvarchar(512)");
b.Property<string>("CompanyType")
.HasMaxLength(64)
.HasColumnType("nvarchar(64)");
b.Property<DateOnly?>("DissolutionDate")
.HasColumnType("date");
b.Property<DateOnly?>("IncorporationDate")
.HasColumnType("date");
b.Property<string>("SicCodesJson")
.HasMaxLength(256)
.HasColumnType("nvarchar(256)");
b.Property<string>("Status")
.IsRequired()
.HasMaxLength(64)
.HasColumnType("nvarchar(64)");
b.HasKey("CompanyNumber");
b.ToTable("CompanyCache");
});
modelBuilder.Entity("RealCV.Infrastructure.Identity.ApplicationUser", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("uniqueidentifier");
b.Property<int>("AccessFailedCount")
.HasColumnType("int");
b.Property<int>("ChecksUsedThisMonth")
.HasColumnType("int");
b.Property<string>("ConcurrencyStamp")
.IsConcurrencyToken()
.HasColumnType("nvarchar(max)");
b.Property<string>("Email")
.HasMaxLength(256)
.HasColumnType("nvarchar(256)");
b.Property<bool>("EmailConfirmed")
.HasColumnType("bit");
b.Property<bool>("LockoutEnabled")
.HasColumnType("bit");
b.Property<DateTimeOffset?>("LockoutEnd")
.HasColumnType("datetimeoffset");
b.Property<string>("NormalizedEmail")
.HasMaxLength(256)
.HasColumnType("nvarchar(256)");
b.Property<string>("NormalizedUserName")
.HasMaxLength(256)
.HasColumnType("nvarchar(256)");
b.Property<string>("PasswordHash")
.HasColumnType("nvarchar(max)");
b.Property<string>("PhoneNumber")
.HasColumnType("nvarchar(max)");
b.Property<bool>("PhoneNumberConfirmed")
.HasColumnType("bit");
b.Property<string>("Plan")
.IsRequired()
.HasMaxLength(32)
.HasColumnType("nvarchar(32)");
b.Property<string>("SecurityStamp")
.HasColumnType("nvarchar(max)");
b.Property<string>("StripeCustomerId")
.HasMaxLength(256)
.HasColumnType("nvarchar(256)");
b.Property<DateTime?>("TermsAcceptedAt")
.HasColumnType("datetime2");
b.Property<bool>("TwoFactorEnabled")
.HasColumnType("bit");
b.Property<string>("UserName")
.HasMaxLength(256)
.HasColumnType("nvarchar(256)");
b.HasKey("Id");
b.HasIndex("NormalizedEmail")
.HasDatabaseName("EmailIndex");
b.HasIndex("NormalizedUserName")
.IsUnique()
.HasDatabaseName("UserNameIndex")
.HasFilter("[NormalizedUserName] IS NOT NULL");
b.ToTable("AspNetUsers", (string)null);
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim<System.Guid>", b =>
{
b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole<System.Guid>", null)
.WithMany()
.HasForeignKey("RoleId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim<System.Guid>", b =>
{
b.HasOne("RealCV.Infrastructure.Identity.ApplicationUser", null)
.WithMany()
.HasForeignKey("UserId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin<System.Guid>", b =>
{
b.HasOne("RealCV.Infrastructure.Identity.ApplicationUser", null)
.WithMany()
.HasForeignKey("UserId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole<System.Guid>", b =>
{
b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole<System.Guid>", null)
.WithMany()
.HasForeignKey("RoleId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.HasOne("RealCV.Infrastructure.Identity.ApplicationUser", null)
.WithMany()
.HasForeignKey("UserId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken<System.Guid>", b =>
{
b.HasOne("RealCV.Infrastructure.Identity.ApplicationUser", null)
.WithMany()
.HasForeignKey("UserId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
});
modelBuilder.Entity("RealCV.Domain.Entities.CVCheck", b =>
{
b.HasOne("RealCV.Infrastructure.Identity.ApplicationUser", null)
.WithMany("CVChecks")
.HasForeignKey("UserId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
});
modelBuilder.Entity("RealCV.Domain.Entities.CVFlag", b =>
{
b.HasOne("RealCV.Domain.Entities.CVCheck", "CVCheck")
.WithMany("Flags")
.HasForeignKey("CVCheckId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.Navigation("CVCheck");
});
modelBuilder.Entity("RealCV.Domain.Entities.CVCheck", b =>
{
b.Navigation("Flags");
});
modelBuilder.Entity("RealCV.Infrastructure.Identity.ApplicationUser", b =>
{
b.Navigation("CVChecks");
});
#pragma warning restore 612, 618
}
}
}

View File

@@ -0,0 +1,29 @@
using System;
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace RealCV.Infrastructure.Data.Migrations
{
/// <inheritdoc />
public partial class AddTermsAcceptedAt : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.AddColumn<DateTime>(
name: "TermsAcceptedAt",
table: "AspNetUsers",
type: "datetime2",
nullable: true);
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropColumn(
name: "TermsAcceptedAt",
table: "AspNetUsers");
}
}
}

View File

@@ -4,11 +4,11 @@ using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Metadata;
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
using TrueCV.Infrastructure.Data;
using RealCV.Infrastructure.Data;
#nullable disable
namespace TrueCV.Infrastructure.Data.Migrations
namespace RealCV.Infrastructure.Data.Migrations
{
[DbContext(typeof(ApplicationDbContext))]
partial class ApplicationDbContextModelSnapshot : ModelSnapshot
@@ -153,7 +153,7 @@ namespace TrueCV.Infrastructure.Data.Migrations
b.ToTable("AspNetUserTokens", (string)null);
});
modelBuilder.Entity("TrueCV.Domain.Entities.AuditLog", b =>
modelBuilder.Entity("RealCV.Domain.Entities.AuditLog", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd()
@@ -199,7 +199,7 @@ namespace TrueCV.Infrastructure.Data.Migrations
b.ToTable("AuditLogs");
});
modelBuilder.Entity("TrueCV.Domain.Entities.CVCheck", b =>
modelBuilder.Entity("RealCV.Domain.Entities.CVCheck", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd()
@@ -253,7 +253,7 @@ namespace TrueCV.Infrastructure.Data.Migrations
b.ToTable("CVChecks");
});
modelBuilder.Entity("TrueCV.Domain.Entities.CVFlag", b =>
modelBuilder.Entity("RealCV.Domain.Entities.CVFlag", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd()
@@ -293,7 +293,7 @@ namespace TrueCV.Infrastructure.Data.Migrations
b.ToTable("CVFlags");
});
modelBuilder.Entity("TrueCV.Domain.Entities.CompanyCache", b =>
modelBuilder.Entity("RealCV.Domain.Entities.CompanyCache", b =>
{
b.Property<string>("CompanyNumber")
.HasMaxLength(32)
@@ -335,7 +335,7 @@ namespace TrueCV.Infrastructure.Data.Migrations
b.ToTable("CompanyCache");
});
modelBuilder.Entity("TrueCV.Infrastructure.Identity.ApplicationUser", b =>
modelBuilder.Entity("RealCV.Infrastructure.Identity.ApplicationUser", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd()
@@ -393,6 +393,9 @@ namespace TrueCV.Infrastructure.Data.Migrations
.HasMaxLength(256)
.HasColumnType("nvarchar(256)");
b.Property<DateTime?>("TermsAcceptedAt")
.HasColumnType("datetime2");
b.Property<bool>("TwoFactorEnabled")
.HasColumnType("bit");
@@ -424,7 +427,7 @@ namespace TrueCV.Infrastructure.Data.Migrations
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim<System.Guid>", b =>
{
b.HasOne("TrueCV.Infrastructure.Identity.ApplicationUser", null)
b.HasOne("RealCV.Infrastructure.Identity.ApplicationUser", null)
.WithMany()
.HasForeignKey("UserId")
.OnDelete(DeleteBehavior.Cascade)
@@ -433,7 +436,7 @@ namespace TrueCV.Infrastructure.Data.Migrations
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin<System.Guid>", b =>
{
b.HasOne("TrueCV.Infrastructure.Identity.ApplicationUser", null)
b.HasOne("RealCV.Infrastructure.Identity.ApplicationUser", null)
.WithMany()
.HasForeignKey("UserId")
.OnDelete(DeleteBehavior.Cascade)
@@ -448,7 +451,7 @@ namespace TrueCV.Infrastructure.Data.Migrations
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.HasOne("TrueCV.Infrastructure.Identity.ApplicationUser", null)
b.HasOne("RealCV.Infrastructure.Identity.ApplicationUser", null)
.WithMany()
.HasForeignKey("UserId")
.OnDelete(DeleteBehavior.Cascade)
@@ -457,25 +460,25 @@ namespace TrueCV.Infrastructure.Data.Migrations
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken<System.Guid>", b =>
{
b.HasOne("TrueCV.Infrastructure.Identity.ApplicationUser", null)
b.HasOne("RealCV.Infrastructure.Identity.ApplicationUser", null)
.WithMany()
.HasForeignKey("UserId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
});
modelBuilder.Entity("TrueCV.Domain.Entities.CVCheck", b =>
modelBuilder.Entity("RealCV.Domain.Entities.CVCheck", b =>
{
b.HasOne("TrueCV.Infrastructure.Identity.ApplicationUser", null)
b.HasOne("RealCV.Infrastructure.Identity.ApplicationUser", null)
.WithMany("CVChecks")
.HasForeignKey("UserId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
});
modelBuilder.Entity("TrueCV.Domain.Entities.CVFlag", b =>
modelBuilder.Entity("RealCV.Domain.Entities.CVFlag", b =>
{
b.HasOne("TrueCV.Domain.Entities.CVCheck", "CVCheck")
b.HasOne("RealCV.Domain.Entities.CVCheck", "CVCheck")
.WithMany("Flags")
.HasForeignKey("CVCheckId")
.OnDelete(DeleteBehavior.Cascade)
@@ -484,12 +487,12 @@ namespace TrueCV.Infrastructure.Data.Migrations
b.Navigation("CVCheck");
});
modelBuilder.Entity("TrueCV.Domain.Entities.CVCheck", b =>
modelBuilder.Entity("RealCV.Domain.Entities.CVCheck", b =>
{
b.Navigation("Flags");
});
modelBuilder.Entity("TrueCV.Infrastructure.Identity.ApplicationUser", b =>
modelBuilder.Entity("RealCV.Infrastructure.Identity.ApplicationUser", b =>
{
b.Navigation("CVChecks");
});

View File

@@ -5,14 +5,15 @@ using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Polly;
using Polly.Extensions.Http;
using TrueCV.Application.Interfaces;
using TrueCV.Infrastructure.Configuration;
using TrueCV.Infrastructure.Data;
using TrueCV.Infrastructure.ExternalApis;
using TrueCV.Infrastructure.Jobs;
using TrueCV.Infrastructure.Services;
using RealCV.Application.Interfaces;
using RealCV.Infrastructure.Clients;
using RealCV.Infrastructure.Configuration;
using RealCV.Infrastructure.Data;
using RealCV.Infrastructure.ExternalApis;
using RealCV.Infrastructure.Jobs;
using RealCV.Infrastructure.Services;
namespace TrueCV.Infrastructure;
namespace RealCV.Infrastructure;
public static class DependencyInjection
{
@@ -74,6 +75,13 @@ public static class DependencyInjection
services.Configure<LocalStorageSettings>(
configuration.GetSection(LocalStorageSettings.SectionName));
// Configure options for additional verification APIs
services.Configure<FcaOptions>(
configuration.GetSection("FcaRegister"));
services.Configure<GitHubOptions>(
configuration.GetSection("GitHub"));
// Configure HttpClient for CompaniesHouseClient with retry policy
services.AddHttpClient<CompaniesHouseClient>((serviceProvider, client) =>
{
@@ -88,16 +96,34 @@ public static class DependencyInjection
})
.AddPolicyHandler(GetRetryPolicy());
// Configure HttpClient for FCA Register API
services.AddHttpClient<FcaRegisterClient>()
.AddPolicyHandler(GetRetryPolicy());
// Configure HttpClient for GitHub API
services.AddHttpClient<GitHubApiClient>()
.AddPolicyHandler(GetRetryPolicy());
// Configure HttpClient for ORCID API
services.AddHttpClient<OrcidClient>()
.AddPolicyHandler(GetRetryPolicy());
// Register services
services.AddScoped<ICVParserService, CVParserService>();
services.AddScoped<ICompanyNameMatcherService, AICompanyNameMatcherService>();
services.AddScoped<ICompanyVerifierService, CompanyVerifierService>();
services.AddScoped<IEducationVerifierService, EducationVerifierService>();
services.AddScoped<ITimelineAnalyserService, TimelineAnalyserService>();
services.AddScoped<ITextAnalysisService, TextAnalysisService>();
services.AddScoped<ICVCheckService, CVCheckService>();
services.AddScoped<IUserContextService, UserContextService>();
services.AddScoped<IAuditService, AuditService>();
// Register additional verification services
services.AddScoped<IProfessionalVerifierService, ProfessionalVerifierService>();
services.AddScoped<IGitHubVerifierService, GitHubVerifierService>();
services.AddScoped<IAcademicVerifierService, AcademicVerifierService>();
// Register file storage - use local storage if configured, otherwise Azure
var useLocalStorage = configuration.GetValue<bool>("UseLocalStorage");
if (useLocalStorage)

View File

@@ -6,10 +6,10 @@ using System.Text.Json;
using System.Text.Json.Serialization;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using TrueCV.Application.DTOs;
using TrueCV.Infrastructure.Configuration;
using RealCV.Application.DTOs;
using RealCV.Infrastructure.Configuration;
namespace TrueCV.Infrastructure.ExternalApis;
namespace RealCV.Infrastructure.ExternalApis;
public sealed class CompaniesHouseClient
{

View File

@@ -1,4 +1,4 @@
namespace TrueCV.Infrastructure.Helpers;
namespace RealCV.Infrastructure.Helpers;
/// <summary>
/// Helper methods for processing AI/LLM JSON responses.

View File

@@ -1,8 +1,8 @@
using Microsoft.AspNetCore.Identity;
using TrueCV.Domain.Entities;
using TrueCV.Domain.Enums;
using RealCV.Domain.Entities;
using RealCV.Domain.Enums;
namespace TrueCV.Infrastructure.Identity;
namespace RealCV.Infrastructure.Identity;
public class ApplicationUser : IdentityUser<Guid>
{
@@ -12,5 +12,7 @@ public class ApplicationUser : IdentityUser<Guid>
public int ChecksUsedThisMonth { get; set; }
public DateTime? TermsAcceptedAt { get; set; }
public ICollection<CVCheck> CVChecks { get; set; } = new List<CVCheck>();
}

View File

@@ -1,14 +1,14 @@
using System.Text.Json;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Logging;
using TrueCV.Application.Helpers;
using TrueCV.Application.Interfaces;
using TrueCV.Application.Models;
using TrueCV.Domain.Entities;
using TrueCV.Domain.Enums;
using TrueCV.Infrastructure.Data;
using RealCV.Application.Helpers;
using RealCV.Application.Interfaces;
using RealCV.Application.Models;
using RealCV.Domain.Entities;
using RealCV.Domain.Enums;
using RealCV.Infrastructure.Data;
namespace TrueCV.Infrastructure.Jobs;
namespace RealCV.Infrastructure.Jobs;
public sealed class ProcessCVCheckJob
{
@@ -18,19 +18,19 @@ public sealed class ProcessCVCheckJob
private readonly ICompanyVerifierService _companyVerifierService;
private readonly IEducationVerifierService _educationVerifierService;
private readonly ITimelineAnalyserService _timelineAnalyserService;
private readonly ITextAnalysisService _textAnalysisService;
private readonly IAuditService _auditService;
private readonly ILogger<ProcessCVCheckJob> _logger;
private const int BaseScore = 100;
private const int UnverifiedCompanyPenalty = 10;
private const int ImplausibleJobTitlePenalty = 15;
private const int CompanyVerificationFlagPenalty = 5; // Base penalty for company flags, actual from flag.ScoreImpact
private const int RapidProgressionPenalty = 10;
private const int EarlyCareerSeniorRolePenalty = 10;
private const int GapMonthPenalty = 1;
private const int MaxGapPenalty = 10;
private const int OverlapMonthPenalty = 2;
private const int DiplomaMillPenalty = 25;
private const int UnaccreditedInstitutionPenalty = 25;
private const int SuspiciousInstitutionPenalty = 15;
private const int UnverifiedEducationPenalty = 5;
private const int EducationDatePenalty = 10;
@@ -42,6 +42,7 @@ public sealed class ProcessCVCheckJob
ICompanyVerifierService companyVerifierService,
IEducationVerifierService educationVerifierService,
ITimelineAnalyserService timelineAnalyserService,
ITextAnalysisService textAnalysisService,
IAuditService auditService,
ILogger<ProcessCVCheckJob> logger)
{
@@ -51,6 +52,7 @@ public sealed class ProcessCVCheckJob
_companyVerifierService = companyVerifierService;
_educationVerifierService = educationVerifierService;
_timelineAnalyserService = timelineAnalyserService;
_textAnalysisService = textAnalysisService;
_auditService = auditService;
_logger = logger;
}
@@ -183,11 +185,11 @@ public sealed class ProcessCVCheckJob
cvData.Employment);
_logger.LogDebug(
"Education verification for check {CheckId}: {Count} entries verified ({Recognised} recognised, {DiplomaMill} diploma mills)",
"Education verification for check {CheckId}: {Count} entries verified ({Recognised} recognised, {Unaccredited} unaccredited)",
cvCheckId,
educationResults.Count,
educationResults.Count(e => e.IsVerified),
educationResults.Count(e => e.IsDiplomaMill));
educationResults.Count(e => e.IsUnaccredited));
// Step 7: Analyse timeline
cvCheck.ProcessingStage = "Analysing Timeline";
@@ -199,10 +201,23 @@ public sealed class ProcessCVCheckJob
"Timeline analysis for check {CheckId}: {GapCount} gaps, {OverlapCount} overlaps",
cvCheckId, timelineAnalysis.Gaps.Count, timelineAnalysis.Overlaps.Count);
// Step 7b: Analyse text for buzzwords, vague achievements, skills alignment, and metrics
cvCheck.ProcessingStage = "Analysing Content";
await _dbContext.SaveChangesAsync(cancellationToken);
var textAnalysis = _textAnalysisService.Analyse(cvData);
_logger.LogDebug(
"Text analysis for check {CheckId}: {BuzzwordCount} buzzwords, {VagueCount} vague statements, {MismatchCount} skill mismatches",
cvCheckId,
textAnalysis.BuzzwordAnalysis.TotalBuzzwords,
textAnalysis.AchievementAnalysis.VagueStatements,
textAnalysis.SkillsAlignment.Mismatches.Count);
// Step 8: Calculate veracity score
cvCheck.ProcessingStage = "Calculating Score";
await _dbContext.SaveChangesAsync(cancellationToken);
var (score, flags) = CalculateVeracityScore(verificationResults, educationResults, timelineAnalysis, cvData);
var (score, flags) = CalculateVeracityScore(verificationResults, educationResults, timelineAnalysis, textAnalysis, cvData);
_logger.LogDebug("Calculated veracity score for check {CheckId}: {Score}", cvCheckId, score);
@@ -241,11 +256,13 @@ public sealed class ProcessCVCheckJob
var report = new VeracityReport
{
CandidateName = cvData.FullName,
OverallScore = score,
ScoreLabel = GetScoreLabel(score),
EmploymentVerifications = verificationResults,
EducationVerifications = educationResults,
TimelineAnalysis = timelineAnalysis,
TextAnalysis = textAnalysis,
Flags = flags,
GeneratedAt = DateTime.UtcNow
};
@@ -290,6 +307,7 @@ public sealed class ProcessCVCheckJob
List<CompanyVerificationResult> verifications,
List<EducationVerificationResult> educationResults,
TimelineAnalysisResult timeline,
TextAnalysisResult textAnalysis,
CVData cvData)
{
var score = BaseScore;
@@ -388,23 +406,23 @@ public sealed class ProcessCVCheckJob
AddPLCExperienceFlag(verifications, flags);
AddVerifiedDirectorFlag(verifications, flags);
// Penalty for diploma mills (critical)
foreach (var edu in educationResults.Where(e => e.IsDiplomaMill))
// Penalty for unaccredited institutions (critical)
foreach (var edu in educationResults.Where(e => e.IsUnaccredited))
{
score -= DiplomaMillPenalty;
score -= UnaccreditedInstitutionPenalty;
flags.Add(new FlagResult
{
Category = FlagCategory.Education.ToString(),
Severity = FlagSeverity.Critical.ToString(),
Title = "Diploma Mill Detected",
Description = $"'{edu.ClaimedInstitution}' is a known diploma mill. {edu.VerificationNotes}",
ScoreImpact = -DiplomaMillPenalty
Title = "Unaccredited Institution",
Description = $"'{edu.ClaimedInstitution}' is not found in the register of recognised institutions. {edu.VerificationNotes}",
ScoreImpact = -UnaccreditedInstitutionPenalty
});
}
// Penalty for suspicious institutions
foreach (var edu in educationResults.Where(e => e.IsSuspicious && !e.IsDiplomaMill))
foreach (var edu in educationResults.Where(e => e.IsSuspicious && !e.IsUnaccredited))
{
score -= SuspiciousInstitutionPenalty;
@@ -412,15 +430,15 @@ public sealed class ProcessCVCheckJob
{
Category = FlagCategory.Education.ToString(),
Severity = FlagSeverity.Warning.ToString(),
Title = "Suspicious Institution",
Description = $"'{edu.ClaimedInstitution}' has suspicious characteristics. {edu.VerificationNotes}",
Title = "Institution Requires Verification",
Description = $"'{edu.ClaimedInstitution}' has characteristics that warrant additional verification. {edu.VerificationNotes}",
ScoreImpact = -SuspiciousInstitutionPenalty
});
}
// Penalty for unverified education (not recognised, but not flagged as fake)
// Penalty for unverified education (not recognised, but not flagged as unaccredited)
// Skip unknown/empty institutions as there's nothing to verify
foreach (var edu in educationResults.Where(e => !e.IsVerified && !e.IsDiplomaMill && !e.IsSuspicious && e.Status == "Unknown"
foreach (var edu in educationResults.Where(e => !e.IsVerified && !e.IsUnaccredited && !e.IsSuspicious && e.Status == "Unknown"
&& !string.IsNullOrWhiteSpace(e.ClaimedInstitution)
&& !e.ClaimedInstitution.Equals("Unknown Institution", StringComparison.OrdinalIgnoreCase)
&& !e.ClaimedInstitution.Equals("Unknown", StringComparison.OrdinalIgnoreCase)))
@@ -484,6 +502,32 @@ public sealed class ProcessCVCheckJob
});
}
// Process text analysis flags (buzzwords, vague achievements, skills alignment, metrics)
foreach (var textFlag in textAnalysis.Flags)
{
score += textFlag.ScoreImpact; // ScoreImpact is already negative
flags.Add(new FlagResult
{
Category = FlagCategory.Plausibility.ToString(),
Severity = textFlag.Severity,
Title = textFlag.Type switch
{
"ExcessiveBuzzwords" => "Excessive Buzzwords",
"HighBuzzwordCount" => "High Buzzword Count",
"VagueAchievements" => "Vague Achievements",
"LackOfQuantification" => "Lack of Quantification",
"SkillsJobMismatch" => "Skills/Job Mismatch",
"UnrealisticMetrics" => "Unrealistic Metrics",
"UnrealisticMetric" => "Unrealistic Metric",
"SuspiciouslyRoundNumbers" => "Suspiciously Round Numbers",
_ => textFlag.Type
},
Description = textFlag.Message,
ScoreImpact = textFlag.ScoreImpact
});
}
// Deduplicate flags based on Title + Description
var uniqueFlags = flags
.GroupBy(f => (f.Title, f.Description))
@@ -522,13 +566,22 @@ public sealed class ProcessCVCheckJob
name == "self employed" ||
name == "selfemployed" ||
name == "contractor" ||
name == "contract" || // Working on contract basis
name == "contract work" ||
name == "contract role" ||
name == "various" || // Multiple short-term contracts
name == "various clients" ||
name == "various companies" ||
name.StartsWith("freelance ") ||
name.StartsWith("self-employed ") ||
name.StartsWith("self employed ") ||
name.StartsWith("contract ") ||
name.StartsWith("contracting ") ||
name.Contains("(freelance)") ||
name.Contains("(self-employed)") ||
name.Contains("(self employed)") ||
name.Contains("(contractor)");
name.Contains("(contractor)") ||
name.Contains("(contract)");
}
private static bool IsPublicSectorEmployer(string companyName)

View File

@@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk">
<ItemGroup>
<ProjectReference Include="..\TrueCV.Application\TrueCV.Application.csproj" />
<ProjectReference Include="..\RealCV.Application\RealCV.Application.csproj" />
</ItemGroup>
<ItemGroup>

Some files were not shown because too many files have changed in this diff Show More