Skip to content
Draft
Show file tree
Hide file tree
Changes from 26 commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
4870d19
⚡ Bolt: [performance improvement] O(1) deduplication in chart exports
seonghobae Sep 8, 2026
46102d9
Trigger CI retry
seonghobae Sep 8, 2026
a8d330c
Trigger CI retry
seonghobae Sep 8, 2026
94cc577
repair(ci): adopt canonical formatter owner by ancestry
seonghobae Sep 8, 2026
db491a9
test(exports): require semantic deduplication identifiers
seonghobae Sep 8, 2026
b838991
refactor(exports): name ordered deduplication state
seonghobae Sep 8, 2026
75f9916
⚡ Bolt: [performance improvement] O(1) deduplication in chart exports
seonghobae Sep 8, 2026
f01b86d
⚡ Bolt: [performance improvement] O(1) deduplication in chart exports
seonghobae Sep 8, 2026
e7eca20
test(exports): require complete semantic export contracts
seonghobae Sep 8, 2026
3f81a30
fix(exports): preserve semantic chart contracts
seonghobae Sep 8, 2026
3b9da96
fix(ci): restore canonical supply-chain policy fixture
seonghobae Sep 8, 2026
e087e64
Acknowledge test and ci fixes
seonghobae Sep 8, 2026
763a70d
test(chart): reproduce benchmark contract drift
seonghobae Sep 8, 2026
79da102
fix(chart): align benchmark with documented method
seonghobae Sep 8, 2026
1898fc4
Trigger CI retry
seonghobae Sep 8, 2026
edbd5d3
Trigger CI retry
seonghobae Sep 8, 2026
884c69a
Trigger CI retry
seonghobae Sep 8, 2026
8c6bcf7
Trigger CI retry
seonghobae Sep 8, 2026
7557259
Trigger CI retry
seonghobae Sep 8, 2026
6484e22
Trigger CI retry
seonghobae Sep 8, 2026
49d6f13
Trigger CI retry
seonghobae Sep 8, 2026
fd4efdc
Trigger CI retry
seonghobae Sep 8, 2026
a121d56
Trigger CI retry
seonghobae Sep 8, 2026
54ed68c
Trigger CI retry
seonghobae Sep 8, 2026
e0dd3d2
Acknowledge measurement constraints
seonghobae Sep 8, 2026
7cfc8d3
Trigger CI retry
seonghobae Sep 8, 2026
8575afd
refactor(exports): stack benchmark evidence on canonical chart owner
seonghobae Sep 10, 2026
2aedc4f
refactor(exports): restack benchmark evidence on repaired chart owner
seonghobae Sep 10, 2026
965f2a0
refactor(exports): inherit canonical formatter stack
seonghobae Sep 10, 2026
528d22b
Trigger CI retry
seonghobae Sep 10, 2026
8da3118
repair(exports): restore benchmark-only evidence lane
seonghobae Sep 22, 2026
0edffa2
refactor(exports): inherit repaired canonical chart owner
seonghobae Sep 22, 2026
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
4 changes: 4 additions & 0 deletions .jules/bolt.md
Original file line number Diff line number Diff line change
Expand Up @@ -61,3 +61,7 @@
## 2026-07-13 - Array.from mapping optimization
**Learning:** Using `Array.from({ length: N }).map(...)` creates an intermediate array of `undefined` values which requires memory allocation and garbage collection, adding O(N) unnecessary overhead in frequently re-rendered UI components.
**Action:** Use `Array.from({ length: N }, (_, index) => ...)` to map elements directly during array creation, avoiding intermediate allocations.

## 2026-09-08 - O(N^2) list-based deduplication replaced with O(1) dict keys
**Learning:** Checking for element existence in a list using `not in` before appending leads to O(N^2) time complexity. However, for bounded small lists ($N < 10$), standard list traversal in CPython can be marginally faster and use less memory overhead than hashing/allocating dict keys. For unbounded or large cardinalities (e.g., thousands of deduplications across a large song export payload with 1000+ sections and highly duplicated roles), dictionary O(1) insertions preserve insertion order while preventing super-linear CPU bounds.
**Action:** Replace `if item not in lst: lst.append(item)` patterns with `dct[item] = None` and `list(dct.keys())` for efficient and order-preserving deduplication in high-throughput data exports, provided we can demonstrate concrete wall-clock wins under profiling.
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@

### Changed

