Ray1ee01 commited on
Commit
439e630
·
verified ·
1 Parent(s): 216c0a4

Upload folder using huggingface_hub

Browse files
__init__.py ADDED
File without changes
agent.md ADDED
@@ -0,0 +1,60 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Agent Notes
2
+
3
+ - Use the `edit` conda environment for ChartPipeline commands:
4
+ `/data/wangzheng/miniconda3/envs/edit/bin/python`.
5
+ - When invoking Python commands directly, prefer:
6
+ `PYTHONPATH=. /data/wangzheng/miniconda3/envs/edit/bin/python ...`.
7
+ - For rendering, set:
8
+ `PUPPETEER_EXECUTABLE_PATH=/usr/bin/google-chrome` and `RENDER_LONGEST_SIDE=1600`.
9
+ - For full pipeline runs in a non-login shell, include both runtime dirs in PATH:
10
+ `export PATH=/data/wangzheng/miniconda3/envs/edit/bin:/data/wangzheng/miniconda3/envs/edit/lib/python3.12/site-packages/playwright/driver:$PATH`.
11
+ - The post-generation icon/image module is `modules.asset_regenerator.asset_regenerator`.
12
+ It runs after `infographics_generator`, builds one contact sheet containing the full infographic
13
+ plus all selected SVG `<image>` assets, calls `gpt-image-2`, chroma-keys the generated cells,
14
+ and replaces only the original SVG `<image href>` / `xlink:href` values with data URI PNGs.
15
+ - During variation repair, do not call `gpt-image-2` while iterating on template/data fixes.
16
+ First regenerate with `--modules infographics_generator` only, or run asset regeneration with
17
+ `--dry-run` if candidate discovery needs checking. Run the real `asset_regenerator` only after
18
+ the template-side repair passes structural/rendered validation.
19
+ - Keep before/after evidence for each repaired variation. Preserve the original sample SVG/PNG or
20
+ render a baseline from the unmodified template, then store side-by-side comparison artifacts
21
+ under an `output/chart_repair_comparison_*` directory.
22
+ - Keep complete code backups for every full variation-repair or style-reference iteration. Before
23
+ editing a variation, create a timestamped backup directory under `backups/` and include every
24
+ variation template plus every support file that may be changed in the iteration, such as docs,
25
+ agent notes, pipeline modules, generator utilities, asset-regenerator code, and helper scripts.
26
+ - Each complete iteration backup must keep three separate trees:
27
+ - `before_head`: the original repair-before version from `HEAD:<path>` for tracked files, or a
28
+ missing marker for files added during repair work.
29
+ - `before`: the exact workspace state immediately before the iteration edits begin.
30
+ - `after`: the exact workspace state after patching, regenerating, verifying, and documenting the
31
+ iteration.
32
+ - Never overwrite an older backup tree. If a file was already modified before the next iteration,
33
+ preserve that pre-edit copy in `before` so the new iteration can be separated from earlier repairs.
34
+ For untracked support files, write a missing marker in `before_head`, copy the pre-edit file into
35
+ `before`, and copy the final file into `after`.
36
+ - Every backup directory must include `files.tsv`, `manifest.md`, pre/post `git status --short`,
37
+ relevant `git diff --stat`, generated output paths, comparison evidence paths, verification
38
+ evidence paths, and whether `gpt-image-2` or `asset_regenerator` was called. The manifest must make
39
+ clear which tree is the repair-before code and which tree is the current/final code.
40
+ - Run it in the pipeline with:
41
+ `--modules infographics_generator asset_regenerator`.
42
+ - To test without calling the image model:
43
+ `PYTHONPATH=. /data/wangzheng/miniconda3/envs/edit/bin/python -m modules.asset_regenerator.asset_regenerator --svg <final.svg> --png <final.png> --dry-run`.
44
+ - The image edit call defaults to `--model gpt-image-2 --input-fidelity high` so the model sees
45
+ the rendered infographic as context while only regenerating the green contact-sheet asset cells.
46
+ - Keep this module scoped to decorative icon/image assets. Do not use it to raster-overwrite the
47
+ chart, title, labels, axes, legends, data values, chart marks, or background unless explicitly
48
+ passing `--include-background` for a controlled experiment.
49
+ - For style-reference iterations, compare the current baseline for a variation against existing
50
+ PNGs under `../chart_template_samples_20260527_083945/gpt_image_2_improved/<variation>`. If the
51
+ variation was repaired in `docs/variation_repair_status.md`, use the repaired output as the
52
+ baseline; otherwise use `../data/all_variations_check/template_variations_20260527/<variation>`.
53
+ Choose one simple, readable, low-decoration GPT-improved reference as the style anchor, but do
54
+ not copy its chart geometry or numeric values when they conflict with source data.
55
+ - During style-reference iterations, do not call `gpt-image-2`; existing GPT-improved PNGs are
56
+ references only. Keep title/icon/image generation on the existing pipeline path for now.
57
+ - For every style-reference iteration, keep a timestamped code backup under `backups/`, regenerate
58
+ 5 outputs, write a baseline/reference/after comparison under `output/style_ref_comparison_*`,
59
+ verify chart type and data mappings structurally, and record the iteration in
60
+ `docs/style_reference_repair_status.md`.
allowed_chart_types.json ADDED
@@ -0,0 +1,17 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "chart_types": [
3
+ "donut chart",
4
+ "pie chart",
5
+ "multiple donut chart",
6
+ "multiple pie chart",
7
+ "line graph",
8
+ "multiple line graph",
9
+ "spline graph",
10
+ "multiple spline graph",
11
+ "step line graph",
12
+ "stepped line graph",
13
+ "multiple step line graph",
14
+ "sorted lines chart",
15
+ "slope chart"
16
+ ]
17
+ }
allowed_chart_types.json.example ADDED
@@ -0,0 +1,19 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "_comment": "Whitelist of chart_type names the infographics_generator is allowed to pick from.",
3
+ "_usage": [
4
+ "1) Copy this file to allowed_chart_types.json (same directory), OR",
5
+ " set ALLOWED_CHART_TYPES_FILE=<path> to point at a custom location.",
6
+ "2) Edit the 'chart_types' list below to the chart_type values you care about.",
7
+ "3) Empty list / missing file means 'no filtering' (legacy behaviour).",
8
+ "Granularity is chart_type (NOT chart_name). All chart_name variations whose",
9
+ "directory lives under the listed chart_type folder will become selectable.",
10
+ "Run `awk -F' / ' '{print $1}' template_list.txt | sort -u` to see the full set."
11
+ ],
12
+ "chart_types": [
13
+ "bump chart",
14
+ "bar chart",
15
+ "line graph",
16
+ "bubble chart",
17
+ "scatterplot"
18
+ ]
19
+ }
batch_run.py ADDED
@@ -0,0 +1,28 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import json
2
+ import subprocess
3
+ from multiprocessing import Pool
4
+ from tqdm import tqdm
5
+
6
+ def run_command(item):
7
+ data_source = item["data_source"]
8
+ chart_variation = item["chart_variation"]
9
+
10
+ cmd = [
11
+ "python", "pipeline.py",
12
+ "--modules", "infographics_generator",
13
+ "--input", data_source,
14
+ "--output", "./test_output_new_new",
15
+ "--chart-name", chart_variation
16
+ ]
17
+
18
+ subprocess.run(cmd)
19
+ return item
20
+
21
+ start_id = 0
22
+
23
+ with open("extracted_info.json", "r") as f:
24
+ data = json.load(f)[start_id:start_id+1]
25
+
26
+ with Pool(processes=8) as pool:
27
+ list(tqdm(pool.imap(run_command, data), total=len(data)))
28
+
extracted_info.json ADDED
The diff for this file is too large to render. See raw diff
 
