okstra 0.44.0 → 0.45.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json
CHANGED
package/runtime/BUILD.json
CHANGED
|
@@ -300,6 +300,10 @@ def _emit_table(lines: list[str], start: int, section_path: list[str]) -> tuple[
|
|
|
300
300
|
rows.append(_split_pipe_row(lines[i]))
|
|
301
301
|
i += 1
|
|
302
302
|
|
|
303
|
+
grouped_spec = _grouped_table_spec(header_cells, section_path)
|
|
304
|
+
if grouped_spec is not None:
|
|
305
|
+
return _emit_grouped_table(header_cells, rows, grouped_spec), i - start
|
|
306
|
+
|
|
303
307
|
is_clarification_table = (
|
|
304
308
|
not _section_forbids_form(section_path)
|
|
305
309
|
and any("Clarification Items" in h for h in section_path)
|
|
@@ -383,6 +387,172 @@ def _emit_table(lines: list[str], start: int, section_path: list[str]) -> tuple[
|
|
|
383
387
|
return f"<table>{head}{body}</table>", i - start
|
|
384
388
|
|
|
385
389
|
|
|
390
|
+
@dataclass(frozen=True)
|
|
391
|
+
class _GroupedSpec:
|
|
392
|
+
"""Plan for rendering a wide table as a compact grouped layout: the
|
|
393
|
+
short columns (``group_cols``) collapse into one stacked ``key:
|
|
394
|
+
value`` metadata cell led by ``headline_col``; the long columns
|
|
395
|
+
(``wide_cols``) each keep their own min-width column.
|
|
396
|
+
|
|
397
|
+
``kind == "clarification"`` additionally re-attaches the §5 form
|
|
398
|
+
widget to the ``user_input_col`` cell and the ``data-*`` row attrs."""
|
|
399
|
+
headline_col: int
|
|
400
|
+
group_cols: tuple[int, ...]
|
|
401
|
+
wide_cols: tuple[int, ...]
|
|
402
|
+
kind: str # "plain" | "clarification"
|
|
403
|
+
id_col: int = -1
|
|
404
|
+
kind_col: int = -1
|
|
405
|
+
status_col: int = -1
|
|
406
|
+
statement_col: int = -1
|
|
407
|
+
user_input_col: int = -1
|
|
408
|
+
|
|
409
|
+
|
|
410
|
+
_FOLLOWUP_WIDE_PREFIXES: tuple[str, ...] = ("title", "scope", "reason")
|
|
411
|
+
|
|
412
|
+
|
|
413
|
+
def _grouped_table_spec(
|
|
414
|
+
header_cells: list[str], section_path: list[str]
|
|
415
|
+
) -> Optional[_GroupedSpec]:
|
|
416
|
+
"""Return a ``_GroupedSpec`` for the three wide final-report tables
|
|
417
|
+
that benefit from the compact layout — Execution Status, §5
|
|
418
|
+
Clarification Items, §7 Follow-up Tasks — or ``None`` for every other
|
|
419
|
+
table (which keeps the default per-cell ``td-narrow`` rendering).
|
|
420
|
+
|
|
421
|
+
Each table is identified by stable header tokens (the i18n token/cost
|
|
422
|
+
columns are never used as anchors). ``wide_cols`` lists the long-prose
|
|
423
|
+
columns that must keep a guaranteed min-width; everything else short
|
|
424
|
+
collapses into the leading metadata cell."""
|
|
425
|
+
norm = [h.strip() for h in header_cells]
|
|
426
|
+
|
|
427
|
+
def _spec(headline: int, wide: tuple[int, ...], **kw) -> _GroupedSpec:
|
|
428
|
+
wide_set = set(wide)
|
|
429
|
+
group = tuple(c for c in range(len(norm)) if c != headline and c not in wide_set)
|
|
430
|
+
return _GroupedSpec(headline_col=headline, group_cols=group, wide_cols=wide, **kw)
|
|
431
|
+
|
|
432
|
+
# Execution Status by Agent — Agent … Summary of Key Findings.
|
|
433
|
+
if len(norm) >= 3 and norm[0] == "Agent" and norm[-1] == "Summary of Key Findings":
|
|
434
|
+
return _spec(0, (len(norm) - 1,), kind="plain")
|
|
435
|
+
|
|
436
|
+
# §5 Clarification Items — keep the interactive form, but collapse the
|
|
437
|
+
# short ID/Kind/Status/… columns and widen Statement + User input.
|
|
438
|
+
if (
|
|
439
|
+
any("Clarification Items" in h for h in section_path)
|
|
440
|
+
and not _section_forbids_form(section_path)
|
|
441
|
+
and "ID" in norm
|
|
442
|
+
and "User input" in norm
|
|
443
|
+
and any(h.startswith("Statement") for h in norm)
|
|
444
|
+
):
|
|
445
|
+
statement_col = next(i for i, h in enumerate(norm) if h.startswith("Statement"))
|
|
446
|
+
user_input_col = norm.index("User input")
|
|
447
|
+
return _spec(
|
|
448
|
+
norm.index("ID"),
|
|
449
|
+
(statement_col, user_input_col),
|
|
450
|
+
kind="clarification",
|
|
451
|
+
id_col=norm.index("ID"),
|
|
452
|
+
kind_col=norm.index("Kind") if "Kind" in norm else -1,
|
|
453
|
+
status_col=norm.index("Status") if "Status" in norm else -1,
|
|
454
|
+
statement_col=statement_col,
|
|
455
|
+
user_input_col=user_input_col,
|
|
456
|
+
)
|
|
457
|
+
|
|
458
|
+
# §7 Follow-up Tasks — widen Title / Scope / Reason, collapse the rest.
|
|
459
|
+
if any("Follow-up Tasks" in h for h in section_path) and "ID" in norm:
|
|
460
|
+
wide = tuple(
|
|
461
|
+
i
|
|
462
|
+
for i, h in enumerate(norm)
|
|
463
|
+
if any(h.lower().startswith(p) for p in _FOLLOWUP_WIDE_PREFIXES)
|
|
464
|
+
)
|
|
465
|
+
if wide:
|
|
466
|
+
return _spec(norm.index("ID"), wide, kind="plain")
|
|
467
|
+
|
|
468
|
+
return None
|
|
469
|
+
|
|
470
|
+
|
|
471
|
+
def _grouped_meta_cell(
|
|
472
|
+
header_cells: list[str], row: list[str], spec: _GroupedSpec
|
|
473
|
+
) -> str:
|
|
474
|
+
"""The leading metadata ``<td>``: a bold headline (``headline_col``)
|
|
475
|
+
above one ``key: value`` line per collapsed short column."""
|
|
476
|
+
headline = row[spec.headline_col] if spec.headline_col < len(row) else ""
|
|
477
|
+
fields = "".join(
|
|
478
|
+
'<div class="grp-field">'
|
|
479
|
+
f'<span class="grp-key">{_inline(header_cells[col])}</span>'
|
|
480
|
+
f'<span class="grp-val">{_inline(row[col] if col < len(row) else "")}</span>'
|
|
481
|
+
"</div>"
|
|
482
|
+
for col in spec.group_cols
|
|
483
|
+
)
|
|
484
|
+
return (
|
|
485
|
+
'<td class="grp-meta">'
|
|
486
|
+
f'<div class="grp-headline">{_inline(headline)}</div>'
|
|
487
|
+
f"{fields}</td>"
|
|
488
|
+
)
|
|
489
|
+
|
|
490
|
+
|
|
491
|
+
def _grouped_clarification_row(
|
|
492
|
+
row: list[str], spec: _GroupedSpec
|
|
493
|
+
) -> tuple[str, str]:
|
|
494
|
+
"""Return ``(tr_attrs, wide_cells_html)`` for one §5 row, re-attaching
|
|
495
|
+
the form widget + ``data-*`` attrs to ``C-\\d+`` rows exactly as the
|
|
496
|
+
non-grouped path does."""
|
|
497
|
+
rid = row[spec.id_col] if 0 <= spec.id_col < len(row) else ""
|
|
498
|
+
is_form_row = bool(re.fullmatch(r"C-\d+", rid)) and spec.user_input_col >= 0
|
|
499
|
+
kind = row[spec.kind_col] if is_form_row and 0 <= spec.kind_col < len(row) else ""
|
|
500
|
+
status = row[spec.status_col] if is_form_row and 0 <= spec.status_col < len(row) else ""
|
|
501
|
+
statement = (
|
|
502
|
+
row[spec.statement_col]
|
|
503
|
+
if is_form_row and 0 <= spec.statement_col < len(row)
|
|
504
|
+
else ""
|
|
505
|
+
)
|
|
506
|
+
tr_attrs = (
|
|
507
|
+
f' data-response-id="{html.escape(rid)}" '
|
|
508
|
+
f'data-kind="{html.escape(kind)}" '
|
|
509
|
+
f'data-status="{html.escape(status)}"'
|
|
510
|
+
if is_form_row
|
|
511
|
+
else ""
|
|
512
|
+
)
|
|
513
|
+
cells: list[str] = []
|
|
514
|
+
for col in spec.wide_cols:
|
|
515
|
+
value = row[col] if col < len(row) else ""
|
|
516
|
+
if is_form_row and col == spec.user_input_col:
|
|
517
|
+
cells.append(
|
|
518
|
+
f'<td class="grp-wide grp-form">'
|
|
519
|
+
f"{_form_control(rid, kind, status, value, statement)}</td>"
|
|
520
|
+
)
|
|
521
|
+
else:
|
|
522
|
+
cells.append(f'<td class="grp-wide">{_inline(value)}</td>')
|
|
523
|
+
return tr_attrs, "".join(cells)
|
|
524
|
+
|
|
525
|
+
|
|
526
|
+
def _emit_grouped_table(
|
|
527
|
+
header_cells: list[str], rows: list[list[str]], spec: _GroupedSpec
|
|
528
|
+
) -> str:
|
|
529
|
+
"""Render a wide table in the compact grouped layout described by
|
|
530
|
+
``spec`` — one metadata cell plus the min-width long columns."""
|
|
531
|
+
head = (
|
|
532
|
+
"<thead><tr>"
|
|
533
|
+
f"<th>{_inline(header_cells[spec.headline_col])}</th>"
|
|
534
|
+
+ "".join(
|
|
535
|
+
f'<th class="grp-wide">{_inline(header_cells[col])}</th>'
|
|
536
|
+
for col in spec.wide_cols
|
|
537
|
+
)
|
|
538
|
+
+ "</tr></thead>"
|
|
539
|
+
)
|
|
540
|
+
body_rows: list[str] = []
|
|
541
|
+
for row in rows:
|
|
542
|
+
meta = _grouped_meta_cell(header_cells, row, spec)
|
|
543
|
+
if spec.kind == "clarification":
|
|
544
|
+
tr_attrs, wide_cells = _grouped_clarification_row(row, spec)
|
|
545
|
+
else:
|
|
546
|
+
tr_attrs = ""
|
|
547
|
+
wide_cells = "".join(
|
|
548
|
+
f'<td class="grp-wide">{_inline(row[col] if col < len(row) else "")}</td>'
|
|
549
|
+
for col in spec.wide_cols
|
|
550
|
+
)
|
|
551
|
+
body_rows.append(f"<tr{tr_attrs}>{meta}{wide_cells}</tr>")
|
|
552
|
+
body = "<tbody>" + "".join(body_rows) + "</tbody>"
|
|
553
|
+
return f'<table class="grouped-table">{head}{body}</table>'
|
|
554
|
+
|
|
555
|
+
|
|
386
556
|
_ENUM_LETTERS = "abcde"
|
|
387
557
|
_ENUM_CUE_WORDS: tuple[str, ...] = (
|
|
388
558
|
"권장은", "권장 ", "추천은", "추천 ", "사유:", "사유 ", "근거:",
|
|
@@ -132,6 +132,38 @@ td.td-narrow, th.td-narrow {
|
|
|
132
132
|
width: 5%;
|
|
133
133
|
white-space: nowrap;
|
|
134
134
|
}
|
|
135
|
+
/* Compact grouped layout for the wide final-report tables (Execution
|
|
136
|
+
* Status, §5 Clarification Items, §7 Follow-up Tasks): the short
|
|
137
|
+
* columns collapse into one stacked `key: value` metadata cell, while
|
|
138
|
+
* each long-prose column keeps its own guaranteed min-width so it never
|
|
139
|
+
* gets squashed into a one-character ladder. */
|
|
140
|
+
table.grouped-table td.grp-meta {
|
|
141
|
+
width: 24%;
|
|
142
|
+
}
|
|
143
|
+
table.grouped-table th.grp-wide,
|
|
144
|
+
table.grouped-table td.grp-wide {
|
|
145
|
+
min-width: 18ch;
|
|
146
|
+
}
|
|
147
|
+
.grp-meta .grp-headline {
|
|
148
|
+
font-weight: 600;
|
|
149
|
+
margin-bottom: 0.4em;
|
|
150
|
+
}
|
|
151
|
+
.grp-meta .grp-field {
|
|
152
|
+
display: flex;
|
|
153
|
+
gap: 0.45em;
|
|
154
|
+
font-size: 0.88rem;
|
|
155
|
+
line-height: 1.55;
|
|
156
|
+
}
|
|
157
|
+
.grp-meta .grp-key {
|
|
158
|
+
color: GrayText;
|
|
159
|
+
white-space: nowrap;
|
|
160
|
+
}
|
|
161
|
+
.grp-meta .grp-key::after {
|
|
162
|
+
content: ":";
|
|
163
|
+
}
|
|
164
|
+
.grp-meta .grp-val {
|
|
165
|
+
overflow-wrap: anywhere;
|
|
166
|
+
}
|
|
135
167
|
thead th {
|
|
136
168
|
position: sticky;
|
|
137
169
|
top: 3rem;
|