690512:1537 Prepare refactor Work Flow [skip ci]
This commit is contained in:
@@ -0,0 +1,230 @@
|
||||
# Phase 0 Research: RFA Approval System Refactor
|
||||
|
||||
**Date**: 2026-05-11
|
||||
**Purpose**: Research technical patterns and validate design decisions
|
||||
|
||||
---
|
||||
|
||||
## Research Topics
|
||||
|
||||
### 1. Parallel Review in Workflow Engine
|
||||
|
||||
**Research Task**: Can Unified Workflow Engine (ADR-001) support parallel tasks with consensus rules?
|
||||
|
||||
**Decision**: ✅ **Yes, with DSL Extension**
|
||||
|
||||
**Rationale**:
|
||||
- Current DSL supports sequential states and transitions
|
||||
- Parallel review requires: (a) Task splitting on state entry, (b) Task aggregation before transition, (c) Consensus rule evaluation
|
||||
- Pattern: `ParallelGateState` - enters sub-workflows for each Discipline, aggregates on completion
|
||||
|
||||
**Implementation Pattern**:
|
||||
```typescript
|
||||
// DSL Extension: Parallel Gateway
|
||||
{
|
||||
type: 'parallel_gateway',
|
||||
config: {
|
||||
discriminator: 'discipline', // Split by discipline
|
||||
minCompletion: 'majority', // Consensus rule
|
||||
vetoConditions: [{ field: 'responseCode', value: '3' }] // Veto triggers
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Alternatives Considered**:
|
||||
- Option A: Multiple Workflow Instances per RFA (rejected - too complex, hard to aggregate)
|
||||
- Option B: Sequential with fast-forward (rejected - doesn't truly parallelize)
|
||||
- Option C: **Parallel Gateway in DSL** (selected - clean abstraction, reusable pattern)
|
||||
|
||||
**References**:
|
||||
- BPMN 2.0 Parallel Gateway pattern
|
||||
- Existing `workflow-dsl.schema.ts` in codebase
|
||||
|
||||
---
|
||||
|
||||
### 2. Response Code Matrix Storage
|
||||
|
||||
**Research Task**: Best structure for Master Approval Matrix with 5 categories × 11 codes?
|
||||
|
||||
**Decision**: **Normalized Relational Model with JSON for flexibility**
|
||||
|
||||
**Rationale**:
|
||||
- Core codes (1A-1G, 2, 3, 4) are stable relational data
|
||||
- Category mappings (which codes apply to which doc types) need flexibility
|
||||
- Project overrides need inheritance tracking
|
||||
|
||||
**Schema Design**:
|
||||
```sql
|
||||
-- Core Response Codes (stable)
|
||||
response_codes (id, code, sub_status, description_th, description_en, category)
|
||||
|
||||
-- Matrix Rules (project-specific overrides)
|
||||
response_code_rules (
|
||||
id,
|
||||
project_id NULLABLE, -- NULL = global default
|
||||
document_type_id,
|
||||
response_code_id,
|
||||
is_enabled,
|
||||
requires_comments,
|
||||
triggers_notification,
|
||||
parent_rule_id -- For inheritance tracking
|
||||
)
|
||||
```
|
||||
|
||||
**Alternatives Considered**:
|
||||
- Single JSON column for entire matrix (rejected - hard to query, validate, index)
|
||||
- Full EAV (Entity-Attribute-Value) (rejected - too complex for this use case)
|
||||
|
||||
---
|
||||
|
||||
### 3. Delegation Pattern & Circular Detection
|
||||
|
||||
**Research Task**: Best approach for delegation with chain depth limit and circular detection?
|
||||
|
||||
**Decision**: **Adjacency List with Path Enumeration, Max Depth = 3**
|
||||
|
||||
**Rationale**:
|
||||
- Adjacency List: Simple, fast for immediate lookup (`delegator_id → delegatee_id`)
|
||||
- Path Enumeration: Store full chain as array for circular detection
|
||||
- Max Depth 3: Prevents runaway chains while supporting realistic use cases
|
||||
|
||||
**Circular Detection Algorithm**:
|
||||
```typescript
|
||||
function detectCircularDelegation(delegatorId: string, proposedDelegateeId: string): boolean {
|
||||
// BFS/DFS from proposedDelegatee, check if can reach delegatorId
|
||||
const visited = new Set<string>();
|
||||
const queue = [proposedDelegateeId];
|
||||
|
||||
while (queue.length > 0) {
|
||||
const current = queue.shift()!;
|
||||
if (current === delegatorId) return true; // Circular!
|
||||
if (visited.has(current)) continue;
|
||||
visited.add(current);
|
||||
|
||||
// Add all delegatees of current
|
||||
const delegatees = getActiveDelegations(current);
|
||||
queue.push(...delegatees.map(d => d.delegateeId));
|
||||
}
|
||||
return false;
|
||||
}
|
||||
```
|
||||
|
||||
**Alternatives Considered**:
|
||||
- Nested Set Model (rejected - overkill for simple chains)
|
||||
- Closure Table (rejected - requires maintenance on delegation expiry)
|
||||
|
||||
---
|
||||
|
||||
### 4. BullMQ Pattern for Reminders & Distribution
|
||||
|
||||
**Research Task**: Best BullMQ patterns for scheduled reminders and async distribution?
|
||||
|
||||
**Decision**: **Delayed Jobs + Repeatable Jobs + Flows**
|
||||
|
||||
**Pattern Breakdown**:
|
||||
|
||||
**Reminders**:
|
||||
- **Delayed Jobs**: Schedule individual reminder at due date
|
||||
- **Repeatable Jobs**: Daily reminder for overdue items (cron pattern)
|
||||
- **Job Data**: `{ rfaId, reviewerId, reminderType, escalationLevel }`
|
||||
|
||||
**Distribution**:
|
||||
- **Job Flow**: Parent (distribution coordinator) → Children (individual deliveries)
|
||||
- **Retry**: 3 attempts with exponential backoff
|
||||
- **Dead Letter**: Failed distributions logged for manual intervention
|
||||
|
||||
```typescript
|
||||
// Reminder Queue Pattern
|
||||
await reminderQueue.add('rfa-reminder', {
|
||||
rfaRevisionId,
|
||||
reviewerId,
|
||||
reminderType: 'DUE_SOON'
|
||||
}, {
|
||||
delay: calculateDelay(dueDate, reminderDaysBefore)
|
||||
});
|
||||
|
||||
// Distribution Flow Pattern
|
||||
await distributionFlow.add({
|
||||
name: 'rfa-distribution',
|
||||
data: { rfaId, responseCode, recipients: [...] },
|
||||
children: recipients.map(r => ({
|
||||
name: 'deliver-document',
|
||||
data: { recipientId: r.id, method: r.deliveryMethod }
|
||||
}))
|
||||
});
|
||||
```
|
||||
|
||||
**Alternatives Considered**:
|
||||
- node-cron for scheduling (rejected - no persistence, no retry)
|
||||
- Custom scheduler service (rejected - BullMQ already provides this)
|
||||
|
||||
---
|
||||
|
||||
### 5. Review Task Status Aggregation
|
||||
|
||||
**Research Task**: How to efficiently calculate aggregate status for parallel reviews?
|
||||
|
||||
**Decision**: **Materialized View + Real-time Counter**
|
||||
|
||||
**Rationale**:
|
||||
- Materialized View: Fast reads for list views ("2 of 3 approved")
|
||||
- Real-time Counter: Immediate update on each review action
|
||||
- Trigger: Update counter on ReviewTask status change
|
||||
|
||||
**Aggregation Logic**:
|
||||
```sql
|
||||
-- Materialized view for fast reads
|
||||
CREATE VIEW review_task_summary AS
|
||||
SELECT
|
||||
rfa_revision_id,
|
||||
COUNT(*) as total_disciplines,
|
||||
SUM(CASE WHEN status = 'COMPLETED' THEN 1 ELSE 0 END) as completed,
|
||||
SUM(CASE WHEN response_code = '3' THEN 1 ELSE 0 END) as rejected_count
|
||||
FROM review_tasks
|
||||
GROUP BY rfa_revision_id;
|
||||
|
||||
-- Real-time consensus check
|
||||
SELECT CASE
|
||||
WHEN rejected_count > 0 THEN 'REJECTED' -- Veto triggered
|
||||
WHEN completed = total_disciplines THEN
|
||||
CASE
|
||||
WHEN approved_count / total_disciplines >= 0.5 THEN 'APPROVED'
|
||||
ELSE 'NEEDS_REVIEW'
|
||||
END
|
||||
ELSE 'IN_PROGRESS'
|
||||
END as consensus_status
|
||||
FROM review_task_summary;
|
||||
```
|
||||
|
||||
**Alternatives Considered**:
|
||||
- Calculate on-demand (rejected - slow with many disciplines)
|
||||
- Application-level cache (rejected - stale data risk)
|
||||
|
||||
---
|
||||
|
||||
## Summary of Decisions
|
||||
|
||||
| Topic | Decision | Key Rationale |
|
||||
|-------|----------|---------------|
|
||||
| Parallel Review | DSL Parallel Gateway | Clean abstraction, reusable |
|
||||
| Response Code Storage | Normalized + JSON | Balance of structure and flexibility |
|
||||
| Delegation | Adjacency List + Path Enum | Simple, sufficient for depth ≤3 |
|
||||
| Queue Pattern | BullMQ Delayed + Flows | Industry standard, reliable |
|
||||
| Status Aggregation | Materialized View + Counter | Fast reads, real-time updates |
|
||||
|
||||
---
|
||||
|
||||
## Risk Assessment
|
||||
|
||||
| Risk | Probability | Mitigation |
|
||||
|------|-------------|------------|
|
||||
| DSL Parallel Gateway complexity | Medium | Prototype with simple 2-discipline case first |
|
||||
| Response Code migration from existing | Low | New tables, existing data untouched |
|
||||
| Performance on large Review Teams | Low | Pagination on aggregation, Redis caching |
|
||||
| Circular delegation algorithm | Low | Unit test with 3-level chains |
|
||||
|
||||
---
|
||||
|
||||
## Next Phase
|
||||
|
||||
**Phase 1**: Design data model and API contracts based on these research decisions.
|
||||
Reference in New Issue
Block a user