Initial commit - WhyRating Engine (Google Reviews Scraper)

This commit is contained in:
Alejandro Gutiérrez
2026-02-02 18:19:00 +00:00
parent 0543a08242
commit 2206ddeff2
136 changed files with 51138 additions and 855 deletions

139
web/lib/taxonomy/data.ts Normal file
View File

@@ -0,0 +1,139 @@
/**
* Static import of URT Taxonomy data
*/
import urtData from './urt-codes.json';
import type { URTTaxonomy } from './types';
export const taxonomy = urtData as URTTaxonomy;
/**
* Get subcode count for a category
*/
export function getSubcodeCount(categoryKey: string): number {
const domainKey = categoryKey[0];
const domain = taxonomy.domains[domainKey];
if (!domain) return 0;
const category = domain.categories[categoryKey];
if (!category) return 0;
return Object.keys(category.subcodes).length;
}
/**
* Get subcode count for a domain
*/
export function getDomainSubcodeCount(domainKey: string): number {
return taxonomy.indices.subcodes_by_domain[domainKey] || 0;
}
/**
* Get category count for a domain
*/
export function getDomainCategoryCount(domainKey: string): number {
const domain = taxonomy.domains[domainKey];
if (!domain) return 0;
return Object.keys(domain.categories).length;
}
/**
* Search taxonomy codes by text
*/
export function searchTaxonomy(query: string): {
domains: string[];
categories: string[];
subcodes: string[];
} {
const normalizedQuery = query.toLowerCase().trim();
if (!normalizedQuery) {
return { domains: [], categories: [], subcodes: [] };
}
const matchedDomains: string[] = [];
const matchedCategories: string[] = [];
const matchedSubcodes: string[] = [];
for (const [domainKey, domain] of Object.entries(taxonomy.domains)) {
const domainMatches =
domainKey.toLowerCase().includes(normalizedQuery) ||
domain.name.toLowerCase().includes(normalizedQuery) ||
domain.description.toLowerCase().includes(normalizedQuery);
if (domainMatches) {
matchedDomains.push(domainKey);
}
for (const [categoryKey, category] of Object.entries(domain.categories)) {
const categoryMatches =
categoryKey.toLowerCase().includes(normalizedQuery) ||
category.name.toLowerCase().includes(normalizedQuery) ||
category.definition.toLowerCase().includes(normalizedQuery);
if (categoryMatches) {
matchedCategories.push(categoryKey);
}
for (const [subcodeKey, subcode] of Object.entries(category.subcodes)) {
const subcodeMatches =
subcodeKey.toLowerCase().includes(normalizedQuery) ||
subcode.name.toLowerCase().includes(normalizedQuery) ||
subcode.definition.toLowerCase().includes(normalizedQuery) ||
subcode.positive_example.toLowerCase().includes(normalizedQuery) ||
subcode.negative_example.toLowerCase().includes(normalizedQuery);
if (subcodeMatches) {
matchedSubcodes.push(subcodeKey);
}
}
}
}
return {
domains: matchedDomains,
categories: matchedCategories,
subcodes: matchedSubcodes,
};
}
/**
* Get parent domain and category for a subcode
*/
export function getSubcodeContext(subcodeKey: string): {
domainKey: string;
categoryKey: string;
} | null {
// Parse subcode key like "O1.01" -> domain "O", category "O1"
const match = subcodeKey.match(/^([OPJEAVR])(\d)/);
if (!match) return null;
const domainKey = match[1];
const categoryKey = `${match[1]}${match[2]}`;
return { domainKey, categoryKey };
}
/**
* Get parent domain for a category
*/
export function getCategoryDomain(categoryKey: string): string | null {
const match = categoryKey.match(/^([OPJEAVR])/);
return match ? match[1] : null;
}
/**
* Get subcode definition by subcode key (e.g., "J1.04" -> "Meeting scheduled times")
*/
export function getSubcodeDefinition(subcodeKey: string): string | null {
const context = getSubcodeContext(subcodeKey);
if (!context) return null;
const domain = taxonomy.domains[context.domainKey];
if (!domain) return null;
const category = domain.categories[context.categoryKey];
if (!category) return null;
const subcode = category.subcodes[subcodeKey];
return subcode?.definition || null;
}

