You are viewing an old version of this page. View the current version.

Compare with Current View Page History

Version 1 Current »

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


1) Context & Rationale

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.


2) High‑Level Solution Overview

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)

3) Requirements → Capabilities Mapping

RequirementCapability
Submissions with rating, category, commentAngular dialog + POST /common-api/feedback; validation
Anonymous or identifiedToggle in UI; platform stores internal user_ref_id only if not anonymous
Submit after logoutPublic /feedback route (Common‑UI), open endpoint with rate limiting
Security & privacyhashed-IP rate limiting in Redis

4) Frontend Design (Common‑UI )

4.1 Packaging & Integration

  • Location: Common‑UI embedded sub‑repo shared by all AMRIT UIs.

  • Route: /feedback (public). Drop‑in feedback-launcher directive/component for any page.

4.2 Components

  • 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.

4.3 Services & Interceptors

  • FeedbackService: submit.

  • FeedbackCategoryService: simple GET calls to common-api.

  • HTTP interceptor attaches non-PII context (app version, route).


5) Backend Design (common-api)

5.1 Modules

  • 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

5.2 Endpoints

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).

5.3 Abuse Protection & Rate Limiting

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.


6) Database Design (MySQL)

6.1 Tables (MVP)


-- 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',
  Value
  CreatedAt DATETIME NOT NULL,
  UpdatedAt DATETIME NOT NULL
) ;


-- 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);



7) API Contract (OpenAPI sketch)

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"
  • No labels