** DONE UOMF: Requirement Analysis and Solution Deciding Template in Yankpad :blog:software:pim: CLOSED: [2026-03-21 Sat 13:12] SCHEDULED: <2026-03-21 Sat> :PROPERTIES: :CREATED: [2026-03-21 Sat 12:30] :ID: 2026-03-21-UOMF-Requirement-Analysis-Template :END: :LOGBOOK: - State "DONE" from "STARTED" [2026-03-21 Sat 13:12] :END: This is an article from a series of blog postings. Please do read [[id:2019-09-25-using-orgmode][my "Using Org Mode Features" (UOMF) series page]] for explanations on articles of this series. In [[id:2021-01-18-tool-choices][my "How to choose a tool" article]], I describe the general approach how I do think that you should make tool choices. Well, it's not just tool choices - the process can be generalized to any choice making process, if you think about it. In order to support this process for a few steps, I generated an Org-mode [[https://github.com/Kungsgeten/yankpad][yankpad]] template which minimizes the manual effort to create the boilerplate. ---------- How the template works: - Instantiate the template using yankpad - Tab stops during expansion (yasnippet =$1–$4=, =$0=): a. =$1= — date prefix for =#+NAME= IDs (auto-filled with today's date, mirrors to all references) b. =$2= — project title in the heading c. =$3=, =$4= — first two solution names d. =$0= — final cursor lands in the first requirement cell - Heading levels adapt automatically via org-current-level - Note the =$= escaping: the =\$1= in the =#+TBLFM= line is literal Org column =$1=, not a yasnippet reference Workflow after expansion: 1. Fill in requirements (rows) in the first table — feature name, class (-2..2), weight (≥1) 2. Add matching rows to the evaluation table, then =C-c C-c= on the =#+TBLFM= to pull feature names 3. Add solution columns with =M-S-right= as needed 4. Fill in coverage values (0/1/2) 5. =C-c C-c= on the source block → breakdown + ranked results *** Requirement Analysis: Example Vacation Project This is how it looks, when applied. Notice that in that example, I omitted the optional weight feature in the "Feature" table to keep it simple. Furthermore, don't be offended by my example rating: it's just for demo purposes. **** Requirements Define features and classify them by importance. - *Class* (integer, =-2= to =2=): how important is this feature? | -2 | must not have | | -1 | don't want | | 0 | neutral | | 1 | nice to have | | 2 | must have | - *Weight* (integer ≥ 1): optional amplifier within a class. Leave at =1= if all requirements in a class are equally important. #+NAME: 2026-03-22-example-requirements | Feature | Class | Weight | |-----------------------+-------+--------| | Good price | 1 | 1 | | Good weather | 2 | | | Availability of ocean | 1 | | | Cultural possbilities | 1 | | **** Solution Evaluation Rate how strongly each solution exhibits each feature. - *Coverage* (integer, =0= to =2=): | 0 | not present | | 1 | somewhat present | | 2 | fully present | - To add solutions: insert columns with =M-S-=. - Feature names auto-fill: press =C-c C-c= on the =TBLFM= line. - Ensure this table has the same number of data rows as the requirements table. #+NAME: 2026-03-22-example-evaluation | Feature | Vienna | Bahamas | Stockholm | |-----------------------+--------+---------+-----------| | Good price | 1 | 0 | 1 | | Good weather | 1 | 2 | 0 | | Availability of ocean | 0 | 2 | 1 | | Cultural possbilities | 2 | 0 | 1 | #+TBLFM: $1=remote(2026-03-22-example-requirements, @@#$1) **** Results Scoring (higher is better): - Class ≥ 0: =class × weight × coverage= - Class < 0: =|class| × weight × (1 − coverage)= Press =C-c C-c= on the source block to compute results. #+NAME: 2026-03-22-example-results #+BEGIN_SRC python :var reqs=2026-03-22-example-requirements ev=2026-03-22-example-evaluation :colnames no :results value raw reqs = [r for r in reqs if r is not None] ev = [r for r in ev if r is not None] solutions = ev[0][1:] req_rows = reqs[1:] ev_rows = ev[1:] h = "| Requirement | Cl | W |" for s in solutions: h += " {} cov | pts |".format(s) lines = [h, "|-"] totals = [0] * len(solutions) for i, rr in enumerate(req_rows): feat = rr[0] cls = int(rr[1]) try: w = int(rr[2]) except (ValueError, TypeError): w = 1 row = "| {} | {} | {} |".format(feat, cls, w) er = ev_rows[i] for j in range(len(solutions)): cov = int(er[1 + j]) if cls >= 0: pts = cls * w * cov else: pts = abs(cls) * w * (1 - cov) totals[j] += pts row += " {} | {:+d} |".format(cov, pts) lines.append(row) lines.append("|-") total_row = "| *Total* | | |" for t in totals: total_row += " | *{:+d}* |".format(t) lines.append(total_row) lines += ["", "| Rank | Solution | Score |", "|-"] ranked = sorted(zip(solutions, totals), key=lambda x: -x[1]) for rank, (sol, sc) in enumerate(ranked, 1): lines.append("| {} | {} | {:+d} |".format(rank, sol, sc)) return "\n".join(lines) #+END_SRC #+RESULTS: 2026-03-22-example-results | Requirement | Cl | W | Vienna cov | pts | Bahamas cov | pts | Stockholm cov | pts | |-----------------------+----+---+------------+------+-------------+------+---------------+------| | Good price | 1 | 1 | 1 | +1 | 0 | +0 | 1 | +1 | | Good weather | 2 | 1 | 1 | +2 | 2 | +4 | 0 | +0 | | Availability of ocean | 1 | 1 | 0 | +0 | 2 | +2 | 1 | +1 | | Cultural possbilities | 1 | 1 | 2 | +2 | 0 | +0 | 1 | +1 | |-----------------------+----+---+------------+------+-------------+------+---------------+------| | *Total* | | | | *+5* | | *+6* | | *+3* | | Rank | Solution | Score | |------+-----------+-------| | 1 | Bahamas | +6 | | 2 | Vienna | +5 | | 3 | Stockholm | +3 | *** The Template Here is the raw template. If you're new to yankpad, please do refer to [[https://github.com/Kungsgeten/yankpad][the project's documentation]] how to use it. It's very similar to [[https://github.com/joaotavora/yasnippet][yasnippet]] but its definitions are managed within an [[id:2021-11-27-orgdown][Orgdown file]]. #+BEGIN_EXAMPLE `(progn (setq yas--ra-h1 (make-string (+ 1 (or (org-current-level) 0)) ?*) yas--ra-h2 (make-string (+ 2 (or (org-current-level) 0)) ?*)) "")` `yas--ra-h1` Requirement Analysis: ${2:Project Title} `yas--ra-h2` Requirements Define features and classify them by importance. - *Class* (integer, =-2= to =2=): how important is this feature? | -2 | must not have | | -1 | don't want | | 0 | neutral | | 1 | nice to have | | 2 | must have | - *Weight* (integer ≥ 1): optional amplifier within a class. Leave at =1= if all requirements in a class are equally important. #+NAME: ${1:`(format-time-string "%Y-%m-%d")`}-requirements | Feature | Class | Weight | |---------+-------+--------| | $0 | | 1 | `yas--ra-h2` Solution Evaluation Rate how strongly each solution exhibits each feature. - *Coverage* (integer, =0= to =2=): | 0 | not present | | 1 | somewhat present | | 2 | fully present | - To add solutions: insert columns with =M-S-=. - Feature names auto-fill: press =C-c C-c= on the =TBLFM= line. - Ensure this table has the same number of data rows as the requirements table. #+NAME: $1-evaluation | Feature | ${3:Solution A} | ${4:Solution B} | |---------+-----------------+-----------------+ | | | | #+TBLFM: \$1=remote($1-requirements, @@#\$1) `yas--ra-h2` Results Scoring (higher is better): - Class ≥ 0: =class × weight × coverage= - Class < 0: =|class| × weight × (1 − coverage)= Press =C-c C-c= on the source block to compute results. #+NAME: $1-results #+BEGIN_SRC python :var reqs=$1-requirements ev=$1-evaluation :colnames no :results value raw reqs = [r for r in reqs if r is not None] ev = [r for r in ev if r is not None] solutions = ev[0][1:] req_rows = reqs[1:] ev_rows = ev[1:] h = "| Requirement | Cl | W |" for s in solutions: h += " {} cov | pts |".format(s) lines = [h, "|-"] totals = [0] * len(solutions) for i, rr in enumerate(req_rows): feat = rr[0] cls = int(rr[1]) try: w = int(rr[2]) except (ValueError, TypeError): w = 1 row = "| {} | {} | {} |".format(feat, cls, w) er = ev_rows[i] for j in range(len(solutions)): cov = int(er[1 + j]) if cls >= 0: pts = cls * w * cov else: pts = abs(cls) * w * (1 - cov) totals[j] += pts row += " {} | {:+d} |".format(cov, pts) lines.append(row) lines.append("|-") total_row = "| *Total* | | |" for t in totals: total_row += " | *{:+d}* |".format(t) lines.append(total_row) lines += ["", "| Rank | Solution | Score |", "|-"] ranked = sorted(zip(solutions, totals), key=lambda x: -x[1]) for rank, (sol, sc) in enumerate(ranked, 1): lines.append("| {} | {} | {:+d} |".format(rank, sol, sc)) return "\n".join(lines) #+END_SRC #+END_EXAMPLE