- Kept chart-export role, cue, and footer de-duplication expected-linear with insertion-ordered dictionaries, semantic internal names, and exact Unicode/order/blank-value regression coverage.
- Made the retained chart-export benchmark reproduce its documented 96-section, 24-role, 100-warmup, 1,000-sample method and report per-sample median and p95 latency.
- Consolidated Bandit, dependency audits, supplemental secret checks, and Trivy into one trusted-branch security backstop, delegated CodeQL to GitHub default setup, and removed duplicate local PR security and release-preflight runs.
- Pinned npm `10.9.9` as the approved lockfile generator, activated it through Node-bundled Corepack before dependency consumption, and fail closed unless its bundled `tar` is at least `7.5.19`; primary CI still consumes the committed lock only through frozen `npm ci` validation, rejects mutable npm resolution in the lock gate, requires integrity evidence for public-registry lock entries, and preserves generator-sensitive root `@esbuild/*` peer metadata.

Expand Down
97 changes: 51 additions & 46 deletions services/analysis-engine/src/bandscope_analysis/exports/chart.py
Original file line number Diff line number Diff line change
Expand Up @@ -73,19 +73,19 @@ def _section_roles(section: Mapping[str, object]) -> list[Mapping[str, object]]:
return [role for role in roles if isinstance(role, Mapping)]


def _active_role_ids(section: Mapping[str, object]) -> list[str] | None:
def _active_role_ids(section_record: Mapping[str, object]) -> list[str] | None:
"""Return active role ids from the part graph, or ``None`` when absent."""
part_graph = section.get("partGraph")
if not isinstance(part_graph, list):
part_graph_nodes = section_record.get("partGraph")
if not isinstance(part_graph_nodes, list):
return None
active: list[str] = []
for node in part_graph:
if not isinstance(node, Mapping) or node.get("is_active") is not True:
active_role_ids_by_value: dict[str, None] = {}
for part_graph_node in part_graph_nodes:
if not isinstance(part_graph_node, Mapping) or part_graph_node.get("is_active") is not True:
continue
role_id = node.get("role_id")
if isinstance(role_id, str) and role_id and role_id not in active:
active.append(role_id)
return active
role_identifier = part_graph_node.get("role_id")
if isinstance(role_identifier, str) and role_identifier:
active_role_ids_by_value[role_identifier] = None
return list(active_role_ids_by_value)


def _active_roles(section: Mapping[str, object]) -> list[Mapping[str, object]]:
Expand Down Expand Up @@ -119,27 +119,27 @@ def _role_display_name(role: Mapping[str, object]) -> str | None:
return None


def _active_role_names(section: Mapping[str, object]) -> list[str]:
def _active_role_names(section_record: Mapping[str, object]) -> list[str]:
"""Return de-duplicated display names for the section's active roles."""
names: list[str] = []
for role in _active_roles(section):
name = _role_display_name(role)
if name is not None and name not in names:
names.append(name)
return names
active_role_names_by_value: dict[str, None] = {}
for role_record in _active_roles(section_record):
role_display_name = _role_display_name(role_record)
if role_display_name is not None:
active_role_names_by_value[role_display_name] = None
return list(active_role_names_by_value)


def _section_cue(section: Mapping[str, object]) -> str:
def _section_cue(section_record: Mapping[str, object]) -> str:
"""Join the active roles' cue values into a single cue string."""
cues: list[str] = []
for role in _active_roles(section):
cue = role.get("cue")
if not isinstance(cue, Mapping):
section_cues_by_value: dict[str, None] = {}
for role_record in _active_roles(section_record):
role_cue_record = role_record.get("cue")
if not isinstance(role_cue_record, Mapping):
continue
value = cue.get("value")
if isinstance(value, str) and value and value not in cues:
cues.append(value)
return "; ".join(cues)
cue_text = role_cue_record.get("value")
if isinstance(cue_text, str) and cue_text:
section_cues_by_value[cue_text] = None
return "; ".join(section_cues_by_value)


def _confidence_level(section: Mapping[str, object]) -> str | None:
Expand Down Expand Up @@ -185,28 +185,33 @@ def _section_lines(sections: list[Mapping[str, object]]) -> list[str]:
return lines


