/** * * **Current State:** The `FFBProductionRepository.vectorSearch` method uses a `$project` aggregation stage targeting the legacy nested `site`, `phase`, and `block` layout. * * **Intended Mutation:** We will completely rewrite the `$project` stage in `vectorSearch` to align with the new flat warehouse-style transaction ledger fields, projecting flat keys such as `phaseCode`, `blockCode`, `blockDesc`, etc. * * **Risk Check:** Any controller or service expecting the older nested output (`site.name`, etc.) from `vectorSearch` calls will encounter empty or undefined parameters upon this structural alignment. */ import { Db, WithId } from 'mongodb'; import { FFBProduction } from './ffb-production.schema'; export class FFBProductionRepository { private readonly collectionName = 'FFB Production'; constructor(private readonly db: Db) { } private get collection() { return this.db.collection(this.collectionName); } async init() { const collections = await this.db.listCollections({ name: this.collectionName }).toArray(); if (collections.length === 0) { await this.db.createCollection(this.collectionName); console.log(`✅ Created collection: ${this.collectionName}`); } } async create(ffb: FFBProduction & { vector?: number[] }): Promise { const result = await this.collection.insertOne({ ...ffb, productionDate: new Date(ffb.productionDate), vector: ffb.vector, // optional vector }); return { ...ffb, _id: result.insertedId.toString() }; } async findAll(filter: Record = {}, options: { page?: number, limit?: number } = {}): Promise<{ data: (FFBProduction & { vector?: number[] })[], total: number }> { const { page = 1, limit = 10 } = options; const skip = (page - 1) * limit; const [results, total] = await Promise.all([ this.collection.find(filter).skip(skip).limit(limit).toArray(), this.collection.countDocuments(filter) ]); const data = results.map((r: WithId) => ({ ...r, _id: r._id?.toString(), })); return { data, total }; } async findById(id: string): Promise<(FFBProduction & { vector?: number[] }) | null> { const ObjectId = require('mongodb').ObjectId; const result = await this.collection.findOne({ _id: new ObjectId(id) as any }); return result ? { ...result, _id: result._id.toString() } : null; } async delete(id: string) { const ObjectId = require('mongodb').ObjectId; return this.collection.deleteOne({ _id: new ObjectId(id) as any }); } async deleteMany(filter: Record) { return this.collection.deleteMany(filter); } async update(id: string, update: Partial): Promise { const ObjectId = require('mongodb').ObjectId; await this.collection.updateOne( { _id: new ObjectId(id) as any }, { $set: update } ); } async findOne(filter: Record = {}): Promise { const result = await this.collection.findOne(filter); return result ? { ...result, _id: result._id.toString() } : null; } async distinct(field: string, filter: Record = {}): Promise { return this.collection.distinct(field, filter); } /** Optional: helper for vector search via aggregation */ async vectorSearch(vector: number[], k = 5, numCandidates = 50, filter: Record = {}) { return this.collection .aggregate([ { $vectorSearch: { index: 'vector_index', queryVector: vector, path: 'vector', numCandidates, limit: k, filter // Add filter here } }, { $project: { _id: 1, productionDate: 1, prjCode: 1, actCode: 1, actName: 1, entityCode: 1, orgnId: 1, orgnCode: 1, orgnFullName: 1, phaseCode: 1, phaseName: 1, phaseDesc: 1, blockCode: 1, blockName: 1, blockDesc: 1, truckNo: 1, millNo: 1, netWeight: 1, noOfBunches: 1, locArea: 1, remarks: 1, issues: 1, vector: 1, score: { "$meta": "vectorSearchScore" } // correctly get the score } } ]) .toArray(); } async aggregate(pipeline: Array>): Promise { const pipelineWithDates = pipeline.map(stage => { if ('$match' in stage && 'productionDate' in stage.$match) { const pd = stage.$match.productionDate; if (pd.$gte) pd.$gte = new Date(pd.$gte); if (pd.$lte) pd.$lte = new Date(pd.$lte); } return stage; }); const results = await this.collection.aggregate(pipelineWithDates).toArray(); return results.map(r => { const { vector, ...rest } = r; return rest; }); } }