Enhance getFamily method in FalukantService for performance and data retrieval

- Implemented detailed timing metrics to track performance across various steps of the getFamily method.
- Refactored data retrieval to load relationships and children in parallel, improving efficiency.
- Enhanced mapping of relationship and child data, including additional attributes for characters.
- Introduced batch loading for related data to minimize database queries and optimize performance.
- Improved error handling and logging for better traceability during execution.
This commit is contained in:
Torsten Schulz (local)
2026-01-12 11:09:21 +01:00
parent 5e26422e9c
commit f487e6d765

View File

@@ -2880,115 +2880,210 @@ class FalukantService extends BaseService {
}
async getFamily(hashedUserId) {
const user = await this.getFalukantUserByHashedId(hashedUserId);
if (!user) throw new Error('User not found');
const character = await FalukantCharacter.findOne({ where: { userId: user.id } });
if (!character) throw new Error('Character not found for this user');
let relationships = await Relationship.findAll({
where: { character1Id: character.id },
attributes: ['createdAt', 'widowFirstName2', 'nextStepProgress'],
include: [
{
model: FalukantCharacter, as: 'character2',
attributes: ['id', 'birthdate', 'gender', 'moodId'],
const startTime = Date.now();
const timings = {};
try {
// 1. User und Character laden
const step1Start = Date.now();
const user = await this.getFalukantUserByHashedId(hashedUserId);
if (!user) throw new Error('User not found');
const character = await FalukantCharacter.findOne({ where: { userId: user.id } });
if (!character) throw new Error('Character not found for this user');
timings.step1_user_character = Date.now() - step1Start;
// 2. Relationships und Children parallel laden
const step2Start = Date.now();
const [relationshipsRaw, charsWithChildren] = await Promise.all([
Relationship.findAll({
where: { character1Id: character.id },
attributes: ['createdAt', 'widowFirstName2', 'nextStepProgress', 'relationshipTypeId'],
include: [
{ model: FalukantPredefineFirstname, as: 'definedFirstName', attributes: ['name'] },
{ model: TitleOfNobility, as: 'nobleTitle', attributes: ['labelTr'] },
{ model: CharacterTrait, as: 'traits' },
{ model: Mood, as: 'mood' },
{
model: FalukantCharacter, as: 'character2',
attributes: ['id', 'birthdate', 'gender', 'moodId', 'firstName', 'lastName', 'titleOfNobility'],
required: false
},
{ model: RelationshipType, as: 'relationshipType', attributes: ['tr'], required: false }
]
},
{ model: RelationshipType, as: 'relationshipType', attributes: ['tr'] }
]
});
relationships = relationships.map(r => ({
createdAt: r.createdAt,
widowFirstName2: r.widowFirstName2,
progress: r.nextStepProgress,
character2: {
id: r.character2.id,
age: calcAge(r.character2.birthdate),
gender: r.character2.gender,
firstName: r.character2.definedFirstName?.name || 'Unknown',
nobleTitle: r.character2.nobleTitle?.labelTr || '',
mood: r.character2.mood,
traits: r.character2.traits
},
relationshipType: r.relationshipType.tr
}));
const charsWithChildren = await FalukantCharacter.findAll({
where: { userId: user.id },
include: [
{
model: ChildRelation,
as: 'childrenFather',
include: [{
model: FalukantCharacter,
as: 'child',
include: [{ model: FalukantPredefineFirstname, as: 'definedFirstName', attributes: ['name'] }]
}]
},
{
model: ChildRelation,
as: 'childrenMother',
include: [{
model: FalukantCharacter,
as: 'child',
include: [{ model: FalukantPredefineFirstname, as: 'definedFirstName', attributes: ['name'] }]
}]
}),
FalukantCharacter.findAll({
where: { userId: user.id },
attributes: ['id'],
include: [
{
model: ChildRelation,
as: 'childrenFather',
attributes: ['nameSet', 'isHeir', 'createdAt', 'childCharacterId'],
include: [{
model: FalukantCharacter,
as: 'child',
attributes: ['id', 'birthdate', 'gender', 'firstName'],
required: false
}],
required: false
},
{
model: ChildRelation,
as: 'childrenMother',
attributes: ['nameSet', 'isHeir', 'createdAt', 'childCharacterId'],
include: [{
model: FalukantCharacter,
as: 'child',
attributes: ['id', 'birthdate', 'gender', 'firstName'],
required: false
}],
required: false
}
]
})
]);
timings.step2_relationships_children = Date.now() - step2Start;
// 3. Batch-Loading für Relationship-Character-Daten
const step3Start = Date.now();
const relationshipCharacters = relationshipsRaw
.filter(r => r.character2)
.map(r => r.character2);
const relationshipCharacterIds = relationshipCharacters.map(c => c.id);
const childCharacters = charsWithChildren
.flatMap(c => [
...(c.childrenFather || []).map(r => r.child).filter(Boolean),
...(c.childrenMother || []).map(r => r.child).filter(Boolean)
]);
const childCharacterIds = childCharacters.map(c => c.id);
// Sammle alle benötigten IDs
const relationshipFirstNameIds = [...new Set(relationshipCharacters.map(c => c.firstName).filter(Boolean))];
const relationshipLastNameIds = [...new Set(relationshipCharacters.map(c => c.lastName).filter(Boolean))];
const relationshipTitleIds = [...new Set(relationshipCharacters.map(c => c.titleOfNobility).filter(Boolean))];
const relationshipMoodIds = [...new Set(relationshipCharacters.map(c => c.moodId).filter(Boolean))];
const childFirstNameIds = [...new Set(childCharacters.map(c => c.firstName).filter(Boolean))];
const allFirstNameIds = [...new Set([...relationshipFirstNameIds, ...childFirstNameIds])];
// Batch-Load alle benötigten Daten parallel
const [firstNames, lastNames, titles, traits, moods] = await Promise.all([
allFirstNameIds.length > 0 ? FalukantPredefineFirstname.findAll({
where: { id: { [Op.in]: allFirstNameIds } },
attributes: ['id', 'name']
}) : [],
relationshipLastNameIds.length > 0 ? FalukantPredefineLastname.findAll({
where: { id: { [Op.in]: relationshipLastNameIds } },
attributes: ['id', 'name']
}) : [],
relationshipTitleIds.length > 0 ? TitleOfNobility.findAll({
where: { id: { [Op.in]: relationshipTitleIds } },
attributes: ['id', 'labelTr']
}) : [],
relationshipCharacterIds.length > 0 ? CharacterTrait.findAll({
where: { characterId: { [Op.in]: relationshipCharacterIds } },
attributes: ['characterId', 'traitId']
}) : [],
relationshipMoodIds.length > 0 ? Mood.findAll({
where: { id: { [Op.in]: relationshipMoodIds } },
attributes: ['id', 'labelTr']
}) : []
]);
// Erstelle Maps für schnellen Zugriff
const firstNameMap = new Map([...firstNames, ...childFirstNames].map(fn => [fn.id, fn.name]));
const lastNameMap = new Map(lastNames.map(ln => [ln.id, ln.name]));
const titleMap = new Map(titles.map(t => [t.id, t.labelTr]));
const moodMap = new Map(moods.map(m => [m.id, m.labelTr]));
const traitsMap = new Map();
traits.forEach(t => {
if (!traitsMap.has(t.characterId)) {
traitsMap.set(t.characterId, []);
}
traitsMap.get(t.characterId).push(t.traitId);
});
timings.step3_batch_loading = Date.now() - step3Start;
// 4. Relationships mappen
const step4Start = Date.now();
const relationships = relationshipsRaw.map(r => {
const char2 = r.character2;
return {
createdAt: r.createdAt,
widowFirstName2: r.widowFirstName2,
progress: r.nextStepProgress,
character2: char2 ? {
id: char2.id,
age: calcAge(char2.birthdate),
gender: char2.gender,
firstName: firstNameMap.get(char2.firstName) || 'Unknown',
nobleTitle: titleMap.get(char2.titleOfNobility) || '',
mood: moodMap.get(char2.moodId) || null,
traits: traitsMap.get(char2.id) || []
} : null,
relationshipType: r.relationshipType?.tr || ''
};
}).filter(r => r.character2 !== null);
timings.step4_map_relationships = Date.now() - step4Start;
// 5. Children mappen
const step5Start = Date.now();
const children = [];
for (const parentChar of charsWithChildren) {
const allRels = [
...(parentChar.childrenFather || []),
...(parentChar.childrenMother || [])
];
for (const rel of allRels) {
const kid = rel.child;
if (!kid) continue;
children.push({
childCharacterId: kid.id,
name: firstNameMap.get(kid.firstName) || 'Unknown',
gender: kid.gender,
age: calcAge(kid.birthdate),
hasName: rel.nameSet,
isHeir: rel.isHeir || false,
_createdAt: rel.createdAt,
});
}
]
});
const children = [];
for (const parentChar of charsWithChildren) {
const allRels = [
...(parentChar.childrenFather || []),
...(parentChar.childrenMother || [])
];
for (const rel of allRels) {
const kid = rel.child;
children.push({
childCharacterId: kid.id,
name: kid.definedFirstName?.name || 'Unknown',
gender: kid.gender,
age: calcAge(kid.birthdate),
hasName: rel.nameSet,
isHeir: rel.isHeir || false,
_createdAt: rel.createdAt,
});
}
}
// Sort children globally by relation createdAt ascending (older first)
children.sort((a, b) => new Date(a._createdAt) - new Date(b._createdAt));
const inProgress = ['wooing', 'engaged', 'married'];
const family = {
relationships: relationships.filter(r => inProgress.includes(r.relationshipType)),
lovers: relationships.filter(r => r.relationshipType === 'lover'),
deathPartners: relationships.filter(r => r.relationshipType === 'widowed'),
children: children.map(({ _createdAt, ...rest }) => rest),
possiblePartners: []
};
const ownAge = calcAge(character.birthdate);
console.log(`[getFamily] Character age: ${ownAge}, relationships: ${family.relationships.length}`);
if (ownAge >= 12 && family.relationships.length === 0) {
family.possiblePartners = await this.getPossiblePartners(character.id);
console.log(`[getFamily] Found ${family.possiblePartners.length} existing proposals`);
if (family.possiblePartners.length === 0) {
console.log(`[getFamily] Creating new possible partners...`);
await this.createPossiblePartners(
character.id,
character.gender,
character.regionId,
character.titleOfNobility,
ownAge
);
children.sort((a, b) => new Date(a._createdAt) - new Date(b._createdAt));
timings.step5_map_children = Date.now() - step5Start;
// 6. Family-Objekt erstellen
const step6Start = Date.now();
const inProgress = ['wooing', 'engaged', 'married'];
const family = {
relationships: relationships.filter(r => inProgress.includes(r.relationshipType)),
lovers: relationships.filter(r => r.relationshipType === 'lover'),
deathPartners: relationships.filter(r => r.relationshipType === 'widowed'),
children: children.map(({ _createdAt, ...rest }) => rest),
possiblePartners: []
};
timings.step6_create_family = Date.now() - step6Start;
// 7. Possible Partners (nur wenn nötig, asynchron)
const step7Start = Date.now();
const ownAge = calcAge(character.birthdate);
if (ownAge >= 12 && family.relationships.length === 0) {
family.possiblePartners = await this.getPossiblePartners(character.id);
console.log(`[getFamily] After creation: ${family.possiblePartners.length} proposals`);
if (family.possiblePartners.length === 0) {
// Asynchron erstellen, nicht blockieren
this.createPossiblePartners(
character.id,
character.gender,
character.regionId,
character.titleOfNobility,
ownAge
).catch(err => console.error('[getFamily] Error creating partners (async):', err));
}
}
} else {
console.log(`[getFamily] Skipping partner search: age=${ownAge}, relationships=${family.relationships.length}`);
timings.step7_possible_partners = Date.now() - step7Start;
const totalTime = Date.now() - startTime;
console.log(`[getFamily] Performance: ${totalTime}ms total`, timings);
return family;
} catch (error) {
console.error('[getFamily] Error:', error);
throw error;
}
return family;
}
async setHeir(hashedUserId, childCharacterId) {