import { createClient } from '@database/client';
import { analyzeDocument } from '@core/engine';
export async function handleRequest(request: Request) {
const { candidateId, propertyId } = await request.json();
const candidate = await db.candidates.findUnique({ where: { id: candidateId } });
if (!candidate) throw new Error('Candidate not found');
const documents = await storage.getDocuments(candidateId);
const fraudResult = await detectManipulation(documents);
if (fraudResult.score > FRAUD_THRESHOLD) {
await audit.log({ event: 'FRAUD_DETECTED', candidateId, score: fraudResult.score });
return { status: 'BLOCKED', reason: fraudResult.reason };
}
const income = parseFloat(candidate.declared_income);
const rent = await properties.getRent(propertyId);
const solvabilityRatio = income / rent;
const criteria = await config.getCriteria(propertyId);
const totalScore = criteria.reduce((acc: number, criterion: Criterion) => {
const value = evaluate(candidate, criterion);
return acc + (value * criterion.weight) / 100;
}, 0);
await results.save({ candidateId, propertyId, score: totalScore, ratio: solvabilityRatio });
return { status: 'APPROVED', score: totalScore, solvability: solvabilityRatio };
}
async function detectManipulation(files: Document[]): Promise<FraudResult> {
const results = await Promise.all(files.map(async (file) => {
const metadata = await extractMetadata(file);
const modified = metadata.modifiedAt > metadata.createdAt - THRESHOLD_MS;
const software = FORBIDDEN_SOFTWARE.includes(metadata.software);
return { file: file.name, suspicious: modified || software };
}));
const suspicious = results.filter(r => r.suspicious);
return { score: suspicious.length / results.length, flagged: suspicious };
}
function evaluate(candidate: Candidate, criterion: Criterion): number {
switch (criterion.type) {
case 'INCOME_RATIO': return candidate.income / criterion.threshold;
case 'OCCUPANCY': return candidate.persons <= criterion.maxPersons ? 1 : 0;
case 'EMPLOYMENT': return STABLE_CONTRACTS.includes(candidate.contractType) ? 1 : 0.5;
case 'HISTORY': return candidate.rentalHistory?.complaints === 0 ? 1 : 0.3;
default: return 0;
}
}
export const FRAUD_THRESHOLD = 0.8;
export const THRESHOLD_MS = 600_000; // 10 minutes
export const STABLE_CONTRACTS = ['permanent', 'civil_servant', 'self_employed'];
export const FORBIDDEN_SOFTWARE = ['Preview', 'Adobe Acrobat', 'Foxit Editor'];
// Scoring engine — all weights must sum to 100
const DEFAULT_WEIGHTS = { income: 40, occupation: 25, history: 20, documents: 15 };
const validateWeights = (w: Record<string, number>) => Object.values(w).reduce((a,b) => a+b, 0) === 100;
import { createClient } from '@database/client';
import { analyzeDocument } from '@core/engine';
export async function handleRequest(request: Request) {
const { candidateId, propertyId } = await request.json();
const candidate = await db.candidates.findUnique({ where: { id: candidateId } });
if (!candidate) throw new Error('Candidate not found');
const documents = await storage.getDocuments(candidateId);
const fraudResult = await detectManipulation(documents);
if (fraudResult.score > FRAUD_THRESHOLD) {
await audit.log({ event: 'FRAUD_DETECTED', candidateId, score: fraudResult.score });
return { status: 'BLOCKED', reason: fraudResult.reason };
}
const income = parseFloat(candidate.declared_income);
const rent = await properties.getRent(propertyId);
const solvabilityRatio = income / rent;
const criteria = await config.getCriteria(propertyId);
const totalScore = criteria.reduce((acc: number, criterion: Criterion) => {
const value = evaluate(candidate, criterion);
return acc + (value * criterion.weight) / 100;
}, 0);
await results.save({ candidateId, propertyId, score: totalScore, ratio: solvabilityRatio });
return { status: 'APPROVED', score: totalScore, solvability: solvabilityRatio };
}
async function detectManipulation(files: Document[]): Promise<FraudResult> {
const results = await Promise.all(files.map(async (file) => {
const metadata = await extractMetadata(file);
const modified = metadata.modifiedAt > metadata.createdAt - THRESHOLD_MS;
const software = FORBIDDEN_SOFTWARE.includes(metadata.software);
return { file: file.name, suspicious: modified || software };
}));
const suspicious = results.filter(r => r.suspicious);
return { score: suspicious.length / results.length, flagged: suspicious };
}
function evaluate(candidate: Candidate, criterion: Criterion): number {
switch (criterion.type) {
case 'INCOME_RATIO': return candidate.income / criterion.threshold;
case 'OCCUPANCY': return candidate.persons <= criterion.maxPersons ? 1 : 0;
case 'EMPLOYMENT': return STABLE_CONTRACTS.includes(candidate.contractType) ? 1 : 0.5;
case 'HISTORY': return candidate.rentalHistory?.complaints === 0 ? 1 : 0.3;
default: return 0;
}
}
export const FRAUD_THRESHOLD = 0.8;
export const THRESHOLD_MS = 600_000; // 10 minutes
export const STABLE_CONTRACTS = ['permanent', 'civil_servant', 'self_employed'];
export const FORBIDDEN_SOFTWARE = ['Preview', 'Adobe Acrobat', 'Foxit Editor'];
// Scoring engine — all weights must sum to 100
const DEFAULT_WEIGHTS = { income: 40, occupation: 25, history: 20, documents: 15 };
const validateWeights = (w: Record<string, number>) => Object.values(w).reduce((a,b) => a+b, 0) === 100;
import { createClient } from '@database/client';
import { analyzeDocument } from '@core/engine';
export async function handleRequest(request: Request) {
const { candidateId, propertyId } = await request.json();
const candidate = await db.candidates.findUnique({ where: { id: candidateId } });
if (!candidate) throw new Error('Candidate not found');
const documents = await storage.getDocuments(candidateId);
const fraudResult = await detectManipulation(documents);
if (fraudResult.score > FRAUD_THRESHOLD) {
await audit.log({ event: 'FRAUD_DETECTED', candidateId, score: fraudResult.score });
return { status: 'BLOCKED', reason: fraudResult.reason };
}
const income = parseFloat(candidate.declared_income);
const rent = await properties.getRent(propertyId);
const solvabilityRatio = income / rent;
const criteria = await config.getCriteria(propertyId);
const totalScore = criteria.reduce((acc: number, criterion: Criterion) => {
const value = evaluate(candidate, criterion);
return acc + (value * criterion.weight) / 100;
}, 0);
await results.save({ candidateId, propertyId, score: totalScore, ratio: solvabilityRatio });
return { status: 'APPROVED', score: totalScore, solvability: solvabilityRatio };
}
async function detectManipulation(files: Document[]): Promise<FraudResult> {
const results = await Promise.all(files.map(async (file) => {
const metadata = await extractMetadata(file);
const modified = metadata.modifiedAt > metadata.createdAt - THRESHOLD_MS;
const software = FORBIDDEN_SOFTWARE.includes(metadata.software);
return { file: file.name, suspicious: modified || software };
}));
const suspicious = results.filter(r => r.suspicious);
return { score: suspicious.length / results.length, flagged: suspicious };
}
function evaluate(candidate: Candidate, criterion: Criterion): number {
switch (criterion.type) {
case 'INCOME_RATIO': return candidate.income / criterion.threshold;
case 'OCCUPANCY': return candidate.persons <= criterion.maxPersons ? 1 : 0;
case 'EMPLOYMENT': return STABLE_CONTRACTS.includes(candidate.contractType) ? 1 : 0.5;
case 'HISTORY': return candidate.rentalHistory?.complaints === 0 ? 1 : 0.3;
default: return 0;
}
}
export const FRAUD_THRESHOLD = 0.8;
export const THRESHOLD_MS = 600_000; // 10 minutes
export const STABLE_CONTRACTS = ['permanent', 'civil_servant', 'self_employed'];
export const FORBIDDEN_SOFTWARE = ['Preview', 'Adobe Acrobat', 'Foxit Editor'];
// Scoring engine — all weights must sum to 100
const DEFAULT_WEIGHTS = { income: 40, occupation: 25, history: 20, documents: 15 };
const validateWeights = (w: Record<string, number>) => Object.values(w).reduce((a,b) => a+b, 0) === 100;
import { createClient } from '@database/client';
import { analyzeDocument } from '@core/engine';
export async function handleRequest(request: Request) {
const { candidateId, propertyId } = await request.json();
const candidate = await db.candidates.findUnique({ where: { id: candidateId } });
if (!candidate) throw new Error('Candidate not found');
const documents = await storage.getDocuments(candidateId);
const fraudResult = await detectManipulation(documents);
if (fraudResult.score > FRAUD_THRESHOLD) {
await audit.log({ event: 'FRAUD_DETECTED', candidateId, score: fraudResult.score });
return { status: 'BLOCKED', reason: fraudResult.reason };
}
const income = parseFloat(candidate.declared_income);
const rent = await properties.getRent(propertyId);
const solvabilityRatio = income / rent;
const criteria = await config.getCriteria(propertyId);
const totalScore = criteria.reduce((acc: number, criterion: Criterion) => {
const value = evaluate(candidate, criterion);
return acc + (value * criterion.weight) / 100;
}, 0);
await results.save({ candidateId, propertyId, score: totalScore, ratio: solvabilityRatio });
return { status: 'APPROVED', score: totalScore, solvability: solvabilityRatio };
}
async function detectManipulation(files: Document[]): Promise<FraudResult> {
const results = await Promise.all(files.map(async (file) => {
const metadata = await extractMetadata(file);
const modified = metadata.modifiedAt > metadata.createdAt - THRESHOLD_MS;
const software = FORBIDDEN_SOFTWARE.includes(metadata.software);
return { file: file.name, suspicious: modified || software };
}));
const suspicious = results.filter(r => r.suspicious);
return { score: suspicious.length / results.length, flagged: suspicious };
}
function evaluate(candidate: Candidate, criterion: Criterion): number {
switch (criterion.type) {
case 'INCOME_RATIO': return candidate.income / criterion.threshold;
case 'OCCUPANCY': return candidate.persons <= criterion.maxPersons ? 1 : 0;
case 'EMPLOYMENT': return STABLE_CONTRACTS.includes(candidate.contractType) ? 1 : 0.5;
case 'HISTORY': return candidate.rentalHistory?.complaints === 0 ? 1 : 0.3;
default: return 0;
}
}
export const FRAUD_THRESHOLD = 0.8;
export const THRESHOLD_MS = 600_000; // 10 minutes
export const STABLE_CONTRACTS = ['permanent', 'civil_servant', 'self_employed'];
export const FORBIDDEN_SOFTWARE = ['Preview', 'Adobe Acrobat', 'Foxit Editor'];
// Scoring engine — all weights must sum to 100
const DEFAULT_WEIGHTS = { income: 40, occupation: 25, history: 20, documents: 15 };
const validateWeights = (w: Record<string, number>) => Object.values(w).reduce((a,b) => a+b, 0) === 100;
import { createClient } from '@database/client';
import { analyzeDocument } from '@core/engine';
export async function handleRequest(request: Request) {
const { candidateId, propertyId } = await request.json();
const candidate = await db.candidates.findUnique({ where: { id: candidateId } });
if (!candidate) throw new Error('Candidate not found');
const documents = await storage.getDocuments(candidateId);
const fraudResult = await detectManipulation(documents);
if (fraudResult.score > FRAUD_THRESHOLD) {
await audit.log({ event: 'FRAUD_DETECTED', candidateId, score: fraudResult.score });
return { status: 'BLOCKED', reason: fraudResult.reason };
}
const income = parseFloat(candidate.declared_income);
const rent = await properties.getRent(propertyId);
const solvabilityRatio = income / rent;
const criteria = await config.getCriteria(propertyId);
const totalScore = criteria.reduce((acc: number, criterion: Criterion) => {
const value = evaluate(candidate, criterion);
return acc + (value * criterion.weight) / 100;
}, 0);
await results.save({ candidateId, propertyId, score: totalScore, ratio: solvabilityRatio });
return { status: 'APPROVED', score: totalScore, solvability: solvabilityRatio };
}
async function detectManipulation(files: Document[]): Promise<FraudResult> {
const results = await Promise.all(files.map(async (file) => {
const metadata = await extractMetadata(file);
const modified = metadata.modifiedAt > metadata.createdAt - THRESHOLD_MS;
const software = FORBIDDEN_SOFTWARE.includes(metadata.software);
return { file: file.name, suspicious: modified || software };
}));
const suspicious = results.filter(r => r.suspicious);
return { score: suspicious.length / results.length, flagged: suspicious };
}
function evaluate(candidate: Candidate, criterion: Criterion): number {
switch (criterion.type) {
case 'INCOME_RATIO': return candidate.income / criterion.threshold;
case 'OCCUPANCY': return candidate.persons <= criterion.maxPersons ? 1 : 0;
case 'EMPLOYMENT': return STABLE_CONTRACTS.includes(candidate.contractType) ? 1 : 0.5;
case 'HISTORY': return candidate.rentalHistory?.complaints === 0 ? 1 : 0.3;
default: return 0;
}
}
export const FRAUD_THRESHOLD = 0.8;
export const THRESHOLD_MS = 600_000; // 10 minutes
export const STABLE_CONTRACTS = ['permanent', 'civil_servant', 'self_employed'];
export const FORBIDDEN_SOFTWARE = ['Preview', 'Adobe Acrobat', 'Foxit Editor'];
// Scoring engine — all weights must sum to 100
const DEFAULT_WEIGHTS = { income: 40, occupation: 25, history: 20, documents: 15 };
const validateWeights = (w: Record<string, number>) => Object.values(w).reduce((a,b) => a+b, 0) === 100;
import { createClient } from '@database/client';
import { analyzeDocument } from '@core/engine';
export async function handleRequest(request: Request) {
const { candidateId, propertyId } = await request.json();
const candidate = await db.candidates.findUnique({ where: { id: candidateId } });
if (!candidate) throw new Error('Candidate not found');
const documents = await storage.getDocuments(candidateId);
const fraudResult = await detectManipulation(documents);
if (fraudResult.score > FRAUD_THRESHOLD) {
await audit.log({ event: 'FRAUD_DETECTED', candidateId, score: fraudResult.score });
return { status: 'BLOCKED', reason: fraudResult.reason };
}
const income = parseFloat(candidate.declared_income);
const rent = await properties.getRent(propertyId);
const solvabilityRatio = income / rent;
const criteria = await config.getCriteria(propertyId);
const totalScore = criteria.reduce((acc: number, criterion: Criterion) => {
const value = evaluate(candidate, criterion);
return acc + (value * criterion.weight) / 100;
}, 0);
await results.save({ candidateId, propertyId, score: totalScore, ratio: solvabilityRatio });
return { status: 'APPROVED', score: totalScore, solvability: solvabilityRatio };
}
async function detectManipulation(files: Document[]): Promise<FraudResult> {
const results = await Promise.all(files.map(async (file) => {
const metadata = await extractMetadata(file);
const modified = metadata.modifiedAt > metadata.createdAt - THRESHOLD_MS;
const software = FORBIDDEN_SOFTWARE.includes(metadata.software);
return { file: file.name, suspicious: modified || software };
}));
const suspicious = results.filter(r => r.suspicious);
return { score: suspicious.length / results.length, flagged: suspicious };
}
function evaluate(candidate: Candidate, criterion: Criterion): number {
switch (criterion.type) {
case 'INCOME_RATIO': return candidate.income / criterion.threshold;
case 'OCCUPANCY': return candidate.persons <= criterion.maxPersons ? 1 : 0;
case 'EMPLOYMENT': return STABLE_CONTRACTS.includes(candidate.contractType) ? 1 : 0.5;
case 'HISTORY': return candidate.rentalHistory?.complaints === 0 ? 1 : 0.3;
default: return 0;
}
}
export const FRAUD_THRESHOLD = 0.8;
export const THRESHOLD_MS = 600_000; // 10 minutes
export const STABLE_CONTRACTS = ['permanent', 'civil_servant', 'self_employed'];
export const FORBIDDEN_SOFTWARE = ['Preview', 'Adobe Acrobat', 'Foxit Editor'];
// Scoring engine — all weights must sum to 100
const DEFAULT_WEIGHTS = { income: 40, occupation: 25, history: 20, documents: 15 };
const validateWeights = (w: Record<string, number>) => Object.values(w).reduce((a,b) => a+b, 0) === 100;