From 24dc2927dab96836e72d02d53071252b311a0f1f Mon Sep 17 00:00:00 2001 From: Daniel Roth Date: Thu, 23 Jul 2026 16:19:22 +0000 Subject: [PATCH 1/7] =?UTF-8?q?Exclude=20coordination=20docs=20from=20the?= =?UTF-8?q?=20Install=20Docs=20badge=20=F0=9F=9F=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Opus 4.8 (1M context) --- .../your-projects/live/docStatus.test.ts | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/src/app/portfolio/[slug]/(portfolio)/your-projects/live/docStatus.test.ts b/src/app/portfolio/[slug]/(portfolio)/your-projects/live/docStatus.test.ts index cd2c1d08..3fedfe12 100644 --- a/src/app/portfolio/[slug]/(portfolio)/your-projects/live/docStatus.test.ts +++ b/src/app/portfolio/[slug]/(portfolio)/your-projects/live/docStatus.test.ts @@ -169,6 +169,21 @@ describe("computeDocStatusMap", () => { expect(result["deal-1"].installStatus).toBe("hasDocs"); }); + it('is "none" when the only docs are coordination docs, which the drawer lists separately', () => { + // Arrange + const deal = makeDeal({ dealId: "deal-1", proposedMeasures: null }); + const coordinationDocs = [ + { fileType: "improvement_option_evaluation", measureName: null }, + ]; + const docs = new Map([["deal-1", coordinationDocs]]); + + // Act + const result = computeDocStatusMap([deal], docs, {}); + + // Assert + expect(result["deal-1"].installStatus).toBe("none"); + }); + it('is "none" when measures are defined but no install docs have been uploaded', () => { const deal = makeDeal({ dealId: "deal-1", proposedMeasures: cwi }); // Only survey docs — no install docs From f5fbb64e3cbd0c05ee85ad8692605d9c0cac9eff Mon Sep 17 00:00:00 2001 From: Daniel Roth Date: Thu, 23 Jul 2026 16:19:38 +0000 Subject: [PATCH 2/7] =?UTF-8?q?Exclude=20coordination=20docs=20from=20the?= =?UTF-8?q?=20Install=20Docs=20badge=20=F0=9F=9F=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Opus 4.8 (1M context) --- .../[slug]/(portfolio)/your-projects/live/docStatus.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/app/portfolio/[slug]/(portfolio)/your-projects/live/docStatus.ts b/src/app/portfolio/[slug]/(portfolio)/your-projects/live/docStatus.ts index 9472b88b..77f03e94 100644 --- a/src/app/portfolio/[slug]/(portfolio)/your-projects/live/docStatus.ts +++ b/src/app/portfolio/[slug]/(portfolio)/your-projects/live/docStatus.ts @@ -3,6 +3,7 @@ import { db } from "@/app/db/db"; import { uploadedFiles } from "@/app/db/schema/uploaded_files"; import { getRequiredDocs } from "@/app/lib/measureDocumentRequirements"; import { + COORDINATION_DOC_TYPES, EXPECTED_RETROFIT_ASSESSMENT_DOC_TYPES, SURVEY_ALL_DOC_TYPES, } from "./types"; @@ -103,7 +104,9 @@ export function computeDocStatusMap( for (const [dealId, docs] of docsByDealId) { const surveyDocs = docs.filter((d) => SURVEY_ALL_DOC_TYPES.has(d.fileType)); - const installDocs = docs.filter((d) => !SURVEY_ALL_DOC_TYPES.has(d.fileType)); + const installDocs = docs.filter( + (d) => !SURVEY_ALL_DOC_TYPES.has(d.fileType) && !COORDINATION_DOC_TYPES.has(d.fileType), + ); const surveyTypeSet = new Set(surveyDocs.map((d) => d.fileType)); const measures = measuresByDealId.get(dealId) ?? []; From a641e95f51e5193119e0265cfc701367e15831aa Mon Sep 17 00:00:00 2001 From: Daniel Roth Date: Thu, 23 Jul 2026 16:19:56 +0000 Subject: [PATCH 3/7] =?UTF-8?q?Exclude=20design=20docs=20from=20the=20Inst?= =?UTF-8?q?all=20Docs=20badge=20=F0=9F=9F=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Opus 4.8 (1M context) --- .../your-projects/live/docStatus.test.ts | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/src/app/portfolio/[slug]/(portfolio)/your-projects/live/docStatus.test.ts b/src/app/portfolio/[slug]/(portfolio)/your-projects/live/docStatus.test.ts index 3fedfe12..df11c310 100644 --- a/src/app/portfolio/[slug]/(portfolio)/your-projects/live/docStatus.test.ts +++ b/src/app/portfolio/[slug]/(portfolio)/your-projects/live/docStatus.test.ts @@ -184,6 +184,19 @@ describe("computeDocStatusMap", () => { expect(result["deal-1"].installStatus).toBe("none"); }); + it('is "none" when the only docs are design docs, which the drawer lists separately', () => { + // Arrange + const deal = makeDeal({ dealId: "deal-1", proposedMeasures: null }); + const designDocs = [{ fileType: "retrofit_design_doc", measureName: null }]; + const docs = new Map([["deal-1", designDocs]]); + + // Act + const result = computeDocStatusMap([deal], docs, {}); + + // Assert + expect(result["deal-1"].installStatus).toBe("none"); + }); + it('is "none" when measures are defined but no install docs have been uploaded', () => { const deal = makeDeal({ dealId: "deal-1", proposedMeasures: cwi }); // Only survey docs — no install docs From d666d0e8009a8fc49b3cbc1be6b43173f9ec84b4 Mon Sep 17 00:00:00 2001 From: Daniel Roth Date: Thu, 23 Jul 2026 16:20:16 +0000 Subject: [PATCH 4/7] =?UTF-8?q?Exclude=20design=20docs=20from=20the=20Inst?= =?UTF-8?q?all=20Docs=20badge=20=F0=9F=9F=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Opus 4.8 (1M context) --- .../[slug]/(portfolio)/your-projects/live/docStatus.ts | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/app/portfolio/[slug]/(portfolio)/your-projects/live/docStatus.ts b/src/app/portfolio/[slug]/(portfolio)/your-projects/live/docStatus.ts index 77f03e94..f929692f 100644 --- a/src/app/portfolio/[slug]/(portfolio)/your-projects/live/docStatus.ts +++ b/src/app/portfolio/[slug]/(portfolio)/your-projects/live/docStatus.ts @@ -4,6 +4,7 @@ import { uploadedFiles } from "@/app/db/schema/uploaded_files"; import { getRequiredDocs } from "@/app/lib/measureDocumentRequirements"; import { COORDINATION_DOC_TYPES, + DESIGN_DOC_TYPES, EXPECTED_RETROFIT_ASSESSMENT_DOC_TYPES, SURVEY_ALL_DOC_TYPES, } from "./types"; @@ -105,7 +106,10 @@ export function computeDocStatusMap( for (const [dealId, docs] of docsByDealId) { const surveyDocs = docs.filter((d) => SURVEY_ALL_DOC_TYPES.has(d.fileType)); const installDocs = docs.filter( - (d) => !SURVEY_ALL_DOC_TYPES.has(d.fileType) && !COORDINATION_DOC_TYPES.has(d.fileType), + (d) => + !SURVEY_ALL_DOC_TYPES.has(d.fileType) && + !COORDINATION_DOC_TYPES.has(d.fileType) && + !DESIGN_DOC_TYPES.has(d.fileType), ); const surveyTypeSet = new Set(surveyDocs.map((d) => d.fileType)); From 8ee27789bca47ebafa4ab49692da1612f6c3fde2 Mon Sep 17 00:00:00 2001 From: Daniel Roth Date: Thu, 23 Jul 2026 16:21:18 +0000 Subject: [PATCH 5/7] =?UTF-8?q?Report=20no=20install=20docs=20for=20coordi?= =?UTF-8?q?nation-=20and=20design-only=20properties=20=F0=9F=9F=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Opus 4.8 (1M context) --- .../your-projects/live/docStatus.test.ts | 35 +++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/src/app/portfolio/[slug]/(portfolio)/your-projects/live/docStatus.test.ts b/src/app/portfolio/[slug]/(portfolio)/your-projects/live/docStatus.test.ts index df11c310..a3a8bf74 100644 --- a/src/app/portfolio/[slug]/(portfolio)/your-projects/live/docStatus.test.ts +++ b/src/app/portfolio/[slug]/(portfolio)/your-projects/live/docStatus.test.ts @@ -197,6 +197,41 @@ describe("computeDocStatusMap", () => { expect(result["deal-1"].installStatus).toBe("none"); }); + it("counts only genuine install docs when coordination and design docs are also present", () => { + // Arrange + const deal = makeDeal({ dealId: "deal-1", proposedMeasures: cwi }); + const mixedDocs = [ + { fileType: "improvement_option_evaluation", measureName: null }, + { fileType: "retrofit_design_doc", measureName: null }, + ...cwiRequiredDocs.map((ft) => ({ fileType: ft, measureName: cwi })), + ]; + const docs = new Map([["deal-1", mixedDocs]]); + + // Act + const result = computeDocStatusMap([deal], docs, {}); + + // Assert + expect(result["deal-1"].measureProgress).toEqual([ + expect.objectContaining({ measureName: cwi, uploadedCount: cwiRequiredDocs.length }), + ]); + }); + + it("reports no install docs when only coordination and design docs are present", () => { + // Arrange + const deal = makeDeal({ dealId: "deal-1", proposedMeasures: null }); + const nonInstallDocs = [ + { fileType: "medium_term_improvement_plan", measureName: null }, + { fileType: "retrofit_design_doc", measureName: null }, + ]; + const docs = new Map([["deal-1", nonInstallDocs]]); + + // Act + const result = computeDocStatusMap([deal], docs, {}); + + // Assert + expect(result["deal-1"].hasInstallDocs).toBe(false); + }); + it('is "none" when measures are defined but no install docs have been uploaded', () => { const deal = makeDeal({ dealId: "deal-1", proposedMeasures: cwi }); // Only survey docs — no install docs From 1039c21c579a8806bda64769a1d6856c901ba6a5 Mon Sep 17 00:00:00 2001 From: Daniel Roth Date: Thu, 23 Jul 2026 16:22:26 +0000 Subject: [PATCH 6/7] =?UTF-8?q?Categorise=20install=20docs=20identically?= =?UTF-8?q?=20in=20the=20table=20badge=20and=20the=20drawer=20=F0=9F=9F=AA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Opus 4.8 (1M context) --- .../your-projects/live/docStatus.ts | 10 ++-------- .../your-projects/live/propertyDocuments.ts | 20 +++++++++++++------ 2 files changed, 16 insertions(+), 14 deletions(-) diff --git a/src/app/portfolio/[slug]/(portfolio)/your-projects/live/docStatus.ts b/src/app/portfolio/[slug]/(portfolio)/your-projects/live/docStatus.ts index f929692f..af37e37b 100644 --- a/src/app/portfolio/[slug]/(portfolio)/your-projects/live/docStatus.ts +++ b/src/app/portfolio/[slug]/(portfolio)/your-projects/live/docStatus.ts @@ -2,9 +2,8 @@ import { inArray } from "drizzle-orm"; import { db } from "@/app/db/db"; import { uploadedFiles } from "@/app/db/schema/uploaded_files"; import { getRequiredDocs } from "@/app/lib/measureDocumentRequirements"; +import { isInstallDocType } from "./propertyDocuments"; import { - COORDINATION_DOC_TYPES, - DESIGN_DOC_TYPES, EXPECTED_RETROFIT_ASSESSMENT_DOC_TYPES, SURVEY_ALL_DOC_TYPES, } from "./types"; @@ -105,12 +104,7 @@ export function computeDocStatusMap( for (const [dealId, docs] of docsByDealId) { const surveyDocs = docs.filter((d) => SURVEY_ALL_DOC_TYPES.has(d.fileType)); - const installDocs = docs.filter( - (d) => - !SURVEY_ALL_DOC_TYPES.has(d.fileType) && - !COORDINATION_DOC_TYPES.has(d.fileType) && - !DESIGN_DOC_TYPES.has(d.fileType), - ); + const installDocs = docs.filter((d) => isInstallDocType(d.fileType)); const surveyTypeSet = new Set(surveyDocs.map((d) => d.fileType)); const measures = measuresByDealId.get(dealId) ?? []; diff --git a/src/app/portfolio/[slug]/(portfolio)/your-projects/live/propertyDocuments.ts b/src/app/portfolio/[slug]/(portfolio)/your-projects/live/propertyDocuments.ts index e9ebf415..9bab6454 100644 --- a/src/app/portfolio/[slug]/(portfolio)/your-projects/live/propertyDocuments.ts +++ b/src/app/portfolio/[slug]/(portfolio)/your-projects/live/propertyDocuments.ts @@ -6,6 +6,19 @@ import { } from "./types"; import type { PropertyDocument, MeasureDocProgress } from "./types"; +// Survey, coordination and design docs each get their own section in the +// property drawer, so none of them count as install docs. Kept as a single +// predicate shared with computeDocStatusMap: when the two drifted apart, the +// Install Docs badge in the properties table read "Has Docs" for properties +// whose drawer showed no install documents at all. +export function isInstallDocType(docType: string): boolean { + return ( + !SURVEY_ALL_DOC_TYPES.has(docType) && + !COORDINATION_DOC_TYPES.has(docType) && + !DESIGN_DOC_TYPES.has(docType) + ); +} + export function splitDocumentsByType(docs: PropertyDocument[]): { docs: PropertyDocument[]; coordinationDocs: PropertyDocument[]; @@ -16,12 +29,7 @@ export function splitDocumentsByType(docs: PropertyDocument[]): { docs: docs.filter((d) => SURVEY_ALL_DOC_TYPES.has(d.docType)), coordinationDocs: docs.filter((d) => COORDINATION_DOC_TYPES.has(d.docType)), designDocs: docs.filter((d) => DESIGN_DOC_TYPES.has(d.docType)), - installDocs: docs.filter( - (d) => - !SURVEY_ALL_DOC_TYPES.has(d.docType) && - !COORDINATION_DOC_TYPES.has(d.docType) && - !DESIGN_DOC_TYPES.has(d.docType), - ), + installDocs: docs.filter((d) => isInstallDocType(d.docType)), }; } From 0ca52d56d911511b751ebd441b29822015cf25a4 Mon Sep 17 00:00:00 2001 From: Daniel Roth Date: Mon, 27 Jul 2026 10:48:54 +0000 Subject: [PATCH 7/7] fix incorrect merge conflict resolution --- .../(portfolio)/your-projects/live/docStatus.ts | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/app/portfolio/[slug]/(portfolio)/your-projects/live/docStatus.ts b/src/app/portfolio/[slug]/(portfolio)/your-projects/live/docStatus.ts index d158ee72..4efae42b 100644 --- a/src/app/portfolio/[slug]/(portfolio)/your-projects/live/docStatus.ts +++ b/src/app/portfolio/[slug]/(portfolio)/your-projects/live/docStatus.ts @@ -123,12 +123,12 @@ export function computeDocStatusMap( for (const [dealId, docs] of docsByDealId) { const surveyDocs = docs.filter((d) => SURVEY_ALL_DOC_TYPES.has(d.fileType)); - // Design documents are their own category — they are neither survey nor - // install docs. Without this carve-out a Retrofit Design Document (absent - // from the survey set) would be mis-bucketed as an install document. - const installDocs = docs.filter( - (d) => !SURVEY_ALL_DOC_TYPES.has(d.fileType) && !DESIGN_DOC_TYPES.has(d.fileType), - ); + // Survey, coordination and design docs each get their own drawer section, + // so none count as install docs. `isInstallDocType` is the shared predicate + // that keeps this in sync with the drawer's own split — re-implementing it + // inline once dropped the coordination carve-out and mis-bucketed + // coordination-only docs as install documents. + const installDocs = docs.filter((d) => isInstallDocType(d.fileType)); const surveyTypeSet = new Set(surveyDocs.map((d) => d.fileType)); const measures = measuresByDealId.get(dealId) ?? [];