Merge pull request 'Add CI pipeline for Docker image builds' (#5) from staging into main
Some checks failed
Build and Push Docker Image / build (push) Failing after 20s

Reviewed-on: #5
This commit is contained in:
ismail 2026-04-19 13:48:05 +03:00
commit 43ef57f1b6
273 changed files with 316 additions and 58 deletions

4
.gitignore vendored
View File

@ -97,4 +97,6 @@ database_export/
# Lock files # Lock files
*.tar.gz *.tar.gz
dump.rdb dump.rdb
.~lock.* .~lock.*
node_modules

View File

@ -0,0 +1,95 @@
# Move Inquiry AI Analysis to Celery Task
## Summary
Replace synchronous AI calls in 3 inquiry creation views with the existing `analyze_inquiry_with_ai.delay()` Celery task.
## Changes
### 1. `apps/complaints/ui_views.py``inquiry_create` (lines 1554-1567)
**Replace:**
```python
from apps.complaints.tasks import _apply_inquiry_ai_analysis
from apps.core.ai_service import AIService
try:
analysis = AIService.analyze_inquiry(
subject=inquiry.subject,
message=inquiry.message,
category=inquiry.get_category_display(),
hospital_id=inquiry.hospital.id,
)
emotion_analysis = AIService.analyze_emotion(text=inquiry.message)
_apply_inquiry_ai_analysis(inquiry, analysis, emotion_analysis)
except Exception as e:
logger.error(f"AI analysis failed on inquiry creation: {e}")
```
**With:**
```python
from apps.complaints.tasks import analyze_inquiry_with_ai
analyze_inquiry_with_ai.delay(str(inquiry.id))
```
Also update line 1572 — change `"Inquiry created with AI analysis."` to `"Inquiry created."`
---
### 2. `apps/complaints/ui_views.py``public_inquiry_submit` (lines 2315-2328)
**Replace:**
```python
from apps.complaints.tasks import _apply_inquiry_ai_analysis
from apps.core.ai_service import AIService
try:
analysis = AIService.analyze_inquiry(
subject=inquiry.subject,
message=inquiry.message,
category=category,
hospital_id=hospital.id,
)
emotion_analysis = AIService.analyze_emotion(text=inquiry.message)
_apply_inquiry_ai_analysis(inquiry, analysis, emotion_analysis)
except Exception as e:
logger.error(f"AI analysis failed on public inquiry creation: {e}")
```
**With:**
```python
from apps.complaints.tasks import analyze_inquiry_with_ai
analyze_inquiry_with_ai.delay(str(inquiry.id))
```
---
### 3. `apps/complaints/views.py``InquiryViewSet.perform_create` (lines 2750-2763)
**Replace:**
```python
from apps.complaints.tasks import _apply_inquiry_ai_analysis
from apps.core.ai_service import AIService
try:
analysis = AIService.analyze_inquiry(
subject=inquiry.subject,
message=inquiry.message,
category=inquiry.get_category_display(),
hospital_id=inquiry.hospital.id,
)
emotion_analysis = AIService.analyze_emotion(text=inquiry.message)
_apply_inquiry_ai_analysis(inquiry, analysis, emotion_analysis)
except Exception as e:
logger.error(f"AI analysis failed on inquiry creation: {e}")
```
**With:**
```python
from apps.complaints.tasks import analyze_inquiry_with_ai
analyze_inquiry_with_ai.delay(str(inquiry.id))
```
Also update docstring on line 2747 from `"Auto-set created_by from request.user and run AI analysis synchronously"` to `"Auto-set created_by from request.user and trigger AI analysis in background"`.

View File

@ -1551,25 +1551,14 @@ def inquiry_create(request):
inquiry.save() inquiry.save()
from apps.complaints.tasks import _apply_inquiry_ai_analysis from apps.complaints.tasks import analyze_inquiry_with_ai
from apps.core.ai_service import AIService
try: analyze_inquiry_with_ai.delay(str(inquiry.id))
analysis = AIService.analyze_inquiry(
subject=inquiry.subject,
message=inquiry.message,
category=inquiry.get_category_display(),
hospital_id=inquiry.hospital.id,
)
emotion_analysis = AIService.analyze_emotion(text=inquiry.message)
_apply_inquiry_ai_analysis(inquiry, analysis, emotion_analysis)
except Exception as e:
logger.error(f"AI analysis failed on inquiry creation: {e}")
InquiryUpdate.objects.create( InquiryUpdate.objects.create(
inquiry=inquiry, inquiry=inquiry,
update_type="note", update_type="note",
message="Inquiry created with AI analysis.", message="Inquiry created.",
created_by=request.user, created_by=request.user,
) )
@ -2312,20 +2301,9 @@ def public_inquiry_submit(request):
reference_number=reference_number, reference_number=reference_number,
) )
from apps.complaints.tasks import _apply_inquiry_ai_analysis from apps.complaints.tasks import analyze_inquiry_with_ai
from apps.core.ai_service import AIService
try: analyze_inquiry_with_ai.delay(str(inquiry.id))
analysis = AIService.analyze_inquiry(
subject=inquiry.subject,
message=inquiry.message,
category=category,
hospital_id=hospital.id,
)
emotion_analysis = AIService.analyze_emotion(text=inquiry.message)
_apply_inquiry_ai_analysis(inquiry, analysis, emotion_analysis)
except Exception as e:
logger.error(f"AI analysis failed on public inquiry creation: {e}")
AuditService.log_event( AuditService.log_event(
event_type="inquiry_created_public", event_type="inquiry_created_public",

View File

@ -2744,23 +2744,12 @@ class InquiryViewSet(viewsets.ModelViewSet):
ordering = ["-created_at"] ordering = ["-created_at"]
def perform_create(self, serializer): def perform_create(self, serializer):
"""Auto-set created_by from request.user and run AI analysis synchronously""" """Auto-set created_by from request.user and trigger AI analysis in background"""
inquiry = serializer.save(created_by=self.request.user) inquiry = serializer.save(created_by=self.request.user)
from apps.complaints.tasks import _apply_inquiry_ai_analysis from apps.complaints.tasks import analyze_inquiry_with_ai
from apps.core.ai_service import AIService
try: analyze_inquiry_with_ai.delay(str(inquiry.id))
analysis = AIService.analyze_inquiry(
subject=inquiry.subject,
message=inquiry.message,
category=inquiry.get_category_display(),
hospital_id=inquiry.hospital.id,
)
emotion_analysis = AIService.analyze_emotion(text=inquiry.message)
_apply_inquiry_ai_analysis(inquiry, analysis, emotion_analysis)
except Exception as e:
logger.error(f"AI analysis failed on inquiry creation: {e}")
AuditService.log_from_request( AuditService.log_from_request(
event_type="inquiry_created", event_type="inquiry_created",

View File

@ -13,6 +13,8 @@ from celery import shared_task
from django.utils import timezone from django.utils import timezone
from django.core.files.base import ContentFile from django.core.files.base import ContentFile
from celery import shared_task from celery import shared_task
from datetime import timedelta
import logging
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)

View File

@ -222,7 +222,7 @@ def observation_create(request):
title=form.cleaned_data.get("title", ""), title=form.cleaned_data.get("title", ""),
location_text=form.cleaned_data.get("location_text", ""), location_text=form.cleaned_data.get("location_text", ""),
incident_datetime=form.cleaned_data.get("incident_datetime"), incident_datetime=form.cleaned_data.get("incident_datetime"),
reporter_staff_id=user.staff_id or "", reporter_staff_id=user.employee_id or "",
reporter_name=user.get_full_name(), reporter_name=user.get_full_name(),
reporter_phone="", reporter_phone="",
reporter_email=user.email, reporter_email=user.email,

37
node_modules/.package-lock.json generated vendored
View File

@ -30,6 +30,15 @@
"node": ">=12" "node": ">=12"
} }
}, },
"node_modules/@fontsource/inter": {
"version": "5.2.8",
"resolved": "https://registry.npmjs.org/@fontsource/inter/-/inter-5.2.8.tgz",
"integrity": "sha512-P6r5WnJoKiNVV+zvW2xM13gNdFhAEpQ9dQJHt3naLvfg+LkF2ldgSLiF4T41lf1SQCM9QmkqPTn4TH568IRagg==",
"license": "OFL-1.1",
"funding": {
"url": "https://github.com/sponsors/ayuhito"
}
},
"node_modules/@jridgewell/gen-mapping": { "node_modules/@jridgewell/gen-mapping": {
"version": "0.3.13", "version": "0.3.13",
"resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz",
@ -220,6 +229,12 @@
"node": ">= 8" "node": ">= 8"
} }
}, },
"node_modules/apexcharts": {
"version": "5.10.6",
"resolved": "https://registry.npmjs.org/apexcharts/-/apexcharts-5.10.6.tgz",
"integrity": "sha512-FJQGbso3iRuOwUYnj0yUhkWeKeJE6aboVol+ae09lsc+lbLMWZqSRbrAWVa/qishLiaeG2icxdvmVkm+9n6kOQ==",
"license": "SEE LICENSE IN LICENSE"
},
"node_modules/arg": { "node_modules/arg": {
"version": "4.1.3", "version": "4.1.3",
"resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz", "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz",
@ -454,6 +469,12 @@
"node": ">= 0.4" "node": ">= 0.4"
} }
}, },
"node_modules/htmx.org": {
"version": "2.0.8",
"resolved": "https://registry.npmjs.org/htmx.org/-/htmx.org-2.0.8.tgz",
"integrity": "sha512-fm297iru0iWsNJlBrjvtN7V9zjaxd+69Oqjh4F/Vq9Wwi2kFisLcrLCiv5oBX0KLfOX/zG8AUo9ROMU5XUB44Q==",
"license": "0BSD"
},
"node_modules/is-binary-path": { "node_modules/is-binary-path": {
"version": "2.1.0", "version": "2.1.0",
"resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz",
@ -547,6 +568,12 @@
"dev": true, "dev": true,
"license": "MIT" "license": "MIT"
}, },
"node_modules/lucide": {
"version": "1.8.0",
"resolved": "https://registry.npmjs.org/lucide/-/lucide-1.8.0.tgz",
"integrity": "sha512-JjV/QnadgFLj1Pyu9IKl0lknrolFEzo04B64QcYLLeRzZl/iEHpdbSrRRKbyXcv45SZNv+WGjIUCT33e7xHO6Q==",
"license": "ISC"
},
"node_modules/make-error": { "node_modules/make-error": {
"version": "1.3.6", "version": "1.3.6",
"resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz", "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz",
@ -1011,6 +1038,16 @@
"url": "https://github.com/sponsors/ljharb" "url": "https://github.com/sponsors/ljharb"
} }
}, },
"node_modules/sweetalert2": {
"version": "11.26.24",
"resolved": "https://registry.npmjs.org/sweetalert2/-/sweetalert2-11.26.24.tgz",
"integrity": "sha512-SLgukW4wicewpW5VOukSXY5Z6DL/z7HCOK2ODSjmQPiSphCN8gJAmh9npoceXOtBRNoDN0xIz+zHYthtfiHmjg==",
"license": "MIT",
"funding": {
"type": "individual",
"url": "https://github.com/sponsors/limonte"
}
},
"node_modules/tailwindcss": { "node_modules/tailwindcss": {
"version": "3.4.19", "version": "3.4.19",
"resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.4.19.tgz", "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.4.19.tgz",

44
package-lock.json generated
View File

@ -7,6 +7,13 @@
"": { "": {
"name": "hh", "name": "hh",
"version": "0.1.0", "version": "0.1.0",
"dependencies": {
"@fontsource/inter": "^5.2.8",
"apexcharts": "^5.10.6",
"htmx.org": "^2.0.8",
"lucide": "^1.8.0",
"sweetalert2": "^11.26.24"
},
"devDependencies": { "devDependencies": {
"@playwright/test": "^1.59.1", "@playwright/test": "^1.59.1",
"@types/node": "^25.5.2", "@types/node": "^25.5.2",
@ -41,6 +48,15 @@
"node": ">=12" "node": ">=12"
} }
}, },
"node_modules/@fontsource/inter": {
"version": "5.2.8",
"resolved": "https://registry.npmjs.org/@fontsource/inter/-/inter-5.2.8.tgz",
"integrity": "sha512-P6r5WnJoKiNVV+zvW2xM13gNdFhAEpQ9dQJHt3naLvfg+LkF2ldgSLiF4T41lf1SQCM9QmkqPTn4TH568IRagg==",
"license": "OFL-1.1",
"funding": {
"url": "https://github.com/sponsors/ayuhito"
}
},
"node_modules/@jridgewell/gen-mapping": { "node_modules/@jridgewell/gen-mapping": {
"version": "0.3.13", "version": "0.3.13",
"resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz",
@ -231,6 +247,12 @@
"node": ">= 8" "node": ">= 8"
} }
}, },
"node_modules/apexcharts": {
"version": "5.10.6",
"resolved": "https://registry.npmjs.org/apexcharts/-/apexcharts-5.10.6.tgz",
"integrity": "sha512-FJQGbso3iRuOwUYnj0yUhkWeKeJE6aboVol+ae09lsc+lbLMWZqSRbrAWVa/qishLiaeG2icxdvmVkm+9n6kOQ==",
"license": "SEE LICENSE IN LICENSE"
},
"node_modules/arg": { "node_modules/arg": {
"version": "4.1.3", "version": "4.1.3",
"resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz", "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz",
@ -480,6 +502,12 @@
"node": ">= 0.4" "node": ">= 0.4"
} }
}, },
"node_modules/htmx.org": {
"version": "2.0.8",
"resolved": "https://registry.npmjs.org/htmx.org/-/htmx.org-2.0.8.tgz",
"integrity": "sha512-fm297iru0iWsNJlBrjvtN7V9zjaxd+69Oqjh4F/Vq9Wwi2kFisLcrLCiv5oBX0KLfOX/zG8AUo9ROMU5XUB44Q==",
"license": "0BSD"
},
"node_modules/is-binary-path": { "node_modules/is-binary-path": {
"version": "2.1.0", "version": "2.1.0",
"resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz",
@ -573,6 +601,12 @@
"dev": true, "dev": true,
"license": "MIT" "license": "MIT"
}, },
"node_modules/lucide": {
"version": "1.8.0",
"resolved": "https://registry.npmjs.org/lucide/-/lucide-1.8.0.tgz",
"integrity": "sha512-JjV/QnadgFLj1Pyu9IKl0lknrolFEzo04B64QcYLLeRzZl/iEHpdbSrRRKbyXcv45SZNv+WGjIUCT33e7xHO6Q==",
"license": "ISC"
},
"node_modules/make-error": { "node_modules/make-error": {
"version": "1.3.6", "version": "1.3.6",
"resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz", "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz",
@ -1037,6 +1071,16 @@
"url": "https://github.com/sponsors/ljharb" "url": "https://github.com/sponsors/ljharb"
} }
}, },
"node_modules/sweetalert2": {
"version": "11.26.24",
"resolved": "https://registry.npmjs.org/sweetalert2/-/sweetalert2-11.26.24.tgz",
"integrity": "sha512-SLgukW4wicewpW5VOukSXY5Z6DL/z7HCOK2ODSjmQPiSphCN8gJAmh9npoceXOtBRNoDN0xIz+zHYthtfiHmjg==",
"license": "MIT",
"funding": {
"type": "individual",
"url": "https://github.com/sponsors/limonte"
}
},
"node_modules/tailwindcss": { "node_modules/tailwindcss": {
"version": "3.4.19", "version": "3.4.19",
"resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.4.19.tgz", "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.4.19.tgz",

View File

@ -26,5 +26,12 @@
"tailwindcss": "^3.4.19", "tailwindcss": "^3.4.19",
"ts-node": "^10.9.2", "ts-node": "^10.9.2",
"typescript": "^6.0.2" "typescript": "^6.0.2"
},
"dependencies": {
"@fontsource/inter": "^5.2.8",
"apexcharts": "^5.10.6",
"htmx.org": "^2.0.8",
"lucide": "^1.8.0",
"sweetalert2": "^11.26.24"
} }
} }

File diff suppressed because one or more lines are too long

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Some files were not shown because too many files have changed in this diff Show More