planning: add Qt to web interface migration plan#9740
planning: add Qt to web interface migration plan#9740oharboe wants to merge 1 commit intoThe-OpenROAD-Project:masterfrom
Conversation
Add migration plan document, Gantt charts, resource allocation charts, and Python script for chart generation. Covers framework analysis (FastAPI + React), architecture design, team structure, milestones, and comparison of classic vs AI-assisted development scenarios. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
|
clang-tidy review says "All clean, LGTM! 👍" |
There was a problem hiding this comment.
Code Review
This pull request introduces a comprehensive migration plan for moving from a Qt-based GUI to a web interface. The plan is well-documented in the markdown file, covering architecture, scenarios, and risks. The Python script for generating Gantt and resource charts is a great addition for visualizing the plan.
My review focuses on the Python script, where I've found some significant data inconsistencies in the resource allocation chart generation that should be addressed to ensure the planning artifacts are accurate. I've also included a suggestion to improve the maintainability of the plotting code.
| def create_resource_chart(output_file="resource_chart.png"): | ||
| """Create resource allocation charts for both scenarios.""" | ||
| fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(18, 7)) | ||
| fig.patch.set_facecolor('#FAFAFA') | ||
|
|
||
| roles = ["Tech Lead", "Backend", "Frontend", "DevOps", "QA", "UX/Docs"] | ||
| colors = ["#2C3E50", "#4A90D9", "#50C878", "#9B59B6", "#E74C3C", "#F5A623"] | ||
|
|
||
| # Classic scenario | ||
| phases_classic = ["Found.\n(M1-3)", "MVP\n(M4-7)", "Feat.\n(M8-11)", | ||
| "Test\n(M12-14)", "Deploy\n(M15-17)", "Retire\n(M18-20)"] | ||
| data_classic = np.array([ | ||
| [1.0, 1.0, 0.5, 0.5, 0.5, 0.25], | ||
| [2.0, 2.0, 1.5, 1.0, 1.0, 0.5], | ||
| [1.0, 3.0, 3.0, 1.0, 1.0, 0.5], | ||
| [0.5, 0.5, 0.5, 1.0, 1.0, 0.5], | ||
| [0.0, 0.5, 0.5, 1.0, 0.5, 0.25], | ||
| [0.5, 0.5, 0.5, 0.25, 0.5, 0.25], | ||
| ]) | ||
|
|
||
| # AI-assisted scenario | ||
| phases_ai = ["Found.\n(M1-2)", "MVP\n(M3-5)", "Feat.\n(M5-8)", | ||
| "Test\n(M8-10)", "Deploy\n(M10-12)", "Retire\n(M12-13)"] | ||
| data_ai = np.array([ | ||
| [1.0, 1.0, 0.5, 0.5, 0.5, 0.25], | ||
| [1.0, 1.0, 1.0, 0.5, 0.5, 0.25], | ||
| [1.0, 2.0, 2.0, 0.5, 0.5, 0.25], | ||
| [0.5, 0.5, 0.5, 1.0, 1.0, 0.5], | ||
| [0.0, 0.5, 0.5, 1.0, 0.5, 0.25], | ||
| [0.5, 0.5, 0.5, 0.25, 0.5, 0.25], | ||
| ]) | ||
|
|
||
| for ax, data, phases, title in [ | ||
| (ax1, data_classic, phases_classic, "Classic Development (9 FTE peak)"), | ||
| (ax2, data_ai, phases_ai, "AI-Assisted / Claude (6 FTE peak)") | ||
| ]: | ||
| ax.set_facecolor('#FAFAFA') | ||
| x = np.arange(len(phases)) | ||
| width = 0.12 | ||
| offsets = np.arange(len(roles)) - (len(roles) - 1) / 2 | ||
|
|
||
| for i, (role, color) in enumerate(zip(roles, colors)): | ||
| bars = ax.bar(x + offsets[i] * width, data[i], width * 0.9, | ||
| label=role, color=color, alpha=0.85, edgecolor='white', linewidth=0.5) | ||
| for bar, val in zip(bars, data[i]): | ||
| if val > 0: | ||
| ax.text(bar.get_x() + bar.get_width() / 2, bar.get_height() + 0.02, | ||
| f'{val:.0f}' if val == int(val) else f'{val:.1f}', | ||
| ha='center', va='bottom', fontsize=6, color='#555') | ||
|
|
||
| totals = data.sum(axis=0) | ||
| ax.plot(x, totals, 'ko-', linewidth=2, markersize=7, label='Total FTE', zorder=5) | ||
| for xi, total in zip(x, totals): | ||
| ax.text(xi, total + 0.12, f'{total:.1f}', ha='center', fontsize=8, | ||
| fontweight='bold', color='#333') | ||
|
|
||
| ax.set_ylabel('FTE', fontsize=10) | ||
| ax.set_title(title, fontsize=11, fontweight='bold', color='#2C3E50') | ||
| ax.set_xticks(x) | ||
| ax.set_xticklabels(phases, fontsize=8) | ||
| ax.legend(loc='upper right', fontsize=7, ncol=2, framealpha=0.9) | ||
| ax.set_ylim(0, max(totals) + 1.5) | ||
| ax.grid(axis='y', alpha=0.3) | ||
|
|
||
| fig.suptitle('Resource Allocation Comparison', fontsize=14, fontweight='bold', | ||
| color='#2C3E50', y=1.02) | ||
| plt.tight_layout() | ||
| plt.savefig(output_file, dpi=200, bbox_inches='tight', | ||
| facecolor=fig.get_facecolor()) | ||
| plt.close() | ||
| print(f"Resource chart saved to: {output_file}") | ||
|
|
There was a problem hiding this comment.
There are several inconsistencies in the data used to generate the resource allocation chart, which makes the resulting chart misleading.
-
Incorrect Peak FTE:
- For the "Classic" scenario, the data results in a peak of 7.5 FTE, but comments and the markdown plan state a peak of 9 FTE.
- For the "AI-Assisted" scenario, the data results in a peak of 5.5 FTE, but the plan states 6 FTE.
-
Inconsistent Person-Months:
- The total effort calculated from this data is ~106 person-months for the classic scenario and ~55 for the AI-assisted one. This contradicts the
~155and~68person-months mentioned elsewhere in this file and in the markdown plan.
- The total effort calculated from this data is ~106 person-months for the classic scenario and ~55 for the AI-assisted one. This contradicts the
-
Incorrect Phase Durations for AI Scenario: The labels in
phases_ai(e.g., "Feat.\n(M5-8)") do not match the actual phase durations from the Gantt chart data (e.g., Feature Parity is from month 5 to 7.5). This makes the x-axis of the AI resource chart incorrect. -
Likely Data Error: The FTE allocations for "Tech Lead", "DevOps", "QA", and "UX/Docs" are identical in both
data_classicanddata_ai. This seems unlikely given the overall team size reduction and is probably a copy-paste error.
Please review and correct the data in data_classic and data_ai arrays and the phases_ai labels to ensure all generated charts are consistent with the overall project plan.
| def draw_gantt(ax, tasks, start_date, total_months, title, subtitle, team_text): | ||
| """Draw a single Gantt chart on the given axes.""" | ||
| bar_height = 0.55 | ||
|
|
||
| for i, (name, phase, start_m, duration, is_milestone) in enumerate(tasks): | ||
| y_pos = len(tasks) - 1 - i | ||
| color = PHASE_COLORS[phase] | ||
|
|
||
| if is_milestone: | ||
| ax.plot(start_m, y_pos, marker='D', markersize=12, color=color, | ||
| markeredgecolor='white', markeredgewidth=2, zorder=5) | ||
| else: | ||
| bar = FancyBboxPatch( | ||
| (start_m, y_pos - bar_height / 2), | ||
| duration, bar_height, | ||
| boxstyle="round,pad=0.05", | ||
| facecolor=color, edgecolor='white', | ||
| linewidth=1.5, alpha=0.85, zorder=3 | ||
| ) | ||
| ax.add_patch(bar) | ||
| if duration >= 0.8: | ||
| ax.text(start_m + duration / 2, y_pos, f"{duration:.1f}m", | ||
| ha='center', va='center', fontsize=6.5, | ||
| color='white', fontweight='bold', zorder=4) |
There was a problem hiding this comment.
This function contains many hardcoded styling values (e.g., bar_height, markersize, fontsize, colors). To improve maintainability and make it easier to adjust the chart's appearance, consider grouping these values into a configuration dictionary or defining them as constants at the top of the script.
For example:
GANTT_STYLE = {
'bar_height': 0.55,
'milestone_marker': 'D',
'milestone_size': 12,
'bar_style': 'round,pad=0.05',
'bar_alpha': 0.85,
'text_color': 'white',
'font_size_bar': 6.5,
'font_size_task': 7.5,
# ... and so on
}
def draw_gantt(...):
bar_height = GANTT_STYLE['bar_height']
...
ax.plot(..., marker=GANTT_STYLE['milestone_marker'], markersize=GANTT_STYLE['milestone_size'], ...)
...This would make the plotting logic cleaner and styling more centralized.
Add migration plan document, Gantt charts, resource allocation charts, and Python script for chart generation. Covers framework analysis (FastAPI + React), architecture design, team structure, milestones, and comparison of classic vs AI-assisted development scenarios.