credl-parser-evaluator
Version:
TypeScript-based CREDL Parser and Evaluator that processes CREDL files and outputs complete Intermediate Representations
519 lines (486 loc) • 17.6 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.templateGenerators = exports.PROPERTY_TYPES = void 0;
exports.generateOfficeTemplate = generateOfficeTemplate;
exports.generateRetailTemplate = generateRetailTemplate;
exports.generateMixedUseTemplate = generateMixedUseTemplate;
exports.generateIndustrialTemplate = generateIndustrialTemplate;
exports.generateResidentialTemplate = generateResidentialTemplate;
exports.getTemplateGenerator = getTemplateGenerator;
exports.validateTemplateContext = validateTemplateContext;
exports.generateTemplateFile = generateTemplateFile;
const files_js_1 = require("./files.js");
// Property type definitions
exports.PROPERTY_TYPES = {
office: {
name: 'Office Building',
description: 'Commercial office space template',
icon: '🏢',
defaultUnits: 'sf'
},
retail: {
name: 'Retail Property',
description: 'Retail commercial space template',
icon: '🛍️',
defaultUnits: 'sf'
},
'mixed-use': {
name: 'Mixed-Use Development',
description: 'Mixed-use property with multiple space types',
icon: '🏬',
defaultUnits: 'sf'
},
industrial: {
name: 'Industrial Property',
description: 'Manufacturing, warehouse, and distribution facilities',
icon: '🏭',
defaultUnits: 'sf'
},
residential: {
name: 'Residential Property',
description: 'Apartment, condo, and residential developments',
icon: '🏠',
defaultUnits: 'sf'
}
};
// Template generators
function generateOfficeTemplate(context) {
const spaceCount = Math.min(parseInt(context.space_count || '10'), 50);
const rentPsf = parseFloat(context.rent_psf || '35');
const buildingSize = parseInt(context.building_size || '50000');
return `metadata:
version: "0.2"
name: "${context.project_name}"
description: "${context.description || `Office building in ${context.location || 'Prime Location'}`}"
created_date: "${context.created_date}"
author: "${context.author || 'CREDL User'}"
assets:
- id: "asset-main"
name: "${context.project_name}"
property_type: "Office"
location: "${context.location || 'Downtown Business District'}"
total_area_sf: ${buildingSize}
year_built: ${context.year_built || new Date().getFullYear()}
buildings:
- id: "building-main"
name: "Main Building"
floors: ${Math.ceil(spaceCount / 10)}
total_area_sf: ${buildingSize}
spaces:
${generateOfficeSpaces(spaceCount, buildingSize, rentPsf)}
assumptions:
${generateBasicAssumptions(context)}
models:
- name: "office_dcf_model"
type: "deterministic"
duration_years: ${context.analysis_period || '10'}
steps_per_year: 12
inputs: ["rent_growth", "expense_growth", "vacancy_rate", "cap_rate"]
outputs: ["NOI", "IRR", "NPV", "Cash_Flow"]
description: "Office building DCF analysis"
simulation:
type: "monte_carlo"
iterations: ${context.simulation_iterations || '1000'}
processes: {}
outputs:
summary_metrics: ["IRR", "NPV"]
outputs:
format: "json"
metrics: ["IRR", "NPV", "Cash_Flow", "NOI"]`;
}
function generateRetailTemplate(context) {
const spaceCount = Math.min(parseInt(context.space_count || '8'), 30);
const rentPsf = parseFloat(context.rent_psf || '45');
const buildingSize = parseInt(context.building_size || '40000');
return `metadata:
version: "0.2"
name: "${context.project_name}"
description: "${context.description || `Retail center in ${context.location || 'High-Traffic Area'}`}"
created_date: "${context.created_date}"
author: "${context.author || 'CREDL User'}"
assets:
- id: "asset-main"
name: "${context.project_name}"
property_type: "Retail"
location: "${context.location || 'Shopping District'}"
total_area_sf: ${buildingSize}
year_built: ${context.year_built || new Date().getFullYear()}
buildings:
- id: "building-main"
name: "Retail Center"
floors: ${Math.ceil(spaceCount / 12)}
total_area_sf: ${buildingSize}
spaces:
${generateRetailSpaces(spaceCount, buildingSize, rentPsf)}
assumptions:
${generateBasicAssumptions(context)}
models:
- name: "retail_dcf_model"
type: "deterministic"
duration_years: ${context.analysis_period || '10'}
steps_per_year: 12
inputs: ["rent_growth", "expense_growth", "vacancy_rate", "cap_rate"]
outputs: ["NOI", "IRR", "NPV", "Cash_Flow"]
description: "Retail property DCF analysis"
simulation:
type: "monte_carlo"
iterations: ${context.simulation_iterations || '1000'}
processes: {}
outputs:
summary_metrics: ["IRR", "NPV"]
outputs:
format: "json"
metrics: ["IRR", "NPV", "Cash_Flow", "NOI"]`;
}
function generateMixedUseTemplate(context) {
const spaceCount = Math.min(parseInt(context.space_count || '15'), 40);
const buildingSize = parseInt(context.building_size || '100000');
return `metadata:
version: "0.2"
name: "${context.project_name}"
description: "${context.description || `Mixed-use development in ${context.location || 'Urban Center'}`}"
created_date: "${context.created_date}"
author: "${context.author || 'CREDL User'}"
assets:
- id: "asset-main"
name: "${context.project_name}"
property_type: "Mixed Use"
location: "${context.location || 'Downtown Mixed-Use District'}"
total_area_sf: ${buildingSize}
year_built: ${context.year_built || new Date().getFullYear()}
buildings:
- id: "building-main"
name: "Mixed-Use Tower"
floors: ${Math.ceil(spaceCount / 8)}
total_area_sf: ${buildingSize}
spaces:
${generateMixedUseSpaces(spaceCount, buildingSize)}
assumptions:
${generateBasicAssumptions(context)}
models:
- name: "mixed_use_dcf_model"
type: "stochastic"
duration_years: ${context.analysis_period || '15'}
steps_per_year: 12
inputs: ["rent_growth", "expense_growth", "vacancy_rate", "cap_rate"]
outputs: ["NOI", "IRR", "NPV", "Cash_Flow"]
description: "Mixed-use property DCF analysis"
simulation:
type: "monte_carlo"
iterations: ${context.simulation_iterations || '2000'}
processes: {}
outputs:
summary_metrics: ["IRR", "NPV"]
outputs:
format: "json"
metrics: ["IRR", "NPV", "Cash_Flow", "NOI"]`;
}
function generateIndustrialTemplate(context) {
const spaceCount = Math.min(parseInt(context.space_count || '5'), 20);
const rentPsf = parseFloat(context.rent_psf || '8');
const buildingSize = parseInt(context.building_size || '200000');
return `metadata:
version: "0.2"
name: "${context.project_name}"
description: "${context.description || `Industrial facility in ${context.location || 'Industrial Park'}`}"
created_date: "${context.created_date}"
author: "${context.author || 'CREDL User'}"
assets:
- id: "asset-main"
name: "${context.project_name}"
property_type: "Industrial"
location: "${context.location || 'Industrial District'}"
total_area_sf: ${buildingSize}
year_built: ${context.year_built || new Date().getFullYear()}
buildings:
- id: "building-main"
name: "Industrial Facility"
floors: 1
total_area_sf: ${buildingSize}
spaces:
${generateIndustrialSpaces(spaceCount, buildingSize, rentPsf)}
assumptions:
${generateBasicAssumptions(context)}
models:
- name: "industrial_dcf_model"
type: "deterministic"
duration_years: ${context.analysis_period || '10'}
steps_per_year: 12
inputs: ["rent_growth", "expense_growth", "vacancy_rate", "cap_rate"]
outputs: ["NOI", "IRR", "NPV", "Cash_Flow"]
description: "Industrial property DCF analysis"
simulation:
type: "monte_carlo"
iterations: ${context.simulation_iterations || '1000'}
processes: {}
outputs:
summary_metrics: ["IRR", "NPV"]
outputs:
format: "json"
metrics: ["IRR", "NPV", "Cash_Flow", "NOI"]`;
}
function generateResidentialTemplate(context) {
const unitCount = Math.min(parseInt(context.unit_count || '20'), 100);
const rentPerUnit = parseFloat(context.rent_per_unit || '2500');
const buildingSize = parseInt(context.building_size || '50000');
return `metadata:
version: "0.2"
name: "${context.project_name}"
description: "${context.description || `Residential development in ${context.location || 'Desirable Neighborhood'}`}"
created_date: "${context.created_date}"
author: "${context.author || 'CREDL User'}"
assets:
- id: "asset-main"
name: "${context.project_name}"
property_type: "Residential"
location: "${context.location || 'Residential District'}"
total_area_sf: ${buildingSize}
year_built: ${context.year_built || new Date().getFullYear()}
buildings:
- id: "building-main"
name: "Residential Building"
floors: ${Math.ceil(unitCount / 8)}
total_area_sf: ${buildingSize}
spaces:
${generateResidentialSpaces(unitCount, rentPerUnit)}
assumptions:
${generateBasicAssumptions(context)}
models:
- name: "residential_dcf_model"
type: "stochastic"
duration_years: ${context.analysis_period || '10'}
steps_per_year: 12
inputs: ["rent_growth", "expense_growth", "vacancy_rate", "cap_rate"]
outputs: ["NOI", "IRR", "NPV", "Cash_Flow"]
description: "Residential property DCF analysis"
simulation:
type: "monte_carlo"
iterations: ${context.simulation_iterations || '1500'}
processes: {}
outputs:
summary_metrics: ["IRR", "NPV"]
outputs:
format: "json"
metrics: ["IRR", "NPV", "Cash_Flow", "NOI"]`;
}
// Helper functions for space generation
function generateOfficeSpaces(count, buildingSize, rentPsf) {
const spaces = [];
const avgSize = Math.floor(buildingSize / count);
for (let i = 1; i <= count; i++) {
const isCommonArea = i > count * 0.9;
const spaceType = isCommonArea ? 'common_area' : 'office';
const size = isCommonArea ? Math.floor(avgSize * 0.5) : avgSize + Math.floor(Math.random() * 1000 - 500);
const isVacant = i % 8 === 0;
spaces.push(` - id: "space-${i.toString().padStart(3, '0')}"
parent_building: "building-main"
type: "${spaceType}"
area_sf: ${size}
${isCommonArea ? 'common_area: true' : `lease:
status: "${isVacant ? 'vacant' : 'leased'}"
${isVacant ? '' : `tenant: "Tenant ${i}"`}
rent_psf: ${rentPsf}
lease_type: "NNN"
start_date: "2024-01-01"
end_date: "2029-01-01"`}`);
}
return spaces.join('\n\n');
}
function generateRetailSpaces(count, buildingSize, rentPsf) {
const spaces = [];
const avgSize = Math.floor(buildingSize / count);
for (let i = 1; i <= count; i++) {
const isCommonArea = i > count * 0.85;
const spaceType = isCommonArea ? 'common_area' : 'retail';
const size = isCommonArea ? Math.floor(avgSize * 0.4) : avgSize + Math.floor(Math.random() * 800 - 400);
const isVacant = i % 6 === 0;
spaces.push(` - id: "space-${i.toString().padStart(3, '0')}"
parent_building: "building-main"
type: "${spaceType}"
area_sf: ${size}
${isCommonArea ? 'common_area: true' : `lease:
status: "${isVacant ? 'vacant' : 'leased'}"
${isVacant ? '' : `tenant: "Retailer ${i}"`}
rent_psf: ${rentPsf}
lease_type: "NNN"
start_date: "2024-01-01"
end_date: "2029-01-01"`}`);
}
return spaces.join('\n\n');
}
function generateMixedUseSpaces(count, _buildingSize) {
const spaces = [];
const officeCount = Math.floor(count * 0.4);
const retailCount = Math.floor(count * 0.3);
const residentialCount = count - officeCount - retailCount;
let spaceId = 1;
// Office spaces
for (let i = 0; i < officeCount; i++) {
spaces.push(` - id: "space-${spaceId.toString().padStart(3, '0')}"
parent_building: "building-main"
type: "office"
area_sf: ${2000 + Math.floor(Math.random() * 3000)}
lease:
status: "${spaceId % 8 === 0 ? 'vacant' : 'leased'}"
${spaceId % 8 === 0 ? '' : `tenant: "Office Tenant ${spaceId}"`}
rent_psf: 35
lease_type: "NNN"
start_date: "2024-01-01"
end_date: "2029-01-01"`);
spaceId++;
}
// Retail spaces
for (let i = 0; i < retailCount; i++) {
spaces.push(` - id: "space-${spaceId.toString().padStart(3, '0')}"
parent_building: "building-main"
type: "retail"
area_sf: ${1500 + Math.floor(Math.random() * 2500)}
lease:
status: "${spaceId % 6 === 0 ? 'vacant' : 'leased'}"
${spaceId % 6 === 0 ? '' : `tenant: "Retail Tenant ${spaceId}"`}
rent_psf: 45
lease_type: "NNN"
start_date: "2024-01-01"
end_date: "2029-01-01"`);
spaceId++;
}
// Residential spaces
for (let i = 0; i < residentialCount; i++) {
spaces.push(` - id: "space-${spaceId.toString().padStart(3, '0')}"
parent_building: "building-main"
type: "residential"
area_sf: ${800 + Math.floor(Math.random() * 800)}
lease:
status: "${spaceId % 10 === 0 ? 'vacant' : 'leased'}"
${spaceId % 10 === 0 ? '' : `tenant: "Resident ${spaceId}"`}
rent_monthly: 2500
lease_type: "residential"
start_date: "2024-01-01"
end_date: "2024-12-31"`);
spaceId++;
}
return spaces.join('\n\n');
}
function generateIndustrialSpaces(count, buildingSize, rentPsf) {
const spaces = [];
const avgSize = Math.floor(buildingSize / count);
for (let i = 1; i <= count; i++) {
const spaceType = i === 1 ? 'office' : i <= count * 0.9 ? 'warehouse' : 'dock';
const size = spaceType === 'office' ? 2000 : spaceType === 'dock' ? 500 : avgSize;
const rent = spaceType === 'office' ? rentPsf * 3 : spaceType === 'dock' ? 0 : rentPsf;
const isVacant = i % 10 === 0;
spaces.push(` - id: "space-${i.toString().padStart(3, '0')}"
parent_building: "building-main"
type: "${spaceType}"
area_sf: ${size}
${spaceType === 'dock' ? 'dock_door: true' : `lease:
status: "${isVacant ? 'vacant' : 'leased'}"
${isVacant ? '' : `tenant: "Industrial Tenant ${i}"`}
rent_psf: ${rent}
lease_type: "NNN"
start_date: "2024-01-01"
end_date: "2029-01-01"`}`);
}
return spaces.join('\n\n');
}
function generateResidentialSpaces(unitCount, avgRent) {
const spaces = [];
const unitTypes = ['studio', '1br', '2br', '3br'];
for (let i = 1; i <= unitCount; i++) {
const unitType = unitTypes[i % unitTypes.length] || '1br';
const size = getUnitSize(unitType);
const rent = getUnitRent(unitType, avgRent);
spaces.push(` - id: "unit-${i.toString().padStart(3, '0')}"
parent_building: "building-main"
type: "residential"
unit_type: "${unitType}"
area_sf: ${size}
lease:
status: "${i % 8 === 0 ? 'vacant' : 'leased'}"
${i % 8 === 0 ? '' : `tenant: "Resident ${i}"`}
rent_monthly: ${rent}
lease_type: "residential"
start_date: "2024-01-01"
end_date: "2024-12-31"`);
}
return spaces.join('\n\n');
}
function generateBasicAssumptions(context) {
return ` - name: "rent_growth"
type: "distribution"
distribution: "normal"
parameters:
mean: ${parseFloat(context.rent_growth || '3') / 100}
stddev: 0.01
scope: "global"
tags: ["revenue", "growth"]
- name: "expense_growth"
type: "distribution"
distribution: "normal"
parameters:
mean: ${parseFloat(context.expense_growth || '2.5') / 100}
stddev: 0.005
scope: "global"
tags: ["expense", "growth"]
- name: "vacancy_rate"
type: "distribution"
distribution: "beta"
parameters:
alpha: 2
beta: 20
min: 0.02
max: 0.15
scope: "global"
tags: ["occupancy", "risk"]
- name: "cap_rate"
type: "fixed"
value: ${parseFloat(context.cap_rate || '6.5') / 100}
scope: "global"
tags: ["valuation", "yield"]`;
}
function getUnitSize(unitType) {
const sizes = { studio: 500, '1br': 750, '2br': 1100, '3br': 1400 };
return sizes[unitType] || 750;
}
function getUnitRent(unitType, avgRent) {
const multipliers = { studio: 0.7, '1br': 1.0, '2br': 1.4, '3br': 1.8 };
return Math.floor(avgRent * (multipliers[unitType] || 1.0));
}
// Template management
exports.templateGenerators = {
office: generateOfficeTemplate,
retail: generateRetailTemplate,
'mixed-use': generateMixedUseTemplate,
industrial: generateIndustrialTemplate,
residential: generateResidentialTemplate
};
function getTemplateGenerator(propertyType) {
return exports.templateGenerators[propertyType];
}
function validateTemplateContext(context, _propertyType) {
const errors = [];
if (!context.project_name || context.project_name.trim().length === 0) {
errors.push('Project name is required');
}
if (!context.created_date) {
errors.push('Created date is required');
}
return errors;
}
async function generateTemplateFile(propertyType, context, outputPath) {
const errors = validateTemplateContext(context, propertyType);
if (errors.length > 0) {
throw new Error(`Template validation failed: ${errors.join(', ')}`);
}
const generator = getTemplateGenerator(propertyType);
if (!generator) {
throw new Error(`No template generator found for property type: ${propertyType}`);
}
const content = generator(context);
await (0, files_js_1.safeWriteFile)(outputPath, content, {
createDirectories: true,
backup: false,
overwrite: false
});
}
//# sourceMappingURL=templates.js.map