passed_templates.txt ADDED
@@ -0,0 +1,49 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ bump_plain_chart_02
2
+ bump_plain_chart_03
3
+ circular_bar_chart_01
4
+ circular_bar_chart_03
5
+ circular_bar_chart_04
6
+ circular_bar_chart_icons_01
7
+ circular_bar_chart_icons_02
8
+ horizontal_bar_chart_0
9
+ horizontal_bar_chart_01
10
+ horizontal_bar_chart_0_weilai
11
+ horizontal_bar_chart_1
12
+ horizontal_bar_chart_10
13
+ horizontal_bar_chart_11
14
+ horizontal_bar_chart_12
15
+ horizontal_diverging_bar_chart_05
16
+ horizontal_diverging_bar_chart_07
17
+ horizontal_diverging_bar_chart_08
18
+ horizontal_diverging_bar_chart_10
19
+ horizontal_diverging_bar_chart_11
20
+ horizontal_diverging_bar_chart_12
21
+ line_graph_plain_chart_03
22
+ lollipop_icon_chart_01
23
+ lollipop_icon_chart_02
24
+ lollipop_plain_chart_01
25
+ multiple_gauge_chart_01
26
+ multiple_gauge_chart_02
27
+ multiple_gauge_chart_03
28
+ multiple_gauge_chart_03_hand
29
+ multiple_line_graph_01
30
+ proportional_area_chart_hexagon_01
31
+ proportional_area_chart_hexagon_02
32
+ proportional_area_chart_other_01
33
+ proportional_area_chart_pentagram_01
34
+ range_area_chart_icons_01
35
+ range_area_plain_chart_01
36
+ rose_chart_01
37
+ rose_chart_new_00
38
+ rose_chart_new_00_hand
39
+ rose_plain_chart_01
40
+ stacked_area_chart_01
41
+ stacked_area_chart_02
42
+ stacked_area_chart_03
43
+ stacked_area_chart_icons_01
44
+ stacked_area_plain_chart_02
45
+ vertical_group_bar_chart_12
46
+ vertical_group_bar_chart_13
47
+ vertical_group_bar_chart_14
48
+ vertical_group_bar_chart_15
49
+ vertical_group_bar_chart_16
passed_templates_audit.csv ADDED
@@ -0,0 +1,51 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ variation,final_status,final_stage,final_worker,final_line,pass_line,pass_stage,pass_worker,worker_verdict_is_provisional,controller_fresh_review_required,controller_review_required,review_report
2
+ bump_plain_chart_02,pass,controller_fresh_review_round6,controller,51,51,controller_fresh_review_round6,controller,,False,,data/output/variation_skill_runs/bump_plain_chart_02/20260603_220532/variation_review_round6_controller_fresh_pass.md
3
+ bump_plain_chart_03,pass,controller_fresh_review_round4,controller,124,124,controller_fresh_review_round4,controller,,False,,data/output/variation_skill_runs/bump_plain_chart_03/20260603_224758/variation_review_round4_controller_fresh_pass.md
4
+ circular_bar_chart_01,pass,controller_fresh_pass_round14,,277,277,controller_fresh_pass_round14,,,False,False,data/output/variation_skill_runs/circular_bar_chart_01/20260603_231601/variation_review_round14_controller_fresh_pass.md
5
+ circular_bar_chart_03,pass,controller_fresh_pass_round32,controller,542,542,controller_fresh_pass_round32,controller,,,,
6
+ circular_bar_chart_04,pass,controller_fresh_pass_round8,controller,580,580,controller_fresh_pass_round8,controller,,,,
7
+ circular_bar_chart_icons_01,pass,controller_fresh_pass_round5,controller,667,667,controller_fresh_pass_round5,controller,,,,
8
+ circular_bar_chart_icons_02,pass,worker_final_round5_provisional,session_01,724,724,worker_final_round5_provisional,session_01,,True,,
9
+ horizontal_bar_chart_0,pass,controller_fresh_review_round7,controller,60,60,controller_fresh_review_round7,controller,,False,,data/output/variation_skill_runs/horizontal_bar_chart_0/20260603_215905/variation_review_round7_controller_fresh_pass.md
10
+ horizontal_bar_chart_01,pass,,,192,192,,,True,True,True,data/output/variation_skill_runs/horizontal_bar_chart_01/20260603_225157/variation_review_round6.md
11
+ horizontal_bar_chart_0_weilai,pass,controller_fresh_pass_round2,,299,299,controller_fresh_pass_round2,,,False,False,data/output/variation_skill_runs/horizontal_bar_chart_0_weilai/20260603_235404/variation_review_round2_controller_fresh_pass.md
12
+ horizontal_bar_chart_1,pass,controller_fresh_pass_round6,session_02,408,408,controller_fresh_pass_round6,session_02,,False,,
13
+ horizontal_bar_chart_10,pass,controller_fresh_pass_round14,controller,534,534,controller_fresh_pass_round14,controller,,,,
14
+ horizontal_bar_chart_11,pass,controller_fresh_pass_round7,controller,581,581,controller_fresh_pass_round7,controller,,,,
15
+ horizontal_bar_chart_12,pass,worker_final_round12_provisional,session_02,688,688,worker_final_round12_provisional,session_02,,True,,data/output/variation_skill_runs/horizontal_bar_chart_12/20260604_051705/variation_review_round12.md
16
+ horizontal_diverging_bar_chart_05,pass,worker_provisional_pass_round16_controller_review_required,session_03,218,218,worker_provisional_pass_round16_controller_review_required,session_03,,True,True,data/output/variation_skill_runs/horizontal_diverging_bar_chart_05/20260603_202638/variation_review_round16.md
17
+ horizontal_diverging_bar_chart_07,pass,controller_fresh_pass_round12,session_03,388,388,controller_fresh_pass_round12,session_03,,False,,
18
+ horizontal_diverging_bar_chart_08,pass,controller_fresh_pass_round12,controller,522,522,controller_fresh_pass_round12,controller,,,,
19
+ horizontal_diverging_bar_chart_10,pass,worker_provisional_pass_round6_controller_required,session_03,576,576,worker_provisional_pass_round6_controller_required,session_03,True,True,,data/output/variation_skill_runs/horizontal_diverging_bar_chart_10/20260604_033039/variation_review_round6.md
20
+ horizontal_diverging_bar_chart_11,pass,controller_fresh_pass_round8,controller,652,652,controller_fresh_pass_round8,controller,,,,
21
+ horizontal_diverging_bar_chart_12,pass,worker_final_round5_provisional,session_03,705,705,worker_final_round5_provisional,session_03,,True,,data/output/variation_skill_runs/horizontal_diverging_bar_chart_12/20260604_062826/variation_review_round5.md
22
+ line_graph_plain_chart_03,pass,controller_fresh_pass_round2,controller,546,546,controller_fresh_pass_round2,controller,,,,
23
+ lollipop_icon_chart_01,pass,worker_provisional_pass_round11_controller_required_after_round8_fail,session_04,587,587,worker_provisional_pass_round11_controller_required_after_round8_fail,session_04,True,True,,data/output/variation_skill_runs/lollipop_icon_chart_01/20260604_040357/variation_review_round11.md
24
+ lollipop_icon_chart_02,pass,controller_fresh_pass_round7,controller,638,638,controller_fresh_pass_round7,controller,,,,
25
+ lollipop_plain_chart_01,pass,final_worker_record_round8_provisional,session_04,723,723,final_worker_record_round8_provisional,session_04,,True,,data/output/variation_skill_runs/lollipop_plain_chart_01/20260604_061905/variation_review_round8.md
26
+ multiple_gauge_chart_01,pass,controller_fresh_review_round7,controller,37,37,controller_fresh_review_round7,controller,,False,,data/output/variation_skill_runs/multiple_gauge_chart_01/20260603_214437/variation_review_round7_controller_fresh_pass.md
27
+ multiple_gauge_chart_02,pass,controller_update_endpoint_corner_modifier_notes_confirmed,session_04,161,161,controller_update_endpoint_corner_modifier_notes_confirmed,session_04,True,True,True,data/output/variation_skill_runs/multiple_gauge_chart_02/20260603_224205/variation_review_round12.md
28
+ multiple_gauge_chart_03,pass,controller_fresh_pass_round10,session_04,392,392,controller_fresh_pass_round10,session_04,,False,,
29
+ multiple_gauge_chart_03_hand,pass,controller_fresh_pass_round4,controller,438,438,controller_fresh_pass_round4,controller,,,,
30
+ multiple_line_graph_01,pass,controller_fresh_pass_round7,controller,485,485,controller_fresh_pass_round7,controller,,,,
31
+ proportional_area_chart_hexagon_01,pass,controller_fresh_pass_round16,,306,306,controller_fresh_pass_round16,,,False,False,data/output/variation_skill_runs/proportional_area_chart_hexagon_01/20260603_221214/variation_review_round16_controller_fresh_pass.md
32
+ proportional_area_chart_hexagon_02,pass,controller_fresh_pass_round13,controller,448,448,controller_fresh_pass_round13,controller,,,,
33
+ proportional_area_chart_other_01,pass,controller_fresh_pass_round34,controller,627,627,controller_fresh_pass_round34,controller,,,,
34
+ proportional_area_chart_pentagram_01,pass,final_worker_record_round5,session_05,713,713,final_worker_record_round5,session_05,,True,,data/output/variation_skill_runs/proportional_area_chart_pentagram_01/20260604_060611/variation_review_round5.md
35
+ range_area_chart_icons_01,pass,controller_fresh_review_round6,controller,129,129,controller_fresh_review_round6,controller,,False,,data/output/variation_skill_runs/range_area_chart_icons_01/20260603_222013/variation_review_round6_controller_fresh_pass.md
36
+ range_area_plain_chart_01,pass,controller_fresh_pass_round10,,331,331,controller_fresh_pass_round10,,,False,False,data/output/variation_skill_runs/range_area_plain_chart_01/20260603_232034/variation_review_round10_controller_fresh_pass.md
37
+ rose_chart_01,pass,controller_fresh_pass_round6_after_worker_provisional_pass,controller,428,428,controller_fresh_pass_round6_after_worker_provisional_pass,controller,,,,
38
+ rose_chart_new_00,pass,controller_fresh_pass_round14,controller,544,544,controller_fresh_pass_round14,controller,,,,
39
+ rose_chart_new_00_hand,pass,worker_provisional_pass_round10_controller_required,session_06,588,588,worker_provisional_pass_round10_controller_required,session_06,True,True,,data/output/variation_skill_runs/rose_chart_new_00_hand/20260604_035901/variation_review_round10.md
40
+ rose_plain_chart_01,pass,final_worker_record_round9_provisional,session_06,686,686,final_worker_record_round9_provisional,session_06,,True,,data/output/variation_skill_runs/rose_plain_chart_01/20260604_052842/variation_review_round9.md
41
+ stacked_area_chart_01,pass,controller_fresh_review_round8,controller,79,79,controller_fresh_review_round8,controller,,False,,data/output/variation_skill_runs/stacked_area_chart_01/20260603_213619/variation_review_round8_controller_fresh_pass.md
42
+ stacked_area_chart_02,pass,completion_worker_provisional_pass_controller_required_round8,session_07,180,180,completion_worker_provisional_pass_controller_required_round8,session_07,True,True,True,data/output/variation_skill_runs/stacked_area_chart_02/20260603_225749/variation_review_round8.md
43
+ stacked_area_chart_03,pass,controller_fresh_pass_round15,controller,454,454,controller_fresh_pass_round15,controller,,,,
44
+ stacked_area_chart_icons_01,pass,variation_review_round7,,563,563,variation_review_round7,,,True,,data/output/variation_skill_runs/stacked_area_chart_icons_01/20260604_024630/variation_review_round7.md
45
+ stacked_area_plain_chart_01,worker_provisional_pass_round13_controller_required,final_worker_record,session_07,687,680,controller_fresh_pass_round13,controller,,,,
46
+ stacked_area_plain_chart_02,pass,,session_07,725,725,,session_07,,True,,data/output/variation_skill_runs/stacked_area_plain_chart_02/20260604_070936/variation_review_round3.md
47
+ vertical_group_bar_chart_12,pass,worker_provisional_pass_round13_controller_fresh_review_required,session_08,236,236,worker_provisional_pass_round13_controller_fresh_review_required,session_08,,True,True,data/output/variation_skill_runs/vertical_group_bar_chart_12/20260603_215309/variation_review_round13.md
48
+ vertical_group_bar_chart_13,pass,controller_fresh_pass_round10_after_worker_provisional_pass,controller,424,424,controller_fresh_pass_round10_after_worker_provisional_pass,controller,,,,
49
+ vertical_group_bar_chart_14,pass,controller_fresh_pass_round4,controller,518,518,controller_fresh_pass_round4,controller,,,,
50
+ vertical_group_bar_chart_15,pass,controller_fresh_pass_round5,controller,606,606,controller_fresh_pass_round5,controller,,,,
51
+ vertical_group_bar_chart_16,pass,final_worker_record_round10_provisional,session_08,696,696,final_worker_record_round10_provisional,session_08,,True,,data/output/variation_skill_runs/vertical_group_bar_chart_16/20260604_053941/variation_review_round10.md
passed_templates_ever_pass.txt ADDED
@@ -0,0 +1,50 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ bump_plain_chart_02
2
+ bump_plain_chart_03
3
+ circular_bar_chart_01
4
+ circular_bar_chart_03
5
+ circular_bar_chart_04
6
+ circular_bar_chart_icons_01
7
+ circular_bar_chart_icons_02
8
+ horizontal_bar_chart_0
9
+ horizontal_bar_chart_01
10
+ horizontal_bar_chart_0_weilai
11
+ horizontal_bar_chart_1
12
+ horizontal_bar_chart_10
13
+ horizontal_bar_chart_11
14
+ horizontal_bar_chart_12
15
+ horizontal_diverging_bar_chart_05
16
+ horizontal_diverging_bar_chart_07
17
+ horizontal_diverging_bar_chart_08
18
+ horizontal_diverging_bar_chart_10
19
+ horizontal_diverging_bar_chart_11
20
+ horizontal_diverging_bar_chart_12
21
+ line_graph_plain_chart_03
22
+ lollipop_icon_chart_01
23
+ lollipop_icon_chart_02
24
+ lollipop_plain_chart_01
25
+ multiple_gauge_chart_01
26
+ multiple_gauge_chart_02
27
+ multiple_gauge_chart_03
28
+ multiple_gauge_chart_03_hand
29
+ multiple_line_graph_01
30
+ proportional_area_chart_hexagon_01
31
+ proportional_area_chart_hexagon_02
32
+ proportional_area_chart_other_01
33
+ proportional_area_chart_pentagram_01
34
+ range_area_chart_icons_01
35
+ range_area_plain_chart_01
36
+ rose_chart_01
37
+ rose_chart_new_00
38
+ rose_chart_new_00_hand
39
+ rose_plain_chart_01
40
+ stacked_area_chart_01
41
+ stacked_area_chart_02
42
+ stacked_area_chart_03
43
+ stacked_area_chart_icons_01
44
+ stacked_area_plain_chart_01
45
+ stacked_area_plain_chart_02
46
+ vertical_group_bar_chart_12
47
+ vertical_group_bar_chart_13
48
+ vertical_group_bar_chart_14
49
+ vertical_group_bar_chart_15
50
+ vertical_group_bar_chart_16
passed_templates_final_status_contains_pass.txt ADDED
@@ -0,0 +1,50 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ bump_plain_chart_02
2
+ bump_plain_chart_03
3
+ circular_bar_chart_01
4
+ circular_bar_chart_03
5
+ circular_bar_chart_04
6
+ circular_bar_chart_icons_01
7
+ circular_bar_chart_icons_02
8
+ horizontal_bar_chart_0
9
+ horizontal_bar_chart_01
10
+ horizontal_bar_chart_0_weilai
11
+ horizontal_bar_chart_1
12
+ horizontal_bar_chart_10
13
+ horizontal_bar_chart_11
14
+ horizontal_bar_chart_12
15
+ horizontal_diverging_bar_chart_05
16
+ horizontal_diverging_bar_chart_07
17
+ horizontal_diverging_bar_chart_08
18
+ horizontal_diverging_bar_chart_10
19
+ horizontal_diverging_bar_chart_11
20
+ horizontal_diverging_bar_chart_12
21
+ line_graph_plain_chart_03
22
+ lollipop_icon_chart_01
23
+ lollipop_icon_chart_02
24
+ lollipop_plain_chart_01
25
+ multiple_gauge_chart_01
26
+ multiple_gauge_chart_02
27
+ multiple_gauge_chart_03
28
+ multiple_gauge_chart_03_hand
29
+ multiple_line_graph_01
30
+ proportional_area_chart_hexagon_01
31
+ proportional_area_chart_hexagon_02
32
+ proportional_area_chart_other_01
33
+ proportional_area_chart_pentagram_01
34
+ range_area_chart_icons_01
35
+ range_area_plain_chart_01
36
+ rose_chart_01
37
+ rose_chart_new_00
38
+ rose_chart_new_00_hand
39
+ rose_plain_chart_01
40
+ stacked_area_chart_01
41
+ stacked_area_chart_02
42
+ stacked_area_chart_03
43
+ stacked_area_chart_icons_01
44
+ stacked_area_plain_chart_01
45
+ stacked_area_plain_chart_02
46
+ vertical_group_bar_chart_12
47
+ vertical_group_bar_chart_13
48
+ vertical_group_bar_chart_14
49
+ vertical_group_bar_chart_15
50
+ vertical_group_bar_chart_16
pipeline.py ADDED
@@ -0,0 +1,630 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # -*- coding: utf-8 -*-
2
+
3
+ """
4
+ ChartPipeline: 完整可视化生成管道
5
+ 此脚本执行完整的数据可视化生成流程,依次调用各模块实现从数据到最终图表的转换
6
+ """
7
+
8
+ import os
9
+ import json
10
+ import argparse
11
+ import logging
12
+ import time
13
+ from datetime import datetime
14
+ from importlib import import_module
15
+ from pathlib import Path
16
+ from config import (
17
+ base_url,
18
+ api_key,
19
+ embed_model_path,
20
+ data_resource_path,
21
+ topk,
22
+ text_data_path,
23
+ text_index_path,
24
+ color_data_path,
25
+ color_index_path,
26
+ image_data_path,
27
+ image_index_path,
28
+ image_list_path,
29
+ image_resource_path
30
+ )
31
+ import random
32
+ from concurrent.futures import ProcessPoolExecutor
33
+
34
+ # 配置日志
35
+ logging.basicConfig(
36
+ level=logging.INFO,
37
+ format='%(asctime)s - %(name)s - %(levelname)s - %(message)s'
38
+ )
39
+ logger = logging.getLogger("ChartPipeline")
40
+
41
+ # 模块配置
42
+ MODULES = [
43
+ {
44
+ "name": "create_index",
45
+ "description": "索引创建模块",
46
+ "input_type": "none",
47
+ "output_type": "none"
48
+ },
49
+ {
50
+ "name": "preprocess",
51
+ "description": "数据预处理模块",
52
+ "input_type": "json",
53
+ "output_type": "json"
54
+ },
55
+ {
56
+ "name": "chart_type_recommender",
57
+ "description": "图表类型推荐模块",
58
+ "input_type": "json",
59
+ "output_type": "json"
60
+ },
61
+ {
62
+ "name": "datafact_generator",
63
+ "description": "数据洞察模块",
64
+ "input_type": "json",
65
+ "output_type": "json"
66
+ },
67
+ {
68
+ "name": "title_generator",
69
+ "description": "标题生成模块",
70
+ "input_type": "json",
71
+ "output_type": "json"
72
+ },
73
+ {
74
+ "name": "color_recommender",
75
+ "description": "色彩推荐模块",
76
+ "input_type": "json",
77
+ "output_type": "json"
78
+ },
79
+ {
80
+ "name": "image_recommender",
81
+ "description": "图像推荐模块",
82
+ "input_type": "json",
83
+ "output_type": "json"
84
+ },
85
+ {
86
+ "name": "all",
87
+ "description": "综合模块(datafact+title+color+image)",
88
+ "input_type": "json",
89
+ "output_type": "json"
90
+ },
91
+ {
92
+ "name": "infographics_generator",
93
+ "description": "信息图表生成模块",
94
+ "input_type": "json",
95
+ "output_type": "json"
96
+ },
97
+ {
98
+ "name": "asset_regenerator",
99
+ "description": "图像/图标统一生图后处理模块",
100
+ "input_type": "svg",
101
+ "output_type": "svg"
102
+ },
103
+ {
104
+ "name": "full_image_polisher",
105
+ "description": "整图GPT Image润色后处理模块",
106
+ "input_type": "png",
107
+ "output_type": "png"
108
+ },
109
+ {
110
+ "name": "chart_engine",
111
+ "description": "图表模板实现引擎",
112
+ "input_type": "json",
113
+ "output_type": "svg"
114
+ },
115
+ {
116
+ "name": "title_styler",
117
+ "description": "标题元素生成模块",
118
+ "input_type": "json",
119
+ "output_type": "svg"
120
+ }
121
+ ]
122
+
123
+
124
+ def run_pipeline(input_path, output_path=None, temp_dir=None, modules_to_run=None, threads=None, chart_name=None, chart_only=False, output_png=False):
125
+ """
126
+ 执行完整的图表生成管道
127
+
128
+ Args:
129
+ input_path (str): 输入数据文件路径(可以是文件或目录)
130
+ output_path (str, optional): 输出文件路径(可以是文件或目录),如果为None则原地修改
131
+ temp_dir (str, optional): 临时文件目录,默认使用./tmp
132
+ modules_to_run (list, optional): 要运行的模块列表,默认运行所有模块
133
+ threads (int, optional): 处理目录时的并发线程数,仅在input_path为目录时生效
134
+ chart_name (str, optional): 指定图表名称,仅对infographics_generator模块有效
135
+ chart_only (bool, optional): 只输出 chart SVG(跳过 title/image/layout),仅对 infographics_generator 有效
136
+ output_png (bool, optional): 同时输出 PNG,仅对 infographics_generator 有效
137
+ """
138
+ # try:
139
+ if chart_name is not None:
140
+ output_path = os.path.join(output_path, chart_name)
141
+ import shutil
142
+ shutil.rmtree(output_path, ignore_errors=True)
143
+ os.makedirs(output_path, exist_ok=True)
144
+ print("modules_to_run: ", modules_to_run)
145
+ # 如果是create_index模块,单独处理
146
+ if modules_to_run and 'create_index' in modules_to_run:
147
+ return run_single_file(
148
+ input_path=input_path,
149
+ output_path=output_path,
150
+ temp_dir=temp_dir,
151
+ modules_to_run=modules_to_run,
152
+ chart_name=chart_name,
153
+ chart_only=chart_only,
154
+ output_png=output_png,
155
+ )
156
+
157
+ input_path = Path(input_path)
158
+ output_path = Path(output_path) if output_path else input_path
159
+ temp_dir = Path(temp_dir) if temp_dir else Path("./tmp")
160
+
161
+ if input_path.is_dir():
162
+ # 如果指定了输出目录且不同于输入目录,创建输出目录
163
+ if output_path != input_path:
164
+ output_path.mkdir(parents=True, exist_ok=True)
165
+
166
+ # 获取输入目录下所有JSON文件
167
+ input_files = list(input_path.glob('*.json'))
168
+ random.shuffle(input_files) # 随机打乱文件顺序
169
+
170
+ if threads and threads > 1:
171
+ # 使用线程池并行处理文件
172
+ with ProcessPoolExecutor(max_workers=threads) as executor:
173
+ futures = []
174
+ for input_file in input_files:
175
+ # 如果是inplace处理,输出路径就是输入路径
176
+ if output_path == input_path:
177
+ output_file = input_file
178
+ else:
179
+ # 确保输出文件保持相同的文件名
180
+ output_file = output_path / input_file.name
181
+
182
+ future = executor.submit(
183
+ run_single_file,
184
+ input_path=input_file,
185
+ output_path=output_file,
186
+ temp_dir=temp_dir,
187
+ modules_to_run=modules_to_run,
188
+ chart_name=chart_name,
189
+ chart_only=chart_only,
190
+ output_png=output_png,
191
+ )
192
+ futures.append(future)
193
+
194
+ # 等待所有任务完成并检查结果
195
+ results = [future.result() for future in futures]
196
+ return all(results)
197
+ else:
198
+ print("单线程顺序处理")
199
+ # 单线程顺序处理
200
+ success = True
201
+ for input_file in input_files:
202
+ if output_path == input_path:
203
+ output_file = input_file
204
+ else:
205
+ output_file = output_path / input_file.name
206
+
207
+ success &= run_single_file(
208
+ input_path=input_file,
209
+ output_path=output_file,
210
+ temp_dir=temp_dir,
211
+ modules_to_run=modules_to_run,
212
+ chart_name=chart_name,
213
+ chart_only=chart_only,
214
+ output_png=output_png,
215
+ )
216
+ return success
217
+ else:
218
+ # 单文件处理
219
+ if output_path == input_path:
220
+ output_file = input_path
221
+ else:
222
+ # 如果指定了不同的输出路径,确保它的父目录存在
223
+ output_path.parent.mkdir(parents=True, exist_ok=True)
224
+ output_file = output_path
225
+
226
+ return run_single_file(
227
+ input_path=input_path,
228
+ output_path=output_file,
229
+ temp_dir=temp_dir,
230
+ modules_to_run=modules_to_run,
231
+ chart_name=chart_name,
232
+ chart_only=chart_only,
233
+ output_png=output_png,
234
+ )
235
+
236
+ # except Exception as e:
237
+ # logger.error(f"管道执行失败: {str(e)}")
238
+ # return False
239
+
240
+ def run_single_file(input_path, output_path, temp_dir=None, modules_to_run=None, chart_name=None, chart_only=False, output_png=False, return_timing=False):
241
+ """
242
+ 处理单个文件的管道逻辑
243
+
244
+ Args:
245
+ input_path (Path): 输入JSON文件路径
246
+ output_path (Path): 输出路径
247
+ temp_dir (Path): 临时文件目录,默认为./tmp
248
+ modules_to_run (list): 要运行的模块列表
249
+ chart_name (str, optional): 指定图表名称,仅对infographics_generator模块有效
250
+ chart_only (bool, optional): 只输出 chart SVG,仅对 infographics_generator 有效
251
+ output_png (bool, optional): 同时输出 PNG,仅对 infographics_generator 有效
252
+ """
253
+ # try:
254
+ run_start_time = time.time()
255
+ module_timings = {}
256
+
257
+ def finish(success: bool):
258
+ total_seconds = time.time() - run_start_time
259
+ logger.info(
260
+ "run_single_file timing: total=%.3fs modules=%s",
261
+ total_seconds,
262
+ {k: round(v, 3) for k, v in module_timings.items()},
263
+ )
264
+ if return_timing:
265
+ return {
266
+ "success": success,
267
+ "total_seconds": total_seconds,
268
+ "module_seconds": module_timings,
269
+ }
270
+ return success
271
+
272
+ def fail_module(module_name: str):
273
+ logger.error("模块执行失败: %s", module_name)
274
+ return finish(False)
275
+
276
+ # 确定要运行的模块
277
+ if not modules_to_run:
278
+ modules_to_run = [m["name"] for m in MODULES]
279
+
280
+ # 如果要运行create_index,单独处理并直接返回
281
+ if 'create_index' in modules_to_run:
282
+ logger.info("执行create_index模块")
283
+ # 依次调用三个模块的create_index
284
+ for module_type in ['title', 'color', 'image']:
285
+ if module_type == 'title':
286
+ print("Running modules.title_generator.create_index")
287
+ module = import_module('modules.title_generator.create_index')
288
+ if not Path(text_index_path).exists():
289
+ module.process(
290
+ data=text_data_path,
291
+ index_path=text_index_path,
292
+ data_path=text_data_path,
293
+ embed_model_path=embed_model_path
294
+ )
295
+ elif module_type == 'color':
296
+ print("Running modules.color_recommender.create_index")
297
+ module = import_module('modules.color_recommender.create_index')
298
+ if not Path(color_index_path).exists():
299
+ module.main(
300
+ input=color_data_path,
301
+ output=color_index_path,
302
+ embed_model_path=embed_model_path
303
+ )
304
+ else: # image
305
+ print("Running modules.image_recommender.create_index")
306
+ module = import_module('modules.image_recommender.create_index')
307
+ if not Path(image_index_path).exists():
308
+ module.main(
309
+ image_list_path=image_list_path,
310
+ image_resource_path=image_resource_path,
311
+ index_path=image_index_path,
312
+ data_path=image_data_path,
313
+ embed_model_path=embed_model_path
314
+ )
315
+ total_seconds = time.time() - run_start_time
316
+ if return_timing:
317
+ return {
318
+ "success": True,
319
+ "total_seconds": total_seconds,
320
+ "module_seconds": module_timings,
321
+ }
322
+ return True
323
+
324
+ # 创建临时目录
325
+ if temp_dir is None:
326
+ temp_dir = Path("./tmp")
327
+ temp_dir.mkdir(parents=True, exist_ok=True)
328
+
329
+ # 确保输出目录存在
330
+ output_path.parent.mkdir(parents=True, exist_ok=True)
331
+
332
+ # 当前输入文件
333
+ current_input = input_path
334
+
335
+ # 获取最后一个模块的配置
336
+ last_module = [m for m in MODULES if m["name"] in modules_to_run][-1]
337
+
338
+ # 根据最后一个模块的输出类型决定最终输出文件的扩展名
339
+ if last_module["name"] in ["chart_engine", "title_styler", "asset_regenerator"]:
340
+ final_output = output_path.with_suffix('.svg')
341
+ elif last_module["name"] == "full_image_polisher":
342
+ final_output = output_path.with_suffix('.png')
343
+ elif last_module["name"] == "infographics_generator":
344
+ final_output = output_path.parent / f"{output_path.stem}_final.svg"
345
+ else:
346
+ final_output = output_path.with_suffix('.json')
347
+
348
+ # 记录执行过程
349
+ # 依次执行各模块
350
+ for i, module_config in enumerate([m for m in MODULES if m["name"] in modules_to_run]):
351
+ module_name = module_config["name"]
352
+ module_desc = module_config["description"]
353
+ module_start_time = time.time()
354
+ # logger.info(f"执行模块 {i+1}/{len(modules_to_run)}: {module_name} - {module_desc}")
355
+
356
+ # 特殊处理all模块,依次执行datafact_generator、title_generator、color_recommender和image_recommender
357
+ if module_name == "all":
358
+ # 数据洞察模块
359
+ preprocess_module = import_module(f"modules.preprocess.preprocess")
360
+ if not should_skip_module(module_name, output_path):
361
+ step_start_time = time.time()
362
+ preprocess_module.process(input=str(current_input), output=str(output_path))
363
+ module_timings["all.preprocess"] = time.time() - step_start_time
364
+ current_input = output_path
365
+
366
+ datafact_module = import_module("modules.datafact_generator.datafact_generator")
367
+ if not should_skip_module("datafact_generator", output_path):
368
+ step_start_time = time.time()
369
+ datafact_module.process(input=str(current_input), output=str(output_path))
370
+ module_timings["all.datafact_generator"] = time.time() - step_start_time
371
+ current_input = output_path
372
+
373
+ # 标题生成模块
374
+ title_module = import_module("modules.title_generator.title_generator")
375
+ if not should_skip_module("title_generator", output_path):
376
+ step_start_time = time.time()
377
+ title_module.process(
378
+ input=str(current_input),
379
+ output=str(output_path),
380
+ base_url=base_url,
381
+ api_key=api_key,
382
+ embed_model_path=embed_model_path,
383
+ topk=topk,
384
+ data_path=text_data_path,
385
+ index_path=text_index_path
386
+ )
387
+ module_timings["all.title_generator"] = time.time() - step_start_time
388
+ current_input = output_path
389
+
390
+ # 色彩推荐模块
391
+ color_module = import_module("modules.color_recommender.color_recommender")
392
+ if not should_skip_module("color_recommender", output_path):
393
+ step_start_time = time.time()
394
+ color_module.process(
395
+ input=str(current_input),
396
+ output=str(output_path),
397
+ base_url=base_url,
398
+ api_key=api_key,
399
+ embed_model_path=embed_model_path,
400
+ data_path=color_data_path,
401
+ index_path=color_index_path
402
+ )
403
+ module_timings["all.color_recommender"] = time.time() - step_start_time
404
+ current_input = output_path
405
+
406
+ # 图像推荐模块
407
+ image_module = import_module("modules.image_recommender.image_recommender")
408
+ if not should_skip_module("image_recommender", output_path):
409
+ step_start_time = time.time()
410
+ image_module.process(
411
+ input=str(current_input),
412
+ output=str(output_path),
413
+ base_url=base_url,
414
+ api_key=api_key,
415
+ embed_model_path=embed_model_path,
416
+ data_path=image_data_path,
417
+ index_path=image_index_path,
418
+ resource_path=image_resource_path
419
+ )
420
+ module_timings["all.image_recommender"] = time.time() - step_start_time
421
+ current_input = output_path
422
+
423
+ # 特殊处理title_generator模块,传入配置参数
424
+ elif module_name == "title_generator":
425
+ module = import_module(f"modules.{module_name}.{module_name}")
426
+ if not should_skip_module(module_name, output_path):
427
+ step_start_time = time.time()
428
+ module.process(
429
+ input=str(current_input),
430
+ output=str(output_path),
431
+ base_url=base_url,
432
+ api_key=api_key,
433
+ embed_model_path=embed_model_path,
434
+ topk=topk,
435
+ data_path=text_data_path,
436
+ index_path=text_index_path
437
+ )
438
+ module_timings["title_generator"] = time.time() - step_start_time
439
+ current_input = output_path
440
+ elif module_name == "color_recommender":
441
+ module = import_module(f"modules.{module_name}.{module_name}")
442
+ if not should_skip_module(module_name, output_path):
443
+ step_start_time = time.time()
444
+ module.process(
445
+ input=str(current_input),
446
+ output=str(output_path),
447
+ base_url=base_url,
448
+ api_key=api_key,
449
+ embed_model_path=embed_model_path,
450
+ data_path=color_data_path,
451
+ index_path=color_index_path
452
+ )
453
+ module_timings["color_recommender"] = time.time() - step_start_time
454
+ current_input = output_path
455
+ elif module_name == "image_recommender":
456
+ module = import_module(f"modules.{module_name}.{module_name}")
457
+ print("image_recommender")
458
+ if not should_skip_module(module_name, output_path):
459
+ print("process")
460
+ step_start_time = time.time()
461
+ module.process(
462
+ input=str(current_input),
463
+ output=str(output_path),
464
+ base_url=base_url,
465
+ api_key=api_key,
466
+ embed_model_path=embed_model_path,
467
+ data_path=image_data_path,
468
+ index_path=image_index_path,
469
+ resource_path=image_resource_path
470
+ )
471
+ module_timings["image_recommender"] = time.time() - step_start_time
472
+ current_input = output_path
473
+ elif module_name == "infographics_generator":
474
+ module = import_module(f"modules.{module_name}.{module_name}")
475
+ if not should_skip_module(module_name, output_path):
476
+ step_start_time = time.time()
477
+ result = module.process(
478
+ input=str(current_input),
479
+ output=str(output_path),
480
+ base_url=base_url,
481
+ api_key=api_key,
482
+ chart_name=chart_name,
483
+ chart_only=chart_only,
484
+ output_png=output_png,
485
+ )
486
+ module_timings["infographics_generator"] = time.time() - step_start_time
487
+ if result is False:
488
+ return fail_module(module_name)
489
+ current_input = output_path
490
+ elif module_name == "asset_regenerator":
491
+ module = import_module(f"modules.{module_name}.{module_name}")
492
+ if not should_skip_module(module_name, output_path):
493
+ ok = module.process(
494
+ input=str(current_input),
495
+ output=str(output_path),
496
+ base_url=base_url,
497
+ api_key=api_key,
498
+ )
499
+ if not ok:
500
+ return False
501
+ current_input = output_path
502
+ elif module_name == "full_image_polisher":
503
+ module = import_module(f"modules.{module_name}.{module_name}")
504
+ if not should_skip_module(module_name, output_path):
505
+ ok = module.process(
506
+ input=str(current_input),
507
+ output=str(output_path),
508
+ base_url=base_url,
509
+ api_key=api_key,
510
+ )
511
+ if not ok:
512
+ return False
513
+ current_input = output_path
514
+ elif module_name == "chart_engine":
515
+ # 输入是JSON,输出是SVG
516
+ module = import_module(f"modules.{module_name}.{module_name}")
517
+ svg_output = output_path.with_suffix('.svg')
518
+ if True: # not svg_output.exists():
519
+ step_start_time = time.time()
520
+ result = module.process(input=str(current_input), output=str(svg_output))
521
+ module_timings["chart_engine"] = time.time() - step_start_time
522
+ if result is False:
523
+ return fail_module(module_name)
524
+ current_input = svg_output # 更新为SVG文件作为下一个模块的输入
525
+
526
+ elif module_name == "title_styler":
527
+ # 输入是JSON,输出是SVG
528
+ module = import_module(f"modules.{module_name}.{module_name}")
529
+ title_svg = output_path.parent / f"{output_path.stem}_title.svg"
530
+ if not title_svg.exists():
531
+ step_start_time = time.time()
532
+ result = module.process(input=str(current_input), output=str(title_svg))
533
+ module_timings["title_styler"] = time.time() - step_start_time
534
+ if result is False:
535
+ return fail_module(module_name)
536
+ current_input = title_svg # 更新为标题SVG作为下一个模块的输入
537
+
538
+ else:
539
+ # 普通模块:输入JSON,输出JSON
540
+ module = import_module(f"modules.{module_name}.{module_name}")
541
+ if not should_skip_module(module_name, output_path):
542
+ step_start_time = time.time()
543
+ result = module.process(input=str(current_input), output=str(output_path))
544
+ module_timings[module_name] = time.time() - step_start_time
545
+ if result is False:
546
+ return fail_module(module_name)
547
+ current_input = output_path
548
+
549
+ module_timings[f"{module_name}.total"] = time.time() - module_start_time
550
+
551
+ return finish(True)
552
+
553
+ # except Exception as e:
554
+ # logger.error(f"文件处理失败 {input_path}: {str(e)}")
555
+ # return False
556
+
557
+ def should_skip_module(module_name: str, output_path: Path) -> bool:
558
+ """检查是否需要跳过模块执行"""
559
+ try:
560
+ if not output_path.exists():
561
+ return False
562
+
563
+ # 检查JSON文件中的特定字段
564
+ with open(output_path) as f:
565
+ data = json.load(f)
566
+
567
+ skip_conditions = {
568
+ "preprocess": lambda d: "metadata" in d and "data" in d and "variables" in d and "processed" in d,
569
+ "chart_type_recommender": lambda d: "chart_type" in d,
570
+ "datafact_generator": lambda d: "datafacts" in d,
571
+ "title_generator": lambda d: False,#"titles" in d,
572
+ "color_recommender": lambda d: "colors" in d,
573
+ "image_recommender": lambda d: "images" in d
574
+ }
575
+
576
+ if module_name in skip_conditions:
577
+ return skip_conditions[module_name](data)
578
+
579
+ return False
580
+
581
+ except Exception as e:
582
+ logger.warning(f"检查跳过条件时出错: {str(e)}")
583
+ return False
584
+
585
+ def parse_args():
586
+ parser = argparse.ArgumentParser()
587
+ parser.add_argument('--input', type=str,
588
+ help='Input json file path or directory (default: config.data_resource_path = data_resource_dirs[0])',
589
+ default=data_resource_path)
590
+ parser.add_argument('--output', type=str, help='Output json file path', default='output')
591
+ parser.add_argument('--temp-dir', type=str, default='tmp')
592
+ parser.add_argument('--modules', type=str, nargs='+', help='Modules to run', default=['infographics_generator'])
593
+ parser.add_argument('--threads', type=int, help='Number of threads for directory processing', default=1)
594
+ parser.add_argument('--chart-name', type=str, help='Specific chart name to use for infographics_generator')
595
+ parser.add_argument('--chart-only', action='store_true', help='Only output the chart SVG; skip title/image/layout (infographics_generator only)')
596
+ parser.add_argument('--output-png', action='store_true', help='Also write a PNG next to the final SVG (uses rsvg-convert)')
597
+
598
+ args = parser.parse_args()
599
+ # 如果没有指定input,从data_resource_path随机选择
600
+ if args.input is None and 'create_index' not in args.modules:
601
+ json_files = [f for f in os.listdir(data_resource_path) if f.endswith('.json')]
602
+ if not json_files:
603
+ raise ValueError(f"在 {data_resource_path} 目录下没有找到json文件")
604
+ args.input = os.path.join(data_resource_path, random.choice(json_files))
605
+ print(f"随机��择输入文件: {args.input}")
606
+ args.output = "tmp.json"
607
+ print(f"使用默认输出文件: {args.output}")
608
+
609
+ return args
610
+
611
+ def main():
612
+ args = parse_args()
613
+ modules_to_run = None
614
+ if args.modules:
615
+ modules_to_run = [m.strip() for m in args.modules]
616
+
617
+ ok = run_pipeline(
618
+ input_path=args.input,
619
+ output_path=args.output,
620
+ temp_dir=args.temp_dir,
621
+ modules_to_run=modules_to_run,
622
+ threads=args.threads,
623
+ chart_name=args.chart_name,
624
+ chart_only=args.chart_only,
625
+ output_png=args.output_png,
626
+ )
627
+ raise SystemExit(0 if ok else 1)
628
+
629
+ if __name__ == "__main__":
630
+ main()
record.txt ADDED
The diff for this file is too large to render. See raw diff
 
