Enhance proposal generation logic in FalukantService

- Implemented exclusion of existing proposals and active directors to avoid duplicate character selections.
- Added detailed logging for SQL queries and fallback mechanisms when insufficient older characters are found.
- Improved character selection process by combining excluded character IDs and ensuring a diverse set of proposals.
- Streamlined the batch creation of director proposals with average knowledge calculations for better income predictions.
This commit is contained in:
Torsten Schulz (local)
2026-01-12 08:39:41 +01:00
parent 36f0bd8eb9
commit 521dec24b2

View File

@@ -2462,12 +2462,82 @@ class FalukantService extends BaseService {
try {
const threeWeeksAgo = new Date(Date.now() - 21 * 24 * 60 * 60 * 1000);
const proposalCount = Math.floor(Math.random() * 3) + 3;
for (let i = 0; i < proposalCount; i++) {
const directorCharacter = await FalukantCharacter.findOne({
where: {
regionId,
createdAt: { [Op.lt]: threeWeeksAgo },
// Hole bereits existierende Proposals, um diese Charaktere auszuschließen
const existingProposals = await DirectorProposal.findAll({
where: { employerUserId: falukantUserId },
attributes: ['directorCharacterId'],
raw: true
});
const proposalCharacterIds = existingProposals.map(p => p.directorCharacterId);
// Hole alle Charaktere, die bereits als Direktor arbeiten (egal für welchen User)
const existingDirectors = await Director.findAll({
attributes: ['directorCharacterId'],
raw: true
});
const directorCharacterIds = existingDirectors.map(d => d.directorCharacterId);
// Kombiniere beide Listen
const excludedCharacterIds = [...new Set([...proposalCharacterIds, ...directorCharacterIds])];
console.log(`[generateProposals] Excluding ${excludedCharacterIds.length} characters (${proposalCharacterIds.length} proposals + ${directorCharacterIds.length} active directors)`);
console.log(`[generateProposals] Region ID: ${regionId}, Proposal count needed: ${proposalCount}`);
// Versuche zuerst Charaktere, die mindestens 3 Wochen alt sind
let whereClause = {
regionId,
userId: null, // Nur NPCs
};
if (excludedCharacterIds.length > 0) {
whereClause.id = { [Op.notIn]: excludedCharacterIds };
}
whereClause.createdAt = { [Op.lt]: threeWeeksAgo };
// Erstelle Query-Objekt für Logging
const queryOptions = {
where: whereClause,
include: [
{
model: TitleOfNobility,
as: 'nobleTitle',
attributes: ['level'],
},
],
order: sequelize.literal('RANDOM()'),
limit: proposalCount,
};
// Logge die SQL-Query
try {
const query = FalukantCharacter.findAll(queryOptions);
const sqlQuery = query.toSQL ? query.toSQL() : query;
console.log(`[generateProposals] SQL Query (older than 3 weeks):`, JSON.stringify(sqlQuery, null, 2));
} catch (e) {
// Fallback: Logge die Query-Optionen direkt
console.log(`[generateProposals] Query Options (older than 3 weeks):`, JSON.stringify(queryOptions, null, 2));
}
console.log(`[generateProposals] WHERE clause:`, JSON.stringify(whereClause, null, 2));
console.log(`[generateProposals] Excluded character IDs:`, excludedCharacterIds);
let directorCharacters = await FalukantCharacter.findAll(queryOptions);
// Fallback: Wenn nicht genug ältere Charaktere gefunden werden, verwende auch neuere
if (directorCharacters.length < proposalCount) {
console.log(`[generateProposals] Only found ${directorCharacters.length} characters older than 3 weeks, trying all NPCs...`);
const fallbackWhereClause = {
regionId,
userId: null, // Nur NPCs
};
if (excludedCharacterIds.length > 0) {
fallbackWhereClause.id = { [Op.notIn]: excludedCharacterIds };
}
const fallbackQueryOptions = {
where: fallbackWhereClause,
include: [
{
model: TitleOfNobility,
@@ -2476,22 +2546,81 @@ class FalukantService extends BaseService {
},
],
order: sequelize.literal('RANDOM()'),
});
if (!directorCharacter) {
throw new Error('No directors available for the region');
limit: proposalCount,
};
// Logge die Fallback-SQL-Query
try {
const fallbackQuery = FalukantCharacter.findAll(fallbackQueryOptions);
const fallbackSqlQuery = fallbackQuery.toSQL ? fallbackQuery.toSQL() : fallbackQuery;
console.log(`[generateProposals] SQL Query (all NPCs):`, JSON.stringify(fallbackSqlQuery, null, 2));
} catch (e) {
console.log(`[generateProposals] Fallback Query Options:`, JSON.stringify(fallbackQueryOptions, null, 2));
}
const avgKnowledge = await this.calculateAverageKnowledge(directorCharacter.id);
console.log(`[generateProposals] Fallback WHERE clause:`, JSON.stringify(fallbackWhereClause, null, 2));
const fallbackCharacters = await FalukantCharacter.findAll(fallbackQueryOptions);
// Kombiniere beide Listen und entferne Duplikate
const allCharacterIds = new Set(directorCharacters.map(c => c.id));
fallbackCharacters.forEach(c => {
if (!allCharacterIds.has(c.id)) {
directorCharacters.push(c);
allCharacterIds.add(c.id);
}
});
// Limitiere auf proposalCount
directorCharacters = directorCharacters.slice(0, proposalCount);
}
if (directorCharacters.length === 0) {
console.error(`[generateProposals] No NPCs found in region ${regionId} at all`);
throw new Error('No directors available for the region');
}
console.log(`[generateProposals] Found ${directorCharacters.length} available NPCs`);
// Batch-Berechnung der Knowledge-Werte
const characterIds = directorCharacters.map(c => c.id);
const allKnowledges = await Knowledge.findAll({
where: { characterId: { [Op.in]: characterIds } },
attributes: ['characterId', 'knowledge'],
raw: true
});
// Gruppiere Knowledge nach characterId und berechne Durchschnitt
const knowledgeMap = new Map();
characterIds.forEach(id => knowledgeMap.set(id, []));
allKnowledges.forEach(k => {
const list = knowledgeMap.get(k.characterId) || [];
list.push(k.knowledge);
knowledgeMap.set(k.characterId, list);
});
// Erstelle alle Proposals in einem Batch
const proposalsToCreate = directorCharacters.map(character => {
const knowledges = knowledgeMap.get(character.id) || [];
const avgKnowledge = knowledges.length > 0
? knowledges.reduce((sum, k) => sum + k, 0) / knowledges.length
: 0;
const proposedIncome = Math.round(
directorCharacter.nobleTitle.level * Math.pow(1.231, avgKnowledge / 1.5)
character.nobleTitle.level * Math.pow(1.231, avgKnowledge / 1.5)
);
await DirectorProposal.create({
directorCharacterId: directorCharacter.id,
return {
directorCharacterId: character.id,
employerUserId: falukantUserId,
proposedIncome,
});
}
};
});
await DirectorProposal.bulkCreate(proposalsToCreate);
console.log(`[generateProposals] Created ${proposalsToCreate.length} director proposals for region ${regionId}`);
} catch (error) {
console.log(error.message, error.stack);
console.error('[generateProposals] Error:', error.message, error.stack);
throw new Error(error.message);
}
}