turash/bugulma/frontend/lib/resource-flow-mapper.ts
Damir Mukimov 6347f42e20
Consolidate repositories: Remove nested frontend .git and merge into main repository
- Remove nested git repository from bugulma/frontend/.git
- Add all frontend files to main repository tracking
- Convert from separate frontend/backend repos to unified monorepo
- Preserve all frontend code and development history as tracked files
- Eliminate nested repository complexity for simpler development workflow

This creates a proper monorepo structure with frontend and backend
coexisting in the same repository for easier development and deployment.
2025-11-25 06:02:57 +01:00

152 lines
4.9 KiB
TypeScript

import type { OrganizationFormData } from '@/types';
import type { CreateResourceFlowRequest } from '@/schemas/backend/resource-flow';
import { resourceTypeSchema } from '@/schemas/backend/resource-flow';
/**
* Maps user-friendly "needs" and "offers" to backend ResourceFlow requests
* This is the translation layer between business-friendly UI and technical backend
*/
/**
* Infer resource type from resource name and category
* This is a simple heuristic - in production, you might want more sophisticated mapping
*/
function inferResourceType(resourceName: string, category?: string): string {
const name = resourceName.toLowerCase();
const cat = category?.toLowerCase() || '';
// Map common resource names to backend resource types
if (name.includes('heat') || name.includes('тепло') || cat.includes('energy')) {
return 'heat';
}
if (name.includes('water') || name.includes('вода') || cat.includes('water')) {
return 'water';
}
if (name.includes('steam') || name.includes('пар')) {
return 'steam';
}
if (name.includes('co2') || name.includes('углекислый') || cat.includes('emission')) {
return 'CO2';
}
if (name.includes('waste') || name.includes('отход') || cat.includes('waste')) {
return 'biowaste';
}
if (name.includes('cool') || name.includes('охлажден') || cat.includes('cooling')) {
return 'cooling';
}
if (name.includes('logistic') || name.includes('логистик') || cat.includes('logistics')) {
return 'logistics';
}
if (name.includes('material') || name.includes('материал') || cat.includes('material')) {
return 'materials';
}
if (name.includes('service') || name.includes('услуг') || cat.includes('service')) {
return 'service';
}
// Default fallback
return 'materials';
}
/**
* Parse quantity string to amount and unit
* Examples: "100 kg", "50 m3/hour", "200"
*/
function parseQuantity(quantityStr?: string): {
amount: number;
unit: string;
temporal_unit?: string;
} {
if (!quantityStr || !quantityStr.trim()) {
return { amount: 0, unit: 'unit' };
}
// Try to parse "amount unit" or "amount unit/temporal_unit"
const parts = quantityStr.trim().split(/\s+/);
const amount = parseFloat(parts[0]) || 0;
if (parts.length >= 2) {
const unitPart = parts[1];
if (unitPart.includes('/')) {
const [unit, temporal_unit] = unitPart.split('/');
return { amount, unit, temporal_unit };
}
return { amount, unit: unitPart };
}
return { amount, unit: 'unit' };
}
/**
* Convert user-friendly needs/offers to ResourceFlow requests
*/
export function convertNeedsAndOffersToResourceFlows(
formData: OrganizationFormData,
organizationId: string,
siteId: string
): CreateResourceFlowRequest[] {
const flows: CreateResourceFlowRequest[] = [];
// Convert needs (direction='input')
if (formData.needs && formData.needs.length > 0) {
for (const need of formData.needs) {
const resourceType = inferResourceType(need.resource_name, need.category);
const quantity = parseQuantity(need.quantity);
// Validate resource type
const validatedType = resourceTypeSchema.safeParse(resourceType);
const finalType = validatedType.success ? validatedType.data : 'materials';
if (!validatedType.success) {
console.warn(`Invalid resource type "${resourceType}", defaulting to "materials"`);
}
flows.push({
organization_id: organizationId,
site_id: siteId,
direction: 'input',
type: finalType,
quantity: {
amount: quantity.amount,
unit: quantity.unit,
temporal_unit: quantity.temporal_unit,
},
// Optional fields can be added later if needed
precision_level: 'declared', // User-entered data is "declared"
source_type: 'declared',
});
}
}
// Convert offers (direction='output')
if (formData.offers && formData.offers.length > 0) {
for (const offer of formData.offers) {
const resourceType = inferResourceType(offer.resource_name, offer.category);
const quantity = parseQuantity(offer.quantity);
// Validate resource type
const validatedType = resourceTypeSchema.safeParse(resourceType);
const finalType = validatedType.success ? validatedType.data : 'materials';
if (!validatedType.success) {
console.warn(`Invalid resource type "${resourceType}", defaulting to "materials"`);
}
flows.push({
organization_id: organizationId,
site_id: siteId,
direction: 'output',
type: finalType,
quantity: {
amount: quantity.amount,
unit: quantity.unit,
temporal_unit: quantity.temporal_unit,
},
// Optional fields can be added later if needed
precision_level: 'declared', // User-entered data is "declared"
source_type: 'declared',
});
}
}
return flows;
}