Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 23 additions & 5 deletions specs/002-analysis-report/research.md
Original file line number Diff line number Diff line change
Expand Up @@ -498,11 +498,29 @@ not known.

**Differences found** (the same at 25 and at 50):

| Part | Sections (25 / 50) | Reason |
| -------------------------------- | ------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Edit history, order within a day | 4 / 8 (and in some of the next row's) | **Bug, fixed (#213).** Today's groups a day's edits into a hash map with `computeIfAbsent`, which puts a new key at the head of its bucket; Created, Authored and Reviewed share one, so a day reads Modified, Revised, Edited, Reviewed, Authored, Created. R2 inferred the order of `put`. |
| Edit history, Modified rows | 11 / 20 | **Listed (decided 14 Nov 2026).** 20 of the 50 pathways have two `modified` edits in the release 98 graph (R2 said one). Today's prints none for them, and one where there is one; pdf-export prints a row for each, kept by the user's decision. `compare-report` lists it itself (T026). |
| References | 1 / 2 | A Greek letter today's typeface lacks, written as U+0000 (an improvement). **For a decision:** a book's and a web page's reference printed in spec 001's form, where today's prints a web page's address and a book's publisher after a comma. |
| Part | Sections (25 / 50) | Reason |
| -------------------------------- | ------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Edit history, order within a day | 4 / 8 (and in some of the next row's) | **Bug, fixed (#213).** Today's groups a day's edits into a hash map with `computeIfAbsent`, which puts a new key at the head of its bucket; Created, Authored and Reviewed share one, so a day reads Modified, Revised, Edited, Reviewed, Authored, Created. R2 inferred the order of `put`. |
| Edit history, Modified rows | 11 / 20 | **Listed (decided 14 Nov 2026).** 20 of the 50 pathways have two `modified` edits in the release 98 graph (R2 said one). Today's prints none for them, and one where there is one; pdf-export prints a row for each, kept by the user's decision. `compare-report` lists it itself (T026). |
| References | 1 / 2 | A Greek letter today's typeface lacks, written as U+0000 (an improvement). **Decided 14 Nov 2026, today's form:** a book's and a web page's reference were printed in spec 001's form, where today's prints a web page's address and, after a comma, what this read as a book's publisher (corrected below). The report now prints today's form (T027). |

**Today's form of a reference, read from its source (T027, 14 Nov 2026).** The analysis
report library's `ApaStyle` (the `1.4.0-SNAPSHOT` build of `reactome/analysis-report` in the
local Maven repository, read from its compiled classes; not shown to be the deployed build's
own copy) writes:

- **an article:** `Authors (year). Title. Journal, volume, pages.`, as spec 001's but for the
authors' form, which the comparison does not read;
- **a web page:** `Title. Retrieved from address`, with no authors and no year;
- **a book:** `Authors (year).` then, in italics, `Chapter, Book` (or `. Book` with no
chapter), then `, pages` and a full stop; no publisher and no ISBN.

So the words after the comma that this table first called a book's publisher are the book's
title, after its chapter's: on R-HSA-1280215 in release 98, `(2002). Cytokines and the immune
system, Cytokine Reference.`, the book's publisher being in the graph but not printed. Spec
001's form, which the report printed until T027, does print the publisher (`… In: Cytokine
Reference. Elsevier Science.`), and links a web page's title to its address without printing
it.

Every other part agreed at both sizes. The edit history's grouping by day (T007) holds:
on the two pathways with two edits of one kind on one day at different times, both
Expand Down
6 changes: 5 additions & 1 deletion specs/002-analysis-report/spec.md
Original file line number Diff line number Diff line change
Expand Up @@ -395,7 +395,11 @@ disk, the printer's memory-backed folder included, for the submitted identifiers
here.
- **One implementation:** the report is made by pdf-export, beside spec 001's
single-event PDF, reusing its theme, fonts, layout, printing and figures; a reported
pathway's detail is close to spec 001's section, and shares its parts. The
pathway's detail is close to spec 001's section, and shares its parts. A book's and a web
page's references are printed in today's report's form, not spec 001's (decided 14 Nov
2026, T027, research R9): a web page's title and address, without authors or year; a
book's authors and year, its chapter's title and the book's parted by a comma, and its
pages, without publisher or ISBN. Spec 001's book keeps its own form. The
constitution's Principle I is amended with this spec (version 1.1.0) to cover the
website's PDFs generally, the analysis report among them.
- **Spec 001's FR-017** (characters no font draws) is merged before this spec.
Expand Down
27 changes: 24 additions & 3 deletions specs/002-analysis-report/tasks.md
Original file line number Diff line number Diff line change
Expand Up @@ -483,9 +483,9 @@ compare-report <today's.pdf> <new.pdf> [--expected <file>]`, reading both with s
two `modified` edits, and three references (R9). The grouping by day holds: one row in
both on the two pathways with two edits of one kind on one day at different times.
- **Left open:** the Modified rows and the book and web-page references, for a decision
(R9; the Modified rows decided 14 Nov 2026, T026); the expression, interactors and
species-comparison tokens, and each figure's overlay check (quickstart §3), not run
here.
(R9; the Modified rows decided 14 Nov 2026, T026, and the references, T027); the
expression, interactors and species-comparison tokens, and each figure's overlay check
(quickstart §3), not run here.

- [x] T020 [US2] The replay and the time (SC-202, SC-203, SC-204, SC-205, SC-207), after
T014, T015 and T017.
Expand Down Expand Up @@ -528,6 +528,27 @@ compare-report <today's.pdf> <new.pdf> [--expected <file>]`, reading both with s
of pdf-export's, is still caught, seen failing with the rule widened.
- **Done (#217).**

- [x] T027 [US1] The references in today's form, as the user decided on 14 Nov 2026 (T019,
research R9): a book's and a web page's, in the analysis report.
- Today's form read from `reactome/analysis-report`'s `ApaStyle` (R9): a web page's title
and address; a book's chapter's title and the book's parted by a comma, and its pages.
T019's "a book's publisher after a comma" was the book's title: today's prints no
publisher.
- Spec 001's `reference` is shared with the report (its `sectionHtml`), so the report has
its own form, `reportReference` (`src/report/layout.ts`), passed to `sectionHtml`; spec
001's book is unchanged. An article is spec 001's in both.
- Read through spec 001's `literature` query, which already reads the address and the
publisher; no field added.
- Tests, each seen failing without the change: the fixture graph's book and web page in
the report's form, spec 001's form of them unchanged (`test/report/body.test.ts`); and
a book and a web page printed in today's form and the report's, agreeing in
`compare-report` (`test/compare/report.test.ts`, its today's-form helper taught both
from `ApaStyle`). The comparison's reader now leaves out pdf-export's list number on a
reference with no year (a web page in today's form), seen failing without it.
- **Follow-up, not done:** whether spec 001's book takes this form too is the user's to
decide later.
- **Done (#218).**

- [ ] T021 [WA] The website routed to pdf-export (research R6; plan, Order of work, step
8), in WebsiteAngular under spec 010 (the analysis gateway, its Story 6), after
T019 and T020. Last; deployed by the user.
Expand Down
4 changes: 3 additions & 1 deletion src/compare/reduce.ts
Original file line number Diff line number Diff line change
Expand Up @@ -600,7 +600,9 @@ function reportItems(part: PartName, texts: readonly string[], own: readonly Lin
return found
.map((text) => {
const year = /\(\d{4}[a-z]?\)/.exec(text);
return unhyphened(year ? text.slice(year.index) : text);
// one with no year (a web page, in today's form) read whole, but pdf-export's
// list number (`3. `), which today's does not print
return unhyphened(year ? text.slice(year.index) : text.replace(/^\d+\.\s+/, ''));
})
.filter((item) => item !== '')
.map(hash);
Expand Down
30 changes: 20 additions & 10 deletions src/layout/section.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,9 +26,10 @@ export interface SectionInput {
report: Report;
/**
* The analysis report's own (spec 002 T009): markup in place of the edit history and what
* an analysis found, and the stable identifier linked to the event's page.
* an analysis found, the stable identifier linked to the event's page, and its own form of
* a reference (spec 002 T027).
*/
analysisReport?: { tail: string; linkStId: true };
analysisReport?: { tail: string; linkStId: true; reference: (r: Reference) => string };
}

/** An event's heading level: the PDF's own event or a chapter's is h1, h6 the deepest. */
Expand Down Expand Up @@ -119,7 +120,7 @@ export function sectionHtml(section: Section, input: SectionInput): string {
if (section.references.length > 0) {
parts.push(
`<p class="part">Literature references</p><ol class="references">${section.references
.map((r) => `<li>${reference(r)}</li>`)
.map((r) => `<li>${(input.analysisReport?.reference ?? reference)(r)}</li>`)
.join('')}</ol>`
);
}
Expand Down Expand Up @@ -159,21 +160,30 @@ function definitions(rows: readonly [string, string][]): string {

const capital = (word: string): string => word.charAt(0).toUpperCase() + word.slice(1);

/** A literature reference: its authors, year, title and where it was published. */
export function reference(r: Reference): string {
/** A reference's authors and year, as markup: `Placeholder, A, Example, B (2003). `, or empty. */
export function referenceLead(r: Reference): string {
const authors =
r.authors.length > 0 ? `${text(r.authors.join(', '))}${r.moreAuthors ? ' et al.' : ''}` : '';
const year = r.year === null ? '' : ` (${String(r.year)})`;
const named = `${authors}${year}`;
// a list of authors may end in an initial's full stop: it is not doubled
const lead = named === '' ? '' : `${named}${named.endsWith('.') ? '' : '.'} `;
return named === '' ? '' : `${named}${named.endsWith('.') ? '' : '.'} `;
}

/** A reference's title as markup, linked to PubMed where it has a PubMed identifier. */
export function referenceTitle(r: Reference): string {
return r.pubMedId === null
? text(r.title)
: Links.a(`https://pubmed.ncbi.nlm.nih.gov/${String(r.pubMedId)}/`, r.title);
}

/** A literature reference: its authors, year, title and where it was published. */
export function reference(r: Reference): string {
const lead = referenceLead(r);
if (r.kind === 'web page') {
return `${lead}${r.url ? Links.a(r.url, r.title) : text(r.title)}.`;
}
const title =
r.pubMedId === null
? text(r.title)
: Links.a(`https://pubmed.ncbi.nlm.nih.gov/${String(r.pubMedId)}/`, r.title);
const title = referenceTitle(r);
if (r.kind === 'book') {
const chapter = r.chapterTitle ? `${text(r.chapterTitle)}. In: ` : '';
const rest = [r.publisher, r.isbn ? `ISBN ${r.isbn}` : null].filter((x) => x !== null);
Expand Down
29 changes: 27 additions & 2 deletions src/report/layout.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,9 @@ import type {
ReportedPathway,
ResourceTable,
} from './model.ts';
import type { Reference } from '../graph/queries.ts';
import type { Report } from '../layout/section.ts';
import { sectionHtml } from '../layout/section.ts';
import { reference, referenceLead, referenceTitle, sectionHtml } from '../layout/section.ts';
import {
KEPT,
OVERVIEW_CAPTION,
Expand Down Expand Up @@ -362,6 +363,30 @@ export interface DetailsInput {
report: Report;
}

/**
* A literature reference as the analysis report prints it (spec 002 T027, decided 14 Nov
* 2026: today's form for a book and a web page, read from `reactome/analysis-report`'s
* `ApaStyle`; research R9). An article is spec 001's, which today's matches.
*
* - **A web page:** its title, then "Retrieved from" and its address, linked; today's names
* no authors and no year for one.
* - **A book:** its authors and year, then its chapter's title and the book's, parted by a
* comma, in italics, then its pages after a comma; today's prints no publisher or ISBN.
*/
export function reportReference(r: Reference): string {
if (r.kind === 'web page') {
return r.url === null
? `${text(r.title)}.`
: `${text(r.title)}. Retrieved from ${Links.a(r.url, r.url)}`;
}
if (r.kind === 'book') {
const chapter = r.chapterTitle === null ? '' : `${text(r.chapterTitle)}, `;
const pages = r.pages === null ? '' : `, ${text(r.pages)}`;
return `${referenceLead(r)}<i>${chapter}${referenceTitle(r)}</i>${pages}.`;
}
return reference(r);
}

/**
* One pathway's detail: spec 001's section for it, with the report's edit history in place
* of spec 001's and its stable identifier linked to its page, then its entities found per
Expand Down Expand Up @@ -392,7 +417,7 @@ export function detailHtml(
figures: input.figures,
found: null,
report: input.report,
analysisReport: { tail, linkStId: true },
analysisReport: { tail, linkStId: true, reference: reportReference },
}
);
}
Expand Down
38 changes: 38 additions & 0 deletions test/compare/report.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,33 @@ const REFERENCE: Reference = {
moreAuthors: false,
};

const BOOK: Reference = {
...REFERENCE,
dbId: 2,
kind: 'book',
title: 'A made-up book',
chapterTitle: 'A made-up chapter',
journal: null,
volume: null,
pages: '1-20',
year: 2010,
isbn: '0000000000',
publisher: 'Placeholder Press',
authors: ['Sample, C'],
};
const WEB_PAGE: Reference = {
...REFERENCE,
dbId: 3,
kind: 'web page',
title: 'A made-up web page',
journal: null,
volume: null,
pages: null,
year: null,
url: 'https://example.org/placeholder',
authors: ['Dummy, D'],
};

/** A made-up detail for a pathway: spec 001's section and an edit history. */
function detail(
stId: string,
Expand Down Expand Up @@ -215,6 +242,17 @@ describe("the analysis report's comparison (FR-208)", { timeout: 600_000 }, () =
assert.ok((t.found?.[0]?.rows.length ?? 0) > 0, 'the identifiers found');
assert.equal(t.notFound?.length, baseline.notFound?.length);
assert.deepEqual(differences(t, baseline), []);
// a book and a web page: today's form, which the report's matches (T027)
const cited = new Map(
[...details(model)].map(([id, d]) => [
id,
{ ...d, section: { ...d.section, references: [REFERENCE, BOOK, WEB_PAGE] } },
])
);
assert.deepEqual(
differences(await today(model, cited), (await printed(model, cited)).reduced),
[]
);
// no references: today's "References" over nothing is as pdf-export's none
const bare = new Map(
[...details(model)].map(([id, d]) => [
Expand Down
12 changes: 12 additions & 0 deletions test/compare/today-form.ts
Original file line number Diff line number Diff line change
Expand Up @@ -54,13 +54,25 @@ const FILLER =
6
);

/**
* A reference as today's `ApaStyle` writes it (reactome/analysis-report): an article's
* authors, year, title and where; a web page's title and address alone; a book's authors and
* year, then (with no space, as today's) its chapter's title and the book's parted by a
* comma, and its pages; no publisher or ISBN.
*/
function reference(r: Reference): string {
const authors = r.authors.map(todayName);
const names =
authors.length > 1
? `${authors.slice(0, -1).join(', ')} & ${authors.at(-1) ?? ''}`
: (authors[0] ?? '');
const year = r.year === null ? '' : ` (${String(r.year)})`;
if (r.kind === 'web page') return `${escape(r.title)}. Retrieved from ${escape(r.url ?? '')}`;
if (r.kind === 'book') {
const title = r.chapterTitle === null ? `. ${r.title}` : `.${r.chapterTitle}, ${r.title}`;
const pages = r.pages === null ? '' : `, ${r.pages}`;
return `${escape(`${names}${year}`)}<i>${escape(title)}</i>${escape(pages)}.`;
}
const where = [r.journal, r.volume === null ? null : String(r.volume), r.pages].filter(
(x) => x !== null
);
Expand Down
Loading
Loading