fix: remove lingering FM references from insights services
tx.fm.count calls + fmsImplicating fields were missed in the main FM removal pass. Drops the field from Category/Manufacturer/PartModel insights types, services, tests, and detail-page stat cards. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -17,7 +17,6 @@ interface FakeArgs {
|
|||||||
};
|
};
|
||||||
repairCount: number;
|
repairCount: number;
|
||||||
distinctFailedBrokenPartIds: string[];
|
distinctFailedBrokenPartIds: string[];
|
||||||
fmCount: number;
|
|
||||||
modelStateGroups: { partModelId: string; state: string; count: number }[];
|
modelStateGroups: { partModelId: string; state: string; count: number }[];
|
||||||
allModels: {
|
allModels: {
|
||||||
id: string;
|
id: string;
|
||||||
@@ -65,9 +64,6 @@ function makeTx(args: FakeArgs): Tx {
|
|||||||
return args.repairsWithModel;
|
return args.repairsWithModel;
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
fm: {
|
|
||||||
count: async () => args.fmCount,
|
|
||||||
},
|
|
||||||
};
|
};
|
||||||
return tx as unknown as Tx;
|
return tx as unknown as Tx;
|
||||||
}
|
}
|
||||||
@@ -79,7 +75,6 @@ const empty: FakeArgs = {
|
|||||||
priceAgg: { sum: null, avg: null, min: null, max: null, count: 0 },
|
priceAgg: { sum: null, avg: null, min: null, max: null, count: 0 },
|
||||||
repairCount: 0,
|
repairCount: 0,
|
||||||
distinctFailedBrokenPartIds: [],
|
distinctFailedBrokenPartIds: [],
|
||||||
fmCount: 0,
|
|
||||||
modelStateGroups: [],
|
modelStateGroups: [],
|
||||||
allModels: [],
|
allModels: [],
|
||||||
eolModels: [],
|
eolModels: [],
|
||||||
@@ -124,15 +119,14 @@ describe('categories.getInsights', () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('counts repairs, distinct failed parts, and FMs implicating the category', async () => {
|
it('counts repairs and distinct failed parts', async () => {
|
||||||
const tx = makeTx({
|
const tx = makeTx({
|
||||||
...empty,
|
...empty,
|
||||||
repairCount: 5,
|
repairCount: 5,
|
||||||
distinctFailedBrokenPartIds: ['p1', 'p2', 'p3'],
|
distinctFailedBrokenPartIds: ['p1', 'p2', 'p3'],
|
||||||
fmCount: 4,
|
|
||||||
});
|
});
|
||||||
const r = await getInsights(tx, 'cat');
|
const r = await getInsights(tx, 'cat');
|
||||||
expect(r!.failures).toEqual({ repairs: 5, distinctFailedParts: 3, fmsImplicating: 4 });
|
expect(r!.failures).toEqual({ repairs: 5, distinctFailedParts: 3 });
|
||||||
});
|
});
|
||||||
|
|
||||||
it('derives topModelsByUnits from modelStateGroups, sorted desc, truncated to 8', async () => {
|
it('derives topModelsByUnits from modelStateGroups, sorted desc, truncated to 8', async () => {
|
||||||
|
|||||||
@@ -28,7 +28,6 @@ export async function getInsights(tx: Tx, id: string): Promise<CategoryInsights
|
|||||||
priceAgg,
|
priceAgg,
|
||||||
repairsCount,
|
repairsCount,
|
||||||
distinctFailedParts,
|
distinctFailedParts,
|
||||||
fmsImplicating,
|
|
||||||
modelStateGroups,
|
modelStateGroups,
|
||||||
allModels,
|
allModels,
|
||||||
eolModels,
|
eolModels,
|
||||||
@@ -50,9 +49,6 @@ export async function getInsights(tx: Tx, id: string): Promise<CategoryInsights
|
|||||||
select: { brokenPartId: true },
|
select: { brokenPartId: true },
|
||||||
distinct: ['brokenPartId'],
|
distinct: ['brokenPartId'],
|
||||||
}),
|
}),
|
||||||
tx.fm.count({
|
|
||||||
where: { problemParts: { some: { part: modelWhere } } },
|
|
||||||
}),
|
|
||||||
tx.part.groupBy({
|
tx.part.groupBy({
|
||||||
by: ['partModelId', 'state'],
|
by: ['partModelId', 'state'],
|
||||||
where: modelWhere,
|
where: modelWhere,
|
||||||
@@ -145,7 +141,6 @@ export async function getInsights(tx: Tx, id: string): Promise<CategoryInsights
|
|||||||
failures: {
|
failures: {
|
||||||
repairs: repairsCount,
|
repairs: repairsCount,
|
||||||
distinctFailedParts: distinctFailedParts.length,
|
distinctFailedParts: distinctFailedParts.length,
|
||||||
fmsImplicating,
|
|
||||||
},
|
},
|
||||||
byManufacturer,
|
byManufacturer,
|
||||||
topModelsByUnits,
|
topModelsByUnits,
|
||||||
|
|||||||
@@ -17,7 +17,6 @@ interface FakeArgs {
|
|||||||
};
|
};
|
||||||
repairCount: number;
|
repairCount: number;
|
||||||
distinctFailedBrokenPartIds: string[];
|
distinctFailedBrokenPartIds: string[];
|
||||||
fmCount: number;
|
|
||||||
// rows used by part.groupBy({ by: ['partModelId','state'] })
|
// rows used by part.groupBy({ by: ['partModelId','state'] })
|
||||||
modelStateGroups: { partModelId: string; state: string; count: number }[];
|
modelStateGroups: { partModelId: string; state: string; count: number }[];
|
||||||
// rows for the partModel.findMany(select: { id, mpn, category }) call
|
// rows for the partModel.findMany(select: { id, mpn, category }) call
|
||||||
@@ -71,9 +70,6 @@ function makeTx(args: FakeArgs): Tx {
|
|||||||
return args.repairsWithModel;
|
return args.repairsWithModel;
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
fm: {
|
|
||||||
count: async () => args.fmCount,
|
|
||||||
},
|
|
||||||
};
|
};
|
||||||
return tx as unknown as Tx;
|
return tx as unknown as Tx;
|
||||||
}
|
}
|
||||||
@@ -85,7 +81,6 @@ const empty: FakeArgs = {
|
|||||||
priceAgg: { sum: null, avg: null, min: null, max: null, count: 0 },
|
priceAgg: { sum: null, avg: null, min: null, max: null, count: 0 },
|
||||||
repairCount: 0,
|
repairCount: 0,
|
||||||
distinctFailedBrokenPartIds: [],
|
distinctFailedBrokenPartIds: [],
|
||||||
fmCount: 0,
|
|
||||||
modelStateGroups: [],
|
modelStateGroups: [],
|
||||||
allModels: [],
|
allModels: [],
|
||||||
eolModels: [],
|
eolModels: [],
|
||||||
@@ -130,15 +125,14 @@ describe('manufacturers.getInsights', () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('counts repairs, distinct failed parts, and FMs implicating the manufacturer', async () => {
|
it('counts repairs and distinct failed parts', async () => {
|
||||||
const tx = makeTx({
|
const tx = makeTx({
|
||||||
...empty,
|
...empty,
|
||||||
repairCount: 5,
|
repairCount: 5,
|
||||||
distinctFailedBrokenPartIds: ['p1', 'p2', 'p3'],
|
distinctFailedBrokenPartIds: ['p1', 'p2', 'p3'],
|
||||||
fmCount: 4,
|
|
||||||
});
|
});
|
||||||
const r = await getInsights(tx, 'mfr');
|
const r = await getInsights(tx, 'mfr');
|
||||||
expect(r!.failures).toEqual({ repairs: 5, distinctFailedParts: 3, fmsImplicating: 4 });
|
expect(r!.failures).toEqual({ repairs: 5, distinctFailedParts: 3 });
|
||||||
});
|
});
|
||||||
|
|
||||||
it('derives topModelsByUnits from modelStateGroups, sorted desc, truncated to 8', async () => {
|
it('derives topModelsByUnits from modelStateGroups, sorted desc, truncated to 8', async () => {
|
||||||
|
|||||||
@@ -27,7 +27,6 @@ export async function getInsights(tx: Tx, id: string): Promise<ManufacturerInsig
|
|||||||
priceAgg,
|
priceAgg,
|
||||||
repairsCount,
|
repairsCount,
|
||||||
distinctFailedParts,
|
distinctFailedParts,
|
||||||
fmsImplicating,
|
|
||||||
modelStateGroups,
|
modelStateGroups,
|
||||||
allModels,
|
allModels,
|
||||||
eolModels,
|
eolModels,
|
||||||
@@ -49,7 +48,6 @@ export async function getInsights(tx: Tx, id: string): Promise<ManufacturerInsig
|
|||||||
select: { brokenPartId: true },
|
select: { brokenPartId: true },
|
||||||
distinct: ['brokenPartId'],
|
distinct: ['brokenPartId'],
|
||||||
}),
|
}),
|
||||||
tx.fm.count({ where: { problemParts: { some: { part: { manufacturerId: id } } } } }),
|
|
||||||
tx.part.groupBy({
|
tx.part.groupBy({
|
||||||
by: ['partModelId', 'state'],
|
by: ['partModelId', 'state'],
|
||||||
where: { manufacturerId: id },
|
where: { manufacturerId: id },
|
||||||
@@ -143,7 +141,6 @@ export async function getInsights(tx: Tx, id: string): Promise<ManufacturerInsig
|
|||||||
failures: {
|
failures: {
|
||||||
repairs: repairsCount,
|
repairs: repairsCount,
|
||||||
distinctFailedParts: distinctFailedParts.length,
|
distinctFailedParts: distinctFailedParts.length,
|
||||||
fmsImplicating,
|
|
||||||
},
|
},
|
||||||
byCategory,
|
byCategory,
|
||||||
topModelsByUnits,
|
topModelsByUnits,
|
||||||
|
|||||||
@@ -17,7 +17,6 @@ function makeTx(args: {
|
|||||||
};
|
};
|
||||||
repairCount: number;
|
repairCount: number;
|
||||||
distinctFailedBrokenPartIds: string[];
|
distinctFailedBrokenPartIds: string[];
|
||||||
fmCount: number;
|
|
||||||
}): Tx {
|
}): Tx {
|
||||||
const tx = {
|
const tx = {
|
||||||
partModel: {
|
partModel: {
|
||||||
@@ -44,9 +43,6 @@ function makeTx(args: {
|
|||||||
findMany: async () =>
|
findMany: async () =>
|
||||||
args.distinctFailedBrokenPartIds.map((brokenPartId) => ({ brokenPartId })),
|
args.distinctFailedBrokenPartIds.map((brokenPartId) => ({ brokenPartId })),
|
||||||
},
|
},
|
||||||
fm: {
|
|
||||||
count: async () => args.fmCount,
|
|
||||||
},
|
|
||||||
};
|
};
|
||||||
return tx as unknown as Tx;
|
return tx as unknown as Tx;
|
||||||
}
|
}
|
||||||
@@ -60,7 +56,6 @@ describe('part-models.getInsights', () => {
|
|||||||
priceAgg: { sum: null, avg: null, min: null, max: null, count: 0 },
|
priceAgg: { sum: null, avg: null, min: null, max: null, count: 0 },
|
||||||
repairCount: 0,
|
repairCount: 0,
|
||||||
distinctFailedBrokenPartIds: [],
|
distinctFailedBrokenPartIds: [],
|
||||||
fmCount: 0,
|
|
||||||
});
|
});
|
||||||
const r = await getInsights(tx, 'nope');
|
const r = await getInsights(tx, 'nope');
|
||||||
expect(r).toBeNull();
|
expect(r).toBeNull();
|
||||||
@@ -77,7 +72,6 @@ describe('part-models.getInsights', () => {
|
|||||||
priceAgg: { sum: 900, avg: 450, min: 100, max: 500, count: 2 },
|
priceAgg: { sum: 900, avg: 450, min: 100, max: 500, count: 2 },
|
||||||
repairCount: 0,
|
repairCount: 0,
|
||||||
distinctFailedBrokenPartIds: [],
|
distinctFailedBrokenPartIds: [],
|
||||||
fmCount: 0,
|
|
||||||
});
|
});
|
||||||
|
|
||||||
const r = await getInsights(tx, 'pm');
|
const r = await getInsights(tx, 'pm');
|
||||||
@@ -98,7 +92,6 @@ describe('part-models.getInsights', () => {
|
|||||||
priceAgg: { sum: 600, avg: 300, min: 100, max: 500, count: 2 },
|
priceAgg: { sum: 600, avg: 300, min: 100, max: 500, count: 2 },
|
||||||
repairCount: 0,
|
repairCount: 0,
|
||||||
distinctFailedBrokenPartIds: [],
|
distinctFailedBrokenPartIds: [],
|
||||||
fmCount: 0,
|
|
||||||
});
|
});
|
||||||
|
|
||||||
const r = await getInsights(tx, 'pm');
|
const r = await getInsights(tx, 'pm');
|
||||||
@@ -119,7 +112,6 @@ describe('part-models.getInsights', () => {
|
|||||||
priceAgg: { sum: null, avg: null, min: null, max: null, count: 0 },
|
priceAgg: { sum: null, avg: null, min: null, max: null, count: 0 },
|
||||||
repairCount: 0,
|
repairCount: 0,
|
||||||
distinctFailedBrokenPartIds: [],
|
distinctFailedBrokenPartIds: [],
|
||||||
fmCount: 0,
|
|
||||||
});
|
});
|
||||||
|
|
||||||
const r = await getInsights(tx, 'pm');
|
const r = await getInsights(tx, 'pm');
|
||||||
@@ -132,23 +124,20 @@ describe('part-models.getInsights', () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('counts repairs, distinct failed parts, and FMs implicating the model', async () => {
|
it('counts repairs and distinct failed parts', async () => {
|
||||||
const tx = makeTx({
|
const tx = makeTx({
|
||||||
modelExists: true,
|
modelExists: true,
|
||||||
partCount: 5,
|
partCount: 5,
|
||||||
stateRows: [],
|
stateRows: [],
|
||||||
priceAgg: { sum: 0, avg: null, min: null, max: null, count: 0 },
|
priceAgg: { sum: 0, avg: null, min: null, max: null, count: 0 },
|
||||||
repairCount: 3,
|
repairCount: 3,
|
||||||
// one part failed twice → 2 distinct broken parts
|
|
||||||
distinctFailedBrokenPartIds: ['part-a', 'part-b'],
|
distinctFailedBrokenPartIds: ['part-a', 'part-b'],
|
||||||
fmCount: 2,
|
|
||||||
});
|
});
|
||||||
|
|
||||||
const r = await getInsights(tx, 'pm');
|
const r = await getInsights(tx, 'pm');
|
||||||
expect(r!.failures).toEqual({
|
expect(r!.failures).toEqual({
|
||||||
repairs: 3,
|
repairs: 3,
|
||||||
distinctFailedParts: 2,
|
distinctFailedParts: 2,
|
||||||
fmsImplicating: 2,
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -43,7 +43,7 @@ export async function getInsights(tx: Tx, id: string): Promise<PartModelInsights
|
|||||||
const model = await tx.partModel.findUnique({ where: { id }, select: { id: true } });
|
const model = await tx.partModel.findUnique({ where: { id }, select: { id: true } });
|
||||||
if (!model) return null;
|
if (!model) return null;
|
||||||
|
|
||||||
const [totalParts, stateRows, priceAgg, repairs, failedParts, fmsImplicating] = await Promise.all([
|
const [totalParts, stateRows, priceAgg, repairs, failedParts] = await Promise.all([
|
||||||
tx.part.count({ where: { partModelId: id } }),
|
tx.part.count({ where: { partModelId: id } }),
|
||||||
tx.part.groupBy({
|
tx.part.groupBy({
|
||||||
by: ['state'],
|
by: ['state'],
|
||||||
@@ -65,7 +65,6 @@ export async function getInsights(tx: Tx, id: string): Promise<PartModelInsights
|
|||||||
select: { brokenPartId: true },
|
select: { brokenPartId: true },
|
||||||
distinct: ['brokenPartId'],
|
distinct: ['brokenPartId'],
|
||||||
}),
|
}),
|
||||||
tx.fm.count({ where: { problemParts: { some: { part: { partModelId: id } } } } }),
|
|
||||||
]);
|
]);
|
||||||
|
|
||||||
const byState = stateRows.map((row) => ({
|
const byState = stateRows.map((row) => ({
|
||||||
@@ -87,7 +86,6 @@ export async function getInsights(tx: Tx, id: string): Promise<PartModelInsights
|
|||||||
failures: {
|
failures: {
|
||||||
repairs,
|
repairs,
|
||||||
distinctFailedParts: failedParts.length,
|
distinctFailedParts: failedParts.length,
|
||||||
fmsImplicating,
|
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -270,9 +270,9 @@ export default function CategoryDetail() {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="grid gap-4 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-6">
|
<div className="grid gap-4 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-5">
|
||||||
{insightsQuery.isPending || !insights ? (
|
{insightsQuery.isPending || !insights ? (
|
||||||
Array.from({ length: 6 }).map((_, i) => <Skeleton key={i} className="h-20" />)
|
Array.from({ length: 5 }).map((_, i) => <Skeleton key={i} className="h-20" />)
|
||||||
) : (
|
) : (
|
||||||
<>
|
<>
|
||||||
<StatCard label="MPNs" value={insights.totalPartModels.toLocaleString()} />
|
<StatCard label="MPNs" value={insights.totalPartModels.toLocaleString()} />
|
||||||
@@ -300,10 +300,6 @@ export default function CategoryDetail() {
|
|||||||
: undefined
|
: undefined
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
<StatCard
|
|
||||||
label="FMs implicated"
|
|
||||||
value={insights.failures.fmsImplicating.toLocaleString()}
|
|
||||||
/>
|
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -256,9 +256,9 @@ export default function ManufacturerDetail() {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="grid gap-4 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-6">
|
<div className="grid gap-4 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-5">
|
||||||
{insightsQuery.isPending || !insights ? (
|
{insightsQuery.isPending || !insights ? (
|
||||||
Array.from({ length: 6 }).map((_, i) => <Skeleton key={i} className="h-20" />)
|
Array.from({ length: 5 }).map((_, i) => <Skeleton key={i} className="h-20" />)
|
||||||
) : (
|
) : (
|
||||||
<>
|
<>
|
||||||
<StatCard label="MPNs" value={insights.totalPartModels.toLocaleString()} />
|
<StatCard label="MPNs" value={insights.totalPartModels.toLocaleString()} />
|
||||||
@@ -286,10 +286,6 @@ export default function ManufacturerDetail() {
|
|||||||
: undefined
|
: undefined
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
<StatCard
|
|
||||||
label="FMs implicated"
|
|
||||||
value={insights.failures.fmsImplicating.toLocaleString()}
|
|
||||||
/>
|
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -262,9 +262,9 @@ export default function PartModelDetail() {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="grid gap-4 sm:grid-cols-2 lg:grid-cols-5">
|
<div className="grid gap-4 sm:grid-cols-2 lg:grid-cols-4">
|
||||||
{insightsQuery.isPending || !insights ? (
|
{insightsQuery.isPending || !insights ? (
|
||||||
Array.from({ length: 5 }).map((_, i) => <Skeleton key={i} className="h-20" />)
|
Array.from({ length: 4 }).map((_, i) => <Skeleton key={i} className="h-20" />)
|
||||||
) : (
|
) : (
|
||||||
<>
|
<>
|
||||||
<StatCard label="Units" value={insights.totalParts.toLocaleString()} />
|
<StatCard label="Units" value={insights.totalParts.toLocaleString()} />
|
||||||
@@ -291,10 +291,6 @@ export default function PartModelDetail() {
|
|||||||
: undefined
|
: undefined
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
<StatCard
|
|
||||||
label="FMs implicated"
|
|
||||||
value={insights.failures.fmsImplicating.toLocaleString()}
|
|
||||||
/>
|
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -9,7 +9,6 @@ export interface CategoryPriceStats {
|
|||||||
export interface CategoryFailureStats {
|
export interface CategoryFailureStats {
|
||||||
repairs: number;
|
repairs: number;
|
||||||
distinctFailedParts: number;
|
distinctFailedParts: number;
|
||||||
fmsImplicating: number;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface CategoryManufacturerCount {
|
export interface CategoryManufacturerCount {
|
||||||
|
|||||||
@@ -9,7 +9,6 @@ export interface ManufacturerPriceStats {
|
|||||||
export interface ManufacturerFailureStats {
|
export interface ManufacturerFailureStats {
|
||||||
repairs: number;
|
repairs: number;
|
||||||
distinctFailedParts: number;
|
distinctFailedParts: number;
|
||||||
fmsImplicating: number;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ManufacturerCategoryCount {
|
export interface ManufacturerCategoryCount {
|
||||||
|
|||||||
@@ -18,7 +18,6 @@ export interface PartModelPriceStats {
|
|||||||
export interface PartModelFailureStats {
|
export interface PartModelFailureStats {
|
||||||
repairs: number;
|
repairs: number;
|
||||||
distinctFailedParts: number;
|
distinctFailedParts: number;
|
||||||
fmsImplicating: number;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface PartModelInsights {
|
export interface PartModelInsights {
|
||||||
|
|||||||
Reference in New Issue
Block a user