Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
41 commits
Select commit Hold shift + click to select a range
637f082
feat: :sparkles: WIP: Initial implementation of go ocr service with g…
alexszilagyi Jul 23, 2025
b636457
feat: :sparkles: WIP on FE and BE flow support, integrate OCR scan an…
alexszilagyi Jul 23, 2025
a2f7035
feat: :sparkles: Add camera scan support to send to OCR service
alexszilagyi Jul 24, 2025
e317857
revert: Revert stylus package update
alexszilagyi Jul 28, 2025
1491c8f
feat: WIP on flow revamp, add pdf scan with a maximum of pages
alexszilagyi Jul 28, 2025
41f2648
feat: WIP on flow revamp, add multiple image scan with a maximum of i…
alexszilagyi Jul 28, 2025
bef4984
feat: WIP on camera ocr resource component
alexszilagyi Jul 29, 2025
47bfd18
feat: Update path and add header nav button
alexszilagyi Aug 20, 2025
829fae5
feat: Add drag and drop area, remove select flow and detect file type…
alexszilagyi Aug 20, 2025
d771cde
style: Add icons to upload section/buttons
alexszilagyi Aug 20, 2025
f8d3d28
feat: WIP on OCR data processing, adjust UI layout for pdf/image
alexszilagyi Aug 21, 2025
14387f2
feat: Adjust form UI and logic, add encounter type handler on OCR
alexszilagyi Aug 22, 2025
4376476
feat: Add OCR data service on FE, adjust UI
alexszilagyi Aug 22, 2025
31a4b92
feat: WIP on integrating OCR service prefill for encounter type
alexszilagyi Aug 22, 2025
fb7a213
feat: WIP on flow revamp, improve UX on scan process
alexszilagyi Aug 26, 2025
02a602d
feat: WIP on UI refactor, adjust sections into single form card
alexszilagyi Aug 27, 2025
ef3131e
feat: WIP on automatic OCR detection, adjust form fields
alexszilagyi Aug 27, 2025
8b36888
feat: Add form edit state, send first page to OCR by default (will be…
alexszilagyi Aug 27, 2025
485ace3
feat: Add scanned encounter to form
alexszilagyi Sep 1, 2025
c45aee8
feat: WIP on medications flow for BE and FE
alexszilagyi Sep 1, 2025
1ff20f4
feat: Adjust medication mapping on FE and date format
alexszilagyi Sep 2, 2025
dceb2a4
feat: WIP on procedures, practitioners and organizations forms flow o…
alexszilagyi Sep 2, 2025
f69ac61
feat: WIP on practitioner mapping in encounter and prefill
alexszilagyi Sep 3, 2025
2a7dfb7
feat: Fix OCR found practitioner add in current practitioner array
alexszilagyi Sep 4, 2025
42c6abb
fix: Fix practitioner from OCR mapping
alexszilagyi Sep 4, 2025
7f20ff2
fix: Fix organizations form mapping
alexszilagyi Sep 5, 2025
1fa8b98
feat: Add delete support for practitioners/organizations, fix organiz…
alexszilagyi Sep 5, 2025
5e6f914
feat: Adjust edit mode UI and default state
alexszilagyi Sep 5, 2025
911924e
feat: Adjust UI on png scan, support single pdf file or multiple uplo…
alexszilagyi Sep 5, 2025
b225651
feat: Adjust form formatting before sending it to BE, fix other mappi…
alexszilagyi Sep 8, 2025
d558db8
feat: Add scanning files as attachments to current encounter form
alexszilagyi Sep 9, 2025
db9d994
feat: Fixes on camera scan flow, adjust sent images
alexszilagyi Sep 9, 2025
52dd847
fix: Fix practitioner name mapping on submit
alexszilagyi Sep 9, 2025
75bb553
feat: Improve formatting on submit
alexszilagyi Sep 9, 2025
777024b
Code cleanup
alexszilagyi Sep 9, 2025
acdf166
Code adjustment after rebase
alexszilagyi Oct 14, 2025
a773d2f
Fix chart configuration option types after update
alexszilagyi Oct 14, 2025
9397876
Yarn lock update
alexszilagyi Oct 15, 2025
a4d1090
Update yarn.lock file, remove package-lock
alexszilagyi Oct 15, 2025
a8ec53f
Update specs for OCR components
alexszilagyi Oct 15, 2025
658dc97
Update imports and modules in OCR components
alexszilagyi Oct 16, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1,106 changes: 1,106 additions & 0 deletions backend/pkg/web/handler/ocr.go

Large diffs are not rendered by default.

26 changes: 15 additions & 11 deletions backend/pkg/web/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ import (
"strings"
"time"

"os"

"github.com/fastenhealth/fasten-onprem/backend/pkg"
"github.com/fastenhealth/fasten-onprem/backend/pkg/config"
"github.com/fastenhealth/fasten-onprem/backend/pkg/database"
Expand All @@ -22,14 +24,13 @@ import (
"github.com/fastenhealth/fasten-onprem/backend/pkg/web/middleware"
"github.com/gin-gonic/gin"
"github.com/sirupsen/logrus"
"os"
)

type AppEngine struct {
Config config.Interface
Logger *logrus.Entry
EventBus event_bus.Interface
deviceRepo database.DatabaseRepository
Config config.Interface
Logger *logrus.Entry
EventBus event_bus.Interface
deviceRepo database.DatabaseRepository
StandbyMode bool

RelatedVersions map[string]string //related versions metadata provided & embedded by the build process
Expand Down Expand Up @@ -99,8 +100,8 @@ func (ae *AppEngine) Setup() (*gin.RouterGroup, *gin.Engine) {
c.JSON(http.StatusOK, gin.H{
"success": true,
"data": gin.H{
"first_run_wizard": firstRunWizard,
"standby_mode": true,
"first_run_wizard": firstRunWizard,
"standby_mode": true,
},
})
return
Expand All @@ -125,8 +126,8 @@ func (ae *AppEngine) Setup() (*gin.RouterGroup, *gin.Engine) {
c.JSON(http.StatusOK, gin.H{
"success": true,
"data": gin.H{
"first_run_wizard": firstRunWizard,
"standby_mode": false,
"first_run_wizard": firstRunWizard,
"standby_mode": false,
},
})
})
Expand Down Expand Up @@ -178,13 +179,16 @@ func (ae *AppEngine) Setup() (*gin.RouterGroup, *gin.Engine) {
secure.GET("/resource/fhir/:sourceId/:resourceId", handler.GetResourceFhir)
secure.PATCH("/resource/fhir/:resourceType/:resourceId", handler.UpdateResourceFhir)
secure.DELETE("/resource/fhir/:resourceType/:resourceId", handler.DeleteResourceFhir)
secure.POST("/resource/ocr", handler.OcrFileUploadHandler)

secure.GET("/dashboards", handler.GetDashboard)
secure.POST("/dashboards", handler.AddDashboardLocation)
//secure.GET("/dashboard/:dashboardId", handler.GetDashboard)

secure.POST("/resource/composition", handler.CreateResourceComposition)
secure.POST("/resource/related", handler.CreateRelatedResources)
secure.DELETE("/encounter/:encounterId/related/:resourceType/:resourceId", handler.EncounterUnlinkResource)

secure.GET("/dashboards", handler.GetDashboard)
secure.POST("/dashboards", handler.AddDashboardLocation)
//secure.GET("/dashboard/:dashboardId", handler.GetDashboard)

secure.GET("/jobs", handler.ListBackgroundJobs)
Expand Down
7 changes: 7 additions & 0 deletions docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -20,3 +20,10 @@ services:
- ./db:/opt/fasten/db
- ./certs:/opt/fasten/certs/shared
# - ./config.example.yaml:/opt/fasten/config/config.yaml
ocr-service:
build:
context: ./ocr-service
dockerfile: Dockerfile
ports:
- "9091:8080"
restart: unless-stopped
5 changes: 5 additions & 0 deletions frontend/angular.json
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,11 @@
"glob": "**/*.png",
"input": "./node_modules/lforms/dist/lforms/webcomponent/",
"output": "/assets/css/lforms/"
},
{
"glob": "**/*",
"input": "node_modules/pdfjs-dist/build/",
"output": "/assets/pdfjs/"
}
],
"styles": [
Expand Down
3 changes: 3 additions & 0 deletions frontend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,8 @@
"ngx-moment": "^6.0.2",
"parse-full-name": "^1.2.6",
"qrcode": "^1.5.4",
"pdf-lib": "^1.17.1",
"pdfjs-dist": "^2.14.305",
"rtf.js": "^3.0.9",
"rxjs": "~6.5.4",
"tslib": "^2.0.0",
Expand All @@ -78,6 +80,7 @@
"@types/jasmine": "~3.5.0",
"@types/jasminewd2": "~2.0.3",
"@types/qrcode": "^1.5.5",
"@types/pdfjs-dist": "^2.10.377",
"chromatic": "^6.19.8",
"codelyzer": "^5.1.2",
"fishery": "^2.2.2",
Expand Down
5 changes: 5 additions & 0 deletions frontend/src/app/app-routing.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ import { PractitionerHistoryComponent } from "./pages/practitioner-history/pract
import { SettingsComponent } from './pages/settings/settings.component';
import { GetEncryptionKeyWizardComponent } from './pages/get-encryption-key-wizard/get-encryption-key-wizard.component';
import { SetupEncryptionKeyComponent } from './pages/setup-encryption-key/setup-encryption-key.component';
import { ResourceOcrComponent } from "./pages/resource-ocr/resource-ocr.component";

const routes: Routes = [
{ path: 'auth/signup/wizard', component: AuthSignupWizardComponent },
Expand All @@ -56,6 +57,10 @@ const routes: Routes = [
{ path: 'resource/create', component: ResourceCreatorComponent, canActivate: [ IsAuthenticatedAuthGuard ] },

{ path: 'desktop/callback/:state', component: DesktopCallbackComponent, canActivate: [ IsAuthenticatedAuthGuard ] },
{ path: 'resource/ocr-scan', component: ResourceOcrComponent, canActivate: [ IsAuthenticatedAuthGuard] },
{ path: 'scan', component: ResourceOcrComponent, canActivate: [ IsAuthenticatedAuthGuard] },

{ path: 'desktop/callback/:state', component: DesktopCallbackComponent, canActivate: [ IsAuthenticatedAuthGuard] },

{ path: 'background-jobs', component: BackgroundJobsComponent, canActivate: [ IsAuthenticatedAuthGuard ] },
{ path: 'patient-profile', component: PatientProfileComponent, canActivate: [ IsAuthenticatedAuthGuard ] },
Expand Down
10 changes: 10 additions & 0 deletions frontend/src/app/app.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,11 @@ import { PractitionerHistoryComponent } from './pages/practitioner-history/pract
import { SettingsComponent } from './pages/settings/settings.component';
import { SetupEncryptionKeyComponent } from './pages/setup-encryption-key/setup-encryption-key.component';
import { GetEncryptionKeyWizardComponent } from './pages/get-encryption-key-wizard/get-encryption-key-wizard.component';
import { ResourceOcrComponent } from './pages/resource-ocr/resource-ocr.component';
import { PdfOcrComponent } from './pages/resource-ocr/pdf-ocr/pdf-ocr.component';
import { ImageOcrComponent } from './pages/resource-ocr/image-ocr/image-ocr.component';
import { CameraOcrComponent } from './pages/resource-ocr/camera-ocr/camera-ocr.component';
import { EncounterFormComponent } from './pages/resource-ocr/encounter-form/encounter-form.component';

@NgModule({
declarations: [
Expand All @@ -69,6 +74,11 @@ import { GetEncryptionKeyWizardComponent } from './pages/get-encryption-key-wiza
PractitionerHistoryComponent,
SetupEncryptionKeyComponent,
GetEncryptionKeyWizardComponent,
ResourceOcrComponent,
PdfOcrComponent,
ImageOcrComponent,
CameraOcrComponent,
EncounterFormComponent
],
imports: [
FormsModule,
Expand Down
2 changes: 2 additions & 0 deletions frontend/src/app/components/header/header.component.html
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,8 @@
</li>
<li class="nav-item" ngbDropdown [ngClass]="{ 'active': practitioners?.isActive }">
<a routerLink="/practitioners" routerLinkActive="active" #practitioners="routerLinkActive" class="nav-link"><fa-icon [icon]="['fas', 'id-card']"></fa-icon>&nbsp; Address book</a>
<li class="nav-item" *ngIf="isAdmin" ngbDropdown [ngClass]="{ 'active': users?.isActive }">
<a routerLink="/scan" routerLinkActive="active" #users="routerLinkActive" class="nav-link"><fa-icon [icon]="['fas', 'file']"></fa-icon>&nbsp; Scan</a>
</li>

</ul>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
<!-- LIVE CAMERA + CAPTURE -->
<div *ngIf="isCapturingNew" class="mb-3">
<label class="form-label">Live Camera</label>
<video #video class="w-100 border rounded" autoplay playsinline></video>

<button class="btn btn-primary w-100" (click)="captureImage()" [disabled]="capturedImages.length >= 30">
Capture Image ({{ capturedImages.length }}/30)
</button>
</div>

<!-- Canvas for internal use (hidden) -->
<canvas #canvas hidden></canvas>

<!-- Error -->
<div *ngIf="error" class="alert alert-danger">{{ error }}</div>

<!-- REVIEW + SEND TO OCR -->
<div *ngIf="capturedImages.length && !isCapturingNew && !ocrFiles.length" class="row">
<!-- LEFT SIDE: Image Preview -->
<div class="col-md-12 d-flex flex-column align-items-center">
<h6 class="text-muted mb-2">
Image {{ currentIndex + 1 }} of {{ capturedImages.length }}
</h6>

<img [src]="capturedImages[currentIndex]" class="img-thumbnail mb-3"
style="max-width: 100%; height: auto; max-height: 500px; object-fit: contain;" />

<div class="d-flex justify-content-center mb-3">
<button class="btn btn-outline-secondary mr-2" (click)="goToPrevious()" [disabled]="currentIndex === 0">
</button>
<button class="btn btn-outline-secondary" (click)="goToNext()"
[disabled]="currentIndex === capturedImages.length - 1">
</button>
</div>

<div class="d-flex justify-content-between w-100">
<button class="btn btn-secondary" (click)="prepareNewCapture()" [disabled]="capturedImages.length >= 30">
Capture Next Image
</button>

<button class="btn btn-success" (click)="sendAllToOcr()" [disabled]="isProcessing">
<span *ngIf="isProcessing" class="spinner-border spinner-border-sm me-2"></span>
Send All to OCR
</button>
</div>
</div>
</div>

<!-- OCR RESULT + ENCOUNTER FORM -->
<div *ngIf="ocrFiles.length" class="row">
<div class="col-md-6 d-flex flex-column align-items-center border-right">
<h6 class="text-muted mb-2">
Image {{ currentIndex + 1 }} of {{ capturedImages.length }}
</h6>

<img [src]="capturedImages[currentIndex]" class="img-thumbnail mb-3"
style="max-width: 100%; height: auto; max-height: 500px; object-fit: contain;" />

<div class="d-flex justify-content-center mb-3">
<button class="btn btn-outline-secondary mr-2" (click)="goToPrevious()" [disabled]="currentIndex === 0">
</button>
<button class="btn btn-outline-secondary" (click)="goToNext()"
[disabled]="currentIndex === capturedImages.length - 1">
</button>
</div>
<div *ngIf="isProcessing" class="text-center mb-3">
<div class="spinner-border text-primary" role="status">
<span class="sr-only">Processing...</span>
</div>
<div class="mt-2 text-muted">Processing...</div>
</div>

<div *ngIf="foundKeys && foundKeys.length" class="text-center p-2 border rounded bg-light w-100">
<strong>Extracted:</strong> {{ foundKeys.join(', ') }}
</div>
</div>

<!-- RIGHT SIDE: Encounter form -->
<div class="col-md-6 d-flex flex-column">
<div class="form-group flex-grow-1">
<app-encounter-form [uploadedFiles]="ocrFiles"></app-encounter-form>
</div>
</div>
</div>
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
video {
max-height: 400px;
object-fit: cover;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';

import { CameraOcrComponent } from './camera-ocr.component';
import { HTTP_CLIENT_TOKEN } from 'src/app/dependency-injection';
import { HttpClient } from '@angular/common/http';
import { HttpClientTestingModule } from '@angular/common/http/testing';

describe('CameraOcrComponent', () => {
let component: CameraOcrComponent;
let fixture: ComponentFixture<CameraOcrComponent>;

beforeEach(async () => {
await TestBed.configureTestingModule({
declarations: [CameraOcrComponent],
imports: [HttpClientTestingModule],
providers: [
{
provide: HTTP_CLIENT_TOKEN,
useClass: HttpClient,
},
],
}).compileComponents();

fixture = TestBed.createComponent(CameraOcrComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});

it('should create', () => {
expect(component).toBeTruthy();
});
});
Loading