summary added in response
This commit is contained in:
parent
e62230cc10
commit
84b6f834a5
@ -202,3 +202,4 @@
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@ -153,3 +153,4 @@
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@ -181,3 +181,4 @@ document.addEventListener('DOMContentLoaded', () => {
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@ -499,3 +499,4 @@
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@ -155,11 +155,54 @@ class ChartFormatter {
|
|||||||
return visualizations;
|
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) {
|
createLineChart(data, parsedQuery) {
|
||||||
if (!data || !Array.isArray(data) || data.length === 0) {
|
if (!data || !Array.isArray(data) || data.length === 0) {
|
||||||
return {
|
return {
|
||||||
type: 'line',
|
type: 'line',
|
||||||
title: 'No Data Available',
|
title: 'No Data Available',
|
||||||
|
summary: 'No data available for this chart.',
|
||||||
data: { labels: [], datasets: [] },
|
data: { labels: [], datasets: [] },
|
||||||
options: { responsive: true }
|
options: { responsive: true }
|
||||||
};
|
};
|
||||||
@ -176,7 +219,7 @@ class ChartFormatter {
|
|||||||
const labels = data.map(row => row.month || row.date || row.period || 'Unknown');
|
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));
|
const values = data.map(row => Math.round(row.avg_price || row.avg_value || 0));
|
||||||
|
|
||||||
return {
|
const chart = {
|
||||||
type: 'line',
|
type: 'line',
|
||||||
title: 'Price Trend Over Time',
|
title: 'Price Trend Over Time',
|
||||||
data: {
|
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) {
|
createMultiAreaLineChart(data, parsedQuery) {
|
||||||
@ -262,7 +352,7 @@ class ChartFormatter {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
return {
|
const chart = {
|
||||||
type: 'line',
|
type: 'line',
|
||||||
title: 'Average Price by Area Over Time',
|
title: 'Average Price by Area Over Time',
|
||||||
data: {
|
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) {
|
createBarChart(data, parsedQuery) {
|
||||||
@ -304,6 +436,7 @@ class ChartFormatter {
|
|||||||
return {
|
return {
|
||||||
type: 'bar',
|
type: 'bar',
|
||||||
title: 'No Data Available',
|
title: 'No Data Available',
|
||||||
|
summary: 'No data available for this chart.',
|
||||||
data: { labels: [], datasets: [] },
|
data: { labels: [], datasets: [] },
|
||||||
options: { responsive: true }
|
options: { responsive: true }
|
||||||
};
|
};
|
||||||
@ -311,7 +444,7 @@ class ChartFormatter {
|
|||||||
|
|
||||||
const labels = data.map(row => {
|
const labels = data.map(row => {
|
||||||
// For fast moving projects, prioritize project names
|
// 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;
|
return row.project_en;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -338,9 +471,9 @@ class ChartFormatter {
|
|||||||
0)
|
0)
|
||||||
);
|
);
|
||||||
|
|
||||||
return {
|
const chart = {
|
||||||
type: 'bar',
|
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: {
|
data: {
|
||||||
labels: labels,
|
labels: labels,
|
||||||
datasets: [{
|
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) {
|
createPieChart(data, parsedQuery) {
|
||||||
@ -379,6 +550,7 @@ class ChartFormatter {
|
|||||||
return {
|
return {
|
||||||
type: 'pie',
|
type: 'pie',
|
||||||
title: 'No Data Available',
|
title: 'No Data Available',
|
||||||
|
summary: 'No data available for this chart.',
|
||||||
data: { labels: [], datasets: [] },
|
data: { labels: [], datasets: [] },
|
||||||
options: { responsive: true }
|
options: { responsive: true }
|
||||||
};
|
};
|
||||||
@ -423,7 +595,7 @@ class ChartFormatter {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
const chart = {
|
||||||
type: 'pie',
|
type: 'pie',
|
||||||
title: 'Distribution Analysis',
|
title: 'Distribution Analysis',
|
||||||
data: {
|
data: {
|
||||||
@ -444,6 +616,11 @@ class ChartFormatter {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Add summary
|
||||||
|
chart.summary = this.generatePieChartSummary(chart, data, parsedQuery);
|
||||||
|
|
||||||
|
return chart;
|
||||||
}
|
}
|
||||||
|
|
||||||
createCards(data, parsedQuery) {
|
createCards(data, parsedQuery) {
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user