TraumAID 2.0
A Sketchy Guide through Uncharted Terrain
Covers
General Information about TraumAID
Running & Using TraumAID
X-Trauma
MacTrauma
TexTrauma
HyperTrauma
May Cover
the bottom of your bird cage
unsightly spots on your rug
Compiled By
Jonathan Kaye
Contributing Authors
Jonathan Kaye
Ulf Cahn von Steele
Abigail Gertner
Michael Niv
Last updated August, 1994
Part I — Understanding TraumAID
TraumAID has been developed over many years, but has remained principally in the programming hands of few. These few people have not always been careful in documenting what and how something works, because they are typically the only ones who had to deal with it. As TraumAID 2.0 progresses from the stages after its validation, our priorities need to include the importance of maintenance. A great part of this involves documenting how the program works and how our extensions to the program work, with hopes that future project members are not so confused by the program as are present project members.
Being confronted by the program, people in the beginning are naturally overwhelmed, though in reality the program is not very large. By understanding the key concepts, which we hope to elucidate here, one can gain a certain sense of control over how the program works, and later develop a better sense of pieces of the program by closer inspection.
Brief Description of TraumAID
TraumAID is a program to assist physicians during the critical period after the patient comes into the hospital and has been stabilized (the time referred to as ‘the initial definitive management’). In such an emergency situation, it is important to assess when it is appropriate to diagnose, and when it is appropriate to treat the injuries. Often, a physician must initiate therapy (abbreviated ‘Rx’) before completely diagnosing the extent of the injuries. Sometimes, therapeutic actions can also help diagnose the patient’s condition. From the known information, TraumAID proposes goals to address, such as a goal for diagnosing a particular condition or a goal for treating an injury. From these goals, TraumAID produces a plan (consisting of actions) that address the goals as best as possible, based on costs, time, location (some actions can only take place in certain areas, such as in the Operating Room), and other factors.
This document concentrates on the development version of TraumAID, where the program guides the user explicitly, or takes an active role in pursuing the diagnosis and treatment. For example, when the user enters the clinical findings and lets the program guide, the program may solicit more information or may decide it has enough information to act, in which case it suggests an action (the topmost from the plan). In general, bedside questions are asked first, and then the steps of the plan are executed.
It is important to note that the program does not compile a complete plan for diagnosis and treatment, then simply execute it. The program suggests a plan that addresses all the currently known goals, but as actions are done, more information becomes available which might change the plan. One of the most important aspects of TraumAID is its ability to change the plan to incorporate more information when it becomes available. The central philosophy behind this is that sometimes it is more important to act than it is to gather more information. TraumAID decides whether it is more worthwhile at the moment to pursue information gathering or perform treatment.
In contrast, the TraumAID installed in the Emergency Room takes a passive role. This means that it does not ask a question or tell the doctor what to do, rather it accepts the actions that the doctors have performed, and displays the plan to the physicians.
Brief History
The TraumAID project began around 1984 with TraumAID 1.0. The original version was developed on a Symbolics machine, in the Genera dialect of Lisp. TraumAID’s domain of expertise was limited to the abdomen. Over the next few years, Dr. Clarke and Michael Niv worked to expand the domain to the chest and abdomen.
Strictly speaking, TraumAID 1.0 was a production system, or expert system, that consisted of a rulebase that mapped findings to conclusions and prescriptions (actions to be done). After a few years, it was recognized that the system might perform better if the task were divided between an expert system to propose goals to achieve (based on findings and intermediate conclusions) and a planner that would design a plan of actions that were part of the chosen procedures for the known goals. The planner would consider factors such as which goals were currently relevant, which goals were urgent, where procedures had to take place, and how much time a procedure required. These factors would help in determining an optimal mix of actions for the procedures that were deemed necessary.
Since the goal was to have the system operational in an emergency room, the project took on another direction in the late eighties with the development of the HyperCard interface to TraumAID. The HyperCard interface was designed to be used at a nurse’s or technician’s station, where a person would enter information on one card and relevant information would be displayed on an external monitor, next to or above the patient’s bed. The interface would combine recording the case for hospital records and passing the information to TraumAID, where TraumAID would respond with its advice. The interface was originally designed and implemented as senior projects.
TraumAID 1.0 and 2.0 were developed on a Symbolics machine. TraumAID was linked with HyperCard using Symbolics’ MacIvory board, which essentially put a Symbolics machine inside a Macintosh. The original TraumAID 1.0 was also ported to C, and shown to execute on a laptop, for demonstration purposes.
Unfortunately, the Symbolics machines did not stand the test of time, as other machines became faster and much cheaper. It was clear that program development needed to be moved to a more general platform. In 1991, TraumAID was ported to the X-windows environment, under Lucid Common Lisp, and from there to the Macintosh, under Macintosh Common Lisp. TraumAID communicates with HyperCard using Apple Events, a System 7 feature that enables interprocess communication.
When we talk about TraumAID as a whole (the project), we talk about two different types of interfaces for TraumAID, the development interface and the actual interface. Quite naturally, we use the development interface to develop the TraumAID reasoner. The development interface is executable in X-windows, on the Mac, and through a simple text interface. The text interface would be useful if you were forced to run TraumAID from a VT100, or similar non-X-based platform. In this document, we talk about both interfaces, but concentrate almost exclusively on the programs that run with the development interface. The program that runs with the actual interface, HyperTrauma, is described in detail as well.
The Different Versions of TraumAID
While theoretically there is only one version of the TraumAID program, it exists on a few platforms. Therefore, there is some variation with respect to the interface on each platform. However, the core program (everything but the interface-specific routines) are exactly the same on all platforms. Currently, there exist the following versions of TraumAID 2.0:
|
Version Name |
Interface |
Platform(s) |
|
X-Trauma |
for program development |
Lucid Common Lisp 4.1, under X-Windows, using Tcl/Tk/wish package for window/menu interface |
|
HyperTrauma |
for actual use |
Macintosh Common Lisp 2.0 (MCL), on the Macintosh, with HyperCard 2.1 |
|
MacTrauma |
for program development |
MCL 2.0, on the Macintosh, using the Mac Toolbox for window/menu interface |
|
TexTrauma |
for program development |
Any Common Lisp, compatible with CLtL2 (Steele’s "Common Lisp the Language, version 2"). Uses plain text for interface, to enable program development on a terminal without windowing capabilities |
The program is divided into three layers:
1. The core routines
2. The interface-independent routines
3. The interface-specific routines
X-Trauma
X-Trauma is the version of TraumAID running in Lucid Common Lisp 4.1, that uses a package called ‘Tk/Tcl’ as an interface in the X-windows environment.
‘Tcl’, or ‘Tool Command Language,’ is a language for writing programs. This language has special commands for creating windows, menus, and other graphics. It is a very straightforward language. It is designed as a scripting language, similar in concept to Motif’s UIL (User Interface Language, I believe). The idea is that you write your C code to perform the functions you want in your program, but you don’t have a main program to control program execution. Instead, you write a script, in tcl, that calls your functions you have defined. The script is interpreted by an executable, built from a combination of your object code and Tk libraries. The Tk libraries provide routines for managing windows, creating graphics, managing events (like key entries, and button pushes), and all the system stuff you don’t want to have to do yourself in X.
So why do you want to do things like this? The point is that you do not have to write the code to manage windows, or menus. All the functions are defined for you. The scripting language gives you a convenient framework to integrate an interface with your specific code. This means that if you write C code, and link it with the Tk libraries, not only will you be able to do all the neat window stuff, but you will also get a scripting language for your program for free! This is useful so other people can write scripts that use your functions. So the heart of your application becomes an interpreter of tcl (which is provided), and when the interpreter reaches your command, which is not part of the predefined tcl (namely those routines in Tk that have been provided for you), it will call your routine and pass you the arguments nicely. As a bonus, interpreters can talk to each other (inter-process communication), even over a network (I believe). The ‘Tk/Tcl’ community is growing in popularity, and because it is completely free (even for commercial use), it is an attractive option for creating very useful programs.
What does this have to do with X-Trauma? The X-Trauma interface is actually a tcl script, and we use a default interpreter (we haven’t built in any of our own C functions) called wish. Oftentimes in this document we will mention wish, where we mean the process running wish (which is a tcl interpreter).
The following figure illustrates the connection between the X-Trauma system and the wish process that is executing the X-Trauma interface script.

The details of this interface will be discussed in a subsequent section. The one drawback in this scheme, as implemented, is that the communication is synchronous. This means that there is no way to interrupt the lisp processing (without killing the job). The user has to wait until the interface is waiting for a user command.
TexTrauma
MacTrauma
HyperTrauma
HyperTrauma is the version of the core TraumAID program that will be used in actual care. Its interface is the TraumAID HyperCard stacks. The programs communicate via Apple Events, which are asynchronous interprocess messages available in System 7.