177
web/lib/taxonomy/types.ts Normal file
View File

@@ -0,0 +1,177 @@
/**
* TypeScript types for URT Taxonomy Explorer
*/
// ==================== Subcode Types ====================
export interface Subcode {
name: string;
definition: string;
positive_example: string;
negative_example: string;
dont_confuse_with: string;
dont_confuse_reason: string;
}
export interface Category {
name: string;
definition: string;
subcodes: Record<string, Subcode>;
}
export interface Domain {
name: string;
description: string;
core_question: string;
default_owner: string;
categories: Record<string, Category>;
}
// ==================== Causal Code Types ====================
export interface CausalCode {
name: string;
definition: string;
}
export interface CausalLayer {
layer: string;
prefix: string;
description: string;
codes: Record<string, CausalCode>;
}
// ==================== Metadata Types ====================
export interface MetadataValue {
label: string;
definition: string;
example?: string;
constraint?: string;
markers?: string[];
trigger_words?: string[];
}
export interface MetadataDimension {
code: string;
name: string;
description: string;
default?: string;
values: Record<string, MetadataValue>;
}
// ==================== Profile Types ====================
export interface Profile {
name: string;
use_case: string;
code_tier: number;
code_count: number;
code_type: string;
complexity: string;
required_fields: string[];
optional_fields: string[];
forbidden_fields: string[];
primary_code_pattern: string;
secondary_codes_allowed: boolean;
secondary_codes_max?: number;
secondary_codes_tier?: number;
}
// ==================== Statistics ====================
export interface TaxonomyStatistics {
domains: number;
categories: number;
subcodes_spec_claims: number;
subcodes_actual: number;
causal_codes: number;
metadata_dimensions: number;
metadata_values: number;
total_classification_codes_spec_claims: number;
total_classification_codes_actual: number;
note: string;
}
// ==================== Indices ====================
export interface TaxonomyIndices {
all_domains: string[];
all_categories: string[];
all_subcodes: string[];
all_causal_codes: string[];
subcodes_by_domain: Record<string, number>;
}
// ==================== Main Taxonomy ====================
export interface URTTaxonomy {
version: string;
status: string;
release_date: string;
statistics: TaxonomyStatistics;
domains: Record<string, Domain>;
causal_codes: Record<string, CausalLayer>;
metadata_dimensions: Record<string, MetadataDimension>;
profiles: Record<string, Profile>;
indices: TaxonomyIndices;
}
// ==================== UI State Types ====================
export type TaxonomyTab = 'codes' | 'causal' | 'metadata' | 'profiles';
export interface TreeNodeState {
expanded: boolean;
}
export interface SelectedSubcode {
code: string;
domainKey: string;
domainName: string;
categoryKey: string;
categoryName: string;
subcode: Subcode;
}
// ==================== Domain Colors ====================
export const DOMAIN_COLORS: Record<string, string> = {
O: '#f97316', // Offering - orange
P: '#3b82f6', // People - blue
J: '#8b5cf6', // Journey - purple
E: '#06b6d4', // Environment - cyan
A: '#10b981', // Access - green
V: '#ec4899', // Value - pink
R: '#f59e0b', // Relationship - amber
};
export const DOMAIN_BG_COLORS: Record<string, string> = {
O: 'bg-orange-500/10',
P: 'bg-blue-500/10',
J: 'bg-purple-500/10',
E: 'bg-cyan-500/10',
A: 'bg-emerald-500/10',
V: 'bg-pink-500/10',
R: 'bg-amber-500/10',
};
export const DOMAIN_BORDER_COLORS: Record<string, string> = {
O: 'border-orange-500/30',
P: 'border-blue-500/30',
J: 'border-purple-500/30',
E: 'border-cyan-500/30',
A: 'border-emerald-500/30',
V: 'border-pink-500/30',
R: 'border-amber-500/30',
};
export const DOMAIN_TEXT_COLORS: Record<string, string> = {
O: 'text-orange-400',
P: 'text-blue-400',
J: 'text-purple-400',
E: 'text-cyan-400',
A: 'text-emerald-400',
V: 'text-pink-400',
R: 'text-amber-400',
};