Refactor proposal generation in FalukantService to improve character selection logic

- Removed the tracking of used character IDs and streamlined the exclusion of characters already proposed or currently active as directors.
- Enhanced logging for SQL queries and fallback mechanisms to ensure better visibility during character selection.
- Implemented a more efficient approach to gather and process character knowledge for proposal creation, ensuring accurate average calculations.
- Improved error handling to provide clearer feedback when no eligible characters are found.
This commit is contained in:
Torsten Schulz (local)
2026-01-12 08:33:26 +01:00
parent 92d6b15c3f
commit d74f7b852b

View File

@@ -2462,8 +2462,6 @@ class FalukantService extends BaseService {
try {
const threeWeeksAgo = new Date(Date.now() - 21 * 24 * 60 * 60 * 1000);
const proposalCount = Math.floor(Math.random() * 3) + 3;
let createdProposals = 0;
const usedCharacterIds = new Set(); // Verhindere doppelte Proposals
// Hole bereits existierende Proposals, um diese Charaktere auszuschließen
const existingProposals = await DirectorProposal.findAll({
@@ -2471,18 +2469,34 @@ class FalukantService extends BaseService {
attributes: ['directorCharacterId'],
raw: true
});
existingProposals.forEach(p => usedCharacterIds.add(p.directorCharacterId));
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}`);
for (let i = 0; i < proposalCount; i++) {
// Versuche zuerst Charaktere, die mindestens 3 Wochen alt sind
let whereClause = {
regionId,
userId: null, // Nur NPCs
id: { [Op.notIn]: Array.from(usedCharacterIds) }, // Keine bereits verwendeten
createdAt: { [Op.lt]: threeWeeksAgo },
};
let directorCharacter = await FalukantCharacter.findOne({
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: [
{
@@ -2492,18 +2506,38 @@ class FalukantService extends BaseService {
},
],
order: sequelize.literal('RANDOM()'),
});
limit: proposalCount,
};
// Fallback: Wenn keine älteren Charaktere gefunden werden, verwende auch neuere
if (!directorCharacter) {
console.log(`[generateProposals] No characters older than 3 weeks found in region ${regionId}, trying all NPCs...`);
whereClause = {
// 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
id: { [Op.notIn]: Array.from(usedCharacterIds) },
};
directorCharacter = await FalukantCharacter.findOne({
where: whereClause,
if (excludedCharacterIds.length > 0) {
fallbackWhereClause.id = { [Op.notIn]: excludedCharacterIds };
}
const fallbackQueryOptions = {
where: fallbackWhereClause,
include: [
{
model: TitleOfNobility,
@@ -2512,35 +2546,79 @@ class FalukantService extends BaseService {
},
],
order: sequelize.literal('RANDOM()'),
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));
}
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 (!directorCharacter) {
console.warn(`[generateProposals] No more available NPCs in region ${regionId} (${usedCharacterIds.size} already used)`);
// Wenn keine Charaktere gefunden werden, breche die Schleife ab
// aber wirf keinen Fehler, wenn bereits einige Proposals erstellt wurden
if (createdProposals === 0) {
if (directorCharacters.length === 0) {
console.error(`[generateProposals] No NPCs found in region ${regionId} at all`);
throw new Error('No directors available for the region');
}
break; // Stoppe die Schleife, aber wirf keinen Fehler
}
// Füge diesen Charakter zu den verwendeten hinzu
usedCharacterIds.add(directorCharacter.id);
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 avgKnowledge = await this.calculateAverageKnowledge(directorCharacter.id);
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,
};
});
createdProposals++;
}
console.log(`[generateProposals] Created ${createdProposals} director proposals for region ${regionId}`);
await DirectorProposal.bulkCreate(proposalsToCreate);
console.log(`[generateProposals] Created ${proposalsToCreate.length} director proposals for region ${regionId}`);
} catch (error) {
console.error('[generateProposals] Error:', error.message, error.stack);
throw new Error(error.message);