def _footer_lines(song: Mapping[str, object], sections: list[Mapping[str, object]]) -> list[str]:
def _footer_lines(
song_record: Mapping[str, object], section_records: list[Mapping[str, object]]
) -> list[str]:
"""Build the footer: per-role rehearsal priorities and the export focus."""
lines: list[str] = []
priorities: list[str] = []
for section in sections:
for role in _section_roles(section):
name = _role_display_name(role)
priority = role.get("rehearsalPriority")
if name is None or not isinstance(priority, str) or not priority:
footer_lines: list[str] = []
rehearsal_priority_lines_by_value: dict[str, None] = {}
for section_record in section_records:
for role_record in _section_roles(section_record):
role_display_name = _role_display_name(role_record)
rehearsal_priority = role_record.get("rehearsalPriority")
if (
role_display_name is None
or not isinstance(rehearsal_priority, str)
or not rehearsal_priority
):
continue
entry = f" - {name}: {priority}"
if entry not in priorities:
priorities.append(entry)
if priorities:
lines.append("Priorities:")
lines.extend(priorities)
summary = song.get("exportSummary")
if isinstance(summary, Mapping):
headline = summary.get("headline")
if isinstance(headline, str) and headline:
lines.append(f"Focus: {headline}")
return lines
priority_line = f" - {role_display_name}: {rehearsal_priority}"
rehearsal_priority_lines_by_value[priority_line] = None
if rehearsal_priority_lines_by_value:
footer_lines.append("Priorities:")
footer_lines.extend(rehearsal_priority_lines_by_value)
export_summary = song_record.get("exportSummary")
if isinstance(export_summary, Mapping):
focus_headline = export_summary.get("headline")
if isinstance(focus_headline, str) and focus_headline:
footer_lines.append(f"Focus: {focus_headline}")
return footer_lines


def build_chart_text(song: Mapping[str, object] | None) -> str:
Expand Down
103 changes: 103 additions & 0 deletions services/analysis-engine/tests/benchmark_chart_export.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
"""Measure chart-export runtime and traced allocation on a realistic song fixture."""

import statistics
import time
import tracemalloc

from bandscope_analysis.exports.chart import build_chart_text, build_cue_sheet_rows


def make_large_song_fixture(
section_count: int = 96, roles_per_section: int = 24
) -> dict[str, object]:
"""Build a realistic large-song export fixture for benchmarking."""
song_sections: list[dict[str, object]] = []
for section_index in range(section_count):
section_roles: list[dict[str, object]] = []
part_graph_nodes: list[dict[str, object]] = []
for role_index in range(roles_per_section):
role_identifier = f"role_{role_index % 5}"
section_roles.append(
{
"id": role_identifier,
"name": f"Role Name {role_identifier}",
"cue": {"value": f"Cue {role_index % 4}"},
"rehearsalPriority": f"Priority {role_index % 2}",
}
)
part_graph_nodes.append({"role_id": role_identifier, "is_active": True})

song_sections.append(
{
"label": f"Section {section_index}",
"timeRange": {
"start": section_index * 10,
"end": section_index * 10 + 5,
},
"roles": section_roles,
"partGraph": part_graph_nodes,
"confidence": {"level": "high"},
}
)

return {
"title": "Benchmark Large Song",
"bpm": 120,
"key": "C major",
"feel": "Straight",
"sections": song_sections,
"exportSummary": {"headline": "Benchmark"},
}


def chart_export_benchmark() -> None:
"""Print runtime and traced peak allocation for repeated chart exports."""
benchmark_song = make_large_song_fixture()

for _warmup_iteration in range(100):
build_chart_text(benchmark_song)
build_cue_sheet_rows(benchmark_song)

print("Running Latency Benchmark...")

# Phase 1: Pure Latency (no tracemalloc overhead)
benchmark_iteration_count = 1000
benchmark_sample_durations_seconds: list[float] = []
for _benchmark_iteration in range(benchmark_iteration_count):
benchmark_sample_started_at = time.perf_counter()
build_chart_text(benchmark_song)
build_cue_sheet_rows(benchmark_song)
benchmark_sample_finished_at = time.perf_counter()
benchmark_sample_durations_seconds.append(
benchmark_sample_finished_at - benchmark_sample_started_at
)

print("Running Allocation Benchmark...")
# Phase 2: Pure Allocation (no timing structures)
tracemalloc.start()

allocation_iteration_count = 10
for _allocation_iteration in range(allocation_iteration_count):
build_chart_text(benchmark_song)
build_cue_sheet_rows(benchmark_song)

_current_allocation_bytes, peak_allocation_bytes = tracemalloc.get_traced_memory()
tracemalloc.stop()

total_duration_seconds = sum(benchmark_sample_durations_seconds)
median_duration_seconds = statistics.median(benchmark_sample_durations_seconds)
p95_duration_seconds = statistics.quantiles(
benchmark_sample_durations_seconds, n=100, method="inclusive"
)[94]
print(f"Total time for {benchmark_iteration_count} iterations: {total_duration_seconds:.4f}s")
print(
"Average time per iteration: "
f"{(total_duration_seconds / benchmark_iteration_count) * 1000:.2f}ms"
)
print(f"Median time per sample: {median_duration_seconds * 1000:.2f}ms")
print(f"P95 time per sample: {p95_duration_seconds * 1000:.2f}ms")
print(f"Peak memory overhead: {peak_allocation_bytes / 1024 / 1024:.2f} MB")


if __name__ == "__main__":
chart_export_benchmark()
Loading
Loading