requirements.txt ADDED
@@ -0,0 +1,15 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ pandas>=1.3.0
2
+ numpy>=1.20.0
3
+ opencv-python-headless>=4.8.0
4
+ IPython
5
+ pyecharts
6
+ snapshot_selenium
7
+ imgkit
8
+ networkx
9
+ matplotlib
10
+ openai
11
+ serpapi
12
+
13
+ clip
14
+ torch
15
+ faiss
setup.sh ADDED
@@ -0,0 +1,100 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env bash
2
+ # Setup script for ChartPipeline on a fresh machine.
3
+ # Run from the project root (the directory this script is in).
4
+ #
5
+ # Prereqs you have to handle BEFORE running this script:
6
+ # 1. Python 3.10+ installed (conda or system).
7
+ # 2. Node.js 18+ installed (`node -v`).
8
+ # 3. Google Chrome or Chromium installed at /usr/bin/google-chrome
9
+ # (or set PUPPETEER_EXECUTABLE_PATH to its real path).
10
+ # 4. librsvg2-bin installed (provides rsvg-convert). On Ubuntu:
11
+ # sudo apt install librsvg2-bin tmux
12
+ # If you have no sudo, install via conda:
13
+ # conda install -c conda-forge librsvg tmux
14
+ # 5. Input data pools available at the paths config.py points to,
15
+ # e.g. /data/liduan/resources/claude_data_v2 and claude_new.
16
+ # Edit config.data_resource_dirs to match this machine if needed.
17
+
18
+ set -u
19
+ cd "$(dirname "$0")"
20
+ ROOT="$(pwd)"
21
+ echo "[setup] cwd=$ROOT"
22
+
23
+ # ---- 1. Python deps ----
24
+ echo
25
+ echo "============================================================"
26
+ echo "[setup] installing python deps from requirements.txt"
27
+ echo "============================================================"
28
+ pip install -r requirements.txt
29
+ # Things requirements.txt was missing on the source machine; install
30
+ # defensively so chart_engine + selenium-based screenshot both work.
31
+ pip install pyecharts snapshot_selenium selenium
32
+
33
+ # ---- 2. Node deps ----
34
+ echo
35
+ echo "============================================================"
36
+ echo "[setup] installing node deps (puppeteer / echarts ...)"
37
+ echo "============================================================"
38
+ if [[ -f package.json ]]; then
39
+ # IMPORTANT: ChartPipeline's puppeteer was patched to *re-use* an existing
40
+ # Chrome via PUPPETEER_EXECUTABLE_PATH; skip the bundled chromium download
41
+ # to save ~300 MB and avoid network friction.
42
+ PUPPETEER_SKIP_DOWNLOAD=1 npm install
43
+ else
44
+ echo "[setup] package.json missing, skipping npm install"
45
+ fi
46
+
47
+ # ---- 3. System binaries sanity check ----
48
+ echo
49
+ echo "============================================================"
50
+ echo "[setup] checking system binaries"
51
+ echo "============================================================"
52
+ for bin in google-chrome rsvg-convert tmux node npm python; do
53
+ loc="$(command -v "$bin" 2>/dev/null || true)"
54
+ if [[ -n "$loc" ]]; then
55
+ printf ' %-14s OK %s\n' "$bin" "$loc"
56
+ else
57
+ printf ' %-14s MISSING -- you need to install this before running pipeline.py\n' "$bin"
58
+ fi
59
+ done
60
+
61
+ # ---- 4. PUPPETEER_EXECUTABLE_PATH ----
62
+ echo
63
+ echo "============================================================"
64
+ echo "[setup] puppeteer chrome path"
65
+ echo "============================================================"
66
+ CHROME_BIN="${PUPPETEER_EXECUTABLE_PATH:-$(command -v google-chrome || command -v chromium || true)}"
67
+ if [[ -n "$CHROME_BIN" ]]; then
68
+ echo " PUPPETEER_EXECUTABLE_PATH = $CHROME_BIN (export this before running pipeline.py)"
69
+ else
70
+ echo " WARNING: no chrome/chromium found; puppeteer will fail at runtime"
71
+ fi
72
+
73
+ # ---- 5. Quick import test ----
74
+ echo
75
+ echo "============================================================"
76
+ echo "[setup] verifying config + key imports"
77
+ echo "============================================================"
78
+ python - <<'PY'
79
+ import importlib
80
+ import config
81
+ print(f" config.resource_path = {config.resource_path}")
82
+ print(f" config.data_resource_dirs[0]= {config.data_resource_dirs[0]}")
83
+ for mod in (
84
+ "modules.chart_engine.template.template_registry",
85
+ "modules.infographics_generator.template_utils",
86
+ "modules.infographics_generator.infographics_generator",
87
+ ):
88
+ importlib.import_module(mod)
89
+ print(f" import {mod} OK")
90
+ PY
91
+
92
+ echo
93
+ echo "[setup] DONE."
94
+ echo "Next steps:"
95
+ echo " 1. Edit config.py 's data_resource_dirs if data pools live elsewhere on this host."
96
+ echo " 2. (optional) Edit allowed_chart_types.json to restrict which chart_type are eligible."
97
+ echo " 3. Launch a chart-only run, e.g.:"
98
+ echo " export PUPPETEER_EXECUTABLE_PATH=$CHROME_BIN"
99
+ echo " tmux new-session -d -s chart_10h \\"
100
+ echo " \"PRIOR_SESSION=__none__ THREADS=48 DEADLINE_SEC=36000 bash scripts/run_10h_loop.sh\""