medicodeapp / backend /src /modules /codes /codes.service.ts
Denisijcu's picture
upload files
c98875e
import { Injectable, NotFoundException } from '@nestjs/common';
import { InjectModel } from '@nestjs/mongoose';
import { Model } from 'mongoose';
import { DiagnosticCode, DiagnosticCodeDocument } from './schemas/diagnostic-code.schema';
import { ProcedureCode, ProcedureCodeDocument } from './schemas/procedure-code.schema';
import { CreateDiagnosticCodeDto } from './dto/create-diagnostic-code.dto';
import { CreateProcedureCodeDto } from './dto/create-procedure-code.dto';
import { SearchCodesDto } from './dto/search-codes.dto';
export interface GlobalSearchResult {
icd10: Array<Record<string, any> & { type: string }>;
cpt: Array<Record<string, any> & { type: string }>;
}
@Injectable()
export class CodesService {
constructor(
@InjectModel(DiagnosticCode.name) private icd10Model: Model<DiagnosticCodeDocument>,
@InjectModel(ProcedureCode.name) private cptModel: Model<ProcedureCodeDocument>,
) {}
// ─── ICD-10 ───────────────────────────────────────────────────────────────
async searchICD10(dto: SearchCodesDto) {
const { q, page = 1, limit = 20, category, billable } = dto;
const filter: any = { isActive: true };
if (q) {
filter.$or = [
{ code: { $regex: q, $options: 'i' } },
{ description: { $regex: q, $options: 'i' } },
];
}
if (category) filter.category = category;
if (billable !== undefined) filter.billable = billable;
const [data, total] = await Promise.all([
this.icd10Model
.find(filter)
.skip((page - 1) * limit)
.limit(limit)
.sort({ code: 1 }),
this.icd10Model.countDocuments(filter),
]);
return { data, total, page, limit, pages: Math.ceil(total / limit) };
}
async getICD10ByCode(code: string) {
const result = await this.icd10Model.findOne({ code: code.toUpperCase() });
if (!result) throw new NotFoundException(`ICD-10 code ${code} not found`);
return result;
}
async createICD10(dto: CreateDiagnosticCodeDto) {
return this.icd10Model.create(dto);
}
async getICD10Categories() {
return this.icd10Model.distinct('category', { isActive: true });
}
// ─── CPT ──────────────────────────────────────────────────────────────────
async searchCPT(dto: SearchCodesDto) {
const { q, page = 1, limit = 20, category, billable } = dto;
const filter: any = { isActive: true };
if (q) {
filter.$or = [
{ code: { $regex: q, $options: 'i' } },
{ description: { $regex: q, $options: 'i' } },
];
}
if (category) filter.category = category;
if (billable !== undefined) filter.billable = billable;
const [data, total] = await Promise.all([
this.cptModel
.find(filter)
.skip((page - 1) * limit)
.limit(limit)
.sort({ code: 1 }),
this.cptModel.countDocuments(filter),
]);
return { data, total, page, limit, pages: Math.ceil(total / limit) };
}
async getCPTByCode(code: string) {
const result = await this.cptModel.findOne({ code });
if (!result) throw new NotFoundException(`CPT code ${code} not found`);
return result;
}
async createCPT(dto: CreateProcedureCodeDto) {
return this.cptModel.create(dto);
}
async getCPTCategories() {
return this.cptModel.distinct('category', { isActive: true });
}
// ─── Global search ────────────────────────────────────────────────────────
async globalSearch(q: string, limit = 10): Promise<GlobalSearchResult> {
const filter = {
isActive: true,
$or: [
{ code: { $regex: q, $options: 'i' } },
{ description: { $regex: q, $options: 'i' } },
],
};
const [icd10, cpt] = await Promise.all([
this.icd10Model.find(filter).limit(limit).lean(),
this.cptModel.find(filter).limit(limit).lean(),
]);
return {
icd10: icd10.map((c) => ({ ...c, type: 'ICD-10' })),
cpt: cpt.map((c) => ({ ...c, type: 'CPT' })),
};
}
}