supa-seed
Version:
A constraint-aware, framework-agnostic database seeding framework with deep PostgreSQL business logic discovery and MakerKit integration support
419 lines • 18.3 kB
JavaScript
;
/**
* Distribution Algorithms for Asset Association Intelligence
* Phase 3, Checkpoint C1 - Smart asset-to-entity distribution
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.DistributionEngine = void 0;
const logger_1 = require("../core/utils/logger");
class DistributionEngine {
/**
* Distribute assets to targets using the specified algorithm
*/
static distribute(assets, targets, config) {
const startTime = Date.now();
logger_1.Logger.info(`🎯 Distributing ${assets.length} assets to ${targets.length} targets using ${config.algorithm}`);
// Validate inputs
if (assets.length === 0) {
return this.createEmptyResult(targets, config, startTime);
}
if (targets.length === 0) {
return this.createResultWithUnassigned(assets, config, startTime);
}
// Apply distribution algorithm
let result;
switch (config.algorithm) {
case 'weighted_random':
result = this.weightedRandomDistribution(assets, targets, config);
break;
case 'round_robin':
result = this.roundRobinDistribution(assets, targets, config);
break;
case 'even_spread':
result = this.evenSpreadDistribution(assets, targets, config);
break;
case 'custom':
if (!config.customAlgorithm) {
throw new Error('Custom algorithm function is required when using custom algorithm');
}
result = config.customAlgorithm(assets, targets);
break;
default:
throw new Error(`Unknown distribution algorithm: ${config.algorithm}`);
}
// Apply constraint validation if enabled
if (config.respectConstraints) {
result = this.enforceConstraints(result, config);
}
// Update metadata
const executionTime = Date.now() - startTime;
result.metadata.executionTime = executionTime;
result.successRate = this.calculateSuccessRate(result);
logger_1.Logger.info(`✅ Distribution complete: ${result.successRate.toFixed(1)}% success rate (${executionTime}ms)`);
return result;
}
/**
* Weighted random distribution - assigns assets based on target weights
*/
static weightedRandomDistribution(assets, targets, config) {
const seed = config.seed || this.DEFAULT_SEED;
const rng = this.createSeededRandom(seed);
// Calculate weights and cumulative distribution
const weights = targets.map(target => target.weight || 1);
const totalWeight = weights.reduce((sum, weight) => sum + weight, 0);
const cumulativeWeights = weights.reduce((acc, weight, index) => {
acc.push((acc[index - 1] || 0) + weight);
return acc;
}, []);
// Initialize assignments
const assignments = targets.map(target => ({
target,
assets: [],
fulfilled: false,
constraintViolations: [],
assignmentReason: 'weighted_random'
}));
// Distribute assets
for (const asset of assets) {
const random = rng() * totalWeight;
const targetIndex = cumulativeWeights.findIndex(cw => cw >= random);
if (targetIndex !== -1) {
assignments[targetIndex].assets.push(asset);
}
}
// Mark all assignments as fulfilled by default (no constraints applied yet)
assignments.forEach(assignment => {
assignment.fulfilled = true;
});
return {
assignments,
unassigned: [],
algorithm: 'weighted_random',
seed,
totalAssets: assets.length,
totalTargets: targets.length,
successRate: 0, // Will be calculated later
metadata: {
executionTime: 0, // Will be set by caller
retries: 0,
constraintViolations: 0,
averageAssignmentsPerTarget: assets.length / targets.length,
distributionEfficiency: 100 // All assets assigned in weighted random
}
};
}
/**
* Round robin distribution - assigns assets in sequential order to targets
*/
static roundRobinDistribution(assets, targets, config) {
// Initialize assignments
const assignments = targets.map(target => ({
target,
assets: [],
fulfilled: false,
constraintViolations: [],
assignmentReason: 'round_robin'
}));
// Distribute assets in round-robin fashion
let targetIndex = 0;
for (const asset of assets) {
assignments[targetIndex].assets.push(asset);
targetIndex = (targetIndex + 1) % targets.length;
}
// Mark all assignments as fulfilled by default (no constraints applied yet)
assignments.forEach(assignment => {
assignment.fulfilled = true;
});
return {
assignments,
unassigned: [],
algorithm: 'round_robin',
seed: config.seed,
totalAssets: assets.length,
totalTargets: targets.length,
successRate: 0, // Will be calculated later
metadata: {
executionTime: 0, // Will be set by caller
retries: 0,
constraintViolations: 0,
averageAssignmentsPerTarget: assets.length / targets.length,
distributionEfficiency: 100 // All assets assigned in round robin
}
};
}
/**
* Even spread distribution - tries to assign equal number of assets to each target
*/
static evenSpreadDistribution(assets, targets, config) {
const seed = config.seed || this.DEFAULT_SEED;
const rng = this.createSeededRandom(seed);
// Calculate base assignment count and remainder
const baseCount = Math.floor(assets.length / targets.length);
const remainder = assets.length % targets.length;
// Initialize assignments
const assignments = targets.map((target, index) => ({
target,
assets: [],
fulfilled: false,
constraintViolations: [],
assignmentReason: 'even_spread'
}));
// Shuffle assets for fair distribution
const shuffledAssets = [...assets];
for (let i = shuffledAssets.length - 1; i > 0; i--) {
const j = Math.floor(rng() * (i + 1));
[shuffledAssets[i], shuffledAssets[j]] = [shuffledAssets[j], shuffledAssets[i]];
}
// Distribute assets evenly
let assetIndex = 0;
for (let targetIndex = 0; targetIndex < targets.length; targetIndex++) {
const assignmentCount = baseCount + (targetIndex < remainder ? 1 : 0);
for (let i = 0; i < assignmentCount && assetIndex < shuffledAssets.length; i++) {
assignments[targetIndex].assets.push(shuffledAssets[assetIndex]);
assetIndex++;
}
}
// Mark all assignments as fulfilled by default (no constraints applied yet)
assignments.forEach(assignment => {
assignment.fulfilled = true;
});
return {
assignments,
unassigned: [],
algorithm: 'even_spread',
seed,
totalAssets: assets.length,
totalTargets: targets.length,
successRate: 0, // Will be calculated later
metadata: {
executionTime: 0, // Will be set by caller
retries: 0,
constraintViolations: 0,
averageAssignmentsPerTarget: assets.length / targets.length,
distributionEfficiency: 100 // All assets assigned in even spread
}
};
}
/**
* Enforce constraints on distribution result
*/
static enforceConstraints(result, config) {
const maxRetries = config.maxRetries || 3;
let retries = 0;
let constraintViolations = 0;
const unassigned = [];
for (const assignment of result.assignments) {
const { target, assets } = assignment;
const constraints = target.constraints;
if (!constraints) {
assignment.fulfilled = true;
continue;
}
// Check and enforce constraints
let validAssets = assets;
let violations = [];
// Filter by required types
if (constraints.requiredTypes && constraints.requiredTypes.length > 0) {
const beforeCount = validAssets.length;
validAssets = validAssets.filter(asset => constraints.requiredTypes.includes(asset.type));
if (validAssets.length < beforeCount) {
violations.push(`Filtered ${beforeCount - validAssets.length} assets not matching required types`);
}
}
// Filter by excluded types
if (constraints.excludedTypes && constraints.excludedTypes.length > 0) {
const beforeCount = validAssets.length;
validAssets = validAssets.filter(asset => !constraints.excludedTypes.includes(asset.type));
if (validAssets.length < beforeCount) {
violations.push(`Filtered ${beforeCount - validAssets.length} assets matching excluded types`);
}
}
// Filter by required tags
if (constraints.requiredTags && constraints.requiredTags.length > 0) {
const beforeCount = validAssets.length;
validAssets = validAssets.filter(asset => {
const assetTags = asset.metadata.tags || [];
return constraints.requiredTags.every(tag => assetTags.includes(tag));
});
if (validAssets.length < beforeCount) {
violations.push(`Filtered ${beforeCount - validAssets.length} assets not having required tags`);
}
}
// Filter by excluded tags
if (constraints.excludedTags && constraints.excludedTags.length > 0) {
const beforeCount = validAssets.length;
validAssets = validAssets.filter(asset => {
const assetTags = asset.metadata.tags || [];
return !constraints.excludedTags.some(tag => assetTags.includes(tag));
});
if (validAssets.length < beforeCount) {
violations.push(`Filtered ${beforeCount - validAssets.length} assets having excluded tags`);
}
}
// Apply custom filter
if (constraints.customFilter) {
const beforeCount = validAssets.length;
validAssets = validAssets.filter(constraints.customFilter);
if (validAssets.length < beforeCount) {
violations.push(`Filtered ${beforeCount - validAssets.length} assets by custom filter`);
}
}
// Check minimum items constraint
if (constraints.minItems && validAssets.length < constraints.minItems) {
violations.push(`Has ${validAssets.length} assets but requires minimum ${constraints.minItems}`);
if (!config.allowPartialFulfillment) {
// Move all assets to unassigned if we can't meet minimum
unassigned.push(...validAssets);
validAssets = [];
}
}
// Enforce maximum items constraint
if (constraints.maxItems && validAssets.length > constraints.maxItems) {
const excess = validAssets.splice(constraints.maxItems);
unassigned.push(...excess);
violations.push(`Trimmed ${excess.length} assets to respect maximum ${constraints.maxItems}`);
}
// Update assignment
assignment.assets = validAssets;
assignment.constraintViolations = violations;
assignment.fulfilled = violations.length === 0 &&
(!constraints.minItems || validAssets.length >= constraints.minItems);
if (violations.length > 0) {
constraintViolations++;
}
}
// Update result metadata
result.unassigned.push(...unassigned);
result.metadata.retries = retries;
result.metadata.constraintViolations = constraintViolations;
result.metadata.distributionEfficiency =
((result.totalAssets - result.unassigned.length) / result.totalAssets) * 100;
return result;
}
/**
* Calculate success rate of distribution
*/
static calculateSuccessRate(result) {
if (result.assignments.length === 0)
return 0;
const fulfilledAssignments = result.assignments.filter(a => a.fulfilled).length;
return (fulfilledAssignments / result.assignments.length) * 100;
}
/**
* Create seeded random number generator
*/
static createSeededRandom(seed) {
let hash = 0;
for (let i = 0; i < seed.length; i++) {
const char = seed.charCodeAt(i);
hash = ((hash << 5) - hash) + char;
hash = hash & hash; // Convert to 32-bit integer
}
// Use the hash as seed for a simple PRNG (Mulberry32)
let state = Math.abs(hash);
return function () {
state |= 0;
state = state + 0x6D2B79F5 | 0;
let t = Math.imul(state ^ state >>> 15, state | 1);
t ^= t + Math.imul(t ^ t >>> 7, t | 61);
return ((t ^ t >>> 14) >>> 0) / 4294967296;
};
}
/**
* Create empty result when no assets provided
*/
static createEmptyResult(targets, config, startTime) {
return {
assignments: targets.map(target => ({
target,
assets: [],
fulfilled: false,
constraintViolations: ['No assets to distribute'],
assignmentReason: 'empty_input'
})),
unassigned: [],
algorithm: config.algorithm,
seed: config.seed,
totalAssets: 0,
totalTargets: targets.length,
successRate: 0,
metadata: {
executionTime: Date.now() - startTime,
retries: 0,
constraintViolations: targets.length,
averageAssignmentsPerTarget: 0,
distributionEfficiency: 0
}
};
}
/**
* Create result with all assets unassigned when no targets provided
*/
static createResultWithUnassigned(assets, config, startTime) {
return {
assignments: [],
unassigned: assets,
algorithm: config.algorithm,
seed: config.seed,
totalAssets: assets.length,
totalTargets: 0,
successRate: 0,
metadata: {
executionTime: Date.now() - startTime,
retries: 0,
constraintViolations: 0,
averageAssignmentsPerTarget: 0,
distributionEfficiency: 0
}
};
}
/**
* Analyze distribution result and provide insights
*/
static analyzeDistribution(result) {
const totalAssigned = result.assignments.reduce((sum, a) => sum + a.assets.length, 0);
const fulfilledTargets = result.assignments.filter(a => a.fulfilled).length;
const targetsWithViolations = result.assignments.filter(a => a.constraintViolations.length > 0).length;
const statistics = {
totalAssigned,
totalUnassigned: result.unassigned.length,
avgAssetsPerTarget: result.totalTargets > 0 ? totalAssigned / result.totalTargets : 0,
fulfilmentRate: result.successRate,
constraintViolationRate: result.totalTargets > 0 ? (targetsWithViolations / result.totalTargets) * 100 : 0
};
const insights = [];
const recommendations = [];
// Generate insights
insights.push(`Used ${result.algorithm} algorithm to distribute ${result.totalAssets} assets to ${result.totalTargets} targets`);
insights.push(`${fulfilledTargets}/${result.totalTargets} targets were successfully fulfilled`);
if (result.unassigned.length > 0) {
insights.push(`${result.unassigned.length} assets could not be assigned to any target`);
}
if (result.metadata.constraintViolations > 0) {
insights.push(`${result.metadata.constraintViolations} targets had constraint violations`);
}
// Generate recommendations
if (statistics.fulfilmentRate < 80) {
recommendations.push('Consider relaxing constraints or using a different distribution algorithm');
}
if (statistics.constraintViolationRate > 20) {
recommendations.push('Review target constraints - many targets have violations');
}
if (result.unassigned.length > result.totalAssets * 0.1) {
recommendations.push('High number of unassigned assets - consider adding more targets or relaxing constraints');
}
if (result.algorithm === 'weighted_random' && statistics.avgAssetsPerTarget < 1) {
recommendations.push('Consider using even_spread algorithm for better asset distribution');
}
const summary = `Distribution completed with ${statistics.fulfilmentRate.toFixed(1)}% success rate using ${result.algorithm} algorithm`;
return {
summary,
insights,
recommendations,
statistics
};
}
}
exports.DistributionEngine = DistributionEngine;
DistributionEngine.DEFAULT_SEED = 'supa-seed-distribution';
//# sourceMappingURL=distribution-algorithms.js.map