In this scheme, each program is oblivious for whom it is processing a request. Each program defines a protocol for how to communicate with it, and any program that wants to send a message to another program must send the information according to the receiver program’s protocol.
Where it All Happens (John’s account)
The program currently resides in Dr. Clarke’s account on l
inc.cis.upenn.edu. This directory is ~jclarke. In this document, we will refer to files under the ~jclarke/Xtrauma2 directory (in Lucid) and the pathname on the Macintosh without the qualifying prefix, since nothing really happens relevant to TraumAID outside these directories. Here are some important files and directories, described by their logical directory name (though in Lucid they are absolute pathnames):
|
File or Directory |
Platform |
Contents |
|
~jclarke/traumaid-2.0 (on linc.cis.upenn.edu) |
Unix |
The home for X-Trauma and TexTrauma, the versions of the development interface on the Unix platform |
|
linc:TraumAID;Lisp System |
Mac |
Home of MacTrauma, the version of the development interface on the Mac |
|
Registry |
All |
The makefile definitions for TraumAID 2.0 |
|
system |
All |
The core system files |
|
system;batch-validation |
All |
Routines needed for validating rulebase |
|
system;case-io |
All |
Routines for loading/saving/printing cases |
|
system;load-kb |
All |
Routines for loading the knowledge base |
|
kb |
All |
The knowledge base files |
|
interface;independent |
All |
The interface-independent files |
|
interface;xwindows |
X-Trauma |
X-Trauma specific interface files |
|
interface;textrauma |
TexTrauma |
TexTrauma specific interface files |
|
interface;mactrauma |
MacTrauma |
MacTrauma specific interface files |
|
interface;hypertrauma |
HyperTrauma |
HyperTrauma specific interface files |
|
utils |
All |
Utility routines not needed for running TraumAID |
What do I need to know to begin?
The TraumAID engine is written in Common Lisp, as documented in Steele’s Common Lisp: the Language, Version 2 (on the newsgroups like
comp.lang.lisp this is often referred to simply as CLtL2) but really does not use ‘advanced’ concepts of the language. This, of course, does not mean you should avoid features of Lisp if you feel they are appropriate. Probably the most unfamiliar area that the program uses is CLOS, which is the object-oriented part of Common Lisp (Common Lisp Object System). Actually, TraumAID uses only basic features of CLOS, nevertheless it would be worthwhile understanding the first few chapters of a book such as Sonya Keene’s introduction to CLOS if you are not familiar with it already. It is very difficult to learn CLOS from Steele’s book, because that is meant to be a reference rather than a tutorial.As with any project where efficiency is a concern, one should feel somewhat comfortable in Lisp before modifying program code.
This document currently resides in electronic form on the Macintosh II fx in the Linc Lab, in the directory
LINC:TraumAID:Documentation:TraumAID 2.0 Tech Doc.
Differences Between Lucid Common Lisp and MCL
On
linc, we run Lucid Common Lisp (currently version 4.1). On the Macintosh, we use Macintosh Common Lisp (MCL, currently version 2.0p2). They are relatively compatible, but MCL adheres slightly more to the standard (CLtL2) than Lucid. Particularly, MCL supports logical pathnames (machine-independent specifications for pathnames), whereas the current version of Lucid does not. This will certainly change in the future. This is accommodated in the program by special tags that load relevant parts for different platforms. Ultimately, when Lucid has logical pathnames, we should remove this distinction (but will we?).
Also, Lucid, upon loading lisp, initially places the user in the
USER package. MCL puts the user in the COMMON-LISP-USER package. To promote harmony between the versions of TraumAID on different platforms, we load our files into the COMMON-LISP-USER package in Lucid. This is accomplished by prefacing each file with
(in-package :common-lisp-user)
This ensures the programs are loaded into the correct package. In this way, we can transfer the same files from the Macintosh to the Unix platform without changes. However, when one starts lisp in Lucid (X-Windows), it puts the user in the USER package. Before running TraumAID, one must be in the
common-lisp-user package. This means you must enter
> (in-package :common-lisp-user)
to transfer yourself to that package (when in Lucid). Alternatively, you may include the following alias in you .cshrc file (in your root directory):
alias lisp 'lisp+clos -e "(in-package :common-lisp-user)"'
which puts you in the
common-lisp-user package upon entering lisp (although it does not get activated if you enter lisp through emacs). The ‘lisp+clos’ is necessary on linc machines because it is an image with CLOS preloaded. If your lisp image already has CLOS loaded, use the plain ‘lisp’ in place of ‘lisp+clos’.The Representations in TraumAID
Overview
There are only a few different types of things in TraumAID, yet they can be confusing at first. Especially because the names are not always so intuitive. Nevertheless, most of the names have stuck and without a major overhaul, they may be here much longer than any of us. The next few paragraphs give a general overview of the types of knowledge in TraumAID.
‘Facts’ define the type of knowledge that TraumAID has access to. If a concept is not a ‘fact,’ then it cannot be reasoned with. A fact has properties, such as a SIDE. For example, a WOUND has, among other properties, a SIDE, a WOUND-DIRECTION, and a WOUND-TYPE. Each of these properties is called an ‘attribute.’ Facts are not something that is true or false; they merely define the concepts. ‘Instantiations’ are the objects in the system that represent that current knowledge about the case. So a gunshot wound to the abdomen, on the right side is the kind of information encoded in an instantiation. Instantiations can be true, false, suspected, and in some cases unknown.
The relationship of the knowledge is encoded in the rulebase, in the form of rules. There are currently three types of rules: conclude rules, suspect rules, and procedure rules. They will be discussed shortly.
The ultimate job of TraumAID is to suggest diagnostic and therapeutic action. To drive actions, TraumAID proposes goals. To satisfy goals, TraumAID (the planner) proposes procedures, which are made of collections of actions. Goals, actions, and procedures are themselves subclasses of ‘fact.’
Lastly, there are menus. As part of the interface-independent portion of TraumAID, we define virtual menus, on which to select and report information. There are a few types of menus, depending on the information to present.
Facts
The system is defined as a collection of objects of class "fact" (more correctly sub-classes of fact). When the rule-proving process starts, the system takes the fact objects (originally made in mobjects.lisp) that represent things such as (medical) procedures, actions, therapeutic goals, etc. and instantiates them as objects of some sub-class of "instantiation." This is a little confusing terminology, since fact objects are different from fact instantiations. When it refers to "instantiations" in the program, it means things of the "instantiation" class, not the conventional meaning of an instantiation of a fact. A fact object merely defines the characteristics of the fact.
For example, when an action is to be performed, or even comes up in the chaining process (something like the system is 'considering' it), the system instantiates the action fact as an action-instantiation. The action instantiation contains the dynamic information, such as which rule caused the instantiation, what variables bindings are present, and the like. The fact object tells the system what types of variables should get bound (i.e. get info from the user, like SIDE, BONE-NAME, etc.) when it creates an "instantiation" of the fact.
The system keeps track of instantiations in various places. One place is in the fact object itself, which has an "instances" slot that is a list of instantiations (class instantiation) of that fact. Note that this is NOT another instantiation of the fact (class fact), but an object of class 'xxx-instantiation.' We are warned in logic.lisp that the same instantiation in different places may have different values for its slots. See the note about this in logic.lisp or in the discussion of the instantiation class.
Some Naming Conventions
Rx_ "TREAT" convention for therapeutic goals
RO_ "RULE OUT" convention for diagnostic goals
Numbering
Facts, as with rules, must be numbered uniquely.
Attributes
User defines attribute types (a class of sorts) in def-attribute-type. Example attribute types are side (Left/Right), or test results (positive/negative). They are like enum types in Pascal. User gives the type name, range of possible values, and whether the values are exclusive (something can't be of more than one type, like test results). def-attribute-type creates an instance of the attribute type and puts it in the system-variables’ attribute-types list, the special-symbols list, and creates a property of the name of the type (property: attribute-type-flavor) that points to the instance in the system variables’ list.
Attribute types are defined in aux.lisp.
Note: ranges of all enumeration types must be unique, since they all end up on the special-symbols list as one big list. That means two types cannot have the same value (such as "Left").
In init.lisp, the (com-add-fact) routine and (com-add-neg-fact) use the function (pick-fact) to get the user to select a fact. Once that is selected, the system looks at the attributes for the fact and prompts the user for values for these attributes (with the menus associated with the attribute objects).
|
Name |
Description |
|
REV |
is the revision number associated with some types of tests within a test interpretation menu. It is used to distinguish different tests that need to be repeated but should be considered distinct. Rather than use the same name, the system attaches a revision number to indicate to which trial this refers. |
|
SIDE |
the atom LEFT or RIGHT, depending on the side referred to. |
Slots
Instantiations
Values
T/F/U.
Treating UNKNOWN
TraumAID treats the
UNKNOWN value differently, depending on the context.If a bedside question is asked, one possible answer is UNKNOWN. This is neither YES or NO, which means any rule that depends on the question being TRUE or the rule being FALSE cannot be pursued. For bedside questions, the program simply records that the question was asked, and does not ask it again. However, it does not commit to either the answer being TRUE or FALSE, which means that any rule that depends on one of these answers will never be fired, in this case. Unfortunately, this situation makes it impossible to distinguish, in the rules, a question being answered as UNKNOWN and a question that has not been asked at all.
Actions (from the planner), as opposed to bedside questions (from the reasoner), can only be TRUE. There is no representation for an action that is explicitly not done and not planned for. When the planner asks questions, it makes an action that has a TEST-RESULT attribute. The TEST-RESULT can be POSITIVE, NEGATIVE, or UNKNOWN. When a TEST-RESULT is answered as one of these values, the program creates a positive instantiation (on fact-true-instantiations for the fact) of the fact with the TEST-RESULT given. It also instantiates TWO false instantiations (on fact-false-instantiations for the the fact) with the remaining two possible values of TEST-RESULT. This means that if a test is positive, the program says 'it is false that the test result is NEGATIVE and it is false that the test result is UNKNOWN.' If the test result is NEGATIVE, the two false instantiations are that the test result is POSITIVE and the test result is UNKNOWN. If the test result is UNKNOWN, then the two false instantiations are that the test result is POSITIVE and the test result in NEGATIVE. One should note carefully what this means: if a test result is NEGATIVE, it means that it is not true that the result is POSITIVE and it is not true that the result is UNKNOWN. If a test result is UNKNOWN, then it is not true that the test result is POSITIVE and it is not true that the test result is NEGATIVE. Note that this does not mean that the patient has or does not have the condition; rather it means that the result of the current test is not conclusive.
One noticeable difference between questions with TEST-RESULT attributes is the difference between questions asked in a test-interpretations menu (such as an X-Ray) and a question asked as a lone action-question (such as Urinalysis_Rbc). For test-interpretation menus, when a person says that a finding is UNKNOWN, TraumAID does not record that the TEST-RESULT is UNKNOWN. Rather, the program does not record anything about that fact. For example, if the user does not say whether X_Ray_Simple_Pneumothorax is POSITIVE or NEGATIVE, then the system does not record that the question was asked.
On the other hand, with lone action-questions, when the user enters the finding (such as Urinalysis_Rbc(UNKNOWN)), the program records that the action Urinalysis_Rbc was true (since the act of asking the question is true), but the TEST-RESULT is UNKNOWN.
One rationale for this inconsistency is that test-interpretation menus are recorded as being done separately from the individual questions on the menu. For example, when the program asks for a Survey_Chest_X_Ray, it records that the X-ray was done, regardless of the answers the user provides. So following the example in the second to last paragraph, the program does not record that the Simple_Pneumothorax question was asked, but it does remember that the chest X-ray was asked.
Rules
Types of Rules
Conclude rules
nnn: regular rule :-
antecedent1,
antecedent2,
...
antecedentn.
If the antecedents are true, then the consequent is true. A true consequent will then force the reasoner to evaluate the rules that the consequent participates in (participating as an antecedent). This is a forward-chaining process that finishes when there are no more rules to consider.
If any antecedent is false, then the consequent cannot be proven. If some antecedents are unknown (possibly the rest being true), then the consequent is unknown. If all conclude rules cannot be proven, then the conclusion is deemed false.
Procedure Rules
nnnP procedure-based rule. When the head is
a goal
head-goal :- alternative
procedures
a procedure
head-proc :- actions of
the procedure
a procedure
head-proc :- set of subprocedures
this distinction resolved by type of head (consequent) by
virtue of its class (goal/proc)
The point of procedure rules is to organize knowledge about which procedures can address which goals (the rules with the goal at the head specifies the alternative procedures, in order or preference, that can be used to achieve that goal). The planner uses this information to decide which procedures (and subsequently which actions to perform) to use to achieve the current set of projected goals.
Suspect Rules
Suspect rules are used to drive the asking of bedside questions. The idea is that if the antecedents of a suspect rule are true, then the fact becomes suspected. From the collection of suspected facts, TraumAID looks through (in a backward chaining fashion) how to conclude that fact. It gathers all the bedside questions (all the facts that can be ASKED) from the conclude rules and then presents those to the user.
nnn? goal suspected if anteceds true
Example:
55? Concl_X :-
Shock.
This says to suspect Concl_X if Shock is true.
60: Concl_X :-
Unconsciousness,
Shock.
If Concl_X is suspected, then TraumAID will add the question Unconsciousness to its bedside questions to ask.
Rule Processing
In general, T/Unknown/F. If something in a rule is reported as Unknown, the rule succeeds in assigning Unknown to conseq. Something like that.
Rule Syntax
|
Symbol |
Assertion about a fact |
fact value |
contribution to rule success |
|
(blank) |
fact is true
|
true unknown false |
succeeds rule is unknown cannot prove rule |
|
- |
fact is false |
true unknown false |
cannot prove rule rule is unknown succeeds |
|
% |
compatible with (fact is true or unknown) |
true unknown false |
succeeds succeeds cannot prove rule |
|
-% |
unless (fact is false or unknown) |
true unknown false |
cannot prove rule succeeds succeeds |
|
! |
only with action facts |
done not done |
succeeds rule is unknown |
|
? |
if fact is suspected |
suspected true false unknown |
succeeds cannot prove rule cannot prove rule
|
N.B.
One important point should be made about the effect of the exclamation point (!), because it is not obvious how the exclamation point differs from the (blank). When the program is searching for bedside questions (i.e. those questions that derive from the reasoner trying to prove conclusions, rather than the planner trying to satisfy goals), it looks through all the rules that can prove the current suspected instantiations (those conclusions which the program suspects). For example, if we suspect a tension pneumothorax, the reasoner will look through all the rules that can prove (conclude rules) tension pneumothorax, and come up with a list of questions (those facts that are ASKABLE) to integrate with any pending questions. If the reasoner encounters an exclamation point, this requires that the action must be done before considering any other questions that might come out of that conclude rule. For example, given rules such as100: Some_conclusion :-
! Some_test(RESULT='POSITIVE),
Shock.
101: Some_conclusion :-
Some_test(RESULT='POSITIVE),
Tenderness.
Suppose that the reasoner suspects
Some_conclusion. It looks through the rules, in this case 100 and 101, to find questions to prove Some_conclusion. Also suppose that Some_test has not been done yet. The exclamation point in rule 100 will block the reasoner from looking for questions from that rule, such as Shock, since Some_test has not been done yet. On the other hand, the reasoner will pick up Tenderness as a bedside question, from rule 101, since it does not require that Some_test has been done yet.Axiomatization
This subsection gives a declarative semantics for the TraumAID inference engine, described by Michael Niv. In the current knowledge base, this covers how findings are mapped to questions and goals, but does not describe the mapping from goals to procedures or procedures to actions.
Facts are abstract concepts and have no truth value. An instantiation is an ordered pair <fact,attribute-value-list>. Instantiations can have the following 'truth' values
• known true
• known false
• suspected
• unknown
Unknown instantiations are not explicitly represented, since some attributes range over (possibly) infinite value-sets (e.g., cycle number) the set of unknown attributes is infinite. No instantiation may have more than one truth value simultanously.
An instantiation may be a question.
[informally: determined by backchaining to be of potential value]
Rules are an ordered quadruple
<rule-number,susp/conclude,antecedents,consequence> where:
rule number is used only for reference
susp/conclude is either 'suspect' or 'conclude'
antecedents is a nonempty list of antecedent
antecedent is a list of ordered triples <sign,mode,instantiation> where:
sign is '+' or '-'
mode is 'suspected', 'prescribed', 'compatible-with', or ''
The user may enter some instantiations as present and some as absent.
An instantiation is suspected just in case it is provably suspected. An instantiation is known true just in case it is provably true or entered as present. An instantiation is known false just in case it is provably false or entered as absent.
An instantiation i is provably suspected just in case
• it is not known true
• it is not known false
• there is a suspect rule for i all of whose antecedents are satisfied by a unifying substitution compatible with i].
An instantiation i is provably true just in case
• there is a conclude rule got i all of whose antecedents are satisfied [by a unifying substitution compatible with i].
An instantiation i is provably false just in case
• for every conclude rule r for i, r is falsified for a substitution s compatible with i.
A rule is falsified for a particular unifying substitution s just in case it has an antecedent which is falsified.
An antecedent is falsified just in case it has mode '' and
• it has sign '+' and there is a unifiable instantiation known false
• or it has sign '-' and there is a unifiable instantiation known true
• or it is a wound antecedent and has sign '+' and no-more-wounds is true
An antecedent is satisfied just in case
• it has mode 'compatible-with' and it is not falsified
• or it has mode 'prescribed' and the corresponding instantiation is prescribe
• or it has mode 'suspected' and the corresponding instantiation is suspected
• or it has mode '' there is a known instantiation of the truth value false for - and true for +
An instantiation i is a question just in case
• it is not known true
• and it is not known false
• and either
it is suspected
or there is a conclude rule r such that for some substitution s r's consequence is a
question q
and r is not falsified for a substitution compatible with q
(comment: this is good for turning off silly questions)
and i is an antecedent of r with mode ''
Actions
Procedures
Goals
Menus
Simple Menus
Choose a fact from a list of facts, or a value for an attribute from a list of values. My interface does not distinguish fact menus from attribute menus, so the interface-independent part of the system must be aware of which it expects at a time. Of course the system should not use the same name for an attributes and a fact menu (or a test interpretations menu, for that matter).
Some facts are labelled as having FINDINGS menus. This is the same as a SIMPLE menu.
Test Interpretations Menus
For a list of facts, choose Positive,Negative, or Unknown. Typically, these menus are groups of related questions, such as the results of a test.
Grouped Questions Menus
This is a new distinction within test-interpretation menus, prompted by menus like ‘Pulses.’ Pulses is a test-interpretation menu, but it is not like an X-ray. For an X-ray, all the results are available at once. For Pulses, each entry is a separate question, and must be addressed individually.
Single Test Menus
This type of test-interpretation menu (the most common) tells the machine the results of some test, typically an X-ray or report on a chest tube.
Resource Menus
Attribute Types
def-attribute-type (in readkb.lisp), presumably called in defining the type of an attribute (left/right, positive/negative), creates a menu structure (in :menu slot) consisting of the possible values for the type. These menus are brought up in (ask-attribute), after the user has chosen a fact. Since each fact object contains a list of attributes (bindings), the system knows to prompt the user for values from those attribute menus.
Building Menus from menu-structure.lisp
When one loads the knowledge base, the program reads the menu structure and puts it in a ready-to-display form
compile-menu-tree.At one time or another someone (automatically or manually) created the menu-structure file rom the contents of the menu tree slots in the system variables. Since these slots now hold the compiled version, compiled from reading the menu-structure file, the program relies on
menu-structure.lisp to get the structure of the menus.I don't believe the dump-menu-... routines are used anywhere anymore.
During the loading of the knowledge base operation, the system loads the main interface menus (not attribute menus) from the file menu-structure.lisp. It goes through the structured list in that file to create different menus or windows to communicate with the user. Each menu has a 'root fact' from which the system determines the name of the menu to present and what type of menu it should use for that fact (the menu-type field of a fact, e.g. FINDINGS, INTERPRETATIONS, SIMPLE).
We need to pay attention to one special option that is used in the menu structure, the ":not-here" optional argument. For each fact, we record which menus the fact is present on. That way, if we want to know about a fact, we can prompt the user by displaying an appropriate menu. When a fact is followed by the symbol :not-here, as in
(CT_SCAN_ABD
...
(IVP_URETERAL_INJURY :not-here)
...)
it means that the IVP_URETAL_INJURY should be displayed in the menu at the current point, but that we SHOULD NOT RECORD this menu as the place to display when we want to ask about this fact. In this example, we want to display the face in the cat scan of the abdomen (since it can be observed there), but if the system wants to ask about it the system should present the IVP menu (a different test) to the user.
The program on the Symbolics compiles the menus from menu-structure.lisp into a form easily invokable. I have redefined the compilation in a more general way. The compilation produces an interface-independent representation of the menu, and provides a slot in the interface-independent menu object for an interface to hold interface-specific info. During compilation, compile-menu-tree calls an interface-specific function to do its stuff, then puts those results in the slot.
I think the interface-independent representation is better than a purely interface-specific representation because in this way, the interface can be implemented more simply (though it still is not trivial)- Lisp does the work in organizing the information, and the interface merely has to display it.
This requires essentially 3 routines from the specific interface:
1. build a simple menu (for user to choose an item)
2. build a test-interpretations menu
3. display and handle communication between menus (esp. test interp menus) and Lisp when system wants to display such a menu.
1. To do this, we have to build a new window with the items selected. I therefore create a new procedure, with the name of the root string, that displays the window and lets the user select an item.
2. Since all the info in a test interpretations menu could be dynamic, I have only one test interpretation menu and send Wish the info right before it displays the menu. We don't need to hold onto the name of the procedure for displaying the window, since that never changes (TImenu).
IMPORTANT NOTE!
Note that I keep track of the current setting for items in the test interpretations menus in the "interface-hook" slot of the test interp menu structure for the menu. I thought it would just be wasteful to have another slot to hold this information.
CAVEATS concerning details in menu-building:
As I mention in menus.lisp, I think it's a reasonable assumption that before we finish building a menu, we have to build (tell the real interface to build) any sub-menus. That way, when the real interface is told to build the menu, it can rely on the fact that it has already built any sub-menu of that menu (so the information in the interface-specific slot is correct, since it put it there when it built the sub-menu).
Class Hierarchy
Save-Case Formats
Validation Format One
Validation Format Two
The Functional Parts of TraumAID
Motivating Issues
Importance of Goals
The Rules
The rule processing in TraumAID 2.0 is relatively straightforward, but since it originally was more complex, the code is designed to handle more unusual circumstances (and is therefore more complex than necessary).
TraumAID 2.0 uses forward chaining principally. Forward chaining means that when all the antecedents have been satisfied (or deemed unsatisfiable), the rule is fired and the consequent is instantiated, either as true (if the antecedents were satisfied) or false (if they cannot be). Backward chaining is the process where a consequent is attempted to be proven by trying to prove each antecedent. Backward chaining is still used to derive questions (N.B. not actions, simply bedside questions like
Shock, Unconsciousness, Distended Abdomen, Ileus, etc.).The basic way the rules are used is as follows. When the system starts a new patient, some facts automatically become instantiated. You can see which facts these are by looking in the rulebase for rules of the form:
#### Some_Initial_Info :-
Truth.
This says to conclude
Some_Initial_Info from the beginning (Truth is always true). The user then enters the wound(s), and perhaps some clinical findings. Based on the known information, the system comes up with which rules might fire (TraumAID knows which rules each fact participates in). If a conclude rule’s antecedents are all satisfied, TraumAID instantiates the consequent. If a suspect rule is satisfied, it means that the antecedent of that rule should be considered a relevant goal, and TraumAID should do backward chaining on any conclude rule with that fact as a consequent (i.e. head), to try to prove or disprove the fact. This is how questions get proposed. Bedside questions are part of the antecedents in many conclude rules. The idea is that the suspect rule says ‘this fact may be relevant,’ and so the system pursues the proving or disproving of that fact.The Planner (Abigail Gertner)
The main function in the planner is
plan-ahead (unless you are using let-system-guide, in which case it is plan-ahead-and-follow).Plan-ahead has the following steps:
• Clear the previous plan info using
• Sort the currently active treatment-goals (which are really both treatment and diagnostic goals) according to urgency and priority (uses
sort-by-priority and highest-goal).• Create a plan using {\tt plan-ahead-for-sorted-tgs}. This basically means going through each goal and finding the first procedure to address it that fits into the current (partially developed) plan.
• Optimize the plan.
• Display the plan.
Here is how these steps work in more detail:
• Clear Plan (this seems like a pretty complicated way of doing it. Why not just set all the instantiations to nil?)
1. Set all of the procedures for each current goal to nil and all the procedures covering each instantiated action to nil using init-coverage.
2. Set all the system's sub-goals to nil.
3. For each instantiated procedure, if that procedure has not been done, set the goals covering it to nil.
4. For each instantiated action, if it hasn't been done yet, set all the relevant information about it to nil (including cover-proc, although it looks like this was already done in init coverage).
• Sort goals -- pretty self-explanatory.
• Create Plan (
This is done by recursively looking at each goal in the list and integrating actions to address that goal into the plan created so far using
solve-one-tg. (For some reason, this also compiles a list of the actions added to the plan during this process, but this never seems to be used, and anyway, it should be just the same actions as are in the plan itself.)The following functions are used in creating a plan for a list of goals, They are listed in order of specificity (each one calls the next one down the list):
1.
There are 4 choices of what to do, depending on the status of the procedures that could be used to address the goal in question:
a. If there are no such procedures, don't change the plan.
b. If at least one has already been planned for, all you have to do is make sure the time ordering is right (using
c. If one of the procedures has already been performed, don't change the plan.
d. If none of the above, choose a procedure from the list and add it to the plan using
choose-and-add-proc. If this works, return the result as the new plan. If it doesn't work, for some reason (like if the only appropriate site has already been passed), don't change the plan.2.
Choose-and-add-procLoops through the available procedures, trying to add each one to the plan (with the appropriate priority and urgency, based on the goal) using
add-proc-to-plan. Returns new plan when successful or 'error if not successful.3.
Add-proc-to-plana. Uses
add-proc-elts-to-plan to add the proc to the plan one element at a time.b. When done, checks for circularity (a condition in which there is no action in the plan that doesn't require some other action to be done before it can be done, and
c. adds the proc to all the appropriate instantiation coverage lists (proc-cover tgs, tg-cover-procs, proc-subactions).
d. Returns a list (plan actions-added).
4.
Add-proc-elts-to-planAdds the elements of the procedure to the plan, one at a time.
There are two cases:
a. If the element is an action, use
b. If the element is a goal, use
plan-ahead-for-sorted-tgs with just the one tg to recursively add the solution to that goal to the plan.5.
Add-action-to-planTries to position an action in the right site, obeying constraints due to urgency, priority, etc. Returns the new plan if it is successful.
• Optimize the plan (
optimize-plan)1. If possible, replaces the first procedure in the plan (that is, the procedure covering the first action in the plan) with a procedure that covers additional goals. (
2. If there is an urgent OR procedure that must be done, move all the actions in the plan to the OR. (What if they can't be done in the OR? Is this possible?) (
opt-rush-to-OR)3. If there is a need to intubate, but a thoracotomy will be done soon, do not intubate. (
opt-avoid-intubation)• Display Plan -- see interface.
More Planner Details
This is an attempt to describe the functioning of TraumAID's planner as of this time. The code for the planner can be found mainly in the file planner.lisp. This description can also be found in
~jclarke/traumaid-2.0/notes/planner.doc.There are three top level planning functions: plan-ahead, plan-ahead-and-follow, and construct-plan. All three do pretty much the same thing except that plan-ahead displays the completed plan on the screen. Plan-ahead-and-follow displays the plan and also is responsible for the "let system guide" feature, so it triggers the asking of questions and execution of actions. Construct-plan just returns the completed plan, but doesn't display it on the screen. This is used mainly during the optimization phase.
Basically, these functions work as follows:
If the goals have changed since the last time a plan was created, create and optimise a new plan.
If the goals have not changed but any actions have been done, the plan has to be eoptimized since the new first action in the plan may not be optimal.
Otherwise, there is no need to create a new plan, so the last plan made will be returned.
Planner workings
The planner is basically a nested chain of functions that are called in order and back up whenever there is a failure. This is followed by the optimization phase which I will describe later. The following is a fairly high-level description of this chain of functions. Some of the details have been left out to make it more comprehensible. These details should be commented in the code.
The main function that starts things off is called plan-ahead-for-sorted-tgs and works on a list of goals that have been sorted by priority (actually a function of urgency and priority).
Plan-ahead-for-sorted-tgs works by picking one goal at a time and finding the best procedure to address that goal, in the context of what is in the plan so far. This is done by first calling solve-one-tg.
Solve-one-tg starts off with a list of the possible procedures that may be used to address the goal. It looks to see if any of these procedures are either planned for (which includes being partially executed), or have already been executed. If either of these is the case, that procedure or procedures are chosen as the way to address the goal. Otherwise, a procedure is chosen by choose-and-add-proc.
Choose-and-add-proc goes through the possible procedures for addressing the goal in order and picks the first one that can be added to the plan (without any contraindications, site conflicts, urgency constraints etc.) The adding to the plan is done by add-proc-to-plan.
Add-proc-to-plan first checks that the appropriate resources and personnel are available to carry out this procedure. Then it just calls add-proc-elts-to-plan to add the actions in the procedure to the plan.
Add-proc-elts-to-plan deals with each action in the procedure in turn. It does one of two things depending on whether the action is really an action or if it is a subgoal. If it is an action, it calls add-action-to-plan to add that action to the plan. If it is a goal plan-ahead-for-sorted-tgs is called recursively on the singleton list of that subgoal with the plan already created so far. If, at any time, an action fails to be added to the plan (reasons for this will be described under add-action-to-plan) the function returns 'error, which is propagated back up to choose-and-add-proc, which will then attempt to add the next procedure on the list.
Add-action-to-plan is the lowest level in this chain. It does the actual placing of individual actions in the plan. Each action has a range of sites it can be done in, as well as an urgency, priority, time it takes to do, and specification of how the scheduling of that action should be done (whether to consider urgency, priority, site, etc...).
The first thing that add-action-to-plan does is check a few simple things:
• Is the action already done?
• Is it already planned for?
• Is the action contraindicated?
• Does the action have to be done in a site that we have already left?
• Does the action have to be done in a site later than the sites available to do
• the procedure?
If any of the latter 3 are true then 'error is returned.
If none of these are the case, then we attempt to place the action in the appropriate place in the plan. This is done by looping through the plan constructed so far, adding order constraints between each action already in the plan and the plan we are adding. So, if one action *has* to be done before another because of either urgency (doing one action will take more time than we have before the other action must be done) or precedence constraints (one action must be done before the other because of a potential interaction) this constraint will be added to both actions.
After adding these ordering constraints a couple of final things are done. First, order constraints are added between actions in a single procedure (actions must be done in the order they are listed in the procedure). Next, the plan is checked for circularity -- actions that must be done before actions that must be done before them. If such a case occurs, 'error is returned.
Add-action-to-plan does not actually add all the pairwise ordering constraints that will eventually be in the plan -- just the ones that could result in a circularity, thus affecting whether the action can be added to the plan at all. Instead of fixing the site of an action when it is added to the plan (i.e. before all other actions are added) the site is left as a range and is fixed at the very end of the planning phase. This way, if a constraint appears due to an action added later on in the plan, there will not be an artificially created site constraint blocking one of the actions. The function fix-act-sites was added to determine the site of each action *after* all goals are solved.
fix-act-sites is actually called during the optimization process, after the first phase of optimization -- opt-replace-first-proc-for-more-covering-proc.
Opt-replace-first-proc-for-more-covering-proc is responsible for finding the optimal combination of procedures for addressing the first n goals (where n represents a pre-set optimization horizon). This works by taking the first n goals, one at a time, and creating a new plan by using procedures lower down on the list for those goals. The new plan is then compared to the original, non-optimized plan to see if the use of the alternative procedure results in an overall lower-cost plan.
After the optimization of procedure choices, fix-act-sites is called to determine where each action will be done and the order of actions within each site. Fix-act-sites works as follows:
Loop through the possible sites in order, starting from the *last-site*, which is the place we are in now. The order is ER, XR, OR, OR-OP, TU.
Find all the actions in the plan that can be done in the current site, and that don't have preceding actions that can't be done in the current or an earlier site.
Put all those actions in the current site and add a pairwise ordering constraint (because of 'site) between each of them and all the actions that haven't been scheduled yet.
Add ordering constraints between each of the actions within the current site.
These constraints can be due to:
• precedence constraints
• urgency
• priority
After the sites have been determined, two more optimizations are applied to the plan. The first is done by opt-rush-to-OR. If there is an urgent goal that must be addressed by a procedure in the OR then any action that takes more than 2 minutes to do will be moved to the OR if it can be done there. Actions that take 2 minutes or less will be left in their original site. Other actions are eliminated from the plan.
The last optimization is opt-avoid-intubation, the purpose of which is to avoid intubating the patient in cases where a chest tube is called for but in which a thoracotomy is planned for in the near future, thus overriding the need for intubation. This optimization works as follows: If a thoracotomy is prescribed and there is a need to intubate then
1) if any of the actions in between intubation and thoracotomy takes 200 min or longer, then DO intubate
2) If ALL such actions take 2 min or less, DO NOT intubate.
3) Otherwise, do not intubate if in OR (even if as a result of rush-to-OR) and there is no interfering operation in between.
That's it!
The Conclusion and Goal Hierarchies
The Reasoner vs. The Planner
We have come to refer to the two principle parts of TraumAID's reasoning as the reasoner, which does forward and backward chaining on the rulebase to propose goals to achieve and questions to ask, and the planner, which attempts to create a plan that satisifies the outstanding goals. It may be observed that they are doing similar things in question-asking. The reasoner produces 'bedside questions,' which are those facts that can be asked about in the course of trying to prove a conclusion. The planner, on the other hand, creates a list of actions, but an action may be a question (such as "Did you cover the wound?"). The planner doesn't 'seek' a question the way the reasoner does: the planner is told, as part of a procedure, that a question should be asked. The rules then can use the result of this question to prove a conclusion that will drive a goal that depends on the answer.
To find bedside questions, the reasoner does back-chaining on those conclusions that it suspects. In the course of back-chaining, it collects a set of questions that can be used to prove or disprove the conclusion. One idea that has been floating around for some time, originally attributed to Ron Rymon, is to replace this backward-chaining, question-seeking process with action-questions, or scheduling all questions by making them actions and letting the planner arrange them. This would make the findings-to-goals simpler, because it would eliminate the back-chaining. It would also eliminate any interference between the question-seeking processes (the reasoner's question-seeking and the planner's question-seeking). So far, we have not pursued this, but it remains a possibility if we want to simplify the program.
Batch Verification
Introduction
Over the course of the TraumAID project, we have saved cases in a particular format useful for verification that previously-validated cases do not become invalid because of changes to the rulebase (kb/xtrauma.rul and kb/procbase.rul). This format is a list of the propositions and bindings of the instantiations that the program made during the execution of a patient, along with the rule numbers that derived the propositions. This format was fine for previous versions of TraumAID in which the program was encoded entirely in rules.
This format enables us to verify that the conclusions remain the same, as well as verifying that the same rules were used to derive the conclusions (as a consistency check). We do not, however, have a means to verify the planner's task—that is the goal of the new batch verification addition. It is important to verify the planner to detect problems that changes to the planner (or rulebase) might impact. The idea is to record not only the conclusions that TraumAID reaches, but also the order in which information is solicited. The new saved case format (Validation Format 2), therefore, records the conclusions TraumAID reached in a case (as usual) and the order in which TraumAID asks questions. The questions currently are of two types, but these types are not mutually exclusive (there is no distinction made within the system, this is only to clarify the type of information TraumAID solicits):
• Diagnostic - These include bedside questions or test results
• Action Confirming - Confirming that an action has been performed (diagnostic or therapeutic)
This section describes the previous batch verification system as well as the proposed changes and plan for the new system that incorporates the ordering of information. In the "Problems" section, I briefly outline existing problems in TraumAID with respect to this work.
The Old Batch Verification Process
The current system for batch verification consists mostly of the file guts\slash batch.lisp. For purposes of distinguishing this from the newer format, we call the current format Validation Format One.
Batch verification is run from within the TraumAID program after the knowledge base is loaded. Any routine that has different behavior during batch mode and interactive mode should check the variable *batch-mode* to see if it is non-NIL (meaning program is currently running in batch mode) or NIL (meaning program is currently running in interactive mode. The actual value of *batch-mode*, if non-NIL, indicates which verification method to use (:VF1 or :VF2). In this way, the program can distinguish verification process specific operations. Currently, we need to distinguish these so as not to use the planner verification variables if we are verifying in Validation Format One.
Each file is successively opened and the GIVEN+, GIVEN-, and BULLET-COUNT information is loaded into TraumAID through the do-case-sections function in init.lisp. The other elements of the file (specified below) are ignored.
Work Done in do-case-sections
One important part of
do-case-sections puts each section retrieved from the input file into the system variable slot case-state. This way, the batch verification process simply retrieves the printed form as it was read from the file, and compares that with the existing state of the system after the information has been read in (the GIVEN information) and processed (all the instantiations have been made from that data and the rule firing completed).The work here is straightforward. We read in the BULLET-COUNT, if given, setting the system variable slot
number-of-bullets-in-x-ray, then call the routine update-Odd_Number_Of_Bullets_In_X_Ray to update other values in the system related to the bullet count.We call
handle-given+-entry on each of the GIVEN+ findings is handed to the function where it is determined which type of finding it is and routed appropriately. Each element on the GIVEN+ list will either be• A string: The only acceptable one is ``No other wounds'' currently. This is actually redundant now, since we default to having No Other Wounds when a patient arrives. Eventually this redundancy should be addressed.
• A cons cell whose car is a string: This is an instantiation but not a test-interpretations menu. The string is the fact name of the instantiation, so we proceed to convert the information to its system (object) form.
• A list inside another list, where the inner list's car is the symbol TEST INTERPRETATIONS-MENU: This is obviously an entry for a test interpretations menu. The actual handling of these entries is more complex than I will go into, since the details are unimportant for batch verification.
The last two of these have in common that they ultimately lead to calling
add-true-instantiation, which after making the system object corresponding to the printed form, starts the rule-firing process.Each of the findings in the GIVEN- entry is added as a false instantiation, and after each is added, TraumAID invokes any rules that can fire as a result. This is much simpler than the work done for GIVEN+ elements because of the natural restriction on the type of information in the Given as Negative category (we will never see recorded that TraumAID is NOT performing a particular action or test-interpretation menu).
Lastly, this function calls
update-assumed-assumptions to do something I am not familiar with. Looking over it quickly, I imagine much of it (if not all) could be removed, since it appears to involve the second-set of system variables, which was used in the submarine environment where some equipment was not available. Currently, we can handle unavailable equipment or resources through resource constraints.Work Done in
For the purposes of comparison, the batch-verification compares the following entries in the file with the current system's state: SUSPECT, CONCLUDE+, GOAL, PGOAL, and QUESTION. It does not check CONCLUDE-, though it is passed through to this routine and discarded by
section-changed-p.It retrieves the saved form from the system variable slot, and creates a current representation of the system from the function
construct-section (the critical system function used to prepare the output, whether to the screen or to a file).After this, it removes any entry from the old (saved case) list if it matches an entry in the new list. It uses the function
compare-fact to compare the current system object (from the current handling of the case) with the printed form (from the saved case). Similarly, it removes any entry from the new case (current handling) that appears in the old (saved case) list.If any of SUSPECT, CONCLUDE+, GOAL, PGOAL, or QUESTION is different between cases, the function then determines how they are different. At this point, the list
sections consists of up to five elements (SUSPECT, CONCLUDE+, GOAL, PGOAL, and QUESTION) if there were discrepancies in those sections. Each element consists of a list whose car identifies which element it is (SUSPECT, CONCLUDE+, GOAL, PGOAL, or QUESTION), the second item is a list of the findings that appeared in the original case but not in the new handling (it is possibly NIL), and the third element is a list of those findings that were encountered in the new handling of the case but did not appear in the original (saved case) handling.After silly things are removed from these lists (like Add, Eq, or other system-detail propositions), the process prints out the new and old entries.
If there were differences, the function {\tt verify-case} saves the new case after
record-differences has reported the discrepancies.Entries in a Saved Case (Validation Format One)
GIVEN+: Positive Findings
GIVEN-: Negative Findings
SUSPECT: Suspected goals
CONCLUDE+: Concluded goals
CONCLUDE-: Goals concluded as false (eliminated)
GOALS: Goals to pursue
PGOALS: Pursued goals
PPROC: Procedures performed
BULLET-COUNT: Number of bullets
The Current Verification
The new functions to accomplish the additional requirements are in guts/batch.lisp. For purposes of distinguishing this version from the old format, we call it Validation Format Two.
Routines for verifying the new format will not replace the current routines, rather the current routines will be enhanced to support the new validation. In this way, we never lose the ability to verify cases saved in Validation Format One. The reason this path is chosen is that the current routines are used to validate the rulebase, by comparing the conclusions reached with those that have been saved. We still want to verify the rulebase; now we additionally want to verify the planner.
Implementation
In this section, I discuss the requirements for the new batch verification and the solution implemented.
• Save Case - Adding information to the cases saved in TraumAID to verify the planner
• Save Case - Adjusting the report generator to recognize the format
• Get Case - Changing the retrieve case to retrieve relevant portions of a saved case
• Batch Verify - Verifying the plan as handled by TraumAID is the same as how it was handled in saving the case
• Batch Verify - Reporting any findings requested in the handling of a case that was not reported in the saved case
• Batch Verify - Converting the approximately 350 current cases to the new format that records the findings order
• Batch Verify - Presenting options to perform or not perform the new plan order verification. Note that not performing the plan verification will probably result in a faster batch verification, but most likely nothing dramatic
• Batch Verify - Allowing the user to choose the report file. Currently, problems get reported to cases/new/differences.rep, but one may specify the file to report to.
Changes to Save Case
Validation Format Two contains the entries listed below. The important differences with Validation Format One is that there are no GIVEN+ or GIVEN- entries, rather they are replaced by an INITIAL_FINDINGS section and a REST_OF_FINDINGS section that report findings based on the order in which they were discovered by TraumAID (inputted by the user). Central in the management of these lists is the record-finding routine in
init.lisp
, which records a finding passed to it on the appropriate list.Validation Format Two contains a CASE_FORMAT entry, for future versions that may want to recognize the format version. Additionally, a file may contain a CASE_INFO entry that records case-specific information.
Changes to Get Case (and
The principal change here is to give the user different options about loading a case. Loading a Validation Format One case means loading the GIVEN+ and GIVEN- information. Loading a Validation Format Two case means loading the INITIAL_FINDINGS information. This list consists of cons cells whose car is a finding and whose cdr is either POSITIVE_FACT or NEGATIVE_FACT. We treat POSITIVE_FACTs as we did entries in GIVEN+, and we treat NEGATIVE_FACTs as we treated GIVEN- entries.
When a user wants to retrieve a case, it is also possible to specify ‘Load Wounds Only,’ which means to extract and load only the wounds from a Validation Format One case. Typically, the user will choose the ‘SMART’ setting for loading a case, which finds out the version of the case and loads it according to which version it was saved as.
We also modify do-case-sections to apprise the system of the new entries. As with the other entries in a saved case (except GIVEN+ and GIVEN-), we put REST_OF_FINDINGS on the
case-state slot in the system variables so we can retrieve them easily if necessary (such as during batch verification).When the user has selected to allow the TraumAID to convert old saved cases automatically, we also need to add to the get-case mechanism something which creates an INITIAL_FINDINGS and REST_OF_FINDINGS list from the GIVEN+ and GIVEN-, loads the INITIAL_FINDINGS, and puts a dynamically-built REST_OF_FINDINGS entry on
case-state. It also must set a special variable (*bv-temp-suspend-plan-verif*) that tells the verification process not to verify the plan ordering (since it invariably will be out of the order it was entered in [that information is not available]).In this way, the batch verification simply retrieves the case results from
case-state, oblivious to where the information came from. In the implementation, we create an INITIAL_FINDING list (for converted cases) consisting solely of the given wounds. While we simply need to load the information at the appropriate (loading case) time, we also keep track of the INITIAL_FINDINGS list so that the findings can be recorded when we save the case in the new format.It should be noted that the knowledge base has changed over time, and concepts have appeared and disappeared, and sometimes changed names. We try to record the name changes in
notes/kb-name-changes, but the user will get an error trying to read an old case that has a concept which is no longer defined.Detecting Discrepancies in Plan Order
To verify that TraumAID is acting in the same way it originally handled the case, we want to have TraumAID analyze any discrepancies it finds between the saved version of a case and the way it handles the case currently. The analysis we desire is simple: if there are discrepancies, report about the following:
• Whether the questions and findings are the same in the same order
• Any questions or findings appearing in one that does not appear in the other
• Any difference in relative order of questions or findings
The following algorithm is implemented:
Variable Names (the actual names may be slightly different)
*saved-findings* is a list of findings from the saved case (in
the order which they were recorded)
*new-findings* is a list of findings discovered during the handling of
the case by TraumAID that WERE NOT entered in the saved
case
*current-finding-order* is a variable that records the order in which a
finding is discovered (starting with 0).
(setq *new-findings* NIL
*current-finding-order* 0
*saved-findings* A new list where each 'car' is the findings and
'cdr' is NIL)
;; *saved-findings* is used to record whether or not a saved finding
;; has been observed in the way TraumAID handles the case currently.
;; The cdr will indicate whether the finding is present in the current
;; handling of the case. If the cdr is NIL, it means the finding was
;; not in the current handling. If the cdr is non-NIL, it will be the
;; order it was entered (taken from *finding-order*).
;;
;; This way, when we're done handling the case, any of the findings in
;; *saved-findings* that does not have a non-nil cdr are those absent
;; from the current handling of the case (and hence a discrepancy to be
;; reported). To see how the order has changed, we simply go through
;; *saved-findings* noting if the relative order of the findings
;; remained the same. Here are some examples
;;
;; Ex 1. Ex. 2 Ex. 3
;; ((FINDING1 . 0) ((FINDING1 . 1) ((FINDING1 . 2)
;; (FINDING2 . 1) (FINDING2 . 3) (FINDING2 . 1)
;; (FINDING3 . 2) (FINDING3 . 4) (FINDING3 . 3)
;; ...) ...) ...)
;;
;; In Ex. 1, the order is the same, so there are no discrepancies.
;; In Ex. 2, the relative order is the same, so the only discrepancies
;; will be that *new-findings* will be non-NIL (if there's no finding
;; that is part of *saved-findings* with a cdr of 2).
;; In Ex. 3, the ordering is obviously different, and we can say pretty
;; much how different things are (whether any particular finding is in
;; the same relative order, because the cdrs of its closest
;; predecessor with a number should have a lower cdr value, while the
;; closest following finding with a number should have a higher
;; value). Therefore we can report these situations appropriately.
For each finding TraumAID requests,
1. Find the finding on *saved-findings*
(only search those entries with non-NIL cdrs).
2. If finding is there,
change the cdr to *current-finding-order*
Else
add finding to *new-findings* list with cdr of
*current-finding-order*
3. Increment *current-finding-order*
Implementation Note: These steps are easily implemented by
adding to the function record-finding in init.lisp, which
is called each time a new finding is reported. The finding
is reported as an object (instantiation or question), so we
use the same matching procedure that the original batch
verify uses to match TraumAID's objects (the result of the
current handling of the case) with the objects in their
printed form (from the saved case). This function will
be modified to call match-and-update-finding, in
batch.lisp, which will perform all these steps.
If *new-findings* then report new findings that were added
Go through *saved-findings* seeing if relative order of cdrs agrees
with the implicit ordering of *saved-findings* (since it is in the
order the findings were encountered in the saved case). Report
discrepancies. If a finding still has a NIL relative order, report
that the finding was never requested.
Unrequested Information
When a case is saved in Validation Format Two, we include a list of the findings TraumAID received during the course of diagnosing and treating the patient (currently REST_OF_FINDINGS). For batch verification, we want to make sure not only that the conclusions are the same, but also whether or not TraumAID needed all the information (or more) than when the case was actually saved.
The algorithm presented above resolves this problem as well as recognizing different plan orderings. If a finding has a NIL relative order, it means that finding was never sought by TraumAID in the current handling (verification) of the case.
N.B. One issue which is not resolved in this implementation of this is the impact of a finding being volunteered during question. Typically, the REST_OF_FINDINGS will consist of questions (tests) or actions that TraumAID asks either of the user or for the user to perform. It is possible, though, that during the original handling of the case, the user entered a finding voluntarily, that is without TraumAID asking for it. This finding is recorded at the point it was entered, but during the handling of the case for batch verification, the user is not given an opportunity to enter voluntary information (all the findings must be solicited by TraumAID). Therefore, there will be a discrepancy which may or may not be important to the handling of the case (depending on whether TraumAID finds that information relevant).
Batch Verification Options
The first option is clearly whether to verify the cases in Validation Format One or Validation Format Two. Cases saved in Validation Format Two can be run through the Validation Format One process, since the CONCLUDE+, SUSPECT, and QUESTION elements still appear in the saved case. Typically, though, the user will run using Validation Format Two.
The other options given to the user (assuming Validation Format Two) are:
• Report Plan Order Discrepancies (On or Off)
• Report Additional or Missing Findings (On or Off)
• Automatically Convert Old Cases (On) versus Flag and Skip (Off)
• Let User Specify Report File
Modifying Question-Asking for Batch Verification
The new batch verification routines that verify the information from a saved case (REST_OF_FINDINGS) hook into the existing TraumAID question-asking routines. The modification for ‘Save Case’ involved adding a routine called
record-finding, which noted the findings entered by the user. Users enter information right before those places where {\tt record-finding} is called. Therefore, we will add a test (on *batch-mode*) at the point the information is solicited to determine the source of the input (from our REST_OF_FINDINGS record or from the user).
The actual place this happens is in
ask.lisp, specifically in the functions accept-simple-answer and ask-questions. The difference is that in the former routine, we look are looking for a particular question, such as whether something was performed or observed. In the latter function, we reroute calls that would bring up a test-interpretations menu. In both cases, they call retrieve-saved-finding, in batch.lisp, which determines the type of question (simple question or test--interpretations menu), and returns the answer appropriately. The appropriate answer to a simple question is either YES or NO, and the answer to a test-interpretations menu is the stuff from the menu saved in the case.
The routine
retrieve-saved-finding retrieves the answer from the saved case. This works equally well with simple questions (YES or NO) and test-interpretations menus. For the former, we simply locate the entry in *saved-rest-of-findings*, and then return the answer to the question, in addition updating the findings to record that the question was asked. It takes a little effort to match entries, a process aided by the {\tt compare-fact routine for matching questions in their internal format with questions in their saved-case format (from the list of REST_OF_FINDINGS).
We should also note that any call to
accept during batch verification should result in an error, since we haven't added any general mechanisms for answering questions other than the two specified above. It would be rare for TraumAID to ask questions during a batch verification anyway.
Entries in a Saved Case, Version 2
CASE_FORMAT: For future distinctions (cadr is a string
representing which format the file is saved in).
THIS MUST APPEAR FIRST (AFTER COMMENTS) IN THE
FILE.
CASE_INFO: Information about the case or patient
INITIAL_FINDINGS: Findings reported upon patient arrival
REST_OF_FINDINGS: Questions, Findings, and Actions entered
during diagnosis/treatment
OTHER_AVAILABLE_FINDINGS: Findings that were present during the case handling, but not provided as part of the case stem or asked by TraumAID during the case handling.
SUSPECT: Suspected goals
CONCLUDE+: Concluded goals
CONCLUDE-: Goals concluded as false (eliminated)
GOALS: Goals to pursue
PGOALS: Pursued goals
PPROC: Procedures performed
Notice we do not have GIVEN+, GIVEN-, or BULLET-COUNT as this information is available from INITIAL_FINDINGS.
Default Answers
During batch validation, the system may propose a test or question different from the way in which the case was originally handled. To answer the questions dynamically, Adam Bell implemented a rough system for reporting default answers based on the knowledge of the ultimate diagnoses for the case. This system is in
default.lisp.Defaults show up in the saved case as the symbol DEFAULT, which precedes any question or entry that was derived from the default processing. During batch validation, TraumAID strips away the DEFAULT markers, and reapplies them if the case is to be stored again (meaning there was some change to the case). If an entry in a saved case was derived by default, and during the handling of the case currently (during batch validation) the system asks for the information, the system does not report the results from the file, rather it rederives the default (from the default processing). In this way, we can recognize when a default answer is different from a default answer that was part of the saved case.
The TraumAID Program Layout
The TraumAID Core
The part of the program identified as the core is the part that does not change from platform to platform, and in general does not concern itself with interface issues. It is the part that implements the reasoning in TraumAID.
The associated files for the core reside in the
system directory. Some of the important files are the following:|
Filename |
Description of Contents |
|
class-defs.lisp |
Object (class) hierarchy |
|
main.lisp |
Initialization routines and commonly used functions |
|
planner.lisp |
What do you think? |
The Interface-Independent Module
The interface-independent module is a set of routines that the TraumAID core uses to output information. It consists of a virtual window for each actual window. Routines in TraumAID use these functions to access windows, either to display information or retrieve information from the user. The files reside in
interface/independent (interface:independent, on the Macintosh).When a routine calls an interface-independent routine, the interface-independent routine translates the request (and data) to the appropriate interface-specific routine. The interface-independent routine communicates with the interface-specific interface through exported functions of the interface-specific package. The routines which an interface-specific module must support are listed in
guts/system.lisp.The Knowledge Base
|
Filename |
Description of Contents |
|
aux |
Auxiliary information lists. |
|
defdocprefs |
Default doctor skills. Used for resource-limited situations where doctors cannot perform all possible procedures. |
|
environments |
Assumptions for different environments |
|
equirsrc |
Which equipment available (for resource-limited environments) |
|
fact-defs |
Fact database |
|
hierarchy |
Goal and conclusion hierarchy (for suppressing less important goals or conclusions) |
|
menu-structure |
Structure of menu tree from which user selects information |
|
procbase.rul |
Procedure base |
|
xtrauma.rul |
Rulebase |
CLASS-DEFS: The lexicon contains all names used in the system and their attributes:
- numeric name,
- whether it is askable
- what section of status to display it in (given/conclude/no_section)
- what kind of menu it is the head of (no_menu,interpretations,simple)
- what is its priority as a question
- what is its priority as a treatment
AUX: the aux file contains more information about facts:
- lists of related facts to be grouped as questions
- items of the form (context . fact) which restric the context in which
some facts should be asked as questions
- the part-of anatomical hierarchy
- safe assumptions
- autoasserts
The menu structure encodes the menu tree. The type of menu for a particular fact is determined by the fact’s MENU-TYPE slot.
The rulebase file (
kb/xtrauma.rul) contains a representation of the rules. The procedure base file (kb/procbase.rul) describes the mapping between goals and procedures, and procedures and actions.
The Interface-Specific Modules
The interface-specific modules are designed in such as way to free the TraumAID code from a particular platform. In the file
guts/system.lisp, we define the functions that an interface-specific module must provide. All interface traffic from TraumAID is sent via these functions to the specific interface routines.To enforce the separation, we have defined the interface-specific routines in their own package ":
interface". If you look at interface-specific files, you will see they begin with (in-package :interface).X-Trauma
As outlined in the Introduction, X-Trauma is made up of a lisp process running Lucid and another process running wish. The files for X-Trauma are in the
interface/xwindows directory. The two communicate via a bidirectional stream that the X-Trauma lisp code creates when X-Trauma starts up.As far as communicating is concerned, when lisp wants to send a message (a string) to the interface, it writes to the variable that holds the stream pointer. When it wants to receive a message, it reads from the stream pointer. A central routine on the lisp side in the interface-specific module is called
tellwish. This function sends a string to the waiting wish process. When the lisp process is finished communicating its message, it must relinquish control, so it sends an end-of-message broadcast, with the routine finish-lisp-broadcast.When the tcl xtrauma script wants to read a message from lisp, it listens to stdin (standard input stream). When it write to lisp, it writes to stdout (standard output stream). An important routine on this side is
sendlisp. This sends a message to the waiting lisp process, and then relinquishes control to lisp (waiting for lisp’s end-of-broadcast).Because of pecularities with tcl/tk, we have to go about linking the programs in a no-so-direct manner. The wish process invokes an awk script, which merely prints out whatever is on stdin. Only then can the tcl command
gets (‘get string’) capture the incoming message. When we try to use gets on stdin directly, we have run into problems. So we have avoided updating this part of the program. Messages to the supervisors of tcl/tk have not been answered (though the authors are pretty responsible about answering other questions).The communication is clearly synchronous, which, although adequate, may ultimately be undesirable. The problem with making the communication asynchronous is that we would need a way of getting the attention (like an interrupt) of the processes while they were running. There may be solutions (polling, or defining routines that get called in idle system moments), but we have not pursued them.
Essentially, the Lisp process (LP) and the Wish process (WP) have two modes
1. The LP is the reader and the WP is the writer
2. The LP is the writer and the WP is the reader
The LP controls when the roles are changed. In Mode 1, the WP waits for user commands, such as Add Fact, etc. When it receives a command, it sends it to the LP which is waiting for information. The LP then takes control of the writing, and the WP listens. If the LP has information to display, or wants the WP to take some further action (such as put up windows for question answering) it goes into Mode 2, sending a message to the WP with the request. When the LP is done, it tells the WP and goes back to Mode 1.
Although on the Suns this is implemented in multiple processes, it does not have to be, since the flow-of-control is completely sequential (no asynchronous operations necessary). The LP is the real controller here, since it forks a process that runs the wish subprocess.
The Lisp process can be characterized by the following algorithm:
Initialization
Create I/O streams
Make WP (Wish Process) writer, LP reader.
Loop until user quits {
/* Invariant: at this point, LP is reader and WP is writer */
Wait for some command from user (WP)
If user quits, exit loop
Upon some information, invert reader/writer roles of LP & WP
/* Invariant: at this point, LP is writer and WP is reader */
LP processes request,
sending output to WP when appropriate,
OR
If LP wants to direct questioning or get info from WP,
Send questions to WP
LP sends end-of-transmission message to WP
invert reader/writer roles back to start of loop
}
The sending part is very straightforward. Based on events in the WP, the WP sends a predetermined code to the listening Lisp process. The LP has a hash table, keyed by these codes, containing functions to invoke when the code is passed.
The LP has a function "define-trauma-command" which registers which function to call when a button (or what I call more generally 'a field activator') is activated.
About Tcl and Wish
The Lisp side
TexTrauma
MacTrauma
About the Mac Interface Toolkit
The TraumAID side
HyperTrauma
About Apple Events and the Eval Queue
The TraumAID side
Part II — Using TraumAID
General Operation
Standalone
Batch Verification
Case Test Suites
With HyperCard on the Macintosh
Extending/Revising TraumAID
Changing Existing Code
Writing new Trauma Commands
Running & Using TraumAID
X-Trauma
Directories
The most up-to-date version of TraumAID resides on Dr. Clarke’s account, in the directory ~jclarke/traumaid-2.0 on linc.cis.upenn.edu. Within this directory, there are the typical TraumAID subdirectories: guts, interface, kb, and cases. Also, there are directories such as Registry, holding the makefile information, and notes, holding notes about different system features (this document contains most of these descriptions, more completely and better organized). Within the interface directory, there are independent, textrauma, and xwindows.
Running
From the Unix Command Line
Type
~jclarke/run-xtrauma
Note
: Because TraumAID is now compiled, you must be on a Sun 4 (LINC, UNAGI, SAUL, CENTRAL, etc.) to run it. Of course you must also be at a terminal capable of running X-Windows. Some of the HPs, however, cannot run Wish (probably because of the way Wish was setup rather than anything inherently weird about Wish), so you can't run TraumAID using some of the HPs as X-terminals.Remember to load the knowledge base before starting a new patient (or retrieving a case). Also, when you quit or suspend TraumAID, the program leaves the lisp session automatically. If you want to stay in the lisp session, you must run TraumAID from Lisp (or modify the run-xtrauma shell script).
From within Lisp
You first need to load the defsystem. You do this by
(load "~jclarke/Xtrauma2/defsystem")
Since CLOS is already built into our images of Lucid 4.1, you do not need to load CLOS. If your Lisp image does not contain CLOS already, you would have to load it at this point.
The defsystem contains pointers to the makefile for
xtrauma and textrauma. At this point, you would type(require "xtrauma")
or
(require "textrauma")
depending on which system you want to use. Once this is done, Lisp will load the appropriate system. To run TraumAID at this point, you must be in the common-lisp-user package, if you are not there already. To get into this package, type
(in-package :common-lisp-user)
We load TraumAID into this package for compatibility reasons with MacTrauma. The later versions of Lisp require that the starting package is the
common-lisp-user package (which Macintosh Common Lisp follows), whereas it used to be the user package (which Lucid continues to follow).Since the preceding commands were tiresome to type, we have put them in the
lisp-init.lisp file in ~jclarke/. Lucid loads this file when it starts up. However, you cannot issue the in-package command to have it work properly from within the file. Instead, you can make an alias in your .login or .cshrc file, which tells Lisp to execute this command upon startup:alias lisp 'lisp -e "(in-package :common-lisp-user)"'
If you have problems, look in
~jclarke/.cshrc for the proper format.Now, to run traumaid, type
(traumaid)
Maintaining
Compiling X-Trauma
To ease program development somewhat, we use Mark Kantrowitz’s ‘defsystem’ package (from Carnegie Mellon University) to load and compile files (it hooks into Common Lisp commands). The main (lisp) function that invokes the package is operate-on-system. Here are some possibly useful commands you might need. These are all called from within the Lisp environment (once traumaid has been loaded).
(operate-on-system "xtrauma" :compile :force :new-source)
This recompiles source files which were modified since being compiled
(operate-on-system "xtrauma" :compile :force :all)
Recompiles all source files
(operate-on-system "xtrauma" :compile :force
:new-source-and-dependents)
Recompiles source files and those files that depend on them. We have not gone to much trouble to verify that the dependency tree is exactly accurate, so this may invoke the compilation of many, many files.
You probably get the idea by now. More than just compiling, you can also give the
:load option (instead of :compile) which is used to load the files of the system that you specify. Also, we used "xtrauma" above, but each of these forms is equally fine with "textrauma" (if you want to use textrauma).For more details about
operate-on-system, look into the file ~jclarke/Xtrauma2/defsystem.lisp where it is defined. There are lots of comments as well describing it in more detail.On-Line Help
A limited amount of on-line help is available in X-Trauma via the ‘TraumAID Help’ menu item in the ‘System’ menu. When you select this item, X-Trauma recalls the information contained in
notes/help, and presents the help topics to the user. To get information on a topic, double-click the topic name.The reason there isn’t much help available is that this a new feature, and no one has spent the time to add more information. You can easily add new topics and information, by following the format for the information in the file. The format is very simple, consisting of:
~topic_name(no_spaces)
<newline>
Put your information here. While it may span as many lines as you’d like, it may not contain any newlines. I told you it was simple. The next newlines ends this entry.
<newline>
This is the format for both the TraumAID help file (
notes/help) and the concept description help file (notes/concept.description). The concept description help file contains definitions