@retrivora-ai/rag-engine 1.8.9 → 1.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -29,8 +29,7 @@ export class UITransformer {
29
29
  ? retrievedData.filter(item => this.determineStockStatus(item))
30
30
  : retrievedData;
31
31
 
32
- const groupByField = this.determineGroupByField(userQuery, filteredData);
33
- const categories = this.detectCategories(filteredData, groupByField);
32
+ const categories = this.detectCategories(filteredData);
34
33
  const hasProducts = filteredData.some(item => this.isProductData(item));
35
34
  const isTimeSeries = filteredData.some(item => this.isTimeSeriesData(item));
36
35
  const isTrendQuery = this.isTrendQuery(userQuery);
@@ -40,16 +39,14 @@ export class UITransformer {
40
39
  return this.transformToLineChart(filteredData);
41
40
  }
42
41
 
43
- const explicitChartRequest = this.isExplicitChartQuery(userQuery);
44
-
45
42
  // 2. High Priority: Products (Always prefer carousel for products unless it's a specific breakdown request)
46
- if (hasProducts && !explicitChartRequest && !this.shouldShowCategoryChart(userQuery)) {
43
+ if (hasProducts && !this.shouldShowCategoryChart(userQuery, categories)) {
47
44
  return this.transformToProductCarousel(filteredData, config, trainedSchema);
48
45
  }
49
46
 
50
47
  // 3. Category breakdowns
51
- if (explicitChartRequest || (categories.length > 1 && this.shouldShowCategoryChart(userQuery))) {
52
- return this.transformToPieChart(filteredData, groupByField);
48
+ if (categories.length > 1 && this.shouldShowCategoryChart(userQuery, categories)) {
49
+ return this.transformToPieChart(filteredData);
53
50
  }
54
51
 
55
52
  // 4. Secondary: Products as carousel if not already handled
@@ -90,16 +87,13 @@ export class UITransformer {
90
87
  * Transform data to pie chart format
91
88
  */
92
89
  private static transformToPieChart(
93
- data: VectorMatch[],
94
- groupByField: string
90
+ data: VectorMatch[]
95
91
  ): UITransformationResponse {
96
- let categories = this.detectCategories(data, groupByField);
97
- if (categories.length === 0) categories = ['All Data'];
98
-
99
- const categoryData = this.aggregateByCategory(data, categories, groupByField);
92
+ const categories = this.detectCategories(data);
93
+ const categoryData = this.aggregateByCategory(data, categories);
100
94
 
101
95
  const pieData: PieChartData[] = Object.entries(categoryData).map(([label, count]) => {
102
- const { inStockCount, outOfStockCount } = this.calculateStockCounts(label, data, groupByField);
96
+ const { inStockCount, outOfStockCount } = this.calculateStockCounts(label, data);
103
97
  return {
104
98
  label,
105
99
  value: count as number,
@@ -110,7 +104,7 @@ export class UITransformer {
110
104
 
111
105
  return {
112
106
  type: 'pie_chart',
113
- title: `Distribution by ${groupByField.charAt(0).toUpperCase() + groupByField.slice(1)}`,
107
+ title: 'Distribution by Category',
114
108
  description: `Showing breakdown across ${categories.length} categories`,
115
109
  data: pieData,
116
110
  };
@@ -377,16 +371,11 @@ export class UITransformer {
377
371
  return hasTimeKeyword || hasValidDateValue;
378
372
  }
379
373
 
380
- private static isExplicitChartQuery(query: string): boolean {
381
- const normalized = query.toLowerCase();
382
- return normalized.includes('pie chart') ||
383
- normalized.includes('piechart') ||
384
- normalized.includes('bar chart') ||
385
- normalized.includes('barchart') ||
386
- normalized.includes('radar chart');
387
- }
374
+ private static shouldShowCategoryChart(query: string, categories: string[]): boolean {
375
+ if (categories.length < 2) {
376
+ return false;
377
+ }
388
378
 
389
- private static shouldShowCategoryChart(query: string): boolean {
390
379
  const normalized = query.toLowerCase();
391
380
  const chartKeywords = [
392
381
  'distribution',
@@ -400,10 +389,6 @@ export class UITransformer {
400
389
  'split',
401
390
  'category breakdown',
402
391
  'category distribution',
403
- 'pie chart',
404
- 'piechart',
405
- 'bar chart',
406
- 'barchart'
407
392
  ];
408
393
 
409
394
  return chartKeywords.some(keyword => normalized.includes(keyword));
@@ -516,57 +501,38 @@ export class UITransformer {
516
501
  }
517
502
 
518
503
  /**
519
- * Helper: Dynamically determine what field to group by based on user query
520
- */
521
- private static determineGroupByField(query: string, data: VectorMatch[]): string {
522
- const normalized = query.toLowerCase();
523
- const match = normalized.match(/(?:by|across|per|based on)\s+([a-zA-Z]+)/i);
524
-
525
- let targetField = 'category';
526
- if (match && match[1]) {
527
- let field = match[1].toLowerCase();
528
- // Un-pluralize common words to match schema keys
529
- if (field.endsWith('ies')) field = field.replace(/ies$/, 'y');
530
- else if (field.endsWith('s') && field !== 'status') field = field.slice(0, -1);
531
-
532
- const stopWords = ['the', 'all', 'different', 'various', 'some', 'these', 'those'];
533
- if (!stopWords.includes(field)) {
534
- targetField = field;
535
- }
536
- }
537
-
538
- // Verify field exists, or fallback
539
- const hasTargetField = data.some(d => d.metadata && d.metadata[targetField] !== undefined);
540
- if (!hasTargetField && targetField === 'category') {
541
- if (data.some(d => d.metadata && d.metadata['type'] !== undefined)) return 'type';
542
- if (data.some(d => d.metadata && d.metadata['brand'] !== undefined)) return 'brand';
543
- }
544
-
545
- return targetField;
546
- }
547
-
548
- /**
549
- * Helper: Detect grouping values dynamically
504
+ * Helper: Detect categories in data
550
505
  */
551
- private static detectCategories(data: VectorMatch[], groupByField: string): string[] {
506
+ private static detectCategories(data: VectorMatch[]): string[] {
552
507
  const categories = new Set<string>();
553
508
 
554
509
  data.forEach(item => {
555
510
  const meta = item.metadata || {};
556
511
 
557
- if (meta[groupByField] !== undefined) {
558
- const val = meta[groupByField];
559
- if (Array.isArray(val)) val.forEach(v => categories.add(String(v)));
560
- else categories.add(String(val));
561
- } else if (groupByField === 'category') {
562
- // Fallbacks for legacy 'category' extraction
563
- if (meta.type) categories.add(String(meta.type));
564
- if (meta.tag) {
565
- const tags = Array.isArray(meta.tag) ? meta.tag : [meta.tag];
566
- tags.forEach(t => categories.add(String(t)));
567
- }
568
- const categoryMatch = item.content.match(/(?:Category|Type|Class):\s*([^\n]+)/i);
569
- if (categoryMatch) categories.add(categoryMatch[1].trim());
512
+ // Check for category field
513
+ if (meta.category) {
514
+ categories.add(String(meta.category));
515
+ }
516
+ if (meta.type) {
517
+ categories.add(String(meta.type));
518
+ }
519
+ if (meta.tag) {
520
+ const tags = Array.isArray(meta.tag) ? meta.tag : [meta.tag];
521
+ tags.forEach(t => categories.add(String(t)));
522
+ }
523
+
524
+ // Parse category strings from content lines like "Shoes & Footwear: • Product"
525
+ const contentCategories = Array.from(new Set(
526
+ Array.from(item.content.matchAll(/^\s*([^:\n]+):\s*(?:•|\*|-)*/gm))
527
+ .map(match => match[1].trim())
528
+ .filter(Boolean)
529
+ ));
530
+ contentCategories.forEach((category) => categories.add(category));
531
+
532
+ // Parse from structured category hints in content
533
+ const categoryMatch = item.content.match(/(?:Category|Type|Class):\s*([^\n]+)/i);
534
+ if (categoryMatch) {
535
+ categories.add(categoryMatch[1].trim());
570
536
  }
571
537
  });
572
538
 
@@ -574,12 +540,11 @@ export class UITransformer {
574
540
  }
575
541
 
576
542
  /**
577
- * Helper: Aggregate data dynamically
543
+ * Helper: Aggregate data by category
578
544
  */
579
545
  private static aggregateByCategory(
580
546
  data: VectorMatch[],
581
- categories: string[],
582
- groupByField: string
547
+ categories: string[]
583
548
  ): Record<string, number> {
584
549
  const result: Record<string, number> = {};
585
550
 
@@ -589,13 +554,7 @@ export class UITransformer {
589
554
 
590
555
  data.forEach(item => {
591
556
  const meta = item.metadata || {};
592
- let itemCategory = 'Other';
593
-
594
- if (meta[groupByField] !== undefined) {
595
- itemCategory = String(meta[groupByField]);
596
- } else if (groupByField === 'category' && meta.type) {
597
- itemCategory = String(meta.type);
598
- }
557
+ const itemCategory = (meta.category || meta.type || 'Other') as string;
599
558
 
600
559
  if (Object.prototype.hasOwnProperty.call(result, itemCategory)) {
601
560
  result[itemCategory]++;
@@ -656,21 +615,15 @@ export class UITransformer {
656
615
  }
657
616
 
658
617
  /**
659
- * Helper: Calculate stock counts dynamically
618
+ * Helper: Calculate stock counts by category
660
619
  */
661
- private static calculateStockCounts(category: string, data: VectorMatch[], groupByField: string): { inStockCount: number; outOfStockCount: number } {
620
+ private static calculateStockCounts(category: string, data: VectorMatch[]): { inStockCount: number; outOfStockCount: number } {
662
621
  let inStock = 0;
663
622
  let outOfStock = 0;
664
623
 
665
624
  data.forEach(d => {
666
625
  const meta = d.metadata || {};
667
- let itemCategory = 'Other';
668
- if (meta[groupByField] !== undefined) {
669
- itemCategory = String(meta[groupByField]);
670
- } else if (groupByField === 'category' && meta.type) {
671
- itemCategory = String(meta.type);
672
- }
673
-
626
+ const itemCategory = (meta.category || meta.type || 'Other') as string;
674
627
  if (itemCategory === category) {
675
628
  if (this.determineStockStatus(d)) {
676
629
  inStock++;
@@ -819,13 +772,12 @@ DATA SHAPE per type:
819
772
  - text: { "content": "<prose answer>" }
820
773
 
821
774
  DECISION RULES (follow strictly):
822
- 1. IF the user explicitly asks for a "pie chart", "bar chart", or "table", you MUST output that exact type. Do not deviate from their request.
823
- 2. bar_chart comparing quantities across categories (e.g. sales by region, price by product). Use this when there is only ONE value per category.
824
- 3. line_chart trends or changes over time (dates, months, years, sequential events)
825
- 4. pie_chart proportional breakdown or percentage distribution
826
- 5. radar_chart comparing 2 or more products across MULTIPLE attributes.
827
- 6. table multi-field structured records where each item has ≥ 3 attributes
828
- 7. text → conversational or free-form answers where no chart adds value
775
+ 1. bar_chart → comparing quantities across categories (e.g. sales by region, price by product). Use this when there is only ONE value per category.
776
+ 2. line_chart trends or changes over time (dates, months, years, sequential events)
777
+ 3. pie_chart proportional breakdown or percentage distribution
778
+ 4. radar_chart comparing 2 or more products across MULTIPLE attributes (e.g. comparing features, ratings, or characteristics of 2 specific products). If the user asks to "compare" products and the data contains multiple dimensions or ratings for them, you MUST use this.
779
+ 5. table multi-field structured records where each item has ≥ 3 attributes
780
+ 6. text conversational or free-form answers where no chart adds value
829
781
 
830
782
  IMPORTANT:
831
783
  - Aggregate numeric values from the raw data — do not pass raw object arrays as data.