|
@@ -1,10 +1,15 @@
|
|
|
|
|
+/**
|
|
|
|
|
+ * * **Current State:** The legacy `FFBProductionService.create` method validates relational models (`site`, `phase`, `block`) using database `ObjectId` entries sequentially and then casts ID strings to `ObjectId`s.
|
|
|
|
|
+ * * **Intended Mutation:** We will strip out all legacy `Site` and `Phase` relational validations. We will query the master block configuration from `BlockService` using the flat `blockCode` and throw a `BadRequestException` if missing. We will implement an enrichment hook to copy `blockDesc` (and default `blockName` if null) from the master reference if they are omitted. We will also cast metric strings to actual numbers and pass the enriched record to `FFBVectorService`.
|
|
|
|
|
+ * * **Risk Check:** Any controller endpoint (such as `FFBProductionController`) that maps incoming client payloads or passes the old nested structure to `create` will break, as the method now strictly expects the flattened `FFBProduction` interface containing flat business keys.
|
|
|
|
|
+ */
|
|
|
|
|
+
|
|
|
import { Injectable, BadRequestException, Inject, forwardRef } from '@nestjs/common';
|
|
import { Injectable, BadRequestException, Inject, forwardRef } from '@nestjs/common';
|
|
|
import { MongoCoreService } from '../../mongo/mongo-core.service';
|
|
import { MongoCoreService } from '../../mongo/mongo-core.service';
|
|
|
import { FFBProduction } from '../ffb-production.schema';
|
|
import { FFBProduction } from '../ffb-production.schema';
|
|
|
-import { FFBProductionRepository } from 'src/FFB/mongo-ffb-production.repository';
|
|
|
|
|
|
|
+import { FFBProductionRepository } from '../mongo-ffb-production.repository';
|
|
|
import { FFBVectorService } from './ffb-vector.service';
|
|
import { FFBVectorService } from './ffb-vector.service';
|
|
|
import { FFBLangChainService } from './ffb-langchain.service';
|
|
import { FFBLangChainService } from './ffb-langchain.service';
|
|
|
-import { SiteService } from '../../site/services/site.service';
|
|
|
|
|
import { PhaseService } from '../../site/services/phase.service';
|
|
import { PhaseService } from '../../site/services/phase.service';
|
|
|
import { BlockService } from '../../site/services/block.service';
|
|
import { BlockService } from '../../site/services/block.service';
|
|
|
|
|
|
|
@@ -14,10 +19,10 @@ export class FFBProductionService {
|
|
|
|
|
|
|
|
constructor(
|
|
constructor(
|
|
|
private readonly mongoCore: MongoCoreService,
|
|
private readonly mongoCore: MongoCoreService,
|
|
|
|
|
+ @Inject(forwardRef(() => FFBVectorService))
|
|
|
private readonly vectorService: FFBVectorService,
|
|
private readonly vectorService: FFBVectorService,
|
|
|
|
|
+ @Inject(forwardRef(() => FFBLangChainService))
|
|
|
private readonly ffbLangChainService: FFBLangChainService,
|
|
private readonly ffbLangChainService: FFBLangChainService,
|
|
|
- @Inject(forwardRef(() => SiteService))
|
|
|
|
|
- private readonly siteService: SiteService,
|
|
|
|
|
@Inject(forwardRef(() => PhaseService))
|
|
@Inject(forwardRef(() => PhaseService))
|
|
|
private readonly phaseService: PhaseService,
|
|
private readonly phaseService: PhaseService,
|
|
|
@Inject(forwardRef(() => BlockService))
|
|
@Inject(forwardRef(() => BlockService))
|
|
@@ -37,40 +42,50 @@ export class FFBProductionService {
|
|
|
|
|
|
|
|
/** Create a new record with embedding */
|
|
/** Create a new record with embedding */
|
|
|
async create(record: FFBProduction) {
|
|
async create(record: FFBProduction) {
|
|
|
- // 1. Validate Site
|
|
|
|
|
- const site = await this.siteService.findById(record.site.id);
|
|
|
|
|
- if (!site) {
|
|
|
|
|
- throw new BadRequestException(`Site with ID ${record.site.id} does not exist.`);
|
|
|
|
|
|
|
+ // 1. Query the master block configuration from BlockService using blockCode
|
|
|
|
|
+ const block = await this.blockService.findById(record.blockCode);
|
|
|
|
|
+ if (!block) {
|
|
|
|
|
+ throw new BadRequestException(`Block with code ${record.blockCode} does not exist.`);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 2. Perform Data Enrichment
|
|
|
|
|
+ if (!record.blockName) {
|
|
|
|
|
+ record.blockName = 'Block ' + record.blockCode;
|
|
|
}
|
|
}
|
|
|
- if (site.name !== record.site.name) {
|
|
|
|
|
- throw new BadRequestException(`Site name mismatch: expected ${site.name}, got ${record.site.name}.`);
|
|
|
|
|
|
|
+ if (!record.blockDesc) {
|
|
|
|
|
+ record.blockDesc = block.blockDesc;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- // 2. Validate Phase
|
|
|
|
|
- const phase = await this.phaseService.findById(record.phase.id);
|
|
|
|
|
- if (!phase) {
|
|
|
|
|
- throw new BadRequestException(`Phase with ID ${record.phase.id} does not exist.`);
|
|
|
|
|
|
|
+ // 3. Perform Type Normalization
|
|
|
|
|
+ if (record.netWeight !== undefined && record.netWeight !== null) {
|
|
|
|
|
+ record.netWeight = Number(record.netWeight);
|
|
|
}
|
|
}
|
|
|
- if (phase.name !== record.phase.name) {
|
|
|
|
|
- throw new BadRequestException(`Phase name mismatch: expected ${phase.name}, got ${record.phase.name}.`);
|
|
|
|
|
|
|
+ if (record.noOfBunches !== undefined && record.noOfBunches !== null) {
|
|
|
|
|
+ record.noOfBunches = Number(record.noOfBunches);
|
|
|
}
|
|
}
|
|
|
- if (phase.siteId !== record.site.id) {
|
|
|
|
|
- throw new BadRequestException(`Phase with ID ${record.phase.id} does not belong to Site ${record.site.id}.`);
|
|
|
|
|
|
|
+ if (record.locArea !== undefined && record.locArea !== null) {
|
|
|
|
|
+ record.locArea = Number(record.locArea);
|
|
|
}
|
|
}
|
|
|
-
|
|
|
|
|
- // 3. Validate Block
|
|
|
|
|
- const block = await this.blockService.findById(record.block.id);
|
|
|
|
|
- if (!block) {
|
|
|
|
|
- throw new BadRequestException(`Block with ID ${record.block.id} does not exist.`);
|
|
|
|
|
|
|
+ if (record.actEntryNo !== undefined && record.actEntryNo !== null) {
|
|
|
|
|
+ record.actEntryNo = Number(record.actEntryNo);
|
|
|
}
|
|
}
|
|
|
- if (block.name !== record.block.name) {
|
|
|
|
|
- throw new BadRequestException(`Block name mismatch: expected ${block.name}, got ${record.block.name}.`);
|
|
|
|
|
|
|
+ if (record.actRound !== undefined && record.actRound !== null) {
|
|
|
|
|
+ record.actRound = Number(record.actRound);
|
|
|
}
|
|
}
|
|
|
- if (block.phaseId !== record.phase.id) {
|
|
|
|
|
- throw new BadRequestException(`Block with ID ${record.block.id} does not belong to Phase ${record.phase.id}.`);
|
|
|
|
|
|
|
+ if (record.docActQty !== undefined && record.docActQty !== null) {
|
|
|
|
|
+ record.docActQty = Number(record.docActQty);
|
|
|
|
|
+ }
|
|
|
|
|
+ if (record.ownNetWeight !== undefined && record.ownNetWeight !== null) {
|
|
|
|
|
+ record.ownNetWeight = Number(record.ownNetWeight);
|
|
|
|
|
+ }
|
|
|
|
|
+ if (record.budgetedFfb !== undefined && record.budgetedFfb !== null) {
|
|
|
|
|
+ record.budgetedFfb = Number(record.budgetedFfb);
|
|
|
|
|
+ }
|
|
|
|
|
+ if (record.orgnId !== undefined && record.orgnId !== null) {
|
|
|
|
|
+ record.orgnId = Number(record.orgnId);
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- // Use vector service to insert with embedding
|
|
|
|
|
|
|
+ // 4. Pass the fully enriched, flattened transaction ledger document to FFBVectorService.insertWithVector for final indexing
|
|
|
return this.vectorService.insertWithVector(record);
|
|
return this.vectorService.insertWithVector(record);
|
|
|
}
|
|
}
|
|
|
|
|
|
|
@@ -111,23 +126,19 @@ export class FFBProductionService {
|
|
|
let locationContext = '';
|
|
let locationContext = '';
|
|
|
|
|
|
|
|
if (data && Object.keys(data).length > 0) {
|
|
if (data && Object.keys(data).length > 0) {
|
|
|
- const { siteId, phaseId, blockId } = data;
|
|
|
|
|
|
|
+ const { phaseId, blockId } = data;
|
|
|
|
|
|
|
|
// Fetch additional context if IDs are provided
|
|
// Fetch additional context if IDs are provided
|
|
|
- const [site, phase, block] = await Promise.all([
|
|
|
|
|
- siteId ? this.siteService.findById(siteId) : null,
|
|
|
|
|
|
|
+ const [phase, block] = await Promise.all([
|
|
|
phaseId ? this.phaseService.findById(phaseId) : null,
|
|
phaseId ? this.phaseService.findById(phaseId) : null,
|
|
|
blockId ? this.blockService.findById(blockId) : null,
|
|
blockId ? this.blockService.findById(blockId) : null,
|
|
|
]);
|
|
]);
|
|
|
|
|
|
|
|
- if (site) {
|
|
|
|
|
- locationContext += `Site "${site.name}": ${site.description || 'No description available.'} Location: ${site.address}. `;
|
|
|
|
|
- }
|
|
|
|
|
if (phase) {
|
|
if (phase) {
|
|
|
- locationContext += `Phase "${phase.name}": ${phase.description || 'No description available.'} Status: ${phase.status}. `;
|
|
|
|
|
|
|
+ locationContext += `Phase "${phase.phaseCode}": ${phase.description || 'No description available.'}. `;
|
|
|
}
|
|
}
|
|
|
if (block) {
|
|
if (block) {
|
|
|
- locationContext += `Block "${block.name}": ${block.description || 'No description available.'} Size: ${block.size} ${block.sizeUom || 'units'}, Number of trees: ${block.numOfTrees}. `;
|
|
|
|
|
|
|
+ locationContext += `Block "${block.blockCode}": ${block.blockDesc || 'No description available.'} Size: ${block.plantedArea} ${block.plantedLocUOM || 'units'}, Number of trees: ${block.totalTrees}. `;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
contextInfo = `Production Data: ${JSON.stringify(data)}. `;
|
|
contextInfo = `Production Data: ${JSON.stringify(data)}. `;
|
|
@@ -145,6 +156,7 @@ ${locationContext ? `IMPORTANT: The remark should be SPECIFIC to the following l
|
|
|
${locationContext}
|
|
${locationContext}
|
|
|
` : ''}
|
|
` : ''}
|
|
|
|
|
|
|
|
|
|
+
|
|
|
Choose 2-3 specific aspects to focus on. Do not cover everything. Keep it grounded, practical, and observational.
|
|
Choose 2-3 specific aspects to focus on. Do not cover everything. Keep it grounded, practical, and observational.
|
|
|
|
|
|
|
|
You may choose from (but are not limited to) the following aspects:
|
|
You may choose from (but are not limited to) the following aspects:
|
|
@@ -179,23 +191,19 @@ Remark:`;
|
|
|
let locationContext = '';
|
|
let locationContext = '';
|
|
|
|
|
|
|
|
if (data && Object.keys(data).length > 0) {
|
|
if (data && Object.keys(data).length > 0) {
|
|
|
- const { siteId, phaseId, blockId } = data;
|
|
|
|
|
|
|
+ const { phaseId, blockId } = data;
|
|
|
|
|
|
|
|
// Fetch additional context if IDs are provided
|
|
// Fetch additional context if IDs are provided
|
|
|
- const [site, phase, block] = await Promise.all([
|
|
|
|
|
- siteId ? this.siteService.findById(siteId) : null,
|
|
|
|
|
|
|
+ const [phase, block] = await Promise.all([
|
|
|
phaseId ? this.phaseService.findById(phaseId) : null,
|
|
phaseId ? this.phaseService.findById(phaseId) : null,
|
|
|
blockId ? this.blockService.findById(blockId) : null,
|
|
blockId ? this.blockService.findById(blockId) : null,
|
|
|
]);
|
|
]);
|
|
|
|
|
|
|
|
- if (site) {
|
|
|
|
|
- locationContext += `Site "${site.name}": ${site.description || 'No description available.'} Location: ${site.address}. `;
|
|
|
|
|
- }
|
|
|
|
|
if (phase) {
|
|
if (phase) {
|
|
|
- locationContext += `Phase "${phase.name}": ${phase.description || 'No description available.'} Status: ${phase.status}. `;
|
|
|
|
|
|
|
+ locationContext += `Phase "${phase.phaseCode}": ${phase.description || 'No description available.'}. `;
|
|
|
}
|
|
}
|
|
|
if (block) {
|
|
if (block) {
|
|
|
- locationContext += `Block "${block.name}": ${block.description || 'No description available.'} Size: ${block.size} ${block.sizeUom || 'units'}, Number of trees: ${block.numOfTrees}. `;
|
|
|
|
|
|
|
+ locationContext += `Block "${block.blockCode}": ${block.blockDesc || 'No description available.'} Size: ${block.plantedArea} ${block.plantedLocUOM || 'units'}, Number of trees: ${block.totalTrees}. `;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
contextInfo = `Production Data: ${JSON.stringify(data)}. `;
|
|
contextInfo = `Production Data: ${JSON.stringify(data)}. `;
|