22 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
155 changed files with 5715 additions and 964 deletions

2
.gitignore vendored
View File

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

View File

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

View File

@@ -2,7 +2,7 @@
TRUECV UK MARKET STRATEGY - COMPLETE DELIVERY PACKAGE 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 Date Delivered: January 20, 2026
Total Documents: 8 comprehensive strategy guides Total Documents: 8 comprehensive strategy guides
Total Content: ~200 pages Total Content: ~200 pages
@@ -68,7 +68,7 @@ MARKET OPPORTUNITY:
✓ £4.2B annual cost of CV fraud to UK employers ✓ £4.2B annual cost of CV fraud to UK employers
✓ 1 in 5 UK candidates falsify university degrees ✓ 1 in 5 UK candidates falsify university degrees
✓ 24% of screened CVs fail verification ✓ 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 ✓ No existing competitor offers integrated UK CV verification
COMPETITIVE ADVANTAGE: COMPETITIVE ADVANTAGE:
@@ -136,7 +136,7 @@ EXPECTED OUTCOMES:
COMPETITIVE LANDSCAPE ANALYSIS COMPETITIVE LANDSCAPE ANALYSIS
================================================================================ ================================================================================
COMPETITOR FEATURES OFFERED TrueCV ADVANTAGE COMPETITOR FEATURES OFFERED RealCV ADVANTAGE
───────────────────────────────────────────────────────────── ─────────────────────────────────────────────────────────────
Workable ATS + basic screening HEDD integration (exclusive) Workable ATS + basic screening HEDD integration (exclusive)
Deel Global hiring + screening UK-specific stack Deel Global hiring + screening UK-specific stack
@@ -150,7 +150,7 @@ No existing competitor integrates:
- GMC/NMC healthcare registers - GMC/NMC healthcare registers
- Timeline fraud detection - Timeline fraud detection
- Companies House director verification - Companies House director verification
TrueCV is only player filling this gap RealCV is only player filling this gap
MOAT BUILDING: MOAT BUILDING:
- Deep integrations difficult to replicate (6+ months each) - Deep integrations difficult to replicate (6+ months each)
@@ -365,17 +365,17 @@ FOR SALES/MARKETING:
DOCUMENT LOCATIONS DOCUMENT LOCATIONS
================================================================================ ================================================================================
All files have been created in: /mnt/d/Git/TrueCV/ All files have been created in: /mnt/d/Git/RealCV/
FILE STRUCTURE: FILE STRUCTURE:
/mnt/d/Git/TrueCV/QUICK_REFERENCE.md (Start here) /mnt/d/Git/RealCV/QUICK_REFERENCE.md (Start here)
/mnt/d/Git/TrueCV/EXECUTIVE_SUMMARY.md (Execs/investors) /mnt/d/Git/RealCV/EXECUTIVE_SUMMARY.md (Execs/investors)
/mnt/d/Git/TrueCV/UK_FEATURE_PRIORITIZATION.md (Product) /mnt/d/Git/RealCV/UK_FEATURE_PRIORITIZATION.md (Product)
/mnt/d/Git/TrueCV/PHASE1_TECHNICAL_IMPLEMENTATION.md (Engineering) /mnt/d/Git/RealCV/PHASE1_TECHNICAL_IMPLEMENTATION.md (Engineering)
/mnt/d/Git/TrueCV/UK_MARKET_STRATEGY.md (Strategy/Sales/Marketing) /mnt/d/Git/RealCV/UK_MARKET_STRATEGY.md (Strategy/Sales/Marketing)
/mnt/d/Git/TrueCV/API_RESOURCES_AND_CONTACTS.md (Implementation) /mnt/d/Git/RealCV/API_RESOURCES_AND_CONTACTS.md (Implementation)
/mnt/d/Git/TrueCV/README_UK_STRATEGY.md (Navigation) /mnt/d/Git/RealCV/README_UK_STRATEGY.md (Navigation)
/mnt/d/Git/TrueCV/INDEX.md (Reference index) /mnt/d/Git/RealCV/INDEX.md (Reference index)
FILES READY FOR USE IMMEDIATELY. FILES READY FOR USE IMMEDIATELY.
@@ -445,7 +445,7 @@ PLANNED UPDATES:
CLOSING NOTES 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: opportunity in the UK CV verification market. It provides:
✓ Clear market opportunity quantification (£3.3M addressable) ✓ 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 WORKDIR /src
# Copy solution and project files first for better layer caching # Copy solution and project files first for better layer caching
COPY TrueCV.sln ./ COPY RealCV.sln ./
COPY src/TrueCV.Domain/TrueCV.Domain.csproj src/TrueCV.Domain/ COPY src/RealCV.Domain/RealCV.Domain.csproj src/RealCV.Domain/
COPY src/TrueCV.Application/TrueCV.Application.csproj src/TrueCV.Application/ COPY src/RealCV.Application/RealCV.Application.csproj src/RealCV.Application/
COPY src/TrueCV.Infrastructure/TrueCV.Infrastructure.csproj src/TrueCV.Infrastructure/ COPY src/RealCV.Infrastructure/RealCV.Infrastructure.csproj src/RealCV.Infrastructure/
COPY src/TrueCV.Web/TrueCV.Web.csproj src/TrueCV.Web/ COPY src/RealCV.Web/RealCV.Web.csproj src/RealCV.Web/
# Restore dependencies # Restore dependencies
RUN dotnet restore RUN dotnet restore
@@ -16,7 +16,7 @@ RUN dotnet restore
COPY src/ src/ COPY src/ src/
# Build and publish # Build and publish
WORKDIR /src/src/TrueCV.Web WORKDIR /src/src/RealCV.Web
RUN dotnet publish -c Release -o /app/publish --no-restore RUN dotnet publish -c Release -o /app/publish --no-restore
# Runtime stage # Runtime stage
@@ -27,16 +27,16 @@ WORKDIR /app
RUN apt-get update && apt-get install -y curl && rm -rf /var/lib/apt/lists/* RUN apt-get update && apt-get install -y curl && rm -rf /var/lib/apt/lists/*
# Create non-root user for security # 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 published app
COPY --from=build /app/publish . COPY --from=build /app/publish .
# Set ownership # Set ownership
RUN chown -R truecv:truecv /app RUN chown -R realcv:realcv /app
# Switch to non-root user # Switch to non-root user
USER truecv USER realcv
# Expose port # Expose port
EXPOSE 8080 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 CMD curl -f http://localhost:8080/health || exit 1
# Start the app # 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" ENV PATH="$PATH:/root/.dotnet/tools"
# Copy solution and project files # Copy solution and project files
COPY TrueCV.sln ./ COPY RealCV.sln ./
COPY src/TrueCV.Domain/TrueCV.Domain.csproj src/TrueCV.Domain/ COPY src/RealCV.Domain/RealCV.Domain.csproj src/RealCV.Domain/
COPY src/TrueCV.Application/TrueCV.Application.csproj src/TrueCV.Application/ COPY src/RealCV.Application/RealCV.Application.csproj src/RealCV.Application/
COPY src/TrueCV.Infrastructure/TrueCV.Infrastructure.csproj src/TrueCV.Infrastructure/ COPY src/RealCV.Infrastructure/RealCV.Infrastructure.csproj src/RealCV.Infrastructure/
COPY src/TrueCV.Web/TrueCV.Web.csproj src/TrueCV.Web/ COPY src/RealCV.Web/RealCV.Web.csproj src/RealCV.Web/
# Restore dependencies # Restore dependencies
RUN dotnet restore RUN dotnet restore
@@ -20,7 +20,7 @@ RUN dotnet restore
COPY src/ src/ COPY src/ src/
# Build the project # 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 # 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 **Prepared for:** Product Leadership
**Date:** January 2026 **Date:** January 2026
@@ -8,7 +8,7 @@
## The Opportunity ## 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 ### Market Problem
- **1 in 5 UK candidates** falsify university degrees - **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:** **Integrated CV verification platform leveraging UK-specific data sources:**
@@ -42,7 +42,7 @@
## Competitive Advantage ## 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) 1. ✅ Integrates with HEDD (no competitors do)
2. ✅ Targets healthcare recruiting niche (GMC/NMC) 2. ✅ Targets healthcare recruiting niche (GMC/NMC)
@@ -59,7 +59,7 @@
### Addressable Market ### Addressable Market
- **18,300 potential customers** (recruitment agencies + corporate HR) - **18,300 potential customers** (recruitment agencies + corporate HR)
- **£2.8B UK pre-employment screening market** - **£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 ### Year 1 Revenue Target
- **50-75 paying customers** at £49-199/month - **50-75 paying customers** at £49-199/month
@@ -230,7 +230,7 @@
## Contact & Next Steps ## Contact & Next Steps
**Product Lead:** [Name] - TrueCV Product Strategy **Product Lead:** [Name] - RealCV Product Strategy
**Engineering Lead:** [Name] - Phase 1 Technical Implementation **Engineering Lead:** [Name] - Phase 1 Technical Implementation
**Next Meeting:** [Date] - Review technical implementation plan + finalize go-to-market **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 Documents:** 6 comprehensive strategy guides
**Total Pages:** ~200 pages **Total Pages:** ~200 pages
@@ -392,7 +392,7 @@ Each document has been:
## Copyright & Distribution ## Copyright & Distribution
**Ownership:** TrueCV Product Team **Ownership:** RealCV Product Team
**Classification:** Internal Only **Classification:** Internal Only
**Distribution:** Leadership, Product, Engineering only **Distribution:** Leadership, Product, Engineering only
@@ -433,13 +433,13 @@ Each document has been:
``` ```
MARKET OPPORTUNITY MARKET OPPORTUNITY
UK CV fraud cost: £4.2B annually 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%) Candidates lying: 1 in 5 (20%)
Failed verifications: 24% of CVs Failed verifications: 24% of CVs
Current verification time: 5-10 DAYS Current verification time: 5-10 DAYS
COMPETITIVE ADVANTAGE COMPETITIVE ADVANTAGE
Features only TrueCV offers: 4 major features Features only RealCV offers: 4 major features
Market gap size: Unexploited (£3.3M) Market gap size: Unexploited (£3.3M)
Time to market advantage: 6-12 months Time to market advantage: 6-12 months
@@ -479,14 +479,14 @@ Phase 3 (Q3): +1 customer success + 1 analyst
## Quick Links ## Quick Links
**Files Created:** **Files Created:**
- `/mnt/d/Git/TrueCV/QUICK_REFERENCE.md` - `/mnt/d/Git/RealCV/QUICK_REFERENCE.md`
- `/mnt/d/Git/TrueCV/EXECUTIVE_SUMMARY.md` - `/mnt/d/Git/RealCV/EXECUTIVE_SUMMARY.md`
- `/mnt/d/Git/TrueCV/UK_FEATURE_PRIORITIZATION.md` - `/mnt/d/Git/RealCV/UK_FEATURE_PRIORITIZATION.md`
- `/mnt/d/Git/TrueCV/PHASE1_TECHNICAL_IMPLEMENTATION.md` - `/mnt/d/Git/RealCV/PHASE1_TECHNICAL_IMPLEMENTATION.md`
- `/mnt/d/Git/TrueCV/UK_MARKET_STRATEGY.md` - `/mnt/d/Git/RealCV/UK_MARKET_STRATEGY.md`
- `/mnt/d/Git/TrueCV/API_RESOURCES_AND_CONTACTS.md` - `/mnt/d/Git/RealCV/API_RESOURCES_AND_CONTACTS.md`
- `/mnt/d/Git/TrueCV/README_UK_STRATEGY.md` - `/mnt/d/Git/RealCV/README_UK_STRATEGY.md`
- `/mnt/d/Git/TrueCV/INDEX.md` (this file) - `/mnt/d/Git/RealCV/INDEX.md` (this file)
**Total:** 8 comprehensive strategy documents (~200 pages) **Total:** 8 comprehensive strategy documents (~200 pages)

View File

@@ -10,7 +10,7 @@
### Overview ### Overview
Real-time integration with HEDD (Higher Education Degree Datacheck) to verify UK degrees against 140+ university records. 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 **Gap:** No verification against actual university records
**Value:** Eliminates 90%+ of fake degree claims **Value:** Eliminates 90%+ of fake degree claims
@@ -32,10 +32,10 @@ Report & UI
### Phase 1a: Create Infrastructure (Days 1-5) ### Phase 1a: Create Infrastructure (Days 1-5)
#### File 1: `src/TrueCV.Infrastructure/Configuration/HeddSettings.cs` #### File 1: `src/RealCV.Infrastructure/Configuration/HeddSettings.cs`
```csharp ```csharp
namespace TrueCV.Infrastructure.Configuration; namespace RealCV.Infrastructure.Configuration;
public class HeddSettings 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 ```csharp
using System.Net.Http.Json; using System.Net.Http.Json;
@@ -55,9 +55,9 @@ using System.Text.Json;
using System.Text.Json.Serialization; using System.Text.Json.Serialization;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options; using Microsoft.Extensions.Options;
using TrueCV.Infrastructure.Configuration; using RealCV.Infrastructure.Configuration;
namespace TrueCV.Infrastructure.ExternalApis; namespace RealCV.Infrastructure.ExternalApis;
public sealed class HeddClient 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 ```csharp
using TrueCV.Application.Models; using RealCV.Application.Models;
namespace TrueCV.Application.Interfaces; namespace RealCV.Application.Interfaces;
public interface IEducationVerifierService 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 ```csharp
namespace TrueCV.Application.Models; namespace RealCV.Application.Models;
public sealed record EducationVerificationResult public sealed record EducationVerificationResult
{ {
@@ -370,15 +370,15 @@ public enum ManualVerificationStatus
### Phase 1b: Implement Service Layer (Days 6-10) ### 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 ```csharp
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
using TrueCV.Application.Interfaces; using RealCV.Application.Interfaces;
using TrueCV.Application.Models; using RealCV.Application.Models;
using TrueCV.Infrastructure.ExternalApis; using RealCV.Infrastructure.ExternalApis;
namespace TrueCV.Infrastructure.Services; namespace RealCV.Infrastructure.Services;
public sealed class EducationVerifierService : IEducationVerifierService public sealed class EducationVerifierService : IEducationVerifierService
{ {
@@ -558,10 +558,10 @@ public sealed class EducationVerifierService : IEducationVerifierService
### Phase 1c: Database & Flag Integration (Days 11-12) ### Phase 1c: Database & Flag Integration (Days 11-12)
#### Update: `src/TrueCV.Domain/Enums/FlagCategory.cs` #### Update: `src/RealCV.Domain/Enums/FlagCategory.cs`
```csharp ```csharp
namespace TrueCV.Domain.Enums; namespace RealCV.Domain.Enums;
public enum FlagCategory 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 ```csharp
using TrueCV.Application.Models; using RealCV.Application.Models;
using TrueCV.Domain.Entities; using RealCV.Domain.Entities;
using TrueCV.Domain.Enums; using RealCV.Domain.Enums;
namespace TrueCV.Infrastructure.Services; namespace RealCV.Infrastructure.Services;
public sealed class EducationFlagGenerator public sealed class EducationFlagGenerator
{ {
@@ -683,16 +683,16 @@ public sealed class EducationFlagGenerator
### Phase 1d: Companies House Enhancement - Director Verification ### Phase 1d: Companies House Enhancement - Director Verification
#### File: `src/TrueCV.Infrastructure/ExternalApis/CompaniesHouseDirectorsClient.cs` #### File: `src/RealCV.Infrastructure/ExternalApis/CompaniesHouseDirectorsClient.cs`
```csharp ```csharp
using System.Net.Http.Json; using System.Net.Http.Json;
using System.Text.Json; using System.Text.Json;
using System.Text.Json.Serialization; using System.Text.Json.Serialization;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
using TrueCV.Infrastructure.ExternalApis; using RealCV.Infrastructure.ExternalApis;
namespace TrueCV.Infrastructure.ExternalApis; namespace RealCV.Infrastructure.ExternalApis;
public sealed class CompaniesHouseDirectorsClient 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 ```csharp
namespace TrueCV.Application.Interfaces; namespace RealCV.Application.Interfaces;
public interface IDirectorshipVerifierService 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 ```csharp
using FuzzySharp; using FuzzySharp;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
using TrueCV.Application.Interfaces; using RealCV.Application.Interfaces;
using TrueCV.Infrastructure.ExternalApis; using RealCV.Infrastructure.ExternalApis;
namespace TrueCV.Infrastructure.Services; namespace RealCV.Infrastructure.Services;
public sealed class DirectorshipVerifierService : IDirectorshipVerifierService public sealed class DirectorshipVerifierService : IDirectorshipVerifierService
{ {
@@ -1070,15 +1070,15 @@ public sealed class DirectorshipVerifierService : IDirectorshipVerifierService
### Phase 1e: Enhanced Timeline Analysis ### Phase 1e: Enhanced Timeline Analysis
#### File: `src/TrueCV.Infrastructure/Services/EnhancedTimelineAnalyserService.cs` #### File: `src/RealCV.Infrastructure/Services/EnhancedTimelineAnalyserService.cs`
```csharp ```csharp
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
using TrueCV.Application.Models; using RealCV.Application.Models;
using TrueCV.Domain.Entities; using RealCV.Domain.Entities;
using TrueCV.Domain.Enums; using RealCV.Domain.Enums;
namespace TrueCV.Infrastructure.Services; namespace RealCV.Infrastructure.Services;
public sealed class EnhancedTimelineAnalyserService public sealed class EnhancedTimelineAnalyserService
{ {
@@ -1226,7 +1226,7 @@ public sealed class EnhancedTimelineAnalyserService
### Phase 1f: Dependency Injection & Integration (Days 13-14) ### Phase 1f: Dependency Injection & Integration (Days 13-14)
#### Update: `src/TrueCV.Infrastructure/DependencyInjection.cs` #### Update: `src/RealCV.Infrastructure/DependencyInjection.cs`
```csharp ```csharp
// Add to existing DependencyInjection class: // Add to existing DependencyInjection class:
@@ -1244,7 +1244,7 @@ services.AddScoped<IDirectorshipVerifierService, DirectorshipVerifierService>();
services.AddScoped<EnhancedTimelineAnalyserService>(); 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: 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) ### Phase 1g: Testing & QA (Days 15-16)
#### Test File: `tests/TrueCV.Tests/Services/EducationVerifierServiceTests.cs` #### Test File: `tests/RealCV.Tests/Services/EducationVerifierServiceTests.cs`
```csharp ```csharp
using Moq; using Moq;
using Xunit; using Xunit;
using TrueCV.Application.Models; using RealCV.Application.Models;
using TrueCV.Infrastructure.ExternalApis; using RealCV.Infrastructure.ExternalApis;
using TrueCV.Infrastructure.Services; using RealCV.Infrastructure.Services;
namespace TrueCV.Tests.Services; namespace RealCV.Tests.Services;
public class EducationVerifierServiceTests public class EducationVerifierServiceTests
{ {
@@ -1467,7 +1467,7 @@ public class EducationVerifierServiceTests
Create migration for storing verification results: Create migration for storing verification results:
```bash ```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: 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** **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%) Candidates Lying: 1 in 5 (20%)
Failed Verifications: 24% of CVs Failed Verifications: 24% of CVs
Current Verification Time: 5-10 DAYS Current Verification Time: 5-10 DAYS
TrueCV Solution Time: 5 SECONDS ⚡ RealCV Solution Time: 5 SECONDS ⚡
Market Addressable: £3.3M (UK) Market Addressable: £3.3M (UK)
Year 1 Target Revenue: £30-240K Year 1 Target Revenue: £30-240K
@@ -24,7 +24,7 @@ Expected Profitability: Month 6-7
## Competitive Advantage (Why Now) ## Competitive Advantage (Why Now)
``` ```
FEATURE TrueCV Workable Deel Checkr FEATURE RealCV Workable Deel Checkr
───────────────────────────────────────────────────────── ─────────────────────────────────────────────────────────
HEDD Degree Verification ✅ ❌ ❌ ❌ HEDD Degree Verification ✅ ❌ ❌ ❌
GMC/NMC Healthcare ✅ ❌ ❌ ❌ GMC/NMC Healthcare ✅ ❌ ❌ ❌
@@ -370,7 +370,7 @@ Based on your role:
## One-Liner Summary ## 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 ## 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 **Audience:** Product Team, Marketing, Sales
**Purpose:** Comprehensive market analysis and go-to-market strategy **Purpose:** Comprehensive market analysis and go-to-market strategy
**Key Sections:** **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 - Competitive landscape analysis
- 3-phase product roadmap (Q1-Q3 2026) - 3-phase product roadmap (Q1-Q3 2026)
- GTM strategy (4 sales channels) - GTM strategy (4 sales channels)
@@ -336,7 +336,7 @@ This directory contains the complete product strategy and implementation plan fo
## File Manifest ## File Manifest
``` ```
/mnt/d/Git/TrueCV/ /mnt/d/Git/RealCV/
├── EXECUTIVE_SUMMARY.md (5-page exec overview) ├── EXECUTIVE_SUMMARY.md (5-page exec overview)
├── UK_FEATURE_PRIORITIZATION.md (30-page detailed prioritization) ├── UK_FEATURE_PRIORITIZATION.md (30-page detailed prioritization)
├── PHASE1_TECHNICAL_IMPLEMENTATION.md (60-page technical specs + code) ├── PHASE1_TECHNICAL_IMPLEMENTATION.md (60-page technical specs + code)
@@ -403,7 +403,7 @@ These documents will be updated quarterly with:
## License & Confidentiality ## 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 - Market sizing & financial projections
- Competitive positioning - Competitive positioning
- Product roadmap - Product roadmap

View File

@@ -5,17 +5,17 @@ VisualStudioVersion = 17.0.31903.59
MinimumVisualStudioVersion = 10.0.40219.1 MinimumVisualStudioVersion = 10.0.40219.1
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{F25C3740-9240-46DF-BC34-985BC577216B}" Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{F25C3740-9240-46DF-BC34-985BC577216B}"
EndProject 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 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 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 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 EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tests", "tests", "{80890010-EDA6-418B-AD6C-5A9D875594C4}" Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tests", "tests", "{80890010-EDA6-418B-AD6C-5A9D875594C4}"
EndProject 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 EndProject
Global Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution GlobalSection(SolutionConfigurationPlatforms) = preSolution

View File

@@ -1,4 +1,4 @@
# TrueCV UK Market Feature Prioritization # RealCV UK Market Feature Prioritization
**Date:** January 2026 **Date:** January 2026
**Focus:** UK-Only Market Opportunities **Focus:** UK-Only Market Opportunities
@@ -8,7 +8,7 @@
## Executive Summary ## 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) - **Cost:** Typically £1-5 per verification (commercial rates)
**Implementation Effort:** **Medium (2-3 weeks)** **Implementation Effort:** **Medium (2-3 weeks)**
- Iframe/form integration into TrueCV UI - Iframe/form integration into RealCV UI
- Candidate consent workflow - Candidate consent workflow
- Result polling for manual verifications - Result polling for manual verifications
- Database sync with CVData.Education entries - 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** **Impact Score:** **6.5/10**
- Targets 1.5M NHS workers + private doctors - Targets 1.5M NHS workers + private doctors
- High value for healthcare recruitment - 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 - 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) ### 4. Companies House API (Already Integrated)
**Status:** ✓ Already implemented in TrueCV **Status:** ✓ Already implemented in RealCV
**Current Coverage:** **Current Coverage:**
- Fuzzy matching on company names (70%+ threshold) - 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 - Links to individual regulators
- Government-maintained reference - Government-maintained reference
**Use Case for TrueCV:** **Use Case for RealCV:**
- **Enrichment layer:** When CV claims regulated profession, cross-check against GOV.UK registry - **Enrichment layer:** When CV claims regulated profession, cross-check against GOV.UK registry
- **Flag generation:** "Claims regulated profession but regulator not found" - **Flag generation:** "Claims regulated profession but regulator not found"
- **Guidance:** Link to correct regulator for user lookup - **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) - **Pricing:** Pass-through cost model ($1-2 per verification to user)
- **Implementation:** - **Implementation:**
``` ```
src/TrueCV.Infrastructure/ExternalApis/HeddClient.cs src/RealCV.Infrastructure/ExternalApis/HeddClient.cs
src/TrueCV.Application/Interfaces/IEducationVerifierService.cs src/RealCV.Application/Interfaces/IEducationVerifierService.cs
src/TrueCV.Infrastructure/Services/EducationVerifierService.cs src/RealCV.Infrastructure/Services/EducationVerifierService.cs
FlagCategory += EducationVerification FlagCategory += EducationVerification
Add new flag types: Add new flag types:
- DegreeNotFound - DegreeNotFound
@@ -304,7 +304,7 @@ MEDIUM VALUE + EASY │ MEDIUM VALUE + HARD
- Identify degree end date before employment start anomalies - Identify degree end date before employment start anomalies
- **Implementation:** - **Implementation:**
``` ```
src/TrueCV.Infrastructure/Services/TimelineAnalyserService.cs src/RealCV.Infrastructure/Services/TimelineAnalyserService.cs
- Add: UKEmploymentPatternAnalyzer - Add: UKEmploymentPatternAnalyzer
- Add: EducationEmploymentSequenceValidator - Add: EducationEmploymentSequenceValidator
- New flags: - New flags:
@@ -322,8 +322,8 @@ MEDIUM VALUE + EASY │ MEDIUM VALUE + HARD
- Recurring revenue potential - Recurring revenue potential
- **Implementation:** - **Implementation:**
``` ```
src/TrueCV.Infrastructure/ExternalApis/HealthcareRegisterClient.cs src/RealCV.Infrastructure/ExternalApis/HealthcareRegisterClient.cs
src/TrueCV.Application/Interfaces/IHealthcareVerifierService.cs src/RealCV.Application/Interfaces/IHealthcareVerifierService.cs
FlagCategory += HealthcareRegistration FlagCategory += HealthcareRegistration
New flags: New flags:
- GMCNotFound / GMCRestricted / GMCLapsed - GMCNotFound / GMCRestricted / GMCLapsed
@@ -338,7 +338,7 @@ MEDIUM VALUE + EASY │ MEDIUM VALUE + HARD
- Detects employment after company dissolution - Detects employment after company dissolution
- **Implementation:** - **Implementation:**
``` ```
Extend: src/TrueCV.Infrastructure/ExternalApis/CompaniesHouseClient.cs Extend: src/RealCV.Infrastructure/ExternalApis/CompaniesHouseClient.cs
Add: OfficerAppointmentsClient.GetDirectorAppointments(name, companyNumber) Add: OfficerAppointmentsClient.GetDirectorAppointments(name, companyNumber)
New Service: DirectorshipVerificationService New Service: DirectorshipVerificationService
FlagCategory += DirectorshipVerification FlagCategory += DirectorshipVerification
@@ -360,8 +360,8 @@ MEDIUM VALUE + EASY │ MEDIUM VALUE + HARD
- Regulatory appeal - Regulatory appeal
- **Implementation:** - **Implementation:**
``` ```
src/TrueCV.Infrastructure/ExternalApis/ProfessionalBodyClient.cs src/RealCV.Infrastructure/ExternalApis/ProfessionalBodyClient.cs
src/TrueCV.Infrastructure/ExternalApis/Scrapers/ src/RealCV.Infrastructure/ExternalApis/Scrapers/
- ICAEWMembershipVerifier.cs - ICAEWMembershipVerifier.cs
- SRALawverVerifier.cs - SRALawverVerifier.cs
- IETEngineerVerifier.cs - IETEngineerVerifier.cs
@@ -569,7 +569,7 @@ public class DirectorshipVerificationService
## Competitive Advantage Summary ## Competitive Advantage Summary
| Feature | TrueCV Advantage | Timeline | | Feature | RealCV Advantage | Timeline |
|---|---|---| |---|---|---|
| **HEDD Integration** | Only dedicated CV tool with instant degree verification | Q1 2026 | | **HEDD Integration** | Only dedicated CV tool with instant degree verification | Q1 2026 |
| **Healthcare Register Targeting** | Only tool targeting healthcare recruitment niche | 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 **Document Date:** January 2026
**Focus:** UK CV verification market positioning **Focus:** UK CV verification market positioning
@@ -37,7 +37,7 @@
### SAM (Serviceable Addressable Market) ### SAM (Serviceable Addressable Market)
**TrueCV Target Segment:** **RealCV Target Segment:**
- Mid-market recruitment agencies (50-500 staff): ~800 companies - Mid-market recruitment agencies (50-500 staff): ~800 companies
- Corporate HR departments (100+ employees): ~15,000 companies - Corporate HR departments (100+ employees): ~15,000 companies
- Specialist vertical recruiters (healthcare, finance, legal): ~2,500 companies - Specialist vertical recruiters (healthcare, finance, legal): ~2,500 companies
@@ -65,9 +65,9 @@
| **Verifile** | Pre-employment screening | Established relationships | Traditional manual process | | **Verifile** | Pre-employment screening | Established relationships | Traditional manual process |
| **Veriff** | Identity verification | Strong deepfake tech | Not employment-focused | | **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 | ❌ | ❌ | ❌ | ❌ | | **Degree Verification (HEDD)** | ✅ Q1 2026 | ❌ | ❌ | ❌ | ❌ |
| **Healthcare Register Checks** | ✅ Q1 2026 | ❌ | ❌ | ❌ | ❌ | | **Healthcare Register Checks** | ✅ Q1 2026 | ❌ | ❌ | ❌ | ❌ |
@@ -194,7 +194,7 @@
### Marketing Messaging ### Marketing Messaging
**Tagline:** "Hire with Confidence. Verify with TrueCV." **Tagline:** "Hire with Confidence. Verify with RealCV."
**Core Messages:** **Core Messages:**
1. **For Recruiters:** "Catch 90% of degree fraud in seconds. One-click HEDD verification." 1. **For Recruiters:** "Catch 90% of degree fraud in seconds. One-click HEDD verification."
@@ -279,7 +279,7 @@
- **API Uptime:** 99.9% - **API Uptime:** 99.9%
### Market Metrics ### 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 - **Market Share:** 0.5-1% of addressable recruitment screening market
- **Vertical Penetration:** 3%+ of healthcare recruiters, 2%+ financial recruiters - **Vertical Penetration:** 3%+ of healthcare recruiters, 2%+ financial recruiters
@@ -369,7 +369,7 @@
## Conclusion ## 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. **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."

View File

@@ -1,4 +1,4 @@
# TrueCV Deployment Guide # RealCV Deployment Guide
## Quick Start ## Quick Start
@@ -40,7 +40,7 @@ After DNS is configured and app is deployed:
```bash ```bash
ssh user@your-server ssh user@your-server
sudo certbot --nginx -d truecv.yourdomain.com sudo certbot --nginx -d realcv.yourdomain.com
``` ```
## Configuration ## Configuration
@@ -54,7 +54,7 @@ The systemd service sets these environment variables:
To add more (like API keys), edit: To add more (like API keys), edit:
```bash ```bash
sudo systemctl edit truecv sudo systemctl edit realcv
``` ```
Add: Add:
@@ -65,12 +65,12 @@ Environment=OpenAI__ApiKey=your-key-here
### appsettings.Production.json ### appsettings.Production.json
For sensitive settings, create `/var/www/truecv/appsettings.Production.json`: For sensitive settings, create `/var/www/realcv/appsettings.Production.json`:
```json ```json
{ {
"ConnectionStrings": { "ConnectionStrings": {
"DefaultConnection": "Server=127.0.0.1;Database=TrueCV;User Id=SA;Password=YourPassword;TrustServerCertificate=True" "DefaultConnection": "Server=127.0.0.1;Database=RealCV;User Id=SA;Password=YourPassword;TrustServerCertificate=True"
}, },
"OpenAI": { "OpenAI": {
"ApiKey": "your-openai-key" "ApiKey": "your-openai-key"
@@ -83,41 +83,41 @@ For sensitive settings, create `/var/www/truecv/appsettings.Production.json`:
### View Logs ### View Logs
```bash ```bash
# Application logs # Application logs
sudo journalctl -u truecv -f sudo journalctl -u realcv -f
# Nginx logs # Nginx logs
sudo tail -f /var/log/nginx/access.log sudo tail -f /var/log/nginx/access.log
sudo tail -f /var/log/nginx/error.log sudo tail -f /var/log/nginx/error.log
# SQL Server logs # SQL Server logs
docker logs truecv-sql -f docker logs realcv-sql -f
``` ```
### Restart Services ### Restart Services
```bash ```bash
sudo systemctl restart truecv sudo systemctl restart realcv
sudo systemctl restart nginx sudo systemctl restart nginx
docker restart truecv-sql docker restart realcv-sql
``` ```
### Database Backup ### Database Backup
```bash ```bash
# Backup # Backup
docker exec truecv-sql /opt/mssql-tools18/bin/sqlcmd \ docker exec realcv-sql /opt/mssql-tools18/bin/sqlcmd \
-S localhost -U SA -P 'YourPassword' -C \ -S localhost -U SA -P 'YourPassword' -C \
-Q "BACKUP DATABASE TrueCV TO DISK='/var/opt/mssql/backup/truecv.bak'" -Q "BACKUP DATABASE RealCV TO DISK='/var/opt/mssql/backup/realcv.bak'"
# Copy backup from container # Copy backup from container
docker cp truecv-sql:/var/opt/mssql/backup/truecv.bak ./truecv-backup.bak docker cp realcv-sql:/var/opt/mssql/backup/realcv.bak ./realcv-backup.bak
``` ```
### Rollback Deployment ### Rollback Deployment
```bash ```bash
# On server - restore previous version # On server - restore previous version
sudo systemctl stop truecv sudo systemctl stop realcv
sudo rm -rf /var/www/truecv sudo rm -rf /var/www/realcv
sudo mv /var/www/truecv.backup.YYYYMMDD_HHMMSS /var/www/truecv sudo mv /var/www/realcv.backup.YYYYMMDD_HHMMSS /var/www/realcv
sudo systemctl start truecv sudo systemctl start realcv
``` ```
## Troubleshooting ## Troubleshooting
@@ -125,23 +125,23 @@ sudo systemctl start truecv
### App won't start ### App won't start
```bash ```bash
# Check status # Check status
sudo systemctl status truecv sudo systemctl status realcv
# Check logs # Check logs
sudo journalctl -u truecv -n 100 sudo journalctl -u realcv -n 100
# Test manually # Test manually
cd /var/www/truecv cd /var/www/realcv
sudo -u www-data dotnet TrueCV.Web.dll sudo -u www-data dotnet RealCV.Web.dll
``` ```
### Database connection issues ### Database connection issues
```bash ```bash
# Check SQL Server is running # Check SQL Server is running
docker ps | grep truecv-sql docker ps | grep realcv-sql
# Test connection # Test connection
docker exec -it truecv-sql /opt/mssql-tools18/bin/sqlcmd \ docker exec -it realcv-sql /opt/mssql-tools18/bin/sqlcmd \
-S localhost -U SA -P 'YourPassword' -C \ -S localhost -U SA -P 'YourPassword' -C \
-Q "SELECT name FROM sys.databases" -Q "SELECT name FROM sys.databases"
``` ```

View File

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

View File

@@ -1,11 +1,11 @@
#!/bin/bash #!/bin/bash
# TrueCV Server Setup Script # RealCV Server Setup Script
# Run this ONCE on a fresh Linux server (Ubuntu 22.04/24.04) # Run this ONCE on a fresh Linux server (Ubuntu 22.04/24.04)
set -e set -e
# Configuration - UPDATE THESE VALUES # Configuration - UPDATE THESE VALUES
DOMAIN="truecv.yourdomain.com" DOMAIN="realcv.yourdomain.com"
DB_PASSWORD="YourStrong!Password123" DB_PASSWORD="YourStrong!Password123"
ADMIN_EMAIL="admin@yourdomain.com" ADMIN_EMAIL="admin@yourdomain.com"
@@ -15,7 +15,7 @@ GREEN='\033[0;32m'
YELLOW='\033[1;33m' YELLOW='\033[1;33m'
NC='\033[0m' NC='\033[0m'
echo -e "${GREEN}=== TrueCV Server Setup ===${NC}" echo -e "${GREEN}=== RealCV Server Setup ===${NC}"
# Check if running as root # Check if running as root
if [[ $EUID -ne 0 ]]; then if [[ $EUID -ne 0 ]]; then
@@ -52,54 +52,54 @@ echo -e "${YELLOW}Step 5: Setting up SQL Server...${NC}"
docker run -e 'ACCEPT_EULA=Y' \ docker run -e 'ACCEPT_EULA=Y' \
-e "SA_PASSWORD=${DB_PASSWORD}" \ -e "SA_PASSWORD=${DB_PASSWORD}" \
-p 127.0.0.1:1433:1433 \ -p 127.0.0.1:1433:1433 \
--name truecv-sql \ --name realcv-sql \
--restart unless-stopped \ --restart unless-stopped \
-v truecv-sqldata:/var/opt/mssql \ -v realcv-sqldata:/var/opt/mssql \
-d mcr.microsoft.com/mssql/server:2022-latest -d mcr.microsoft.com/mssql/server:2022-latest
echo "Waiting for SQL Server to start..." echo "Waiting for SQL Server to start..."
sleep 30 sleep 30
# Create the database # Create the database
docker exec truecv-sql /opt/mssql-tools18/bin/sqlcmd \ docker exec realcv-sql /opt/mssql-tools18/bin/sqlcmd \
-S localhost -U SA -P "${DB_PASSWORD}" -C \ -S localhost -U SA -P "${DB_PASSWORD}" -C \
-Q "CREATE DATABASE TrueCV" -Q "CREATE DATABASE RealCV"
# Step 6: Create application directory # Step 6: Create application directory
echo -e "${YELLOW}Step 6: Creating application directory...${NC}" echo -e "${YELLOW}Step 6: Creating application directory...${NC}"
mkdir -p /var/www/truecv mkdir -p /var/www/realcv
chown -R www-data:www-data /var/www/truecv chown -R www-data:www-data /var/www/realcv
# Step 7: Create systemd service # Step 7: Create systemd service
echo -e "${YELLOW}Step 7: Creating systemd service...${NC}" echo -e "${YELLOW}Step 7: Creating systemd service...${NC}"
cat > /etc/systemd/system/truecv.service << EOF cat > /etc/systemd/system/realcv.service << EOF
[Unit] [Unit]
Description=TrueCV Web Application Description=RealCV Web Application
After=network.target docker.service After=network.target docker.service
Requires=docker.service Requires=docker.service
[Service] [Service]
WorkingDirectory=/var/www/truecv WorkingDirectory=/var/www/realcv
ExecStart=/usr/bin/dotnet /var/www/truecv/TrueCV.Web.dll ExecStart=/usr/bin/dotnet /var/www/realcv/RealCV.Web.dll
Restart=always Restart=always
RestartSec=10 RestartSec=10
KillSignal=SIGINT KillSignal=SIGINT
SyslogIdentifier=truecv SyslogIdentifier=realcv
User=www-data User=www-data
Environment=ASPNETCORE_ENVIRONMENT=Production Environment=ASPNETCORE_ENVIRONMENT=Production
Environment=ASPNETCORE_URLS=http://localhost:5000 Environment=ASPNETCORE_URLS=http://localhost:5000
Environment=ConnectionStrings__DefaultConnection=Server=127.0.0.1;Database=TrueCV;User Id=SA;Password=${DB_PASSWORD};TrustServerCertificate=True Environment=ConnectionStrings__DefaultConnection=Server=127.0.0.1;Database=RealCV;User Id=SA;Password=${DB_PASSWORD};TrustServerCertificate=True
[Install] [Install]
WantedBy=multi-user.target WantedBy=multi-user.target
EOF EOF
systemctl daemon-reload systemctl daemon-reload
systemctl enable truecv systemctl enable realcv
# Step 8: Configure Nginx # Step 8: Configure Nginx
echo -e "${YELLOW}Step 8: Configuring Nginx...${NC}" echo -e "${YELLOW}Step 8: Configuring Nginx...${NC}"
cat > /etc/nginx/sites-available/truecv << EOF cat > /etc/nginx/sites-available/realcv << EOF
server { server {
listen 80; listen 80;
server_name ${DOMAIN}; server_name ${DOMAIN};
@@ -122,7 +122,7 @@ server {
} }
EOF EOF
ln -sf /etc/nginx/sites-available/truecv /etc/nginx/sites-enabled/ ln -sf /etc/nginx/sites-available/realcv /etc/nginx/sites-enabled/
rm -f /etc/nginx/sites-enabled/default rm -f /etc/nginx/sites-enabled/default
nginx -t nginx -t
systemctl reload nginx systemctl reload nginx
@@ -151,9 +151,9 @@ echo "2. Deploy the application using deploy.sh from your dev machine"
echo "3. Run SSL setup: certbot --nginx -d ${DOMAIN}" echo "3. Run SSL setup: certbot --nginx -d ${DOMAIN}"
echo "" echo ""
echo "Useful commands:" echo "Useful commands:"
echo " sudo systemctl status truecv - Check app status" echo " sudo systemctl status realcv - Check app status"
echo " sudo journalctl -u truecv -f - View app logs" echo " sudo journalctl -u realcv -f - View app logs"
echo " docker logs truecv-sql - View SQL Server logs" echo " docker logs realcv-sql - View SQL Server logs"
echo "" echo ""
echo -e "${YELLOW}Database connection string:${NC}" echo -e "${YELLOW}Database connection string:${NC}"
echo " Server=127.0.0.1;Database=TrueCV;User Id=SA;Password=${DB_PASSWORD};TrustServerCertificate=True" 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' version: '3.8'
services: services:
# TrueCV Web Application # RealCV Web Application
truecv-web: realcv-web:
build: build:
context: . context: .
dockerfile: Dockerfile dockerfile: Dockerfile
container_name: truecv-web container_name: realcv-web
ports: ports:
- "5000:8080" - "5000:8080"
environment: environment:
- ASPNETCORE_ENVIRONMENT=Development - ASPNETCORE_ENVIRONMENT=Development
- 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;
- ConnectionStrings__HangfireConnection=Server=sqlserver;Database=TrueCV_Hangfire;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__ConnectionString=DefaultEndpointsProtocol=http;AccountName=devstoreaccount1;AccountKey=Eby8vdM02xNOcqFlqUwJPLlmEtlCDXJ1OUzFT50uSRZ6IFsuFq2UVErCz4I6tq/K1SZFPTOtr/KBHBeksoGMGw==;BlobEndpoint=http://azurite:10000/devstoreaccount1;
- AzureBlob__ContainerName=cv-uploads - AzureBlob__ContainerName=cv-uploads
- CompaniesHouse__BaseUrl=https://api.company-information.service.gov.uk - CompaniesHouse__BaseUrl=https://api.company-information.service.gov.uk
@@ -24,13 +24,13 @@ services:
azurite: azurite:
condition: service_started condition: service_started
networks: networks:
- truecv-network - realcv-network
restart: unless-stopped restart: unless-stopped
# SQL Server Database # SQL Server Database
sqlserver: sqlserver:
image: mcr.microsoft.com/mssql/server:2022-latest image: mcr.microsoft.com/mssql/server:2022-latest
container_name: truecv-sqlserver container_name: realcv-sqlserver
ports: ports:
- "1433:1433" - "1433:1433"
environment: environment:
@@ -40,7 +40,7 @@ services:
volumes: volumes:
- sqlserver-data:/var/opt/mssql - sqlserver-data:/var/opt/mssql
networks: networks:
- truecv-network - realcv-network
healthcheck: healthcheck:
test: /opt/mssql-tools18/bin/sqlcmd -S localhost -U sa -P "TrueCV_P@ssw0rd!" -C -Q "SELECT 1" || exit 1 test: /opt/mssql-tools18/bin/sqlcmd -S localhost -U sa -P "TrueCV_P@ssw0rd!" -C -Q "SELECT 1" || exit 1
interval: 10s interval: 10s
@@ -52,7 +52,7 @@ services:
# Azure Storage Emulator (Azurite) # Azure Storage Emulator (Azurite)
azurite: azurite:
image: mcr.microsoft.com/azure-storage/azurite:latest image: mcr.microsoft.com/azure-storage/azurite:latest
container_name: truecv-azurite container_name: realcv-azurite
ports: ports:
- "10000:10000" # Blob service - "10000:10000" # Blob service
- "10001:10001" # Queue service - "10001:10001" # Queue service
@@ -61,7 +61,7 @@ services:
- azurite-data:/data - 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" command: "azurite --blobHost 0.0.0.0 --queueHost 0.0.0.0 --tableHost 0.0.0.0 --location /data --debug /data/debug.log"
networks: networks:
- truecv-network - realcv-network
restart: unless-stopped restart: unless-stopped
# Database initialization (runs migrations) # Database initialization (runs migrations)
@@ -69,18 +69,18 @@ services:
build: build:
context: . context: .
dockerfile: Dockerfile.migrations dockerfile: Dockerfile.migrations
container_name: truecv-db-init container_name: realcv-db-init
environment: 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: depends_on:
sqlserver: sqlserver:
condition: service_healthy condition: service_healthy
networks: networks:
- truecv-network - realcv-network
restart: "no" restart: "no"
networks: networks:
truecv-network: realcv-network:
driver: bridge driver: bridge
volumes: 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 sealed record CVCheckDto
{ {
public required Guid Id { get; init; } public required Guid Id { get; init; }
public required string OriginalFileName { get; init; } public required string OriginalFileName { get; init; }
public string? CandidateName { get; init; }
public required string Status { get; init; } public required string Status { get; init; }
public int? VeracityScore { get; init; } public int? VeracityScore { get; init; }
public string? ProcessingStage { 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 public sealed record CompanySearchResult
{ {

View File

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

View File

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

View File

@@ -1,6 +1,7 @@
using System.Text.Json; using System.Text.Json;
using System.Text.Json.Serialization;
namespace TrueCV.Application.Helpers; namespace RealCV.Application.Helpers;
public static class JsonDefaults public static class JsonDefaults
{ {
@@ -16,4 +17,13 @@ public static class JsonDefaults
PropertyNameCaseInsensitive = true, PropertyNameCaseInsensitive = true,
WriteIndented = 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 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 public interface IAuditService
{ {

View File

@@ -1,7 +1,7 @@
using TrueCV.Application.DTOs; using RealCV.Application.DTOs;
using TrueCV.Application.Models; using RealCV.Application.Models;
namespace TrueCV.Application.Interfaces; namespace RealCV.Application.Interfaces;
public interface ICVCheckService 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 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 public interface ICompanyNameMatcherService
{ {

View File

@@ -1,7 +1,7 @@
using TrueCV.Application.DTOs; using RealCV.Application.DTOs;
using TrueCV.Application.Models; using RealCV.Application.Models;
namespace TrueCV.Application.Interfaces; namespace RealCV.Application.Interfaces;
public interface ICompanyVerifierService 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 public interface IEducationVerifierService
{ {

View File

@@ -1,4 +1,4 @@
namespace TrueCV.Application.Interfaces; namespace RealCV.Application.Interfaces;
public interface IFileStorageService 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 public interface ITimelineAnalyserService
{ {

View File

@@ -1,4 +1,4 @@
namespace TrueCV.Application.Interfaces; namespace RealCV.Application.Interfaces;
public interface IUserContextService 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 public sealed record CVData
{ {

View File

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

View File

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

View File

@@ -1,12 +1,12 @@
namespace TrueCV.Application.Models; namespace RealCV.Application.Models;
public sealed record EducationVerificationResult public sealed record EducationVerificationResult
{ {
public required string ClaimedInstitution { get; init; } public required string ClaimedInstitution { get; init; }
public string? MatchedInstitution { 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 IsVerified { get; init; }
public bool IsDiplomaMill { get; init; } public bool IsUnaccredited { get; init; }
public bool IsSuspicious { get; init; } public bool IsSuspicious { get; init; }
public string? VerificationNotes { 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 public sealed record EmploymentEntry
{ {

View File

@@ -1,4 +1,4 @@
namespace TrueCV.Application.Models; namespace RealCV.Application.Models;
public sealed record FlagResult 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 public record SemanticMatchResult
{ {
@@ -10,12 +10,6 @@ public record SemanticMatchResult
public bool IsMatch => ConfidenceScore >= 70; 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 record CompanyCandidate
{ {
public required string CompanyName { get; init; } 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 public sealed record TimelineAnalysisResult
{ {

View File

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

View File

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

View File

@@ -1,4 +1,4 @@
namespace TrueCV.Application.Models; namespace RealCV.Application.Models;
public sealed record VeracityReport public sealed record VeracityReport
{ {
@@ -8,6 +8,7 @@ public sealed record VeracityReport
public List<CompanyVerificationResult> EmploymentVerifications { get; init; } = []; public List<CompanyVerificationResult> EmploymentVerifications { get; init; } = [];
public List<EducationVerificationResult> EducationVerifications { get; init; } = []; public List<EducationVerificationResult> EducationVerifications { get; init; } = [];
public required TimelineAnalysisResult TimelineAnalysis { get; init; } public required TimelineAnalysisResult TimelineAnalysis { get; init; }
public TextAnalysisResult? TextAnalysis { get; init; }
public List<FlagResult> Flags { get; init; } = []; public List<FlagResult> Flags { get; init; } = [];
public required DateTime GeneratedAt { get; init; } public required DateTime GeneratedAt { get; init; }
} }

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,4 +1,4 @@
namespace TrueCV.Domain.Enums; namespace RealCV.Domain.Enums;
public enum UserPlan 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 public sealed class AnthropicSettings
{ {

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -5,14 +5,15 @@ using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection;
using Polly; using Polly;
using Polly.Extensions.Http; using Polly.Extensions.Http;
using TrueCV.Application.Interfaces; using RealCV.Application.Interfaces;
using TrueCV.Infrastructure.Configuration; using RealCV.Infrastructure.Clients;
using TrueCV.Infrastructure.Data; using RealCV.Infrastructure.Configuration;
using TrueCV.Infrastructure.ExternalApis; using RealCV.Infrastructure.Data;
using TrueCV.Infrastructure.Jobs; using RealCV.Infrastructure.ExternalApis;
using TrueCV.Infrastructure.Services; using RealCV.Infrastructure.Jobs;
using RealCV.Infrastructure.Services;
namespace TrueCV.Infrastructure; namespace RealCV.Infrastructure;
public static class DependencyInjection public static class DependencyInjection
{ {
@@ -74,6 +75,13 @@ public static class DependencyInjection
services.Configure<LocalStorageSettings>( services.Configure<LocalStorageSettings>(
configuration.GetSection(LocalStorageSettings.SectionName)); 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 // Configure HttpClient for CompaniesHouseClient with retry policy
services.AddHttpClient<CompaniesHouseClient>((serviceProvider, client) => services.AddHttpClient<CompaniesHouseClient>((serviceProvider, client) =>
{ {
@@ -88,16 +96,34 @@ public static class DependencyInjection
}) })
.AddPolicyHandler(GetRetryPolicy()); .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 // Register services
services.AddScoped<ICVParserService, CVParserService>(); services.AddScoped<ICVParserService, CVParserService>();
services.AddScoped<ICompanyNameMatcherService, AICompanyNameMatcherService>(); services.AddScoped<ICompanyNameMatcherService, AICompanyNameMatcherService>();
services.AddScoped<ICompanyVerifierService, CompanyVerifierService>(); services.AddScoped<ICompanyVerifierService, CompanyVerifierService>();
services.AddScoped<IEducationVerifierService, EducationVerifierService>(); services.AddScoped<IEducationVerifierService, EducationVerifierService>();
services.AddScoped<ITimelineAnalyserService, TimelineAnalyserService>(); services.AddScoped<ITimelineAnalyserService, TimelineAnalyserService>();
services.AddScoped<ITextAnalysisService, TextAnalysisService>();
services.AddScoped<ICVCheckService, CVCheckService>(); services.AddScoped<ICVCheckService, CVCheckService>();
services.AddScoped<IUserContextService, UserContextService>(); services.AddScoped<IUserContextService, UserContextService>();
services.AddScoped<IAuditService, AuditService>(); 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 // Register file storage - use local storage if configured, otherwise Azure
var useLocalStorage = configuration.GetValue<bool>("UseLocalStorage"); var useLocalStorage = configuration.GetValue<bool>("UseLocalStorage");
if (useLocalStorage) if (useLocalStorage)

View File

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

View File

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

View File

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

View File

@@ -1,14 +1,14 @@
using System.Text.Json; using System.Text.Json;
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
using TrueCV.Application.Helpers; using RealCV.Application.Helpers;
using TrueCV.Application.Interfaces; using RealCV.Application.Interfaces;
using TrueCV.Application.Models; using RealCV.Application.Models;
using TrueCV.Domain.Entities; using RealCV.Domain.Entities;
using TrueCV.Domain.Enums; using RealCV.Domain.Enums;
using TrueCV.Infrastructure.Data; using RealCV.Infrastructure.Data;
namespace TrueCV.Infrastructure.Jobs; namespace RealCV.Infrastructure.Jobs;
public sealed class ProcessCVCheckJob public sealed class ProcessCVCheckJob
{ {
@@ -18,19 +18,19 @@ public sealed class ProcessCVCheckJob
private readonly ICompanyVerifierService _companyVerifierService; private readonly ICompanyVerifierService _companyVerifierService;
private readonly IEducationVerifierService _educationVerifierService; private readonly IEducationVerifierService _educationVerifierService;
private readonly ITimelineAnalyserService _timelineAnalyserService; private readonly ITimelineAnalyserService _timelineAnalyserService;
private readonly ITextAnalysisService _textAnalysisService;
private readonly IAuditService _auditService; private readonly IAuditService _auditService;
private readonly ILogger<ProcessCVCheckJob> _logger; private readonly ILogger<ProcessCVCheckJob> _logger;
private const int BaseScore = 100; private const int BaseScore = 100;
private const int UnverifiedCompanyPenalty = 10; private const int UnverifiedCompanyPenalty = 10;
private const int ImplausibleJobTitlePenalty = 15; 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 RapidProgressionPenalty = 10;
private const int EarlyCareerSeniorRolePenalty = 10; private const int EarlyCareerSeniorRolePenalty = 10;
private const int GapMonthPenalty = 1; private const int GapMonthPenalty = 1;
private const int MaxGapPenalty = 10; private const int MaxGapPenalty = 10;
private const int OverlapMonthPenalty = 2; private const int OverlapMonthPenalty = 2;
private const int DiplomaMillPenalty = 25; private const int UnaccreditedInstitutionPenalty = 25;
private const int SuspiciousInstitutionPenalty = 15; private const int SuspiciousInstitutionPenalty = 15;
private const int UnverifiedEducationPenalty = 5; private const int UnverifiedEducationPenalty = 5;
private const int EducationDatePenalty = 10; private const int EducationDatePenalty = 10;
@@ -42,6 +42,7 @@ public sealed class ProcessCVCheckJob
ICompanyVerifierService companyVerifierService, ICompanyVerifierService companyVerifierService,
IEducationVerifierService educationVerifierService, IEducationVerifierService educationVerifierService,
ITimelineAnalyserService timelineAnalyserService, ITimelineAnalyserService timelineAnalyserService,
ITextAnalysisService textAnalysisService,
IAuditService auditService, IAuditService auditService,
ILogger<ProcessCVCheckJob> logger) ILogger<ProcessCVCheckJob> logger)
{ {
@@ -51,6 +52,7 @@ public sealed class ProcessCVCheckJob
_companyVerifierService = companyVerifierService; _companyVerifierService = companyVerifierService;
_educationVerifierService = educationVerifierService; _educationVerifierService = educationVerifierService;
_timelineAnalyserService = timelineAnalyserService; _timelineAnalyserService = timelineAnalyserService;
_textAnalysisService = textAnalysisService;
_auditService = auditService; _auditService = auditService;
_logger = logger; _logger = logger;
} }
@@ -183,11 +185,11 @@ public sealed class ProcessCVCheckJob
cvData.Employment); cvData.Employment);
_logger.LogDebug( _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, cvCheckId,
educationResults.Count, educationResults.Count,
educationResults.Count(e => e.IsVerified), educationResults.Count(e => e.IsVerified),
educationResults.Count(e => e.IsDiplomaMill)); educationResults.Count(e => e.IsUnaccredited));
// Step 7: Analyse timeline // Step 7: Analyse timeline
cvCheck.ProcessingStage = "Analysing Timeline"; cvCheck.ProcessingStage = "Analysing Timeline";
@@ -199,10 +201,23 @@ public sealed class ProcessCVCheckJob
"Timeline analysis for check {CheckId}: {GapCount} gaps, {OverlapCount} overlaps", "Timeline analysis for check {CheckId}: {GapCount} gaps, {OverlapCount} overlaps",
cvCheckId, timelineAnalysis.Gaps.Count, timelineAnalysis.Overlaps.Count); 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 // Step 8: Calculate veracity score
cvCheck.ProcessingStage = "Calculating Score"; cvCheck.ProcessingStage = "Calculating Score";
await _dbContext.SaveChangesAsync(cancellationToken); 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); _logger.LogDebug("Calculated veracity score for check {CheckId}: {Score}", cvCheckId, score);
@@ -247,6 +262,7 @@ public sealed class ProcessCVCheckJob
EmploymentVerifications = verificationResults, EmploymentVerifications = verificationResults,
EducationVerifications = educationResults, EducationVerifications = educationResults,
TimelineAnalysis = timelineAnalysis, TimelineAnalysis = timelineAnalysis,
TextAnalysis = textAnalysis,
Flags = flags, Flags = flags,
GeneratedAt = DateTime.UtcNow GeneratedAt = DateTime.UtcNow
}; };
@@ -291,6 +307,7 @@ public sealed class ProcessCVCheckJob
List<CompanyVerificationResult> verifications, List<CompanyVerificationResult> verifications,
List<EducationVerificationResult> educationResults, List<EducationVerificationResult> educationResults,
TimelineAnalysisResult timeline, TimelineAnalysisResult timeline,
TextAnalysisResult textAnalysis,
CVData cvData) CVData cvData)
{ {
var score = BaseScore; var score = BaseScore;
@@ -389,23 +406,23 @@ public sealed class ProcessCVCheckJob
AddPLCExperienceFlag(verifications, flags); AddPLCExperienceFlag(verifications, flags);
AddVerifiedDirectorFlag(verifications, flags); AddVerifiedDirectorFlag(verifications, flags);
// Penalty for diploma mills (critical) // Penalty for unaccredited institutions (critical)
foreach (var edu in educationResults.Where(e => e.IsDiplomaMill)) foreach (var edu in educationResults.Where(e => e.IsUnaccredited))
{ {
score -= DiplomaMillPenalty; score -= UnaccreditedInstitutionPenalty;
flags.Add(new FlagResult flags.Add(new FlagResult
{ {
Category = FlagCategory.Education.ToString(), Category = FlagCategory.Education.ToString(),
Severity = FlagSeverity.Critical.ToString(), Severity = FlagSeverity.Critical.ToString(),
Title = "Diploma Mill Detected", Title = "Unaccredited Institution",
Description = $"'{edu.ClaimedInstitution}' is a known diploma mill. {edu.VerificationNotes}", Description = $"'{edu.ClaimedInstitution}' is not found in the register of recognised institutions. {edu.VerificationNotes}",
ScoreImpact = -DiplomaMillPenalty ScoreImpact = -UnaccreditedInstitutionPenalty
}); });
} }
// Penalty for suspicious institutions // 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; score -= SuspiciousInstitutionPenalty;
@@ -413,15 +430,15 @@ public sealed class ProcessCVCheckJob
{ {
Category = FlagCategory.Education.ToString(), Category = FlagCategory.Education.ToString(),
Severity = FlagSeverity.Warning.ToString(), Severity = FlagSeverity.Warning.ToString(),
Title = "Suspicious Institution", Title = "Institution Requires Verification",
Description = $"'{edu.ClaimedInstitution}' has suspicious characteristics. {edu.VerificationNotes}", Description = $"'{edu.ClaimedInstitution}' has characteristics that warrant additional verification. {edu.VerificationNotes}",
ScoreImpact = -SuspiciousInstitutionPenalty 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 // 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) && !string.IsNullOrWhiteSpace(e.ClaimedInstitution)
&& !e.ClaimedInstitution.Equals("Unknown Institution", StringComparison.OrdinalIgnoreCase) && !e.ClaimedInstitution.Equals("Unknown Institution", StringComparison.OrdinalIgnoreCase)
&& !e.ClaimedInstitution.Equals("Unknown", StringComparison.OrdinalIgnoreCase))) && !e.ClaimedInstitution.Equals("Unknown", StringComparison.OrdinalIgnoreCase)))
@@ -485,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 // Deduplicate flags based on Title + Description
var uniqueFlags = flags var uniqueFlags = flags
.GroupBy(f => (f.Title, f.Description)) .GroupBy(f => (f.Title, f.Description))

View File

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

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