summary added in response

This commit is contained in:
laxman 2025-11-04 14:53:34 +05:30
parent e62230cc10
commit 84b6f834a5
5 changed files with 187 additions and 6 deletions

View File

@ -202,3 +202,4 @@

View File

@ -153,3 +153,4 @@

View File

@ -181,3 +181,4 @@ document.addEventListener('DOMContentLoaded', () => {

View File

@ -499,3 +499,4 @@

View File

@ -155,11 +155,54 @@ class ChartFormatter {
return visualizations;
}
generateLineChartSummary(chart, data, parsedQuery) {
const { labels, datasets } = chart.data;
const values = datasets[0]?.data || [];
if (values.length === 0 || values.every(v => v === null || v === undefined)) {
return "No data available for this time period.";
}
const validValues = values.filter(v => v !== null && v !== undefined && !isNaN(v));
if (validValues.length === 0) {
return "No valid data points available for this chart.";
}
const min = Math.min(...validValues);
const max = Math.max(...validValues);
const avg = Math.round(validValues.reduce((a, b) => a + b, 0) / validValues.length);
const firstValue = validValues[0];
const lastValue = validValues[validValues.length - 1];
const trend = lastValue > firstValue ? 'increased' : lastValue < firstValue ? 'decreased' : 'remained stable';
const changePercent = firstValue > 0 ? Math.round(((lastValue - firstValue) / firstValue) * 100) : 0;
const areaText = parsedQuery.areas && parsedQuery.areas.length > 0
? parsedQuery.areas.join(' and ')
: 'the selected areas';
const timeText = parsedQuery.time_period
? `over the last ${parsedQuery.time_period.count} ${parsedQuery.time_period.type}`
: 'over the selected period';
let summary = `The line chart shows rental price trends for ${areaText} ${timeText}. `;
summary += `Prices ranged from ${this.formatCurrency(min)} to ${this.formatCurrency(max)}, `;
summary += `with an average of ${this.formatCurrency(avg)}. `;
if (changePercent !== 0) {
summary += `Overall, prices ${trend} by ${Math.abs(changePercent)}% `;
summary += `from ${this.formatCurrency(firstValue)} in ${labels[0]} to ${this.formatCurrency(lastValue)} in ${labels[labels.length - 1]}.`;
} else {
summary += `Prices remained relatively stable throughout the period, ending at ${this.formatCurrency(lastValue)}.`;
}
return summary;
}
createLineChart(data, parsedQuery) {
if (!data || !Array.isArray(data) || data.length === 0) {
return {
type: 'line',
title: 'No Data Available',
summary: 'No data available for this chart.',
data: { labels: [], datasets: [] },
options: { responsive: true }
};
@ -176,7 +219,7 @@ class ChartFormatter {
const labels = data.map(row => row.month || row.date || row.period || 'Unknown');
const values = data.map(row => Math.round(row.avg_price || row.avg_value || 0));
return {
const chart = {
type: 'line',
title: 'Price Trend Over Time',
data: {
@ -208,6 +251,53 @@ class ChartFormatter {
}
}
};
// Add summary
chart.summary = this.generateLineChartSummary(chart, data, parsedQuery);
return chart;
}
generateMultiAreaLineChartSummary(chart, data, parsedQuery) {
const { labels, datasets } = chart.data;
const areaCount = datasets.length;
if (datasets.length === 0) {
return "No data available for comparison.";
}
// Find highest and lowest areas by calculating averages
const areaStats = datasets.map(ds => {
const validData = ds.data.filter(v => v !== null && v !== undefined && !isNaN(v));
if (validData.length === 0) return null;
const avg = validData.reduce((a, b) => a + b, 0) / validData.length;
const total = validData.reduce((a, b) => a + b, 0);
const max = Math.max(...validData);
const min = Math.min(...validData);
return {
area: ds.label,
avg: avg,
total: total,
max: max,
min: min
};
}).filter(stat => stat !== null);
if (areaStats.length === 0) {
return "No valid data available for area comparison.";
}
const highest = areaStats.reduce((a, b) => a.avg > b.avg ? a : b);
const lowest = areaStats.reduce((a, b) => a.avg < b.avg ? a : b);
let summary = `This multi-area comparison chart displays price trends across ${areaCount} area${areaCount > 1 ? 's' : ''}. `;
summary += `${highest.area} shows the highest average price at ${this.formatCurrency(Math.round(highest.avg))}, `;
summary += `while ${lowest.area} has the lowest at ${this.formatCurrency(Math.round(lowest.avg))}. `;
summary += `The chart spans ${labels.length} time period${labels.length > 1 ? 's' : ''}, showing how rental prices have evolved across different areas.`;
return summary;
}
createMultiAreaLineChart(data, parsedQuery) {
@ -262,7 +352,7 @@ class ChartFormatter {
});
});
return {
const chart = {
type: 'line',
title: 'Average Price by Area Over Time',
data: {
@ -297,6 +387,48 @@ class ChartFormatter {
}
}
};
// Add summary
chart.summary = this.generateMultiAreaLineChartSummary(chart, data, parsedQuery);
return chart;
}
generateBarChartSummary(chart, data, parsedQuery) {
const { labels, datasets } = chart.data;
const values = datasets[0]?.data || [];
if (values.length === 0) {
return "No data available for comparison.";
}
const validValues = values.filter(v => v !== null && v !== undefined && !isNaN(v) && v > 0);
if (validValues.length === 0) {
return "No valid data points available for this chart.";
}
const maxValue = Math.max(...validValues);
const maxIndex = values.indexOf(maxValue);
const topItem = labels[maxIndex];
const avgValue = Math.round(validValues.reduce((a, b) => a + b, 0) / validValues.length);
const isPriceData = parsedQuery.intent === 'compare';
const metric = isPriceData ? 'average price' : 'transaction count';
const unit = isPriceData ? this.formatCurrency(maxValue) : maxValue.toLocaleString();
let summary = `The bar chart compares ${metric} across ${labels.length} ${labels.length > 1 ? 'items' : 'item'}. `;
summary += `${topItem} leads with ${unit}, `;
summary += `significantly above the average of ${isPriceData ? this.formatCurrency(avgValue) : avgValue.toLocaleString()}. `;
if (parsedQuery.original_query && parsedQuery.original_query.toLowerCase().includes('fast moving')) {
summary += `These projects show high transaction volume, indicating strong market activity and investor interest.`;
} else if (parsedQuery.original_query && parsedQuery.original_query.toLowerCase().includes('off-plan')) {
summary += `These areas demonstrate strong off-plan project activity, reflecting growing investor confidence and market demand.`;
} else {
summary += `This visualization helps identify the top-performing areas in the market.`;
}
return summary;
}
createBarChart(data, parsedQuery) {
@ -304,6 +436,7 @@ class ChartFormatter {
return {
type: 'bar',
title: 'No Data Available',
summary: 'No data available for this chart.',
data: { labels: [], datasets: [] },
options: { responsive: true }
};
@ -311,7 +444,7 @@ class ChartFormatter {
const labels = data.map(row => {
// For fast moving projects, prioritize project names
if (parsedQuery.original_query.includes('fast moving') && row.project_en) {
if (parsedQuery.original_query && parsedQuery.original_query.includes('fast moving') && row.project_en) {
return row.project_en;
}
@ -338,9 +471,9 @@ class ChartFormatter {
0)
);
return {
const chart = {
type: 'bar',
title: parsedQuery.original_query.includes('fast moving') ? 'Fast Moving Projects' : 'Area Comparison',
title: parsedQuery.original_query && parsedQuery.original_query.includes('fast moving') ? 'Fast Moving Projects' : 'Area Comparison',
data: {
labels: labels,
datasets: [{
@ -372,6 +505,44 @@ class ChartFormatter {
}
}
};
// Add summary
chart.summary = this.generateBarChartSummary(chart, data, parsedQuery);
return chart;
}
generatePieChartSummary(chart, data, parsedQuery) {
const { labels, datasets } = chart.data;
const values = datasets[0]?.data || [];
const total = values.reduce((a, b) => a + b, 0);
if (values.length === 0 || total === 0) {
return "No data available for distribution analysis.";
}
// Find largest segment
const maxIndex = values.indexOf(Math.max(...values));
const largestCategory = labels[maxIndex];
const largestValue = values[maxIndex];
const largestPercent = Math.round((largestValue / total) * 100);
// Find smallest segment
const minIndex = values.indexOf(Math.min(...values.filter(v => v > 0)));
const smallestCategory = labels[minIndex];
const smallestValue = values[minIndex];
const smallestPercent = Math.round((smallestValue / total) * 100);
let summary = `The pie chart shows the distribution across ${labels.length} categor${labels.length > 1 ? 'ies' : 'y'}. `;
summary += `${largestCategory} represents the largest share at ${largestPercent}% (${largestValue.toLocaleString()} ${largestValue === 1 ? 'transaction' : 'transactions'}). `;
if (labels.length > 1 && smallestCategory !== largestCategory) {
summary += `${smallestCategory} has the smallest share at ${smallestPercent}%. `;
}
summary += `The distribution provides insights into market composition and category preferences.`;
return summary;
}
createPieChart(data, parsedQuery) {
@ -379,6 +550,7 @@ class ChartFormatter {
return {
type: 'pie',
title: 'No Data Available',
summary: 'No data available for this chart.',
data: { labels: [], datasets: [] },
options: { responsive: true }
};
@ -423,7 +595,7 @@ class ChartFormatter {
return null;
}
return {
const chart = {
type: 'pie',
title: 'Distribution Analysis',
data: {
@ -444,6 +616,11 @@ class ChartFormatter {
}
}
};
// Add summary
chart.summary = this.generatePieChartSummary(chart, data, parsedQuery);
return chart;
}
createCards(data, parsedQuery) {