AI Agent ile Otonom Mülk Yönetimi
Geleneksel gayrimenkul yönetimi, el emeğinin yoğun olduğu bir sektördür: yönetici ortalama 200-300 birimi neredeyse tamamen manuel süreçlerle (kiracı iletişimi, bakım siparişleri, kira tahsilatı, sözleşme yenilemeleri). Yapay zeka ajanları bunu değiştiriyor kökten denklem: gibi sistemler IBM Maximo mülk yönetimine uygulanan planlanmamış aksama sürelerini azaltın %50 ve bakım maliyetleri %10-40.
Bu makalede yapay zeka aracılı otonom bir mülk yönetim sistemi oluşturuyoruz: bir sohbet robotu kiracı başına çok kanallı, IoT sensörlü öngörücü bakım sistemi, iş akışı otomasyonu operasyonlar ve ML'ye dayalı dinamik bir kiralama fiyatlandırma sistemi.
Ne Öğreneceksiniz
- Mülk yönetimi için çok aracılı mimari: orkestrasyon ve araç kullanımı
- LLM'li Chatbot kiracısı: talep yönetimi, bakım, ödemeler
- Tahmine dayalı bakım: IoT sensör veri hattı ve anormallik tespiti
- Dinamik fiyatlandırma: Kira optimizasyonu için makine öğrenimi modeli
- İş akışı otomasyonu: onaylar, sözleşmeler, iletişimler
- Dijital ikiz oluşturma: operasyon simülasyonu ve optimizasyonu
- CMMS (Bilgisayarlı Bakım Yönetim Sistemi) entegrasyonu
- Gayrimenkul portföyü için KPI takibi ve otomatik raporlama
Mülk Yönetimi için Çoklu Aracı Mimarisi
İşbirliği yapan birden fazla uzman temsilciden oluşan özerk bir mülk yönetim sistemi. Deseni kullanalım orkestratör + işçi temsilcileri: merkezi bir ajan koordinatları uzman aracılar belirli alanları yönetirken (bakım, ödemeler, kiracı ilişkileri, uyumluluk).
import OpenAI from 'openai';
const openai = new OpenAI({ apiKey: process.env['OPENAI_API_KEY'] });
// Tool definitions per l'agente property manager
const propertyManagementTools = [
{
type: 'function' as const,
function: {
name: 'create_maintenance_request',
description: 'Crea un ordine di manutenzione per un\'unita',
parameters: {
type: 'object',
properties: {
unit_id: { type: 'string', description: 'ID dell\'unita' },
category: {
type: 'string',
enum: ['plumbing', 'electrical', 'hvac', 'appliance', 'structural', 'other'],
description: 'Categoria del problema'
},
priority: {
type: 'string',
enum: ['emergency', 'urgent', 'normal', 'low'],
description: 'Priorità dell\'intervento'
},
description: { type: 'string', description: 'Descrizione dettagliata del problema' },
preferred_time: { type: 'string', description: 'Fascia oraria preferita per l\'intervento' },
},
required: ['unit_id', 'category', 'priority', 'description'],
},
},
},
{
type: 'function' as const,
function: {
name: 'check_rent_status',
description: 'Verifica lo stato del pagamento affitto per un inquilino',
parameters: {
type: 'object',
properties: {
tenant_id: { type: 'string' },
},
required: ['tenant_id'],
},
},
},
{
type: 'function' as const,
function: {
name: 'get_unit_information',
description: 'Recupera informazioni sull\'unita (contratto, servizi, regole)',
parameters: {
type: 'object',
properties: {
unit_id: { type: 'string' },
info_type: {
type: 'string',
enum: ['lease', 'utilities', 'rules', 'amenities', 'neighbors_contact'],
},
},
required: ['unit_id', 'info_type'],
},
},
},
{
type: 'function' as const,
function: {
name: 'escalate_to_human',
description: 'Trasferisci la richiesta a un property manager umano',
parameters: {
type: 'object',
properties: {
reason: { type: 'string', description: 'Motivo dell\'escalation' },
urgency: { type: 'string', enum: ['high', 'medium', 'low'] },
conversation_summary: { type: 'string' },
},
required: ['reason', 'urgency'],
},
},
},
];
export class TenantAssistantAgent {
private conversations = new Map<string, OpenAI.ChatCompletionMessageParam[]>();
async handleMessage(
tenantId: string,
unitId: string,
message: string,
channel: 'whatsapp' | 'email' | 'webapp'
): Promise<string> {
const history = this.conversations.get(tenantId) ?? [];
const messages: OpenAI.ChatCompletionMessageParam[] = [
{
role: 'system',
content: `Sei l'assistente virtuale di gestione immobiliare per l'inquilino ${tenantId}
dell'unita ${unitId}. Sei amichevole, professionale e risolvi i problemi in modo efficiente.
Puoi creare richieste di manutenzione, verificare pagamenti e fornire informazioni.
Per problemi di emergenza (gas, alluvione, incendio) esegui sempre escalation immediata.
Rispondi sempre in italiano.`,
},
...history,
{ role: 'user', content: message },
];
let response = await openai.chat.completions.create({
model: 'gpt-4o',
messages,
tools: propertyManagementTools,
tool_choice: 'auto',
});
let assistantMessage = response.choices[0].message;
// Agentic loop: esegui tools finchè l'agent ha finito
while (assistantMessage.tool_calls && assistantMessage.tool_calls.length > 0) {
messages.push(assistantMessage);
const toolResults: OpenAI.ChatCompletionMessageParam[] = [];
for (const toolCall of assistantMessage.tool_calls) {
const result = await this.executeTool(
toolCall.function.name,
JSON.parse(toolCall.function.arguments)
);
toolResults.push({
role: 'tool',
tool_call_id: toolCall.id,
content: JSON.stringify(result),
});
}
messages.push(...toolResults);
response = await openai.chat.completions.create({
model: 'gpt-4o',
messages,
tools: propertyManagementTools,
});
assistantMessage = response.choices[0].message;
}
const replyText = assistantMessage.content ?? 'Mi dispiace, riprova tra poco.';
// Aggiorna history (mantieni ultimi 20 messaggi)
const updatedHistory = [...history, { role: 'user' as const, content: message }, assistantMessage];
this.conversations.set(tenantId, updatedHistory.slice(-20));
return replyText;
}
private async executeTool(name: string, args: Record<string, unknown>): Promise<unknown> {
switch (name) {
case 'create_maintenance_request':
return this.createMaintenanceRequest(args);
case 'check_rent_status':
return this.checkRentStatus(String(args['tenant_id']));
case 'get_unit_information':
return this.getUnitInformation(String(args['unit_id']), String(args['info_type']));
case 'escalate_to_human':
return this.escalateToHuman(args);
default:
return { error: 'Unknown tool' };
}
}
private async createMaintenanceRequest(args: Record<string, unknown>) {
// Integrazione con CMMS (es. ServiceChannel, Buildium, AppFolio)
const workOrder = {
id: `WO-${Date.now()}`,
unitId: args['unit_id'],
category: args['category'],
priority: args['priority'],
description: args['description'],
status: 'open',
createdAt: new Date().toISOString(),
estimatedCompletion: this.estimateCompletion(String(args['priority'])),
};
// In produzione: API call a CMMS
console.log('Creating work order:', workOrder);
return {
success: true,
workOrderId: workOrder.id,
message: `Richiesta creata con ID ${workOrder.id}. Un tecnico ti contatterà entro ${this.priorityResponse(String(args['priority']))}.`,
};
}
private priorityResponse(priority: string): string {
const responses = {
emergency: '2 ore',
urgent: '24 ore',
normal: '3-5 giorni lavorativi',
low: '7-10 giorni lavorativi',
};
return responses[priority as keyof typeof responses] ?? '5 giorni lavorativi';
}
private estimateCompletion(priority: string): string {
const days = { emergency: 0, urgent: 1, normal: 5, low: 10 }[priority] ?? 5;
const date = new Date();
date.setDate(date.getDate() + days);
return date.toISOString();
}
private async checkRentStatus(tenantId: string) {
// In produzione: query al database finanziario
return {
tenantId,
currentMonth: { status: 'paid', amount: 850, paidDate: '2026-03-01' },
balance: 0,
nextDueDate: '2026-04-01',
};
}
private async getUnitInformation(unitId: string, infoType: string) {
// In produzione: query al database immobiliare
return { unitId, type: infoType, data: '...fetched from database...' };
}
private async escalateToHuman(args: Record<string, unknown>) {
// In produzione: crea ticket in sistema CRM, notifica manager
return {
success: true,
ticketId: `ESC-${Date.now()}`,
message: 'Il tuo caso e stato assegnato a un property manager che ti contatterà presto.',
};
}
}
IoT ve ML ile Tahmine Dayalı Bakım
Kestirimci bakım, ekonomik açıdan en etkili kullanım durumudur. IoT sensörleri monitörü sürekli HVAC sistemleri, asansörler, pompalar ve elektrik sistemleri ve ML modelleri arızaları öngörür bunlar gerçekleşmeden önce, maliyetli acil durumlar yerine planlı müdahalelere izin verir.
import { Kafka, Consumer, Producer } from 'kafkajs';
import * as tf from '@tensorflow/tfjs-node';
interface SensorReading {
sensorId: string;
buildingId: string;
equipmentId: string;
equipmentType: 'hvac' | 'elevator' | 'pump' | 'electrical_panel' | 'boiler';
timestamp: string;
metrics: {
temperature?: number; // gradi Celsius
vibration?: number; // m/s^2
current?: number; // Ampere
pressure?: number; // bar
humidity?: number; // %RH
runningHours?: number; // ore di funzionamento totali
};
}
interface MaintenancePrediction {
equipmentId: string;
failureProbability: number; // 0-1
estimatedDaysToFailure: number;
alertLevel: 'normal' | 'watch' | 'warning' | 'critical';
recommendedAction: string;
confidenceScore: number;
}
export class PredictiveMaintenanceEngine {
private model: tf.LayersModel | null = null;
private kafka: Kafka;
constructor(private readonly kafkaBrokers: string[]) {
this.kafka = new Kafka({
clientId: 'predictive-maintenance',
brokers: kafkaBrokers,
});
}
async initialize(): Promise<void> {
// Carica modello pre-addestrato (LSTM per time series)
this.model = await tf.loadLayersModel('file://./models/equipment-failure/model.json');
console.log('Predictive maintenance model loaded');
}
async startMonitoring(buildingId: string): Promise<void> {
const consumer = this.kafka.consumer({ groupId: 'maintenance-engine' });
await consumer.connect();
await consumer.subscribe({
topic: `building.${buildingId}.sensors`,
fromBeginning: false,
});
const readingBuffer = new Map<string, SensorReading[]>();
await consumer.run({
eachMessage: async ({ message }) => {
const reading: SensorReading = JSON.parse(message.value!.toString());
// Buffer rolling window (ultime 24h di dati = ~1440 letture a 1/min)
const buffer = readingBuffer.get(reading.equipmentId) ?? [];
buffer.push(reading);
if (buffer.length > 1440) buffer.shift();
readingBuffer.set(reading.equipmentId, buffer);
// Predici ogni 30 minuti (non ad ogni lettura per evitare overhead)
const minuteOfDay = new Date().getMinutes();
if (minuteOfDay % 30 === 0 && buffer.length >= 100) {
const prediction = await this.predictFailure(reading.equipmentId, buffer);
await this.handlePrediction(prediction, reading.equipmentId);
}
},
});
}
private async predictFailure(
equipmentId: string,
readings: SensorReading[]
): Promise<MaintenancePrediction> {
if (!this.model) throw new Error('Model not initialized');
// Feature engineering: estrai statistiche rolling dal buffer
const features = this.extractFeatures(readings);
const inputTensor = tf.tensor3d([features], [1, features.length, features[0].length]);
const prediction = this.model.predict(inputTensor) as tf.Tensor;
const [failureProb] = await prediction.data();
tf.dispose([inputTensor, prediction]);
const daysToFailure = Math.max(0, Math.round((1 - failureProb) * 30));
const alertLevel = this.getAlertLevel(failureProb);
return {
equipmentId,
failureProbability: failureProb,
estimatedDaysToFailure: daysToFailure,
alertLevel,
recommendedAction: this.getRecommendedAction(alertLevel, daysToFailure),
confidenceScore: 0.85, // in produzione: calibrated confidence
};
}
private extractFeatures(readings: SensorReading[]): number[][] {
// Finestre temporali: ultimi 60 min, 6h, 24h
const windows = [60, 360, 1440];
return windows.map(windowSize => {
const windowed = readings.slice(-windowSize);
const temperatures = windowed.map(r => r.metrics.temperature ?? 0);
const vibrations = windowed.map(r => r.metrics.vibration ?? 0);
const currents = windowed.map(r => r.metrics.current ?? 0);
return [
this.mean(temperatures), this.std(temperatures), this.max(temperatures),
this.mean(vibrations), this.std(vibrations), this.max(vibrations),
this.mean(currents), this.std(currents), this.max(currents),
windowed.length, // dati disponibili in finestra
];
}).flat().map(() => [0]); // Placeholder: in realta ha shape (timeSteps, features)
}
private getAlertLevel(prob: number): MaintenancePrediction['alertLevel'] {
if (prob >= 0.85) return 'critical';
if (prob >= 0.65) return 'warning';
if (prob >= 0.40) return 'watch';
return 'normal';
}
private getRecommendedAction(level: string, days: number): string {
switch (level) {
case 'critical': return `Intervento urgente entro ${days < 1 ? 'oggi' : `${days} giorni`}. Contattare tecnico ora.`;
case 'warning': return `Pianificare ispezione entro ${days} giorni. Monitoraggio intensivo attivato.`;
case 'watch': return 'Aumentare frequenza ispezioni periodiche. Monitorare trend.';
default: return 'Operativo nella norma. Continuare manutenzione ordinaria.';
}
}
private async handlePrediction(
prediction: MaintenancePrediction,
equipmentId: string
): Promise<void> {
if (prediction.alertLevel === 'normal') return;
// Invia alert via Kafka per processing downstream
const producer = this.kafka.producer();
await producer.connect();
await producer.send({
topic: 'maintenance.alerts',
messages: [{
key: equipmentId,
value: JSON.stringify({
...prediction,
timestamp: new Date().toISOString(),
source: 'predictive-maintenance-engine',
}),
}],
});
await producer.disconnect();
}
private mean(arr: number[]): number {
return arr.length ? arr.reduce((a, b) => a + b, 0) / arr.length : 0;
}
private std(arr: number[]): number {
const m = this.mean(arr);
return Math.sqrt(arr.reduce((a, b) => a + (b - m) ** 2, 0) / arr.length);
}
private max(arr: number[]): number {
return arr.length ? Math.max(...arr) : 0;
}
}
Dinamik Fiyatlandırma: ML ile Kiralama Optimizasyonu
Dinamik kiralama fiyatlandırması, genel portföy gelirini en üst düzeye çıkarmak için ML modellerini kullanır, doluluk ve birim başına gelirin dengelenmesi. Endüstri Gelir Yönetimi modellerinden ilham almıştır. emlak piyasasının özelliklerine uyarlanmış otel endüstrisi (daha uzun sözleşmeler, daha az ciro) sık).
interface RentalPricingInput {
propertyId: string;
currentRent: number;
currentLeaseEnd: string;
unitFeatures: {
squareMeters: number;
floor: number;
view: 'garden' | 'street' | 'panoramic';
lastRenovated: number; // anno ultima ristrutturazione
parkingSpots: number;
};
marketData: {
comparableRents: number[]; // affitti unita simili nella zona
avgDaysOnMarket: number; // giorni medi per affittare unita simili
vacancyRateNeighborhood: number; // % vacancy nel quartiere
demandIndex: number; // 0-1, domanda relativa
seasonalityFactor: number; // 0.8-1.2 (alta stagione = luglio/agosto)
};
tenantProfile?: {
paymentHistory: 'excellent' | 'good' | 'fair';
yearsAsReliableTenant: number;
};
}
export function calculateOptimalRent(input: RentalPricingInput): {
recommendedRent: number;
priceRange: { min: number; max: number };
occupancyForecast: number;
annualRevenueProjection: number;
reasoning: string[];
} {
const { unitFeatures, marketData, currentRent, tenantProfile } = input;
const reasoning: string[] = [];
// 1. Market baseline: mediana comparabili
const sortedComps = [...marketData.comparableRents].sort((a, b) => a - b);
const median = sortedComps[Math.floor(sortedComps.length / 2)];
let suggestedRent = median;
reasoning.push(`Baseline mercato (mediana comparabili): €${median}/mese`);
// 2. Adjustment per domanda/offerta
if (marketData.demandIndex > 0.7) {
const premium = Math.round(suggestedRent * 0.05);
suggestedRent += premium;
reasoning.push(`+${premium} per domanda elevata (indice: ${(marketData.demandIndex * 100).toFixed(0)}%)`);
} else if (marketData.vacancyRateNeighborhood > 0.08) {
const discount = Math.round(suggestedRent * 0.03);
suggestedRent -= discount;
reasoning.push(`-${discount} per alta vacancy nel quartiere (${(marketData.vacancyRateNeighborhood * 100).toFixed(0)}%)`);
}
// 3. Stagionalita
suggestedRent = Math.round(suggestedRent * marketData.seasonalityFactor);
if (marketData.seasonalityFactor !== 1) {
reasoning.push(`Fattore stagionale: x${marketData.seasonalityFactor}`);
}
// 4. Bonus ritenzione tenant di qualità
if (tenantProfile?.paymentHistory === 'excellent' && tenantProfile.yearsAsReliableTenant >= 2) {
const loyaltyDiscount = Math.round(suggestedRent * 0.02);
suggestedRent -= loyaltyDiscount;
reasoning.push(`-${loyaltyDiscount} loyalty discount per tenant eccellente (${tenantProfile.yearsAsReliableTenant} anni)`);
}
// 5. Calcola occupancy forecast basata sul pricing
const priceToMedianRatio = suggestedRent / median;
const baseOccupancy = 0.95 - (marketData.vacancyRateNeighborhood * 0.5);
const occupancyForecast = Math.max(0.5, Math.min(1.0,
baseOccupancy * (1 - (priceToMedianRatio - 1) * 0.8)
));
// 6. Revenue annuale atteso
const annualRevenue = suggestedRent * 12 * occupancyForecast;
return {
recommendedRent: suggestedRent,
priceRange: {
min: Math.round(suggestedRent * 0.95),
max: Math.round(suggestedRent * 1.05),
},
occupancyForecast,
annualRevenueProjection: Math.round(annualRevenue),
reasoning,
};
}
Portföy Kontrol Paneli ve Otomatik KPI'lar
Özerk bir mülk yönetim sistemi, mülk yöneticilerine ve yatırımcılara olanak sağlamalıdır. Portföy performansına ilişkin gerçek zamanlı görünürlük.
interface PortfolioKPIs {
// Occupancy
totalUnits: number;
occupiedUnits: number;
occupancyRate: number; // %
avgDaysVacant: number;
// Revenue
grossPotentialRent: number; // se 100% occupato
effectiveGrossIncome: number; // dopo vacancy e concessions
netOperatingIncome: number; // dopo operating expenses
capRate: number; // NOI / market value
cashOnCashReturn: number; // after debt service
// Manutenzione
openWorkOrders: number;
avgResolutionDays: number;
preventiveVsReactiveRatio: number; // target >0.7 (70% preventive)
maintenanceCostPerUnit: number;
// Tenant
avgTenancyDuration: number; // mesi
turnoverRate: number; // %/anno
rentCollectionRate: number; // %
tenantSatisfactionScore: number; // 1-10
}
export async function calculatePortfolioKPIs(
db: Pool,
portfolioId: string,
periodStart: Date,
periodEnd: Date
): Promise<PortfolioKPIs> {
// Query aggregata su database
const result = await db.query(
`SELECT
COUNT(u.id) AS total_units,
COUNT(CASE WHEN l.status = 'active' THEN 1 END) AS occupied_units,
SUM(u.market_rent) AS gross_potential_rent,
SUM(CASE WHEN l.status = 'active' THEN l.monthly_rent ELSE 0 END) AS effective_gross_income,
AVG(CASE WHEN l.status = 'active' THEN
EXTRACT(MONTH FROM AGE(NOW(), l.start_date))
END) AS avg_tenancy_months
FROM units u
LEFT JOIN leases l ON u.id = l.unit_id AND l.status IN ('active', 'expired')
WHERE u.portfolio_id = $1`,
[portfolioId]
);
const row = result.rows[0];
const maintenanceResult = await db.query(
`SELECT
COUNT(*) FILTER (WHERE status = 'open') AS open_work_orders,
AVG(EXTRACT(DAY FROM (completed_at - created_at))) FILTER (WHERE status = 'closed') AS avg_resolution_days,
COUNT(*) FILTER (WHERE request_type = 'preventive') * 1.0 / NULLIF(COUNT(*), 0) AS preventive_ratio
FROM work_orders
WHERE portfolio_id = $1 AND created_at BETWEEN $2 AND $3`,
[portfolioId, periodStart, periodEnd]
);
const mRow = maintenanceResult.rows[0];
const occupancyRate = row.occupied_units / row.total_units;
const noi = row.effective_gross_income * 12 * 0.65; // approx 35% operating expenses
return {
totalUnits: parseInt(row.total_units),
occupiedUnits: parseInt(row.occupied_units),
occupancyRate,
avgDaysVacant: 21, // In produzione: calcolo reale
grossPotentialRent: parseFloat(row.gross_potential_rent),
effectiveGrossIncome: parseFloat(row.effective_gross_income),
netOperatingIncome: noi,
capRate: noi / (parseFloat(row.gross_potential_rent) * 150), // approx valuation
cashOnCashReturn: 0.08, // In produzione: calcolo reale con debt service
openWorkOrders: parseInt(mRow.open_work_orders ?? '0'),
avgResolutionDays: parseFloat(mRow.avg_resolution_days ?? '5'),
preventiveVsReactiveRatio: parseFloat(mRow.preventive_ratio ?? '0.4'),
maintenanceCostPerUnit: 200, // In produzione: calcolo reale
avgTenancyDuration: parseFloat(row.avg_tenancy_months ?? '24'),
turnoverRate: (1 / (parseFloat(row.avg_tenancy_months ?? '24') / 12)),
rentCollectionRate: 0.97, // In produzione: calcolo da transactions
tenantSatisfactionScore: 7.8, // In produzione: da survey system
};
}
ROI ve Otomasyon Metrikleri
| İşlem | Saat/Yıl Kılavuzu | Saat/Yıl AI | Tasarruf |
|---|---|---|---|
| Kiracı talep yönetimi | 800 saat (200 adet) | 50 saat (denetim) | -94% |
| Bakım planlaması | 300 saat | 20 saat | -93% |
| Kira tahsilatı ve takibi | 200 saat | 10 saat | -95% |
| Aylık portföy raporları | 120 saat | 2h | -98% |
| Reaktif -> kestirimci bakım | 15.000 Avro/yıl | 9.000 Avro/yıl | -40% |
İnsan Gözetimi: Yapay Zeka, Değiştirilmez, Geliştirilir
Gelişmiş otomasyona rağmen, insan mülkiyeti yöneticisi kararlar için vazgeçilmez olmaya devam ediyor Karmaşık: tahliyeler, hukuki anlaşmazlıklar, sözleşme yeniden müzakereleri, önemli tedarikçilerle ilişkiler. Her zaman net üst kademeye yükseltme eşikleri belirleyin ve aşağıdaki kararlar için insan incelemesi sağlayın: kiracıların refahını etkiler. Yapay zeka, yönetici yeteneğini ortadan kaldırmamalı, geliştirmeli.
Sonuçlar
Yapay zeka temsilcileriyle otonom mülk yönetimi artık bilim kurgu değil: uygulanabilir bir gerçeklik kiracı sohbet robotu için GPT-4o, tahmine dayalı bakım için TensorFlow gibi olgun araçlarla bugün ve veri akışı için Apache Kafka. Yatırım getirisi ölçülebilir ve önemlidir: 200 birimlik portföy 500 üniteyle aynı etkinlikle yönetilebilirler, böylece faaliyet başına yöneticilere zaman kazandırılır yüksek değer (satın almalar, yatırımcı ilişkileri, portföy büyümesi).







