DedupliFHIR
-Upload Patient Records File
To start deduplication, load your file into DedupliFHIR.
- + +
Accepted Types: FHIR, CSV
+ >FHIR or CSV file only
Upload Patient Records File
/>
-
+
- + +
+
DedupliFHIR is secure.
+-
+
- After you download DedupliFHIR, it works locally on your computer, just like other software applications. +
- Any files you load into DedupliFHIR stay on your computer. +
- No information is shared. +
diff --git a/frontend/main.js b/frontend/main.js
index 3e87559..449117f 100644
--- a/frontend/main.js
+++ b/frontend/main.js
@@ -14,15 +14,25 @@ var resultsFile;
function findPython() {
const possibilities = [
- // In packaged app
+ // In packaged app (mac / linux)
path.join(process.resourcesPath, "python", "bin", "python"),
path.join(process.resourcesPath, "python", "bin", "python3"),
path.join(process.resourcesPath, "python", "bin", "python3.10"),
path.join(process.resourcesPath, "python", "bin", "python3.11"),
path.join(process.resourcesPath, "python", "bin", "python3.12"),
- // In development
+ // In development (mac / linux)
path.join("..", ".venv", "bin", "python"),
+
+ // In packaged app (windows)
+ path.join(process.resourcesPath, "python", "Scripts", "python.exe"),
+ path.join(process.resourcesPath, "python", "Scripts", "python3.exe"),
+ path.join(process.resourcesPath, "python", "Scripts", "python3.10.exe"),
+ path.join(process.resourcesPath, "python", "Scripts", "python3.11.exe"),
+ path.join(process.resourcesPath, "python", "Scripts", "python3.12.exe"),
+
+ // In development (windows)
+ path.join("..", ".venv", "Scripts", "python.exe"),
];
for (const path of possibilities) {
if (fs.existsSync(path)) {
@@ -122,6 +132,8 @@ async function handleSaveFile() {
function createWindow() {
mainWindow = new BrowserWindow({
+ width: 900,
+ height: 750,
webPreferences: {
preload: path.join(__dirname, "./preload.js"),
contextIsolation: true,
diff --git a/frontend/package-lock.json b/frontend/package-lock.json
index f60a0d9..54f6e64 100644
--- a/frontend/package-lock.json
+++ b/frontend/package-lock.json
@@ -7066,9 +7066,6 @@
"version": "3.3.3",
"resolved": "https://registry.npmjs.org/prettier/-/prettier-3.3.3.tgz",
"integrity": "sha512-i2tDNA0O5IrMO757lfrdQZCc2jPNDVntV0m/+4whiDfWaTKfMNgR7Qz0NAeGz/nRqF4m5/6CLzbP4/liHt12Ew==",
- "version": "3.3.2",
- "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.3.2.tgz",
- "integrity": "sha512-rAVeHYMcv8ATV5d508CFdn+8/pHPpXeIid1DdrPwXnaAdH7cqjVbpJaT5eq4yRAFU/lsbwYwSF/n5iNrdJHPQA==",
"dev": true
},
"prettier-linter-helpers": {
diff --git a/frontend/pages/error.html b/frontend/pages/error.html
index a2be137..ef35073 100644
--- a/frontend/pages/error.html
+++ b/frontend/pages/error.html
@@ -11,7 +11,7 @@
-
-
- Return to Home +
diff --git a/frontend/pages/success.html b/frontend/pages/success.html
index 44c6413..82ffc75 100644
--- a/frontend/pages/success.html
+++ b/frontend/pages/success.html
@@ -10,60 +10,37 @@
DedupliFHIR
-Results
+Your results are ready.
@@ -21,9 +21,9 @@
Error
- Return to Home +
+ Back to home +
DedupliFHIR
- -Results
- - -
+
-
Your results are ready.
+Success
- Results file downloaded! + Results file saved to your computer.
+
+
+
-
diff --git a/frontend/renderer/renderer-success.js b/frontend/renderer/renderer-success.js
index b5ec5af..4489711 100644
--- a/frontend/renderer/renderer-success.js
+++ b/frontend/renderer/renderer-success.js
@@ -1,14 +1,25 @@
-const successAlert = document.getElementById("download-success-alert");
-const failAlert = document.getElementById("download-fail-alert");
+const resultsText = document.getElementById("results-ready-text");
+const successAlert = document.getElementById("save-file-success-alert");
+const failAlert = document.getElementById("save-file-fail-alert");
+const instructions = document.getElementById(
+ "results-spreadsheet-instructions",
+);
+const homeButton = document.getElementById("return-to-home");
-const downloadButton = document.getElementById("download");
-downloadButton.addEventListener("click", async () => {
+const saveButton = document.getElementById("save-file");
+saveButton.addEventListener("click", async () => {
const filePath = await window.electronAPI.saveFile();
if (filePath) {
+ resultsText.style.display = "none";
successAlert.style.display = "block";
+ instructions.style.display = "block";
failAlert.style.display = "none";
+ saveButton.style.display = "none";
+ homeButton.innerText = "Load another file into DedupliFHIR";
} else {
+ resultsText.style.display = "none";
successAlert.style.display = "none";
+ instructions.style.display = "none";
failAlert.style.display = "block";
}
});
diff --git a/frontend/tests/frontend.spec.js b/frontend/tests/frontend.spec.js
index 882f0af..ce6234a 100644
--- a/frontend/tests/frontend.spec.js
+++ b/frontend/tests/frontend.spec.js
@@ -58,8 +58,10 @@ test("UI elements present", async () => {
const content = await window.locator("div.content");
expect(content).not.toBeNull();
expect(content.locator("h1")).toHaveText("DedupliFHIR");
- await window.waitForSelector("h2");
- expect(content.locator("h2")).toHaveText("Upload Patient Records File");
+ const instructionText = await window.locator("#file-input-copy");
+ expect(instructionText).toHaveText(
+ "To start deduplication, load your file into DedupliFHIR.",
+ );
// Check for file dropper
const fileDropper = await window.locator("input[type='file']");
@@ -77,23 +79,22 @@ test("UI elements present", async () => {
const submitButton = await window.locator("button[type='button']");
expect(submitButton).not.toBeNull();
expect(submitButton).toHaveId("submit");
+
+ // Check for security section
+ const securityText = await window.locator("#security-section");
+ expect(securityText).not.toBeNull();
});
-test("submit without uploading file", async () => {
+test("submit without loading file", async () => {
const submitButton = await window.locator("button[type='button']");
await submitButton.click();
const alert = await window.locator("#file-input-alert");
expect(alert).not.toBeNull();
- expect(alert.locator("p")).toHaveText(
- "File not found. Please upload a file.",
- );
+ expect(alert.locator("p")).toHaveText("File not found. Please load a file.");
});
-test("upload file and submit", async () => {
- // TODO: remove this when height bug is fixed
- await window.setViewportSize({ width: 1000, height: 3000 });
-
+test("load file and submit", async () => {
const fileDropper = await window.locator("input[type='file']");
await fileDropper.setInputFiles(
@@ -125,20 +126,23 @@ test("finished results present", async () => {
expect(content.locator("h1")).toHaveText("DedupliFHIR");
// Wait for results
- await window.waitForSelector("h2", {
+ await window.waitForSelector("p", {
timeout: 1000 * 60 * 2, // two minutes
});
- expect(content.locator("h2")).toHaveText("Results");
+ expect(content.locator("#results-ready-text")).toHaveText(
+ "Your results are ready.",
+ );
expect(content.locator("a[href='../index.html']")).not.toBeNull();
- expect(content.locator("#download")).not.toBeNull();
+ expect(content.locator("#save-file")).not.toBeNull();
- expect(content.locator("#download-success-alert")).toBeHidden();
- expect(content.locator("#download-fail-alert")).toBeHidden();
+ expect(content.locator("#save-file-success-alert")).toBeHidden();
+ expect(content.locator("#results-spreadsheet-instructions")).toBeHidden();
+ expect(content.locator("#save-file-fail-alert")).toBeHidden();
});
-test("download file", async () => {
- const downloadButton = await window.locator("#download");
- expect(downloadButton).not.toBeNull();
+test("save file", async () => {
+ const saveButton = await window.locator("#save-file");
+ expect(saveButton).not.toBeNull();
// https://stackoverflow.com/a/75966734
const xlsxPath = path.join(
@@ -150,12 +154,22 @@ test("download file", async () => {
dialog.showSaveDialog = () =>
Promise.resolve({ filePath: xlsxPath, canceled: false });
}, xlsxPath);
- await downloadButton.click();
+ await saveButton.click();
- await window.waitForSelector("#download-success-alert");
- const successAlert = await window.locator("#download-success-alert");
+ await window.waitForSelector("#save-file-success-alert");
+ const successAlert = await window.locator("#save-file-success-alert");
expect(successAlert).not.toBeNull();
- expect(successAlert.locator("p")).toHaveText("Results file downloaded!");
+ expect(successAlert.locator("p")).toHaveText(
+ "Results file saved to your computer.",
+ );
+
+ const instructions = await window.locator(
+ "#results-spreadsheet-instructions",
+ );
+ expect(instructions).not.toBeNull();
+
+ const homeLink = await window.locator("a[href='../index.html']");
+ expect(homeLink).not.toBeNull();
expect((await fs.promises.stat(xlsxPath)).size).toBeGreaterThan(100 * 1024); // file size should be > 100 KB
});
@@ -171,6 +185,8 @@ test("return home", async () => {
const content = await window.locator("div.content");
expect(content).not.toBeNull();
expect(content.locator("h1")).toHaveText("DedupliFHIR");
- await window.waitForSelector("h2");
- expect(content.locator("h2")).toHaveText("Upload Patient Records File");
+ const instructionText = await window.locator("#file-input-copy");
+ expect(instructionText).toHaveText(
+ "To start deduplication, load your file into DedupliFHIR.",
+ );
});
diff --git a/research/sept-2024/examples-use-clusterid-to-find-possible-duplicates.md b/research/sept-2024/examples-use-clusterid-to-find-possible-duplicates.md
new file mode 100644
index 0000000..869ae2e
--- /dev/null
+++ b/research/sept-2024/examples-use-clusterid-to-find-possible-duplicates.md
@@ -0,0 +1,33 @@
+# Examples of using cluster_id to find possible duplicates
+
+## What are cluster_id and unique_id?
+When you open a spreadsheet saved from DedupliFHIR in Microsoft Excel, you'll notice **2 new columns** not in your original file of patient data:
+- **unique_id:** Identifies a specific patient record
+- **cluster_id:** Identifies a cluster of possible duplicates
+
+DedupliFHIR identifies records about the same fundamental source of information (for example, a patient).
+
+## How to find possible duplicates
+**Records with exactly the same cluster_id could be duplicates.** Below are 2 ways to view them in Excel.
+
+### To sort by cluster_id:
+1. Open the spreadsheet and select the cluster_id column.
+2. In the “Data” menu, select "Sort.
+3. In the "Sort Warning" dialog box, select "Continue with the current selection," then click the "Sort..." button.
+4. In the "Sort" menu, choose options for "Sort on" (for example, Values) and "Order" (for example, Smallest to Largest), then click the "OK" button.
+5. All rows in the spreadsheet will be sorted by cluster_id based on the sorting option you selected (for example, Smallest to Largest).
+
+Note: If you go to "Filter" or “AutoFilter,” there are similar options for sorting.
+
+### To autofilter by cluster_id:
+1. Open the spreadsheet and select the cluster_id column.
+2. In the “Data” menu, select "Filter" or “AutoFilter.”
+3. Click the arrow next to the column heading "cluster_id."
+4. Select a cluster_id from the list.
+5. The spreadsheet will contain only the records with the cluster_id(s) you selected.
+
+
+
+
+
+
Error
- Unable to download file. Please try again. + Unable to save file to your computer. Please try again.
+
- + To find possible duplicates: +
-
+
- Open your results file. +
- Find the records with the same cluster_id. +
-
-
-
- - Return to Home - -
- - - -
+ Back to home +