Создайте angular форму, которая будет следовать за деревом решений - PullRequest
0 голосов
/ 21 января 2020

Я хотел бы создать приложение Angular, которое будет задавать вопросы пользователю и генерировать следующий вопрос в соответствии с его ответом (следуя дереву решений).

Например (я покажу несколько сценариев ios, каждый на одной строке, вопросы на итальянском c и ответы жирным шрифтом):

                  CHEST PAIN ? 
                yes/          \no
                              RATE?
                         <60 /    \ >100
                                 HYPO ?
                              no/    \ yes
                                     **SEDATE**(result of the decision tree)

Не могли бы вы дать мне некоторые подсказки о том, как этого добиться? Это выглядит как дерево (или график). Должен ли я хранить свои вопросы в такой структуре данных?

1 Ответ

1 голос
/ 21 января 2020

Работает ли у вас что-то вроде , это ?

public readonly countries = ["France", "Germany"];
public readonly cities = {
    "France": ["Paris", "Lyon", "Marseille"],
    "Germany": ["Berlin", "Frankfurt", "Hamburg"]
};

public selection = {
    country: '',
    city: ''
};
Pick country
<select [(ngModel)]="selection.country">
  <option *ngFor="let c of countries" [ngValue]="c">{{c}}</option>
</select>

<br/>
<br/>

<ng-container *ngIf="selection.country.length > 0">
    Pick city
    <select [(ngModel)]="selection.city">
      <option *ngFor="let c of cities[selection.country]" [ngValue]="c">{{c}}</option>
    </select>
</ng-container>

Начинается с начального выбора верхнего уровня, а затем показывает следующий вопрос в зависимости от предыдущего ответа.

ОБНОВЛЕНИЕ 1: Я адаптировал ответ на основе опубликованной вами диаграммы. Новое решение не использует жестко закодированные пути, а определяет структуру «дерева решений» и использует ее. Я объясню более подробно позже, но суть в том, что вам нужно изменить только содержимое одной переменной, и приложение все равно будет работать. Новый stackblitz .

Идея заключается в сохранении каждого узла в вашем дереве в виде объекта, подобного следующему:

export class TreeNode {
  public readonly id: nodeId;
  public readonly description: string;
  public decision: boolean | null;
  public readonly yesId: nodeId | null;
  public readonly noId: nodeId | null;
  constructor(
    id: nodeId,
    description: string,
    yesId: nodeId | null,
    noId: nodeId | null
  ) {
    this.id = id;
    this.description = description;
    this.decision = null; // <-- must be null on creation. wait for decision from user.
    this.yesId = yesId;
    this.noId = noId;
  }
}
  • id - это строка, если быть уникальным среди всех узлов. Подробнее об этом позже.
  • description - тело вопроса / ответа.
  • decision - результат вопроса Да / Нет.
  • yesId is id узла, к которому мы должны перейти, когда решение - да.
  • noId - это id узла, к которому мы должны перейти, когда решение - нет. Если узел является листом, то yesId и noId должны быть равны нулю.

Я сохранил все возможные узлы из вашего чертежа как «словарь» под названием nodeList, поэтому мы можем получить доступ к узлу, используя синтаксис nodeList[id]. Теперь это выглядит так:

/**
 * Possible values for a node id. This is optional, but highly encouraged.
 */
export type nodeId =
  | "chestPain"
  | "twaveInversion"
  | "rateOver100"
  | "hypotensive"
  | "nstemi"
  | "unstableAngina"
  | "sedate"
  | "qrsOver012"
  | "vtGetExpertHelp"
  | "qrsRegular"
  | "svtVagal"
  | "pwavesPresent"
  | "atrialFibrilation"
  | "aflutter"
  | "rate100temp";

/**
 * Dictionary of nodes using their id as a key.
 */
export const nodeList = {
  chestPain: new TreeNode(
    "chestPain",
    "Chest Pain?",
    "twaveInversion",
    "rateOver100"
  ),
  twaveInversion: new TreeNode(
    "twaveInversion",
    "Twave Inversion?",
    "nstemi",
    "unstableAngina"
  ),
  unstableAngina: new TreeNode("unstableAngina", "Unstable Angina", null, null),
  nstemi: new TreeNode("nstemi", "NSTEMI", null, null),
  rateOver100: new TreeNode(
    "rateOver100",
    "Rate > 100?",
    "hypotensive",
    "rate100temp"
  ),
  hypotensive: new TreeNode(
    "hypotensive",
    "Hypotensive?",
    "sedate",
    "qrsOver012"
  ),
  sedate: new TreeNode("sedate", "Sedate.", null, null),
  qrsOver012: new TreeNode(
    "qrsOver012",
    "QRS > 0.12s ?",
    "vtGetExpertHelp",
    "qrsRegular"
  ),
  vtGetExpertHelp: new TreeNode(
    "vtGetExpertHelp",
    "VT Get expert help.",
    null,
    null
  ),
  qrsRegular: new TreeNode(
    "qrsRegular",
    "QRS regular?",
    "svtVagal",
    "pwavesPresent"
  ),
  svtVagal: new TreeNode("svtVagal", "SVT Vagal", null, null),
  pwavesPresent: new TreeNode(
    "pwavesPresent",
    "Pwaves present?",
    "aflutter",
    "atrialFibrilation"
  ),
  aflutter: new TreeNode("aflutter", "Aflutter", null, null),
  atrialFibrilation: new TreeNode(
    "atrialFibrilation",
    "Afrial fibrilation",
    null,
    null
  ),
  rate100temp: new TreeNode(
    "rate100temp",
    "Rate < 100 answer (no node given)",
    null,
    null
  )
};

И, наконец, DecisionTreeFormComponent:

export class DecisionTreeFormComponent implements OnInit {
  public decisionTree: IDecisionTree;
  public currentNode: TreeNode;

  public treeJSONhidden: boolean = true;
  public nodeJSONhidden: boolean = true;
  constructor() {}

  ngOnInit() {
    this.reset();    
  }

  public reset() {
    // Init base node and tree here.
    this.decisionTree = [];
    this.currentNode = Object.assign({}, nodeList.chestPain);
  }

  public yes() {
    this.currentNode.decision = true;
    this.pushNode();
    this.currentNode = Object.assign({}, nodeList[this.currentNode.yesId]);
    if( this.isFinal(this.currentNode)) {
      this.pushNode();
    }
  }

  public no() {
    this.currentNode.decision = false;
    this.pushNode();
    this.currentNode = Object.assign({}, nodeList[this.currentNode.noId]);
    if( this.isFinal(this.currentNode)) {
      this.pushNode();
    }
  }

  public isFinal = (node: TreeNode) => node.yesId == null && node.noId == null;

  private pushNode():void {
    this.decisionTree.push({
      node: this.currentNode,
      index: this.decisionTree.length
    });
  }
}

В методе reset() я просто инициализирую currentNode к вопросу о боли в груди а остальное обрабатывается по выбору пользователя. Я сохраняю путь в виде массива для отображения окончательного результата при достижении конечного узла. Посмотрите образец HTML, который я подготовил в стеке, но в данном случае презентация не имеет значения. Вы можете настроить его в соответствии со своими потребностями.

Я думаю, что это правильное решение, где вам нужно всего лишь изменить nodeList, и форма будет работать на основе значений, которые там найдены.

Ограничения: В настоящее время это работает только для вопросов типа ДА / НЕТ. Если вы ожидаете множественный выбор, вы можете попытаться «рефакторинг» вопросов, чтобы они были множеством истинных / ложных вопросов. В противном случае мы можем обсудить это дальше.

...