Owner: Amoghavarsh Desai
Reviewers: Dr Mithun James
Status: Draft v3
Last updated: 28 Aug 2025 (Asia/Kolkata)
Ticket: https://support.piramalfoundation.org/jira/browse/AMM-1337
AMRIT (Accessible Medical Records via Integrated Technologies) spans multiple service lines—1097, 104, HWC, MMU, TM, and ECD. We need a shared, reusable Feedback capability that can be embedded across all these service lines and also works post‑logout. The solution should unify collection and privacy access while minimizing coupling to individual services.
Key drivers
Capture qualitative + quantitative user signals across service lines consistently.
Enable anonymous submissions; optionally identified (internal user ID only) without collecting PII.
Configurable feedback categories via Admin and reflected everywhere.
We add a standalone UI package in Common‑UI and expose ingestion + category config endpoints in common-api. Every service line integrates the widget(s). Analytics is handled by BIDA by reading from the platform tables; the platform does not run aggregation jobs.
[Service Lines: 1097 | 104 | HWC | MMU | TM | ECD]
│
└──> Reusable Feedback Launcher + Dialog (Common‑UI)
│
▼
common-api (ingestion + category config)
┌──────────────────────┬────────────────────────────────────────┐
│/common-api/feedback │ /common-api/feedback/categories (GET)
└─────────────────────────────┴─────────────────────────────────┘
│
MySQL (source)
- feedback (content)
- feedback_category (config)
Requirement | Capability |
---|---|
Submissions with rating, category, comment | Angular dialog + POST /common-api/feedback ; validation |
Anonymous or identified | Toggle in UI; platform stores internal user_ref_id only if not anonymous |
Submit after logout | Public /feedback route (Common‑UI), open endpoint with rate limiting |
Security & privacy | hashed-IP rate limiting in Redis |
Location: Common‑UI embedded sub‑repo shared by all AMRIT UIs.
Route: /feedback
(public). Drop‑in feedback-launcher
directive/component for any page.
FeedbackLauncherComponent
– entry point; accepts context
, defaultCategory
, serviceLine
.
FeedbackDialogComponent
– Reactive form: rating(1–5)
, category
(fetched from API), comment(≤2000)
, anonymous
, optional identified if the user opts-in using internal user id from auth context.
FeedbackPublicPageComponent
– Same form, no auth needed.
FeedbackService
: submit
.
FeedbackCategoryService
: simple GET calls to common-api.
HTTP interceptor attaches non-PII context (app version, route).
com.amrit.feedback.api
– REST (ingestion & categories)
com.amrit.feedback.service
– business logic
com.amrit.feedback.repo
– JPA repositories
com.amrit.feedback.model
– entities & DTOs
Public (no auth required)
POST /common-api/feedback
Body: FeedbackRequest
Validations: size limits, profanity list (optional warning), serviceLine & categoryId exist and active.
Behavior: No role check; if user is identified and not anonymous, persist user_ref_id
(internal id).
Returns { id, createdAt }
.
GET /common-api/feedback/categories
Returns active categories (GLOBAL + serviceLine overlay).
Since the APIs are open we need to secure them from abuse so here are the steps we plan to take to prevent any attack
Hash at app layer: ip_hash = SHA256(client_ip + server_pepper + yyyyMMdd)
; never store raw IP or hash in DB.
Redis keys: rl:fb:min:{ip_hash}
(token bucket, TTL ~10m), rl:fb:day:{yyyyMMdd}:{ip_hash}
(counter, TTL ~48h), optional rl:fb:user:{yyyyMMdd}:{userId}
.
Limits: 10/min burst, 100/day/IP; optional 50/day per user when identified.
Backoff: after 3 rejections in 5 minutes, temporary block (e.g., 15 minutes) with HTTP 429 + Retry-After
.
-- Categories
CREATE TABLE m_feedback_category (
CategoryID CHAR(36) PRIMARY KEY,
Label VARCHAR(128) NOT NULL,
Scope ENUM('GLOBAL','1097','104','HWC','MMU','TM','ECD') NOT NULL DEFAULT 'GLOBAL',
Active BIT(1) NOT NULL DEFAULT b'1',
Slug VARCHAR(64) NOT NULL
COMMENT 'Stable machine identifier; lowercase alphanumeric with optional dashes; must match ^[a-z0-9]+(?:-[a-z0-9]+)*$',
CreatedAt DATETIME NOT NULL,
UpdatedAt DATETIME NOT NULL,
CONSTRAINT uq_category_slug UNIQUE (Slug),
CONSTRAINT chk_category_slug_format CHECK (Slug REGEXP '^[a-z0-9]+(-[a-z0-9]+)*$')
)-- Feedback (content)
CREATE TABLE m_platform_feedback (
FeedbackID CHAR(36) PRIMARY KEY,
CreatedAt DATETIME NOT NULL,
UpdatedAt DATETIME NOT NULL,
Rating TINYINT NOT NULL CHECK (Rating BETWEEN 1 AND 5),
CategoryID CHAR(36) NOT NULL,
Comment TEXT NOT NULL,
ServiceLine ENUM('1097','104','HWC','MMU','TM','ECD') NOT NULL,
IsAnonymous BIT(1) NOT NULL DEFAULT b'1',
UserID INT,
CONSTRAINT fk_platform_feedback_user FOREIGN KEY (UserID) REFERENCES m_user(UserID),
CONSTRAINT fk_platform_feedback_category FOREIGN KEY (CategoryID) REFERENCES m_feedback_category(CategoryID)
);CREATE INDEX ix_feedback_CreatedAt ON m_platform_feedback (CreatedAt);
CREATE INDEX ix_feedback_ServiceLine ON m_platform_feedback (ServiceLine);
CREATE INDEX ix_feedback_CategoryID ON m_platform_feedback (CategoryID);
CREATE INDEX ix_feedback_IsAnonymous ON m_platform_feedback (IsAnonymous);
paths: /common-api/feedback: post: summary: Submit feedback (public, no role required) requestBody: content: application/json: schema: $ref: '#/components/schemas/FeedbackRequest' responses: '201': { description: FeedbackResponse } /common-api/feedback/categories: get: { summary: List categories } components:
schemas:
FeedbackRequest:
type: object
properties:
rating: { type: integer, minimum: 1, maximum: 5 }
categoryId: { type: string, format: uuid }
comment: { type: string, maxLength: 2000 }
isAnonymous:{ type: boolean }
serviceLine:
type: string
enum: ['1097','104','HWC','MMU','TM','ECD']
userId: { type: integer, description: "Internal m_user.UserID; required when isAnonymous=false" }
required:
- rating
- categoryId
- comment
- isAnonymous
- serviceLine
oneOf:
# Case 1: Anonymous submission -> userId MUST be absent
- properties:
isAnonymous:
type: boolean
enum: [true]
not:
required: [userId]
# Case 2: Identified submission -> userId is REQUIRED
- properties:
isAnonymous:
type: boolean
enum: [false]
required: [userId]
examples:
anonymous:
summary: Anonymous feedback
value:
rating: 5
categoryId: "2b1c2c7f-7f1e-4c6f-9f2a-6b5f3a0c1e9a"
comment: "Great UX on TM."
isAnonymous: true
serviceLine: "TM"
identified:
summary: Identified feedback
value:
rating: 4
categoryId: "2b1c2c7f-7f1e-4c6f-9f2a-6b5f3a0c1e9a"
comment: "Queue times improved."
isAnonymous: false
userId: 12345
serviceLine: "TM"