@horizon-modules/property-model-v3
Version:
Modelo de propriedades imobiliárias v3 - Sistema de atributos dinâmicos
1,315 lines (1,305 loc) • 52.9 kB
JavaScript
// src/models/property-type.schema.ts
import { z } from "zod";
var ImageMediaSchema = z.object({
full: z.string(),
md: z.string().optional(),
sm: z.string().optional(),
cover: z.boolean().optional()
});
var VideoMediaSchema = z.object({
provider: z.string().optional(),
id: z.string().optional(),
embed_url: z.string().optional()
});
var DocumentMediaSchema = z.object({
name: z.string(),
url: z.string()
});
var VirtualTourMediaSchema = z.object({
embed_url: z.string()
});
var MediaAssetsSchema = z.object({
images: z.array(ImageMediaSchema).optional(),
videos: z.array(VideoMediaSchema).optional(),
virtual_tours: z.array(VirtualTourMediaSchema).optional(),
documents: z.array(DocumentMediaSchema).optional()
});
var SettingsFormatSchema = z.object({
currency_unit: z.union([z.literal("BRL"), z.literal("USD")]).optional(),
area_unit: z.union([z.literal("m2"), z.literal("ft2")]).optional(),
distance_unit: z.union([z.literal("km"), z.literal("mi"), z.literal("meters")]).optional(),
exibir_no_mapa: z.boolean().optional()
});
var SEOSchema = z.object({
meta_title: z.string().optional(),
meta_description: z.string().optional()
});
var PropertyModelSchema = z.object({
reference: z.string(),
title: z.string(),
description: z.string(),
media_assets: MediaAssetsSchema,
attributes: z.record(z.string(), z.any()),
settings: SettingsFormatSchema.optional(),
seo: SEOSchema.optional(),
updated_at: z.string().optional()
});
var validatePropertyModel = (data) => {
return PropertyModelSchema.parse(data);
};
var safeValidatePropertyModel = (data) => {
return PropertyModelSchema.safeParse(data);
};
var AttributeSchema = z.object({
key: z.string(),
value: z.any(),
valueLabel: z.union([z.string(), z.array(z.string())]).optional(),
composedLabel: z.string().optional(),
type: z.string().optional(),
unit: z.string().optional(),
format: z.string().optional()
});
var PropertyTypeSchema = PropertyModelSchema;
var validatePropertyType = validatePropertyModel;
var safeValidatePropertyType = safeValidatePropertyModel;
// src/models/property-attributes-model.ts
var PropertyAttributesModel = [
// Comercial
{ key: "operacao", label: "Opera\xE7\xE3o", type: "String[]", cat: "comercial", order: 1 },
{ key: "etapa_lancamento", label: "Etapa do lan\xE7amento", type: "String[]", cat: "comercial", order: 2 },
{ key: "status_comercial", label: "Status comercial", type: "String[]", cat: "comercial", order: 3 },
{ key: "destaque", label: "Im\xF3vel em destaque", type: "Boolean", cat: "comercial", order: 4 },
{ key: "exclusividade", label: "Exclusividade do im\xF3vel", type: "Boolean", cat: "comercial", order: 5 },
{ key: "financiavel", label: "Financi\xE1vel", type: "Boolean", cat: "comercial", order: 6 },
{ key: "seguro_fianca", label: "Seguro fian\xE7a", type: "Boolean", cat: "comercial", order: 7 },
{ key: "aceita_permuta", label: "Aceita permuta", type: "Boolean", cat: "comercial", order: 8 },
{ key: "aceita_parcelamento", label: "Aceita parcelamento", type: "Boolean", cat: "comercial", order: 9 },
// Valores
{ key: "valor_condominio", label: "Valor do condom\xEDnio", type: "Number", role: "currency", unit: "BRL", cat: "valores", order: 20 },
{ key: "valor_total_venda", label: "Valor total de venda", type: "Number", role: "currency", unit: "BRL", cat: "valores", composedLabel: "{{valueLabel}}", order: 21 },
{ key: "valor_total_mensal", label: "Valor total de loca\xE7\xE3o", type: "Number", role: "currency", unit: "BRL", cat: "valores", composedLabel: "{{valueLabel}} /m\xEAs", order: 22 },
{ key: "valor_venda", label: "Valor de venda", type: "Number", role: "currency", unit: "BRL", cat: "valores", composedLabel: "{{valueLabel}}", order: 23 },
{ key: "valor_locacao", label: "Valor de loca\xE7\xE3o mensal", type: "Number", role: "currency", unit: "BRL", cat: "valores", composedLabel: "{{valueLabel}} /m\xEAs", order: 24 },
{ key: "valor_diaria", label: "Valor da di\xE1ria", type: "Number", role: "currency", unit: "BRL", cat: "valores", composedLabel: "{{valueLabel}} /dia", order: 25 },
{ key: "valor_pacote_dias", label: "Valor do pacote de dias", type: "Number", role: "currency", unit: "BRL", cat: "valores", order: 26 },
{ key: "valor_iptu", label: "Valor do IPTU", type: "Number", role: "currency", unit: "BRL", cat: "valores", order: 27 },
{ key: "valor_fci", label: "Valor do Fundo de conserva\xE7\xE3o", type: "Number", role: "currency", unit: "BRL", cat: "valores", order: 28 },
{ key: "valor_seguro_incendio", label: "Valor do seguro inc\xEAndio", type: "Number", role: "currency", unit: "BRL", cat: "valores", order: 29 },
{ key: "valor_m2", label: "Valor do m\xB2", type: "Number", role: "currency", unit: "BRL", cat: "valores", order: 30 },
{ key: "valor_taxa_limpeza", label: "Valor da taxa de limpeza", type: "Number", role: "currency", unit: "BRL", cat: "valores", order: 31 },
{ key: "valor_taxa_esgoto", label: "Valor da taxa de esgoto", type: "Number", role: "currency", unit: "BRL", cat: "valores", order: 32 },
// Situações
{ key: "alugado", label: "Alugado", type: "Boolean", cat: "situacoes", order: 40 },
{ key: "vendido", label: "Vendido", type: "Boolean", cat: "situacoes", order: 41 },
{ key: "reservado", label: "Reservado", type: "Boolean", cat: "situacoes", order: 42 },
{ key: "situacao_ocupacao", label: "Situa\xE7\xE3o de ocupa\xE7\xE3o", type: "String", cat: "situacoes", order: 43 },
{ key: "financiado", label: "Financiado", type: "Boolean", cat: "situacoes", order: 44 },
// Legal / Documental
{ key: "averbado", label: "Averbado", type: "Boolean", cat: "legal", order: 60 },
{ key: "escriturado", label: "Escriturado", type: "Boolean", cat: "legal", order: 61 },
{ key: "incorporado", label: "Incorporado", type: "Boolean", cat: "legal", order: 62 },
// Normas / Permissões / Exigências
{ key: "numero_pessoas", label: "N\xFAmero de pessoas", type: "Number", role: "count", cat: "normas", order: 81 },
// Localização
{ key: "endereco_pais", label: "Pa\xEDs", type: "String", cat: "localizacao", order: 101 },
{ key: "endereco_cep", label: "CEP", type: "String", cat: "localizacao", order: 102 },
{ key: "endereco_estado", label: "Estado", type: "String", cat: "localizacao", order: 103 },
{ key: "endereco_cidade", label: "Cidade", type: "String", cat: "localizacao", order: 104 },
{ key: "endereco_bairro", label: "Bairro", type: "String", cat: "localizacao", order: 105 },
{ key: "endereco_logradouro", label: "Logradouro", type: "String", cat: "localizacao", order: 106 },
{ key: "endereco_numero", label: "N\xFAmero do im\xF3vel", type: "String", cat: "localizacao", order: 107 },
{ key: "endereco_complemento", label: "Complemento", type: "String", cat: "localizacao", order: 108 },
{ key: "endereco_referencia", label: "Refer\xEAncia (endere\xE7o)", type: "String", cat: "localizacao", order: 109 },
{ key: "latitude", label: "Latitude", type: "Number", cat: "localizacao", order: 110 },
{ key: "longitude", label: "Longitude", type: "Number", cat: "localizacao", order: 111 },
// Condomínio
{ key: "condominio_nome", label: "Nome do condom\xEDnio", type: "String", cat: "condominio", order: 120 },
{ key: "condominio_tipo", label: "Tipo do condom\xEDnio", type: "String", cat: "condominio", order: 121 },
{ key: "condominio_fechado", label: "Condom\xEDnio fechado", type: "Boolean", cat: "condominio", order: 122 },
// Localidade
{ key: "distancia_mar", label: "Dist\xE2ncia do mar", type: "Number", role: "distance", unit: "m", cat: "localidade", order: 141 },
{ key: "imovel_no_litoral", label: "Im\xF3vel no litoral", type: "Boolean", cat: "localidade", order: 142 },
{ key: "frente_mar", label: "Frente para o mar", type: "Boolean", cat: "localidade", order: 144 },
{ key: "vista_mar", label: "Vista para o mar", type: "Boolean", cat: "localidade", order: 146 },
// Estrutura do imóvel
{ key: "tipo", label: "Tipo", type: "String", cat: "estrutura", order: 160 },
{ key: "subtipo", label: "Subtipo", type: "String", cat: "estrutura", order: 161 },
{ key: "area_privativa", label: "\xC1rea privativa", type: "Number", role: "area", unit: "m\xB2", cat: "estrutura", composedLabel: "{{valueLabel}} priv.", iconName: "area", order: 163 },
{ key: "area_total", label: "\xC1rea Total", type: "Number", role: "area", unit: "m\xB2", cat: "estrutura", composedLabel: "{{valueLabel}} total", iconName: "area", order: 164 },
{ key: "area_terreno", label: "\xC1rea do terreno", type: "Number", role: "area", unit: "m\xB2", cat: "estrutura", composedLabel: "{{valueLabel}} terreno", iconName: "area", order: 165 },
{ key: "padrao_imovel", label: "Padr\xE3o do im\xF3vel", type: "String", cat: "estrutura", order: 166 },
{ key: "fase_obra", label: "Fase da obra", type: "String", cat: "estrutura", order: 167 },
{ key: "em_condominio", label: "Em condom\xEDnio", type: "Boolean", cat: "estrutura", order: 168 },
{ key: "finalidade", label: "Finalidade (prop\xF3sito)", type: "String", cat: "estrutura", order: 169 },
{ key: "estado_conservacao", label: "Estado da conserva\xE7\xE3o do im\xF3vel", type: "String", cat: "estrutura", order: 170 },
{ key: "estado_imovel", label: "Status da obra + uso do im\xF3vel", type: "String", cat: "estrutura", order: 171 },
{ key: "posicao_solar", label: "Posi\xE7\xE3o solar", type: "String", cat: "estrutura", order: 172 },
{ key: "posicao_no_terreno", label: "Posi\xE7\xE3o do im\xF3vel no terreno", type: "String", cat: "estrutura", order: 173 },
{ key: "tipo_piso", label: "Tipo de piso", type: "String", cat: "estrutura", order: 174 },
{ key: "tipos_piso", label: "Tipos de piso", type: "String[]", cat: "estrutura", order: 175 },
{ key: "material_imovel", label: "Material do im\xF3vel", type: "String", cat: "estrutura", order: 176 },
{ key: "materiais_imovel", label: "Materiais do im\xF3vel", type: "String[]", cat: "estrutura", order: 177 },
{ key: "area_construida", label: "\xC1rea constru\xEDda", type: "Number", role: "area", unit: "m\xB2", cat: "estrutura", order: 178 },
{ key: "area_util", label: "\xC1rea \xFAtil", type: "Number", role: "area", unit: "m\xB2", cat: "estrutura", order: 179 },
{ key: "data_entrega_obra", label: "Data de entrega da obra", type: "String", role: "date", cat: "estrutura", order: 180 },
{ key: "ano_construcao", label: "Ano da constru\xE7\xE3o", type: "String", role: "year", cat: "estrutura", order: 181 },
{ key: "topografia_terreno", label: "Topografia do terreno", type: "String", cat: "estrutura", order: 182 },
{ key: "pintura", label: "Pintura", type: "String", cat: "estrutura", order: 183 },
{ key: "pinturas_revestimentos", label: "Pinturas e revestimentos", type: "String[]", cat: "estrutura", order: 184 },
{ key: "tipo_esquadria", label: "Tipo de esquadrias", type: "String", cat: "estrutura", order: 185 },
{ key: "tipos_esquadrias", label: "Tipos de esquadrias", type: "String[]", cat: "estrutura", order: 186 },
{ key: "tipo_forro", label: "Tipo do forro", type: "String", cat: "estrutura", order: 187 },
{ key: "tipos_forros", label: "Tipos de forros", type: "String[]", cat: "estrutura", order: 188 },
{ key: "tipos_coberturas", label: "Tipos de coberturas", type: "String[]", cat: "estrutura", order: 189 },
{ key: "terreno_comprimento", label: "Comprimento do terreno", type: "Number", role: "distance", unit: "m", cat: "estrutura", order: 190 },
{ key: "terreno_largura", label: "Largura do Terreno", type: "Number", role: "distance", unit: "m", cat: "estrutura", order: 191 },
{ key: "cores", label: "Cores do im\xF3vel", type: "String[]", cat: "estrutura", order: 192 },
{ key: "andares", label: "N\xFAmero de andares", type: "Number", role: "count", cat: "estrutura", order: 193 },
{ key: "medida_distancia", label: "Tipo de medida de dist\xE2ncia", type: "String", cat: "estrutura", order: 194 },
{ key: "medida_area", label: "Tipo de medida de \xE1rea", type: "String", cat: "estrutura", order: 195 },
// Corretor
{ key: "corretor_id", label: "Corretor \u2192 ID", type: "String", cat: "corretor", order: 210 },
{ key: "corretor_nome", label: "Corretor \u2192 Nome", type: "String", cat: "corretor", order: 211 },
// Dependências
{ key: "dormitorios", label: "Dormit\xF3rios", type: "Number", role: "count", cat: "dependencias", composedLabel: "{{valueLabel}} dormit\xF3rio{{p:s}}", iconName: "bedroom", order: 230 },
{ key: "banheiros", label: "Banheiros", type: "Number", role: "count", cat: "dependencias", composedLabel: "{{valueLabel}} banheiro{{p:s}}", iconName: "bathroom", order: 231 },
{ key: "suites", label: "Su\xEDtes", type: "Number", role: "count", cat: "dependencias", composedLabel: "Sendo {{valueLabel}} su\xEDte{{p:s}}", iconName: "suite", order: 232 },
{ key: "vagas_garagem", label: "Vagas na garagem", type: "Number", role: "count", cat: "dependencias", composedLabel: "{{valueLabel}} vaga{{p:s}}", iconName: "garage", order: 233 },
// Características em geral
{ key: "mobiliado", label: "Mobiliado", type: "Boolean", cat: "caracteristicas", order: 250 }
// { key: "caracteristicas", label: "Características (lista)", type: "String[]", cat: "caracteristicas" order: 1, },
];
// src/models/property-attributes-fields-list-model.ts
var unitListModel = {
area: [
{
key: "m\xB2",
aliases: ["m2"],
label: "m\xB2",
labelDisplay: "Metros quadrados"
},
{
key: "ha",
aliases: ["hectare", "hectares"],
label: "ha",
labelDisplay: "Hectares"
},
{
key: "km\xB2",
aliases: ["km2"],
label: "km\xB2",
labelDisplay: "Quil\xF4metros quadrados"
}
],
length: [
{
key: "m",
aliases: ["metro", "meter", "meters"],
label: "m",
labelDisplay: "Metros"
},
{
key: "km",
aliases: ["quilometro", "kilometer", "kilometers"],
label: "km",
labelDisplay: "Quil\xF4metros"
}
],
currency: [
{
key: "BRL",
aliases: [],
label: "R$",
labelDisplay: "Real Brasileiro",
locale: "pt-BR"
}
]
};
var formatListModel = {
number: [
{
key: "integer",
label: "Inteiro",
labelDisplay: "N\xFAmero inteiro",
pattern: "0"
},
{
key: "decimal",
label: "Decimal",
labelDisplay: "N\xFAmero decimal",
pattern: "0.00"
},
{
key: "percentage",
label: "Porcentagem",
labelDisplay: "Porcentagem",
pattern: "0.00%"
}
],
currency: [
{
key: "currency",
label: "Moeda",
labelDisplay: "Formato monet\xE1rio",
pattern: "\xA4#,##0.00"
},
{
key: "currency_compact",
label: "Moeda compacta",
labelDisplay: "Formato monet\xE1rio compacto",
pattern: "\xA4#,##0"
}
],
date: [
{
key: "date",
label: "Data",
labelDisplay: "Data (DD/MM/AAAA)",
pattern: "dd/MM/yyyy"
},
{
key: "datetime",
label: "Data e hora",
labelDisplay: "Data e hora completa",
pattern: "dd/MM/yyyy HH:mm"
},
{
key: "year",
label: "Ano",
labelDisplay: "Apenas ano",
pattern: "yyyy"
},
{
key: "month_year",
label: "M\xEAs/Ano",
labelDisplay: "M\xEAs e ano",
pattern: "MM/yyyy"
}
],
text: [
{
key: "uppercase",
label: "Mai\xFAscula",
labelDisplay: "Texto em mai\xFAscula"
},
{
key: "lowercase",
label: "Min\xFAscula",
labelDisplay: "Texto em min\xFAscula"
},
{
key: "capitalize",
label: "Capitalizado",
labelDisplay: "Primeira letra mai\xFAscula"
},
{
key: "title",
label: "T\xEDtulo",
labelDisplay: "Cada palavra capitalizada"
}
]
// Em desenvolvimento futuro
// color: [
// {
// key: "rgb",
// label: "RGB",
// },
// {
// key: "cmyk",
// label: "CMYK",
// },
// ],
};
// src/utils/index.ts
function verifyAttrKeyInPropertyV3Model(key) {
return PropertyAttributesModel.find((attr) => attr.key == key);
}
function mergePropertyAttributesModel(base, overrides) {
const map = new Map(base.map((attr) => [attr.key, { ...attr }]));
for (const overrideAttr of overrides) {
const baseAttr = map.get(overrideAttr.key);
if (baseAttr) {
for (const field in overrideAttr) {
baseAttr[field] = overrideAttr[field];
}
map.set(overrideAttr.key, baseAttr);
} else {
map.set(overrideAttr.key, { ...overrideAttr });
}
}
return Array.from(map.values());
}
function sortAttributes(attributes, sortKeys = []) {
return [...attributes].sort((a, b) => {
for (const rule of sortKeys) {
const [field, direction] = rule.split(":");
const valA = a[field];
const valB = b[field];
if (valA === void 0 || valB === void 0) continue;
const isAsc = direction === "asc";
const isDesc = direction === "desc";
if (!isAsc && !isDesc) {
console.warn(`Dire\xE7\xE3o inv\xE1lida: "${direction}". Use "asc" ou "desc".`);
continue;
}
if (valA < valB) return isAsc ? -1 : 1;
if (valA > valB) return isAsc ? 1 : -1;
}
return 0;
});
}
var preparaAttrValueLabel = function(valor) {
return Array.isArray(valor) ? `${valor.slice(0, -1).join(", ")} e ${valor[valor.length - 1]}` : valor;
};
// src/services/fake-data-generator/index.ts
import * as fs from "fs";
import * as path from "path";
function randomInt(min, max) {
return Math.floor(Math.random() * (max - min + 1)) + min;
}
function randomChoice(array) {
return array[Math.floor(Math.random() * array.length)];
}
function randomFromArray(array, count) {
const shuffled = [...array].sort(() => 0.5 - Math.random());
return shuffled.slice(0, count);
}
var profilingCache = null;
function loadProfilingReport() {
if (profilingCache) {
return profilingCache;
}
try {
const reportPath = path.join(__dirname, "..", "..", "..", "__dev__", "data", "property-v3-profiling", "profiling-report.json");
const reportData = fs.readFileSync(reportPath, "utf8");
profilingCache = JSON.parse(reportData);
return profilingCache;
} catch (error) {
console.warn("N\xE3o foi poss\xEDvel carregar o profiling report, usando valores padr\xE3o");
return {};
}
}
function generateEnhancedAttributes(tipo) {
const attributes = [];
const profiling = loadProfilingReport();
function getProfilingValue(key) {
const profilingKey = `attributes.${key}`;
const values = profiling[profilingKey];
return values && values.length > 0 ? randomChoice(values) : null;
}
function getProfilingNumber(key, fallbackMin, fallbackMax) {
const value = getProfilingValue(key);
if (typeof value === "number") {
return value;
}
return randomInt(fallbackMin, fallbackMax);
}
attributes.push({ key: "tipo", value: tipo });
const operacao = getProfilingValue("operacao") || ["venda"];
attributes.push({ key: "operacao", value: Array.isArray(operacao) ? operacao : [operacao] });
const cidade = getProfilingValue("endereco_cidade") || "S\xE3o Paulo";
const estado = getProfilingValue("endereco_estado") || "SP";
const bairro = getProfilingValue("endereco_bairro") || "Centro";
const cep = getProfilingValue("endereco_cep") || "01000-000";
attributes.push(
{ key: "endereco_cidade", value: cidade },
{ key: "endereco_estado", value: estado },
{ key: "endereco_bairro", value: bairro },
{ key: "endereco_cep", value: cep },
{ key: "endereco_numero", value: getProfilingNumber("endereco_numero", 10, 9999) }
);
const logradouro = getProfilingValue("endereco_logradouro");
if (logradouro) {
attributes.push({ key: "endereco_logradouro", value: logradouro });
}
const complemento = getProfilingValue("endereco_complemento");
if (complemento) {
attributes.push({ key: "endereco_complemento", value: complemento });
}
const valorVenda = getProfilingNumber("valor_venda", 2e5, 2e6);
if (valorVenda && (Array.isArray(operacao) ? operacao.includes("venda") : operacao === "venda")) {
attributes.push({ key: "valor_venda", value: valorVenda });
}
const valorLocacao = getProfilingNumber("valor_locacao", 800, 8e3);
if (valorLocacao && (Array.isArray(operacao) ? operacao.includes("locacao") : operacao === "locacao")) {
attributes.push({ key: "valor_locacao", value: valorLocacao });
}
if (tipo !== "Terreno") {
const dormitorios = getProfilingNumber("dormitorios", 1, 5);
const banheiros = getProfilingNumber("banheiros", 1, 4);
const suites = getProfilingNumber("suites", 0, 3);
const areaUtil = getProfilingNumber("area_util", 40, 400);
attributes.push(
{ key: "dormitorios", value: dormitorios },
{ key: "banheiros", value: banheiros },
{ key: "suites", value: suites },
{ key: "area_util", value: areaUtil }
);
}
const areaTotal = getProfilingNumber("area_total", 50, 500);
if (areaTotal) {
attributes.push({ key: "area_total", value: areaTotal });
}
const vagas = getProfilingNumber("vagas", 1, 4);
if (vagas) {
attributes.push({ key: "vagas", value: vagas });
}
const caracteristicas = profiling["attributes.caracteristicas"];
if (caracteristicas && caracteristicas.length > 0) {
const selectedCaracteristicas = randomFromArray(caracteristicas, randomInt(2, 6));
attributes.push({ key: "caracteristicas", value: selectedCaracteristicas });
}
const mobiliado = getProfilingValue("mobiliado");
if (mobiliado !== null) {
attributes.push({ key: "mobiliado", value: mobiliado });
}
const financiavel = getProfilingValue("financiavel");
if (financiavel !== null) {
attributes.push({ key: "financiavel", value: financiavel });
}
const aceitaPermuta = getProfilingValue("aceita_permuta");
if (aceitaPermuta !== null) {
attributes.push({ key: "aceita_permuta", value: aceitaPermuta });
}
const valorCondominio = getProfilingNumber("valor_condominio", 150, 1500);
if (valorCondominio) {
attributes.push({ key: "valor_condominio", value: valorCondominio });
}
const valorIptu = getProfilingNumber("valor_iptu", 500, 5e3);
if (valorIptu) {
attributes.push({ key: "valor_iptu", value: valorIptu });
}
const corretorId = getProfilingValue("corretor_id");
if (corretorId) {
attributes.push({ key: "corretor_id", value: corretorId });
}
const corretorNome = getProfilingValue("corretor_nome");
if (corretorNome) {
attributes.push({ key: "corretor_nome", value: corretorNome });
}
return attributes;
}
function generateFakeProperty(options = {}) {
const {
tipo = randomChoice(["Casa", "Apartamento", "Sobrado", "Terreno", "Cobertura"]),
exteriorPhotos = 1,
interiorPhotos = 3
} = options;
const reference = `${tipo.substring(0, 3).toUpperCase()}${randomInt(1e3, 9999)}_FK`;
const attributes = generateEnhancedAttributes(tipo);
const operacaoAttr = attributes.find((a) => a.key === "operacao");
const operacao = operacaoAttr?.value.join(" e ") || "venda";
const cidadeAttr = attributes.find((a) => a.key === "endereco_cidade");
const bairroAttr = attributes.find((a) => a.key === "endereco_bairro");
const unsplashImages = generateUnsplashImages(tipo, exteriorPhotos + interiorPhotos);
const images = unsplashImages.map((url, index) => ({
full: url,
md: url.replace("w=800", "w=640").replace("h=600", "h=480"),
sm: url.replace("w=800", "w=320").replace("h=600", "h=240"),
cover: index === 0
}));
const dormitoriosAttr = attributes.find((a) => a.key === "dormitorios");
const areaAttr = attributes.find((a) => a.key === "area_util" || a.key === "area_total");
let title = `${tipo} para ${operacao}`;
if (dormitoriosAttr) {
title += `, ${dormitoriosAttr.value} dormit\xF3rios`;
}
if (areaAttr) {
title += `, ${areaAttr.value}m\xB2`;
}
const caracteristicasAttr = attributes.find((a) => a.key === "caracteristicas");
const caracteristicas = caracteristicasAttr?.value || [];
const featuresText = Array.isArray(caracteristicas) && caracteristicas.length > 0 ? caracteristicas.slice(0, 5).join(", ") : "";
const description = `Excelente ${tipo.toLowerCase()} em ${bairroAttr?.value}, ${cidadeAttr?.value}. Im\xF3vel com \xF3tima localiza\xE7\xE3o e acabamento de qualidade. ` + (featuresText ? `Caracter\xEDsticas: ${featuresText}. ` : "") + `Agende sua visita!`;
const attributesObj = {};
attributes.forEach((attr) => {
attributesObj[attr.key] = attr.value;
});
return {
reference,
title: title || `${tipo} para ${operacao}`,
description: description || `Im\xF3vel dispon\xEDvel para ${operacao}`,
attributes: attributesObj,
media_assets: {
images,
videos: [],
virtual_tours: [],
documents: []
},
settings: {
currency_unit: "BRL",
area_unit: "m2",
exibir_no_mapa: true
},
updated_at: (/* @__PURE__ */ new Date()).toISOString()
};
}
function generateFakeProperties(count = 20, options = {}) {
const { tipos = ["Casa", "Apartamento", "Sobrado", "Terreno", "Cobertura"] } = options;
const properties = [];
for (let i = 0; i < count; i++) {
const tipo = randomChoice(tipos);
properties.push(generateFakeProperty({ ...options, tipo }));
}
return properties;
}
function generateFakePropertyByType(tipo) {
const tiposValidos = ["Casa", "Apartamento", "Sobrado", "Terreno", "Cobertura"];
if (!tiposValidos.includes(tipo)) return null;
return generateFakeProperty({ tipo });
}
function getAvailablePropertyTypes() {
return ["Casa", "Apartamento", "Sobrado", "Terreno", "Cobertura"];
}
var UNSPLASH_IMAGES = {
apartamento: [
"https://images.unsplash.com/photo-1502672260266-1c1ef2d93688?w=800&h=600&q=80",
"https://images.unsplash.com/photo-1522708323590-d24dbb6b0267?w=800&h=600&q=80",
"https://images.unsplash.com/photo-1560448204-e02f11c3d0e2?w=800&h=600&q=80",
"https://images.unsplash.com/photo-1565183997392-2f6f122e5912?w=800&h=600&q=80"
],
casa: [
"https://images.unsplash.com/photo-1512917774080-9991f1c4c750?w=800&h=600&q=80",
"https://images.unsplash.com/photo-1568605114967-8130f3a36994?w=800&h=600&q=80",
"https://images.unsplash.com/photo-1570129477492-45c003edd2be?w=800&h=600&q=80",
"https://images.unsplash.com/photo-1583608205776-bfd35f0d9f83?w=800&h=600&q=80"
],
comercial: [
"https://images.unsplash.com/photo-1497366216548-37526070297c?w=800&h=600&q=80",
"https://images.unsplash.com/photo-1524758631624-e2822e304c36?w=800&h=600&q=80",
"https://images.unsplash.com/photo-1462826303086-329426d1aef5?w=800&h=600&q=80",
"https://images.unsplash.com/photo-1497366811353-6870744d04b2?w=800&h=600&q=80"
],
terreno: [
"https://images.unsplash.com/photo-1500382017468-9049fed747ef?w=800&h=600&q=80",
"https://images.unsplash.com/photo-1416331108676-a22ccb276e35?w=800&h=600&q=80",
"https://images.unsplash.com/photo-1564013799919-ab600027ffc6?w=800&h=600&q=80",
"https://images.unsplash.com/photo-1448630360428-65456885c650?w=800&h=600&q=80"
],
sobrado: [
"https://images.unsplash.com/photo-1512917774080-9991f1c4c750?w=800&h=600&q=80",
"https://images.unsplash.com/photo-1568605114967-8130f3a36994?w=800&h=600&q=80",
"https://images.unsplash.com/photo-1570129477492-45c003edd2be?w=800&h=600&q=80"
],
cobertura: [
"https://images.unsplash.com/photo-1502672260266-1c1ef2d93688?w=800&h=600&q=80",
"https://images.unsplash.com/photo-1560448204-e02f11c3d0e2?w=800&h=600&q=80",
"https://images.unsplash.com/photo-1565183997392-2f6f122e5912?w=800&h=600&q=80"
]
};
function generateUnsplashImages(tipo, count) {
const tipoKey = tipo.toLowerCase();
const availableImages = UNSPLASH_IMAGES[tipoKey] || UNSPLASH_IMAGES.casa;
const images = [];
for (let i = 0; i < count; i++) {
const randomImage = randomChoice(availableImages);
images.push(randomImage);
}
return images;
}
// src/services/profiler/index.ts
import * as fs2 from "fs";
import * as path2 from "path";
var ProfilerService = class {
constructor(config) {
this.fieldData = /* @__PURE__ */ new Map();
this.fieldExamples = /* @__PURE__ */ new Map();
this.config = {
inputDir: config.inputDir || "",
inputData: config.inputData || [],
outputDir: config.outputDir || process.cwd(),
outputFileName: config.outputFileName || "profiling-report.json",
fieldConfigs: config.fieldConfigs || {},
defaultMaxExamples: config.defaultMaxExamples || 10,
verbose: config.verbose ?? true
};
}
/**
* Executa o profiling dos dados
*/
async profile() {
if (this.config.verbose) {
console.log("\u{1F4CA} Iniciando profiling dos dados PropertyModel...");
}
const data = await this.loadData();
this.processData(data);
const result = this.generateResult();
if (this.config.outputDir) {
await this.saveResult(result);
}
return result;
}
/**
* Carrega dados de arquivos ou usa dados fornecidos
*/
async loadData() {
if (this.config.inputData.length > 0) {
if (this.config.verbose) {
console.log(`\u{1F4C2} Usando ${this.config.inputData.length} PropertyModel fornecidos`);
}
return this.config.inputData;
}
if (!this.config.inputDir || !fs2.existsSync(this.config.inputDir)) {
throw new Error(`Diret\xF3rio de entrada n\xE3o encontrado: ${this.config.inputDir}`);
}
const allData = [];
const files = fs2.readdirSync(this.config.inputDir).filter((file) => file.endsWith(".json"));
if (this.config.verbose) {
console.log(`\u{1F4C2} Carregando dados de ${files.length} arquivos...`);
}
for (const file of files) {
const filePath = path2.join(this.config.inputDir, file);
const fileContent = fs2.readFileSync(filePath, "utf8");
const jsonData = JSON.parse(fileContent);
let properties = [];
if (Array.isArray(jsonData)) {
properties = jsonData;
} else if (jsonData.data && Array.isArray(jsonData.data)) {
properties = jsonData.data;
} else if (typeof jsonData === "object") {
const allArrays = Object.values(jsonData).filter(Array.isArray);
properties = allArrays.flat();
}
allData.push(...properties);
if (this.config.verbose) {
console.log(` \u2705 ${file}: ${properties.length} PropertyModel`);
}
}
if (this.config.verbose) {
console.log(`\u{1F4CA} Total carregado: ${allData.length} PropertyModel`);
}
return allData;
}
/**
* Processa os dados coletados e extrai informações estatísticas
*/
processData(data) {
if (this.config.verbose) {
console.log("\u{1F50D} Processando dados...");
}
for (const property of data) {
this.processObject(property, "");
}
if (this.config.verbose) {
console.log(`\u{1F50D} ${this.fieldData.size} campos \xFAnicos encontrados`);
}
}
/**
* Processa recursivamente um objeto para extrair todos os campos
*/
processObject(obj, prefix) {
if (obj === null || obj === void 0) return;
if (Array.isArray(obj)) {
obj.forEach((item) => {
if (item !== null && item !== void 0) {
this.addFieldValue(prefix, item);
}
});
return;
}
if (typeof obj === "object") {
Object.entries(obj).forEach(([key, value]) => {
const fullKey = prefix ? `${prefix}.${key}` : key;
if (Array.isArray(value)) {
this.processObject(value, fullKey);
} else {
this.addFieldValue(fullKey, value);
if (value && typeof value === "object") {
this.processObject(value, fullKey);
}
}
});
} else {
this.addFieldValue(prefix, obj);
}
}
/**
* Adiciona um valor para um campo específico
*/
addFieldValue(fieldName, value) {
if (value === null || value === void 0) return;
const serializedValue = typeof value === "object" ? JSON.stringify(value) : value;
if (!this.fieldData.has(fieldName)) {
this.fieldData.set(fieldName, /* @__PURE__ */ new Set());
this.fieldExamples.set(fieldName, []);
}
const fieldSet = this.fieldData.get(fieldName);
const examples = this.fieldExamples.get(fieldName);
if (!fieldSet.has(serializedValue)) {
fieldSet.add(serializedValue);
const fieldConfig = this.config.fieldConfigs[fieldName];
const maxExamples = fieldConfig?.maxExamples || this.config.defaultMaxExamples;
if (examples.length < maxExamples) {
examples.push(value);
}
}
}
/**
* Gera o resultado final do profiling
*/
generateResult() {
const result = {};
const sortedFields = Array.from(this.fieldData.keys()).sort();
for (const fieldName of sortedFields) {
const fieldConfig = this.config.fieldConfigs[fieldName];
const maxExamples = fieldConfig?.maxExamples !== void 0 ? fieldConfig.maxExamples : this.config.defaultMaxExamples;
if (maxExamples === 0) {
continue;
}
const examples = this.fieldExamples.get(fieldName) || [];
const sortedExamples = examples.slice().sort((a, b) => {
if (typeof a === "string" && typeof b === "string") {
return a.localeCompare(b);
}
if (typeof a === "number" && typeof b === "number") {
return a - b;
}
return String(a).localeCompare(String(b));
});
result[fieldName] = sortedExamples;
}
if (this.config.verbose) {
console.log(`\u{1F4C8} Profiling conclu\xEDdo: ${Object.keys(result).length} campos analisados`);
}
return result;
}
/**
* Salva o resultado em arquivo JSON
*/
async saveResult(result) {
const outputPath = path2.join(this.config.outputDir, this.config.outputFileName);
const outputDir = path2.dirname(outputPath);
if (!fs2.existsSync(outputDir)) {
fs2.mkdirSync(outputDir, { recursive: true });
}
fs2.writeFileSync(outputPath, JSON.stringify(result, null, 2));
if (this.config.verbose) {
console.log(`\u{1F4C1} Relat\xF3rio salvo em: ${outputPath}`);
}
}
};
async function profileProperties(properties, options = {}) {
const profiler = new ProfilerService({
inputData: properties,
...options
});
return profiler.profile();
}
async function profileFromFiles(inputDir, options = {}) {
const profiler = new ProfilerService({
inputDir,
...options
});
return profiler.profile();
}
// src/services/api-client/index.ts
import * as fs3 from "fs";
import * as path3 from "path";
var ApiClientService = class {
constructor(config) {
this.config = {
endpoint: config.endpoint,
headers: config.headers || { "Content-Type": "application/json" },
timeout: config.timeout || 3e4,
batchSize: config.batchSize || 10
};
}
/**
* Envia uma propriedade para a API
*/
async sendProperty(property) {
try {
const response = await this.makeHttpRequest(property);
return response.ok;
} catch (error) {
return false;
}
}
/**
* Envia múltiplas propriedades
*/
async sendProperties(properties) {
for (const property of properties) {
await this.sendProperty(property);
}
}
/**
* Carrega propriedades de uma pasta com arquivos JSON
*/
async sendPropertiesFromDir(inputDir) {
const properties = await this.loadPropertiesFromDir(inputDir);
await this.sendInBatches(properties);
}
/**
* Carrega propriedades de arquivos JSON
*/
async loadPropertiesFromDir(inputDir) {
if (!fs3.existsSync(inputDir)) {
throw new Error(`Diret\xF3rio n\xE3o encontrado: ${inputDir}`);
}
const allData = [];
const files = fs3.readdirSync(inputDir).filter((file) => file.endsWith(".json"));
for (const file of files) {
const filePath = path3.join(inputDir, file);
const fileContent = fs3.readFileSync(filePath, "utf8");
const jsonData = JSON.parse(fileContent);
let properties = [];
if (Array.isArray(jsonData)) {
properties = jsonData;
} else if (jsonData.data && Array.isArray(jsonData.data)) {
properties = jsonData.data;
} else if (typeof jsonData === "object") {
const allArrays = Object.values(jsonData).filter(Array.isArray);
properties = allArrays.flat();
}
allData.push(...properties);
}
return allData;
}
/**
* Envia propriedades em lotes
*/
async sendInBatches(properties) {
const batches = this.createBatches(properties, this.config.batchSize);
for (const batch of batches) {
await Promise.all(
batch.map((property) => this.sendProperty(property))
);
}
}
/**
* Divide array em lotes menores
*/
createBatches(array, batchSize) {
const batches = [];
for (let i = 0; i < array.length; i += batchSize) {
batches.push(array.slice(i, i + batchSize));
}
return batches;
}
/**
* Faz requisição HTTP para a API
*/
async makeHttpRequest(data) {
const controller = new AbortController();
const timeout = setTimeout(() => controller.abort(), this.config.timeout);
try {
const response = await fetch(this.config.endpoint, {
method: "POST",
headers: this.config.headers,
body: JSON.stringify(data),
signal: controller.signal
});
clearTimeout(timeout);
return response;
} catch (error) {
clearTimeout(timeout);
throw error;
}
}
};
// src/services/property-customizer/index.ts
import { clone, union } from "ramda";
function upsertAttribute(attributes, key, value) {
attributes[key] = value;
}
function addToArray(property, action) {
const currentValue = property.attributes[action.key];
const newValues = Array.isArray(action.value) ? action.value : [action.value];
if (!currentValue) {
property.attributes[action.key] = newValues;
} else if (Array.isArray(currentValue)) {
property.attributes[action.key] = union(currentValue, newValues);
} else {
property.attributes[action.key] = union([currentValue], newValues);
}
}
function removeAttr(attributes, key) {
delete attributes[key];
}
function removeFromArray(property, action) {
const currentValue = property.attributes[action.key];
if (!currentValue || !Array.isArray(currentValue)) return;
property.attributes[action.key] = currentValue.filter((item) => item !== action.value);
if (property.attributes[action.key].length === 0) {
delete property.attributes[action.key];
}
}
var actionRunner = {
upsertAttr: (property, action) => {
upsertAttribute(property.attributes, action.key, action.value);
},
removeAttr: (property, action) => {
removeAttr(property.attributes, action.key);
},
addToArray: (property, action) => {
addToArray(property, action);
},
removeFromArray: (property, action) => {
removeFromArray(property, action);
}
};
function processDescriptionTags(property) {
if (!property.description) return;
let description = property.description;
const tagRegex = /\[\[([^\]]+)\]\](.*?)\[\[\/\1\]\]/gs;
const matches = [...description.matchAll(tagRegex)];
for (const match of matches) {
const [, tagName, content] = match;
const cleanTagName = tagName.replace(/-/g, "_");
const cleanContent = content.trim();
if (tagName === "custom-attributes") {
try {
const jsonData = JSON.parse(cleanContent);
if (jsonData && typeof jsonData === "object") {
Object.assign(property.attributes, jsonData);
}
} catch (error) {
console.warn(`\u26A0\uFE0F Erro ao processar JSON em custom-attributes: ${error instanceof Error ? error.message : String(error)}`);
property.attributes[cleanTagName] = cleanContent;
}
} else {
property.attributes[cleanTagName] = cleanContent;
}
}
description = description.replace(tagRegex, "").trim();
description = description.replace(/ +/g, " ");
description = description.replace(/\n{3,}/g, "\n\n");
property.description = description;
}
function applyReferenceRules(reference, rules, property) {
const actions = rules?.[reference];
if (!actions) return;
actions.forEach((action) => {
const fn = actionRunner[action.fn];
if (fn) fn(property, action);
});
}
function matchCondition(attrValue, condition) {
if (!condition) return true;
const attrAsArray = Array.isArray(attrValue) ? attrValue : typeof attrValue === "string" ? [attrValue] : [];
const attrAsString = typeof attrValue === "string" ? attrValue : String(attrValue);
for (const operator in condition) {
const expected = condition[operator];
switch (operator) {
// Igualdade
case "eq":
if (attrValue !== expected) return false;
break;
case "not_eq":
if (attrValue === expected) return false;
break;
// Arrays
case "has": {
const attrAsArray2 = Array.isArray(attrValue) ? attrValue : [attrValue];
const expectedArray = Array.isArray(expected) ? expected : [expected];
if (!expectedArray.every((val) => attrAsArray2.includes(val)))
return false;
break;
}
case "has_any": {
const attrAsArray2 = Array.isArray(attrValue) ? attrValue : [attrValue];
const expectedArray = Array.isArray(expected) ? expected : [expected];
if (!expectedArray.some((val) => attrAsArray2.includes(val)))
return false;
break;
}
case "not_has":
if (Array.isArray(expected)) {
if (expected.some((val) => attrAsArray.includes(val))) return false;
} else {
if (attrAsArray.includes(expected)) return false;
}
break;
// Numéricos
case "gt":
if (!(Number(attrValue) > Number(expected))) return false;
break;
case "gte":
if (!(Number(attrValue) >= Number(expected))) return false;
break;
case "lt":
if (!(Number(attrValue) < Number(expected))) return false;
break;
case "lte":
if (!(Number(attrValue) <= Number(expected))) return false;
break;
// Texto
case "contains":
if (!attrAsString.toLowerCase().includes(String(expected).toLowerCase())) return false;
break;
case "starts_with":
if (!attrAsString.toLowerCase().startsWith(String(expected).toLowerCase())) return false;
break;
case "ends_with":
if (!attrAsString.toLowerCase().endsWith(String(expected).toLowerCase())) return false;
break;
// Booleanos
case "is_true":
if (attrValue !== true && attrValue !== "true" && attrValue !== 1) return false;
break;
case "is_false":
if (attrValue !== false && attrValue !== "false" && attrValue !== 0) return false;
break;
// Existência
case "exists":
if (attrValue === null || attrValue === void 0 || attrValue === "") return false;
break;
case "not_exists":
if (attrValue !== null && attrValue !== void 0 && attrValue !== "") return false;
break;
default:
return false;
}
}
return true;
}
function resolvePlaceholders(action, sourceAttr) {
const resolved = { ...action };
if (resolved.value === "{{value}}") {
resolved.value = sourceAttr.value;
} else if (typeof resolved.value === "string" && resolved.value.includes("{{value}}")) {
resolved.value = resolved.value.replace(
"{{value}}",
String(sourceAttr.value)
);
}
return resolved;
}
function applyAttributesRules(attributes, rules, property) {
for (const rule of rules) {
const attrValue = attributes[rule.key];
if (attrValue === void 0) continue;
if (matchCondition(attrValue, rule.condition)) {
for (const action of rule.rules) {
const fn = actionRunner[action.fn];
if (fn) {
const resolvedAction = resolvePlaceholders(action, { key: rule.key, value: attrValue });
fn(property, resolvedAction);
}
}
}
}
}
function PropertyCustomizer(property, mapRules) {
const newProperty = clone(property);
const { reference } = newProperty;
processDescriptionTags(newProperty);
const { referenceRules, attributesRules } = mapRules;
if (referenceRules && reference) {
applyReferenceRules(reference, referenceRules, newProperty);
}
if (attributesRules) {
applyAttributesRules(newProperty.attributes, attributesRules, newProperty);
}
return newProperty;
}
// src/data/json-schema/property-model.json
var property_model_default = {
$ref: "#/definitions/PropertyModel",
definitions: {
PropertyModel: {
type: "object",
properties: {
reference: {
type: "string"
},
title: {
type: "string"
},
description: {
type: "string"
},
media_assets: {
type: "object",
properties: {
images: {
type: "array",
items: {
type: "object",
properties: {
full: {
type: "string"
},
md: {
type: "string"
},
sm: {
type: "string"
},
cover: {
type: "boolean"
}
},
required: [
"full"
],
additionalProperties: false
}
},
videos: {
type: "array",
items: {
type: "object",
properties: {
provider: {
type: "string"
},
id: {
type: "string"
},
embed_url: {
type: "string"
}
},
additionalProperties: false
}
},
virtual_tours: {
type: "array",
items: {
type: "object",
properties: {
embed_url: {
type: "string"
}
},
required: [
"embed_url"
],
additionalProperties: false
}
},
documents: {
type: "array",
items: {
type: "object",
properties: {
name: {
type: "string"
},
url: {
type: "string"
}
},
required: [
"name",
"url"
],
additionalProperties: false
}
}
},
additionalProperties: false
},
attributes: {
type: "object",
additionalProperties: {}
},
settings: {
type: "object",
properties: {
currency_unit: {
type: "string",
enum: [
"BRL",
"USD"
]
},
area_unit: {
type: "string",
enum: [
"m2",
"ft2"
]
},
distance_unit: {
type: "string",
enum: [
"km",
"mi",
"meters"
]
},
exibir_no_mapa: {
type: "boolean"
}
},
additionalProperties: false
},
updated_at: {
type: "string"
}
},
required: [
"reference",
"title",
"description",
"media_assets",
"attributes"
],
additionalProperties: false
}
},
$schema: "http://json-schema.org/draft-07/schema#"
};
// src/models/property-type.schema.json
var property_type_schema_default = {
$ref: "#/definitions/PropertyModel",
definitions: {
PropertyModel: {
type: "object",
properties: {
reference: {
type: "string"
},
title: {
type: "string"
},
description: {
type: "string"
},
media_assets: {
type: "object",
properties: {
images: {
type: "array",
items: {
type: "object",
properties: {
full: {
type: "string"
},
md: {
type: "string"
},
sm: {
type: "string"
},
cover: {
type: "boolean"
}
},
required: [
"full"
],
additionalProperties: false
}
},
videos: {
type: "array",
items: {
type: "object",
properties: {
provider: {
type: "string"
},
id: {
type: "string"
},
embed_url: {
type: "string"
}
},
additionalProperties: false
}