chronaxis 0.1.0.dev0__py3-none-any.whl → 0.1.0.dev2__py3-none-any.whl
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.
- chronaxis/__init__.py +4 -4
- chronaxis/__main__.py +1 -1
- chronaxis/_batch_worker.py +69 -0
- chronaxis/analysis_cli.py +14 -6
- chronaxis/batch/api.py +11 -4
- chronaxis/batch/cli.py +1 -1
- chronaxis/batch/models.py +29 -1
- chronaxis/batch/process_transport.py +69 -63
- chronaxis/batch/publication.py +35 -15
- chronaxis/batch/runner.py +33 -31
- chronaxis/can_dependencies.py +1 -1
- chronaxis/cli.py +35 -84
- chronaxis/configuration/diagnostics.py +9 -0
- chronaxis/configuration/file_tools.py +12 -18
- chronaxis/configuration/imports.py +6 -6
- chronaxis/configuration/origins.py +7 -7
- chronaxis/configuration/resolution.py +48 -15
- chronaxis/configuration/templates/batch/analysis-annotated.yaml +1 -1
- chronaxis/configuration/templates/batch/analysis-minimal.yaml +1 -1
- chronaxis/configuration/templates/can-asc/annotated.yaml +1 -1
- chronaxis/configuration/templates/can-asc/minimal.yaml +1 -1
- chronaxis/configuration/templates/can-blf/annotated.yaml +1 -1
- chronaxis/configuration/templates/can-blf/minimal.yaml +1 -1
- chronaxis/configuration/templates/can-export/annotated.yaml +1 -1
- chronaxis/configuration/templates/can-export/minimal.yaml +1 -1
- chronaxis/configuration/templates/catalog.json +0 -21
- chronaxis/configuration/templates/charge-session/annotated.yaml +1 -1
- chronaxis/configuration/templates/charge-session/minimal.yaml +1 -1
- chronaxis/configuration/templates/command-response/annotated.yaml +1 -1
- chronaxis/configuration/templates/command-response/minimal.yaml +1 -1
- chronaxis/configuration/templates/fault-window/annotated.yaml +1 -1
- chronaxis/configuration/templates/fault-window/minimal.yaml +1 -1
- chronaxis/configuration/templates/raw-can-csv/annotated.yaml +1 -1
- chronaxis/configuration/templates/raw-can-csv/minimal.yaml +1 -1
- chronaxis/configuration/templates/signal-csv/annotated.yaml +1 -1
- chronaxis/configuration/templates/signal-csv/minimal.yaml +1 -1
- chronaxis/configuration/templates/signal-export/annotated.yaml +1 -1
- chronaxis/configuration/templates/signal-export/minimal.yaml +1 -1
- chronaxis/configuration/templates/stability/annotated.yaml +3 -1
- chronaxis/configuration/templates/stability/minimal.yaml +3 -1
- chronaxis/configuration/templates/threshold/annotated.yaml +1 -1
- chronaxis/configuration/templates/threshold/minimal.yaml +1 -1
- chronaxis/core/__init__.py +2 -1
- chronaxis/core/affine.py +79 -0
- chronaxis/core/affine_numpy.py +84 -0
- chronaxis/core/binding.py +68 -5
- chronaxis/core/calculation_output_numpy.py +29 -0
- chronaxis/core/calculation_output_reference.py +27 -0
- chronaxis/core/calculations.py +134 -51
- chronaxis/core/calculations_memory_numpy.py +17 -9
- chronaxis/core/calculations_memory_reference.py +34 -8
- chronaxis/core/calculations_numpy.py +47 -30
- chronaxis/core/calculations_reference.py +41 -32
- chronaxis/core/conditional_time_reduction_numpy.py +25 -19
- chronaxis/core/conditional_time_reduction_reference.py +25 -17
- chronaxis/core/counts_v4.py +34 -0
- chronaxis/core/declarations.py +25 -0
- chronaxis/core/decoded_binding.py +17 -0
- chronaxis/core/enum_codes.py +223 -0
- chronaxis/core/expression.py +104 -27
- chronaxis/core/expression_integer_numpy.py +60 -0
- chronaxis/core/expression_integer_reference.py +46 -0
- chronaxis/core/expression_integer_v4.py +97 -0
- chronaxis/core/expression_math_numpy.py +47 -0
- chronaxis/core/expression_math_reference.py +49 -0
- chronaxis/core/expression_math_v4.py +148 -0
- chronaxis/core/expression_numpy.py +70 -4
- chronaxis/core/expression_reference.py +42 -4
- chronaxis/core/expression_v4.py +302 -0
- chronaxis/core/extension_points.py +38 -0
- chronaxis/core/function_contracts.py +74 -0
- chronaxis/core/lookup.py +163 -0
- chronaxis/core/lookup_compiler.py +47 -0
- chronaxis/core/lookup_numpy.py +59 -0
- chronaxis/core/lookup_reference.py +50 -0
- chronaxis/core/message_binding.py +42 -2
- chronaxis/core/models.py +1 -0
- chronaxis/core/moments.py +68 -0
- chronaxis/core/moments_numpy.py +87 -0
- chronaxis/core/moments_reference.py +55 -0
- chronaxis/core/normalize_numpy.py +4 -2
- chronaxis/core/numeric.py +12 -3
- chronaxis/core/numeric_numpy.py +14 -0
- chronaxis/core/quantile_numpy.py +33 -0
- chronaxis/core/quantile_reference.py +31 -0
- chronaxis/core/quantiles.py +198 -0
- chronaxis/core/rate_numpy.py +93 -0
- chronaxis/core/rate_reference.py +69 -0
- chronaxis/core/rate_transforms.py +105 -0
- chronaxis/core/rolling_values.py +76 -0
- chronaxis/core/sample_reduction.py +15 -7
- chronaxis/core/sample_reduction_numpy.py +46 -21
- chronaxis/core/sample_reduction_reference.py +50 -20
- chronaxis/core/selection.py +22 -6
- chronaxis/core/sequence_reference.py +4 -2
- chronaxis/core/state.py +17 -6
- chronaxis/core/state_numpy.py +14 -4
- chronaxis/core/temporal_memory_numpy.py +25 -8
- chronaxis/core/temporal_memory_reference.py +24 -13
- chronaxis/core/time_reduction.py +5 -4
- chronaxis/core/time_reduction_numpy.py +25 -19
- chronaxis/core/time_reduction_reference.py +25 -17
- chronaxis/core/types.py +4 -0
- chronaxis/core/unit_conversion.py +104 -0
- chronaxis/core/unit_registry.py +567 -0
- chronaxis/core/units.py +1 -60
- chronaxis/core/where_updates.py +114 -0
- chronaxis/core/window_delta.py +2 -0
- chronaxis/core/windows.py +1 -1
- chronaxis/execution/calculation_blocks.py +4 -5
- chronaxis/execution/calculation_values.py +3 -1
- chronaxis/execution/condition_evidence.py +6 -3
- chronaxis/execution/current_message_calculation.py +30 -5
- chronaxis/execution/driver_expression.py +126 -34
- chronaxis/execution/expressions.py +91 -9
- chronaxis/execution/historical_conditional_blocks.py +4 -5
- chronaxis/execution/historical_conditional_time_calculation.py +4 -3
- chronaxis/execution/historical_sample_calculation.py +6 -2
- chronaxis/execution/historical_selection_rows.py +4 -4
- chronaxis/execution/historical_time_calculation.py +4 -3
- chronaxis/execution/history.py +17 -2
- chronaxis/execution/hosted_sample_reduction.py +62 -0
- chronaxis/execution/record_delta_condition.py +13 -4
- chronaxis/execution/record_event_durations.py +9 -5
- chronaxis/execution/record_integral.py +7 -0
- chronaxis/execution/record_object_counts.py +34 -0
- chronaxis/execution/record_rule_timeline.py +56 -10
- chronaxis/execution/record_scope_calculations.py +34 -13
- chronaxis/execution/record_sequences.py +2 -1
- chronaxis/execution/record_session_calculations.py +103 -28
- chronaxis/execution/record_time_calculation.py +45 -11
- chronaxis/execution/record_value_history.py +5 -4
- chronaxis/execution/record_windows.py +5 -1
- chronaxis/execution/required_expression.py +3 -7
- chronaxis/execution/result_aggregations.py +270 -0
- chronaxis/execution/run_session.py +6 -4
- chronaxis/execution/state.py +37 -3
- chronaxis/execution/transform_partial.py +43 -0
- chronaxis/execution/transform_stream.py +321 -0
- chronaxis/execution/transform_window.py +84 -0
- chronaxis/execution/where_state.py +76 -0
- chronaxis/execution/window_replay.py +6 -1
- chronaxis/extension_runtime.py +279 -0
- chronaxis/extensions.py +188 -0
- chronaxis/fast/__init__.py +3 -0
- chronaxis/fast/__main__.py +13 -0
- chronaxis/fast/baseline.py +22 -0
- chronaxis/fast/batch.py +90 -0
- chronaxis/fast/can_io.py +168 -0
- chronaxis/fast/cli.py +62 -0
- chronaxis/fast/compute.py +204 -0
- chronaxis/fast/config.py +225 -0
- chronaxis/fast/run.py +58 -0
- chronaxis/fast/totals.py +92 -0
- chronaxis/fast/worker.py +56 -0
- chronaxis/guide/README.md +23 -5
- chronaxis/guide/api/AnalysisPlan.md +64 -7
- chronaxis/guide/api/AnalysisResult.md +31 -4
- chronaxis/guide/api/AnalysisRunner.md +3 -2
- chronaxis/guide/api/BatchJobResult.md +2 -1
- chronaxis/guide/api/BatchReport.md +4 -2
- chronaxis/guide/api/BatchRunner.md +2 -2
- chronaxis/guide/api/ExportManifest.md +20 -0
- chronaxis/guide/api/ExtensionRegistry.md +32 -0
- chronaxis/guide/api/FunctionSpec.md +45 -0
- chronaxis/guide/api/OutputManifest.md +1 -1
- chronaxis/guide/api/PlanValidationError.md +56 -4
- chronaxis/guide/api/ReductionSpec.md +47 -0
- chronaxis/guide/api/RunMetadata.md +5 -4
- chronaxis/guide/api/TransformSpec.md +48 -0
- chronaxis/guide/api/fast.batch.run_batch.md +34 -0
- chronaxis/guide/api/fast.run.md +31 -0
- chronaxis/guide/api/index.json +193 -77
- chronaxis/guide/api/load_analysis_plan.md +2 -1
- chronaxis/guide/api/run_batch.md +2 -10
- chronaxis/guide/en/README.md +22 -4
- chronaxis/guide/en/api/AnalysisPlan.md +47 -23
- chronaxis/guide/en/api/AnalysisResult.md +30 -24
- chronaxis/guide/en/api/AnalysisRunner.md +11 -12
- chronaxis/guide/en/api/BatchJob.md +4 -4
- chronaxis/guide/en/api/BatchJobResult.md +21 -20
- chronaxis/guide/en/api/BatchPlan.md +10 -10
- chronaxis/guide/en/api/BatchReport.md +13 -11
- chronaxis/guide/en/api/BatchRunner.md +11 -11
- chronaxis/guide/en/api/ExportManifest.md +25 -0
- chronaxis/guide/en/api/ExtensionRegistry.md +32 -0
- chronaxis/guide/en/api/FunctionSpec.md +45 -0
- chronaxis/guide/en/api/OutputManifest.md +5 -5
- chronaxis/guide/en/api/PlanFragment.md +7 -7
- chronaxis/guide/en/api/PlanValidationError.md +36 -10
- chronaxis/guide/en/api/PresetSpec.md +10 -10
- chronaxis/guide/en/api/ReductionSpec.md +47 -0
- chronaxis/guide/en/api/RunMetadata.md +12 -13
- chronaxis/guide/en/api/TimeConversion.md +10 -10
- chronaxis/guide/en/api/TransformSpec.md +48 -0
- chronaxis/guide/en/api/build_preset.md +4 -4
- chronaxis/guide/en/api/checked_add_ns.md +4 -4
- chronaxis/guide/en/api/convert_time.md +4 -4
- chronaxis/guide/en/api/explain_analysis_plan.md +3 -3
- chronaxis/guide/en/api/fast.batch.run_batch.md +34 -0
- chronaxis/guide/en/api/fast.run.md +31 -0
- chronaxis/guide/en/api/index.json +193 -77
- chronaxis/guide/en/api/load_analysis_plan.md +9 -4
- chronaxis/guide/en/api/load_batch_plan.md +3 -3
- chronaxis/guide/en/api/parse_datetime_ns.md +4 -4
- chronaxis/guide/en/api/parse_duration.md +3 -3
- chronaxis/guide/en/api/resolve_preset_roles.md +4 -4
- chronaxis/guide/en/api/run_batch.md +10 -10
- chronaxis/guide/en/api/shift_times_reference.md +4 -4
- chronaxis/guide/en/api/time_deltas_reference.md +3 -3
- chronaxis/guide/en/api/validate_analysis_plan.md +3 -3
- chronaxis/guide/en/fields/analysis/aggregations-all-1.json +359 -0
- chronaxis/guide/en/fields/analysis/aggregations-all-2.json +301 -0
- chronaxis/guide/en/fields/analysis/aggregations-all-3.json +322 -0
- chronaxis/guide/en/fields/analysis/aggregations-all-4.json +169 -0
- chronaxis/guide/en/fields/analysis/calculations-all-1.json +14 -2
- chronaxis/guide/en/fields/analysis/calculations-all-2.json +55 -53
- chronaxis/guide/en/fields/analysis/calculations-all-3.json +207 -0
- chronaxis/guide/en/fields/{mapping/analysis-all-63.json → analysis/calculations-all-4.json} +154 -162
- chronaxis/guide/en/fields/{mapping/analysis-all-62.json → analysis/calculations-all-5.json} +129 -137
- chronaxis/guide/en/fields/analysis/calculations-all-6.json +172 -0
- chronaxis/guide/en/fields/analysis/config_version-all-1.json +2 -2
- chronaxis/guide/en/fields/analysis/lookups-all-1.json +196 -0
- chronaxis/guide/en/fields/analysis/outputs-all-1.json +1 -1
- chronaxis/guide/en/fields/analysis/presets-charge_session-1.json +1 -1
- chronaxis/guide/en/fields/analysis/presets-command_response-1.json +1 -1
- chronaxis/guide/en/fields/analysis/presets-common-1.json +20 -0
- chronaxis/guide/en/fields/analysis/presets-fault_context-1.json +1 -1
- chronaxis/guide/en/fields/analysis/presets-threshold_excursion-1.json +1 -1
- chronaxis/guide/en/fields/analysis/rules-common-1.json +20 -0
- chronaxis/guide/en/fields/analysis/signals-common-1.json +19 -0
- chronaxis/guide/en/fields/{mapping/analysis-all-80.json → analysis/signals-conditions-1.json} +153 -138
- chronaxis/guide/en/fields/analysis/signals-conditions-2.json +129 -0
- chronaxis/guide/en/fields/analysis/signals-variant-1-2.json +13 -28
- chronaxis/guide/en/fields/analysis/signals-variant-1-3.json +40 -29
- chronaxis/guide/en/fields/analysis/signals-variant-1-4.json +19 -0
- chronaxis/guide/en/fields/analysis/signals-variant-2-2.json +21 -23
- chronaxis/guide/en/fields/analysis/signals-variant-2-3.json +28 -30
- chronaxis/guide/en/fields/analysis/signals-variant-2-4.json +20 -19
- chronaxis/guide/en/fields/analysis/signals-variant-2-5.json +19 -0
- chronaxis/guide/en/fields/analysis/sources-common-1.json +20 -0
- chronaxis/guide/en/fields/analysis/sources-conditions-1.json +110 -0
- chronaxis/guide/en/fields/analysis/transforms-all-1.json +333 -0
- chronaxis/guide/en/fields/analysis/transforms-all-2.json +306 -0
- chronaxis/guide/en/fields/analysis/transforms-all-3.json +55 -0
- chronaxis/guide/en/fields/definitions/signals-all-2.json +16 -18
- chronaxis/guide/en/fields/definitions/signals-all-3.json +23 -22
- chronaxis/guide/en/fields/definitions/signals-all-4.json +12 -53
- chronaxis/guide/en/fields/definitions/signals-all-5.json +70 -52
- chronaxis/guide/en/fields/definitions/signals-all-6.json +57 -58
- chronaxis/guide/en/fields/definitions/signals-all-7.json +48 -0
- chronaxis/guide/en/fields/index.json +262 -1364
- chronaxis/guide/en/semantics.md +22 -7
- chronaxis/guide/en/tasks.json +234 -5
- chronaxis/guide/en/topics/aggregation-results.md +10 -0
- chronaxis/guide/en/topics/aggregations.md +62 -0
- chronaxis/guide/en/topics/expressions.md +249 -0
- chronaxis/guide/en/topics/fast-energy.md +22 -0
- chronaxis/guide/en/topics/integrators.md +8 -0
- chronaxis/guide/en/topics/message-calculations.md +95 -0
- chronaxis/guide/en/topics/message-example.md +10 -0
- chronaxis/guide/en/topics/power-statistics-results.md +43 -0
- chronaxis/guide/en/topics/power-statistics.md +29 -0
- chronaxis/guide/en/workflow.md +10 -2
- chronaxis/guide/examples/analysis-imports.yaml +1 -1
- chronaxis/guide/examples/analysis.yaml +1 -1
- chronaxis/guide/examples/conditional-energy.yaml +1 -1
- chronaxis/guide/examples/data/can/power-statistics.asc +7 -0
- chronaxis/guide/examples/example_support.py +22 -0
- chronaxis/guide/examples/execution-comparison.json +85 -0
- chronaxis/guide/examples/export-can-minimal.yaml +1 -1
- chronaxis/guide/examples/fast/analysis-blf.yaml +22 -0
- chronaxis/guide/examples/fast/analysis.yaml +22 -0
- chronaxis/guide/examples/fast/batch.yaml +5 -0
- chronaxis/guide/examples/fast/data/power.dbc +6 -0
- chronaxis/guide/examples/fast/data/run10.asc +4 -0
- chronaxis/guide/examples/fast/data/run2.asc +5 -0
- chronaxis/guide/examples/fast/data/single.blf +0 -0
- chronaxis/guide/examples/fast/unsupported.yaml +24 -0
- chronaxis/guide/examples/input-csv-minimal.yaml +2 -2
- chronaxis/guide/examples/lookup-power.yaml +27 -0
- chronaxis/guide/examples/message-directions.yaml +57 -0
- chronaxis/guide/examples/message-energy.yaml +40 -0
- chronaxis/guide/examples/message-minimal.yaml +28 -0
- chronaxis/guide/examples/power-statistics.yaml +83 -0
- chronaxis/guide/examples/preset-charge-energy.yaml +2 -2
- chronaxis/guide/examples/preset-charge-minimal.yaml +1 -1
- chronaxis/guide/examples/preset-fault-context.yaml +2 -2
- chronaxis/guide/examples/quality.yaml +1 -1
- chronaxis/guide/examples/readability_examples.py +203 -0
- chronaxis/guide/examples/results/conditional-energy-yaml//321/204/342/225/221/320/233/321/204/342/225/227/342/225/242/321/207/342/225/227/320/243/321/206/320/256/320/254.csv +2 -0
- chronaxis/guide/examples/results/power-statistics-yaml/result-summary.json +76 -0
- chronaxis/guide/examples/results/power-statistics-yaml//321/204/342/225/221/320/233/321/204/342/225/227/342/225/242/321/207/342/225/227/320/243/321/206/320/256/320/254.csv +5 -0
- chronaxis/guide/examples/results/power-statistics-yaml//321/204/342/225/235/320/252/321/210/320/277/320/255/321/207/342/225/227/320/243/321/206/320/256/320/254.csv +2 -0
- chronaxis/guide/examples/results/power-statistics-yaml//321/207/320/272/320/247/321/205/320/237/320/263/321/207/342/225/227/320/243/321/206/320/256/320/254.csv +2 -0
- chronaxis/guide/examples/results/power-statistics-yaml//321/210/320/221/320/252/321/205/320/240/320/230/321/207/342/225/227/320/243/321/206/320/256/320/254.csv +2 -0
- chronaxis/guide/examples/sequence-outcomes.yaml +1 -1
- chronaxis/guide/examples/signal-enum-minimal.yaml +1 -1
- chronaxis/guide/examples/signal-number-conversion.yaml +2 -3
- chronaxis/guide/examples/temporal/analysis.yaml +81 -0
- chronaxis/guide/examples/temporal/host-analysis.yaml +34 -0
- chronaxis/guide/examples/temporal/host_extensions.py +119 -0
- chronaxis/guide/examples/temporal/meter.asc +8 -0
- chronaxis/guide/examples/temporal/meter.dbc +9 -0
- chronaxis/guide/examples/temporal/rolling-actual.yaml +22 -0
- chronaxis/guide/examples/window-merged.yaml +1 -1
- chronaxis/guide/fields/analysis/aggregations-all-1.json +359 -0
- chronaxis/guide/fields/analysis/aggregations-all-2.json +301 -0
- chronaxis/guide/fields/analysis/aggregations-all-3.json +322 -0
- chronaxis/guide/fields/analysis/aggregations-all-4.json +169 -0
- chronaxis/guide/fields/analysis/calculations-all-1.json +14 -17
- chronaxis/guide/fields/analysis/calculations-all-2.json +76 -56
- chronaxis/guide/fields/analysis/calculations-all-3.json +222 -0
- chronaxis/guide/fields/{mapping/analysis-all-62.json → analysis/calculations-all-4.json} +147 -119
- chronaxis/guide/fields/analysis/calculations-all-5.json +310 -0
- chronaxis/guide/fields/analysis/calculations-all-6.json +138 -0
- chronaxis/guide/fields/analysis/config_version-all-1.json +2 -2
- chronaxis/guide/fields/analysis/lookups-all-1.json +196 -0
- chronaxis/guide/fields/analysis/outputs-all-1.json +1 -1
- chronaxis/guide/fields/analysis/presets-charge_session-1.json +1 -1
- chronaxis/guide/fields/analysis/presets-command_response-1.json +1 -1
- chronaxis/guide/fields/analysis/presets-common-1.json +20 -0
- chronaxis/guide/fields/analysis/presets-fault_context-1.json +1 -1
- chronaxis/guide/fields/analysis/presets-threshold_excursion-1.json +1 -1
- chronaxis/guide/fields/analysis/rules-common-1.json +20 -0
- chronaxis/guide/fields/analysis/signals-common-1.json +19 -0
- chronaxis/guide/fields/{mapping/analysis-all-79.json → analysis/signals-conditions-1.json} +153 -138
- chronaxis/guide/fields/analysis/signals-conditions-2.json +129 -0
- chronaxis/guide/fields/analysis/signals-variant-1-2.json +19 -15
- chronaxis/guide/fields/analysis/signals-variant-1-3.json +21 -27
- chronaxis/guide/fields/analysis/signals-variant-1-4.json +17 -0
- chronaxis/guide/fields/analysis/signals-variant-2-2.json +21 -23
- chronaxis/guide/fields/analysis/signals-variant-2-3.json +28 -28
- chronaxis/guide/fields/analysis/signals-variant-2-4.json +18 -35
- chronaxis/guide/fields/analysis/signals-variant-2-5.json +44 -0
- chronaxis/guide/fields/analysis/sources-common-1.json +20 -0
- chronaxis/guide/fields/analysis/sources-conditions-1.json +110 -0
- chronaxis/guide/fields/analysis/transforms-all-1.json +333 -0
- chronaxis/guide/fields/analysis/transforms-all-2.json +306 -0
- chronaxis/guide/fields/analysis/transforms-all-3.json +55 -0
- chronaxis/guide/fields/definitions/signals-all-2.json +16 -18
- chronaxis/guide/fields/definitions/signals-all-3.json +23 -25
- chronaxis/guide/fields/definitions/signals-all-4.json +15 -20
- chronaxis/guide/fields/definitions/signals-all-5.json +37 -34
- chronaxis/guide/fields/definitions/signals-all-6.json +39 -39
- chronaxis/guide/fields/definitions/signals-all-7.json +29 -0
- chronaxis/guide/fields/index.json +271 -1352
- chronaxis/guide/lookup.py +3 -2
- chronaxis/guide/manifest.json +216 -289
- chronaxis/guide/run_analysis.py +4 -3
- chronaxis/guide/schemas/analysis.schema.json +797 -34
- chronaxis/guide/semantics.md +24 -7
- chronaxis/guide/tasks.json +234 -5
- chronaxis/guide/topics/aggregation-results.md +10 -0
- chronaxis/guide/topics/aggregations.md +62 -0
- chronaxis/guide/topics/expressions.md +272 -0
- chronaxis/guide/topics/fast-energy.md +22 -0
- chronaxis/guide/topics/integrators.md +8 -0
- chronaxis/guide/topics/message-calculations.md +93 -0
- chronaxis/guide/topics/message-example.md +10 -0
- chronaxis/guide/topics/power-statistics-results.md +43 -0
- chronaxis/guide/topics/power-statistics.md +29 -0
- chronaxis/guide/workflow.md +10 -2
- chronaxis/planning/__init__.py +5 -2
- chronaxis/planning/aggregations.py +115 -0
- chronaxis/planning/channels.py +7 -4
- chronaxis/planning/checks.py +27 -0
- chronaxis/planning/config_groups.py +69 -53
- chronaxis/planning/dbc_defaults.py +125 -9
- chronaxis/planning/diagnostic_collection.py +358 -0
- chronaxis/planning/input_defaults.py +5 -0
- chronaxis/planning/input_requirements.py +14 -5
- chronaxis/planning/inputs.py +241 -211
- chronaxis/planning/intervals.py +12 -1
- chronaxis/planning/lookups.py +33 -0
- chronaxis/planning/message_calculations.py +71 -62
- chronaxis/planning/models.py +4 -1
- chronaxis/planning/nodes.py +269 -199
- chronaxis/planning/plan.py +190 -79
- chronaxis/planning/record_execution.py +16 -3
- chronaxis/planning/reports.py +83 -5
- chronaxis/planning/sequences.py +17 -9
- chronaxis/planning/signal_groups.py +18 -11
- chronaxis/planning/transforms.py +176 -0
- chronaxis/results/__init__.py +3 -3
- chronaxis/results/aggregation_row.py +78 -0
- chronaxis/results/codec.py +16 -1
- chronaxis/results/export_files.py +64 -19
- chronaxis/results/frame_exports.py +4 -3
- chronaxis/results/manifest.py +35 -5
- chronaxis/results/publication.py +131 -0
- chronaxis/results/readable.py +12 -2
- chronaxis/results/readable_format.py +1 -0
- chronaxis/results/result.py +54 -21
- chronaxis/results/rows.py +5 -2
- chronaxis/results/signal_exports.py +7 -4
- chronaxis/results/spool.py +3 -3
- chronaxis/results/summary.py +30 -2
- chronaxis/runner.py +13 -1
- chronaxis/schemas/analysis.schema.json +9214 -0
- chronaxis/schemas/batch-v1.schema.json +172 -0
- chronaxis/sources/models.py +4 -2
- chronaxis/sources/packing.py +5 -2
- {chronaxis-0.1.0.dev0.dist-info → chronaxis-0.1.0.dev2.dist-info}/METADATA +26 -26
- {chronaxis-0.1.0.dev0.dist-info → chronaxis-0.1.0.dev2.dist-info}/RECORD +409 -436
- chronaxis/configuration/templates/ini-mapping/annotated.yaml +0 -20
- chronaxis/configuration/templates/ini-mapping/minimal.yaml +0 -20
- chronaxis/configuration/templates/shared/source.ini +0 -5
- chronaxis/guide/en/fields/mapping/aliases-all-1.json +0 -91
- chronaxis/guide/en/fields/mapping/analysis-all-1.json +0 -382
- chronaxis/guide/en/fields/mapping/analysis-all-10.json +0 -301
- chronaxis/guide/en/fields/mapping/analysis-all-11.json +0 -312
- chronaxis/guide/en/fields/mapping/analysis-all-12.json +0 -311
- chronaxis/guide/en/fields/mapping/analysis-all-13.json +0 -314
- chronaxis/guide/en/fields/mapping/analysis-all-14.json +0 -310
- chronaxis/guide/en/fields/mapping/analysis-all-15.json +0 -297
- chronaxis/guide/en/fields/mapping/analysis-all-16.json +0 -226
- chronaxis/guide/en/fields/mapping/analysis-all-17.json +0 -230
- chronaxis/guide/en/fields/mapping/analysis-all-18.json +0 -227
- chronaxis/guide/en/fields/mapping/analysis-all-19.json +0 -230
- chronaxis/guide/en/fields/mapping/analysis-all-2.json +0 -323
- chronaxis/guide/en/fields/mapping/analysis-all-20.json +0 -221
- chronaxis/guide/en/fields/mapping/analysis-all-21.json +0 -237
- chronaxis/guide/en/fields/mapping/analysis-all-22.json +0 -233
- chronaxis/guide/en/fields/mapping/analysis-all-23.json +0 -222
- chronaxis/guide/en/fields/mapping/analysis-all-24.json +0 -231
- chronaxis/guide/en/fields/mapping/analysis-all-25.json +0 -225
- chronaxis/guide/en/fields/mapping/analysis-all-26.json +0 -230
- chronaxis/guide/en/fields/mapping/analysis-all-27.json +0 -228
- chronaxis/guide/en/fields/mapping/analysis-all-28.json +0 -230
- chronaxis/guide/en/fields/mapping/analysis-all-29.json +0 -223
- chronaxis/guide/en/fields/mapping/analysis-all-3.json +0 -318
- chronaxis/guide/en/fields/mapping/analysis-all-30.json +0 -233
- chronaxis/guide/en/fields/mapping/analysis-all-31.json +0 -218
- chronaxis/guide/en/fields/mapping/analysis-all-32.json +0 -239
- chronaxis/guide/en/fields/mapping/analysis-all-33.json +0 -233
- chronaxis/guide/en/fields/mapping/analysis-all-34.json +0 -225
- chronaxis/guide/en/fields/mapping/analysis-all-35.json +0 -228
- chronaxis/guide/en/fields/mapping/analysis-all-36.json +0 -226
- chronaxis/guide/en/fields/mapping/analysis-all-37.json +0 -244
- chronaxis/guide/en/fields/mapping/analysis-all-38.json +0 -326
- chronaxis/guide/en/fields/mapping/analysis-all-39.json +0 -299
- chronaxis/guide/en/fields/mapping/analysis-all-4.json +0 -297
- chronaxis/guide/en/fields/mapping/analysis-all-40.json +0 -311
- chronaxis/guide/en/fields/mapping/analysis-all-41.json +0 -310
- chronaxis/guide/en/fields/mapping/analysis-all-42.json +0 -346
- chronaxis/guide/en/fields/mapping/analysis-all-43.json +0 -267
- chronaxis/guide/en/fields/mapping/analysis-all-44.json +0 -327
- chronaxis/guide/en/fields/mapping/analysis-all-45.json +0 -279
- chronaxis/guide/en/fields/mapping/analysis-all-46.json +0 -327
- chronaxis/guide/en/fields/mapping/analysis-all-47.json +0 -312
- chronaxis/guide/en/fields/mapping/analysis-all-48.json +0 -294
- chronaxis/guide/en/fields/mapping/analysis-all-49.json +0 -253
- chronaxis/guide/en/fields/mapping/analysis-all-5.json +0 -312
- chronaxis/guide/en/fields/mapping/analysis-all-50.json +0 -346
- chronaxis/guide/en/fields/mapping/analysis-all-51.json +0 -324
- chronaxis/guide/en/fields/mapping/analysis-all-52.json +0 -335
- chronaxis/guide/en/fields/mapping/analysis-all-53.json +0 -317
- chronaxis/guide/en/fields/mapping/analysis-all-54.json +0 -316
- chronaxis/guide/en/fields/mapping/analysis-all-55.json +0 -302
- chronaxis/guide/en/fields/mapping/analysis-all-56.json +0 -319
- chronaxis/guide/en/fields/mapping/analysis-all-57.json +0 -334
- chronaxis/guide/en/fields/mapping/analysis-all-58.json +0 -333
- chronaxis/guide/en/fields/mapping/analysis-all-59.json +0 -359
- chronaxis/guide/en/fields/mapping/analysis-all-6.json +0 -327
- chronaxis/guide/en/fields/mapping/analysis-all-60.json +0 -303
- chronaxis/guide/en/fields/mapping/analysis-all-61.json +0 -326
- chronaxis/guide/en/fields/mapping/analysis-all-64.json +0 -367
- chronaxis/guide/en/fields/mapping/analysis-all-65.json +0 -334
- chronaxis/guide/en/fields/mapping/analysis-all-66.json +0 -358
- chronaxis/guide/en/fields/mapping/analysis-all-67.json +0 -349
- chronaxis/guide/en/fields/mapping/analysis-all-68.json +0 -331
- chronaxis/guide/en/fields/mapping/analysis-all-69.json +0 -304
- chronaxis/guide/en/fields/mapping/analysis-all-7.json +0 -303
- chronaxis/guide/en/fields/mapping/analysis-all-70.json +0 -349
- chronaxis/guide/en/fields/mapping/analysis-all-71.json +0 -288
- chronaxis/guide/en/fields/mapping/analysis-all-72.json +0 -316
- chronaxis/guide/en/fields/mapping/analysis-all-73.json +0 -266
- chronaxis/guide/en/fields/mapping/analysis-all-74.json +0 -227
- chronaxis/guide/en/fields/mapping/analysis-all-75.json +0 -239
- chronaxis/guide/en/fields/mapping/analysis-all-76.json +0 -274
- chronaxis/guide/en/fields/mapping/analysis-all-77.json +0 -380
- chronaxis/guide/en/fields/mapping/analysis-all-78.json +0 -313
- chronaxis/guide/en/fields/mapping/analysis-all-79.json +0 -338
- chronaxis/guide/en/fields/mapping/analysis-all-8.json +0 -292
- chronaxis/guide/en/fields/mapping/analysis-all-81.json +0 -140
- chronaxis/guide/en/fields/mapping/analysis-all-9.json +0 -332
- chronaxis/guide/en/fields/mapping/io_policy-all-1.json +0 -19
- chronaxis/guide/en/fields/mapping/mapping_version-all-1.json +0 -19
- chronaxis/guide/en/fields/mapping/path_base-all-1.json +0 -19
- chronaxis/guide/en/fields/mapping/sections-all-1.json +0 -402
- chronaxis/guide/en/fields/mapping/sections-all-2.json +0 -232
- chronaxis/guide/fields/mapping/aliases-all-1.json +0 -91
- chronaxis/guide/fields/mapping/analysis-all-1.json +0 -382
- chronaxis/guide/fields/mapping/analysis-all-10.json +0 -307
- chronaxis/guide/fields/mapping/analysis-all-11.json +0 -311
- chronaxis/guide/fields/mapping/analysis-all-12.json +0 -329
- chronaxis/guide/fields/mapping/analysis-all-13.json +0 -294
- chronaxis/guide/fields/mapping/analysis-all-14.json +0 -311
- chronaxis/guide/fields/mapping/analysis-all-15.json +0 -280
- chronaxis/guide/fields/mapping/analysis-all-16.json +0 -227
- chronaxis/guide/fields/mapping/analysis-all-17.json +0 -230
- chronaxis/guide/fields/mapping/analysis-all-18.json +0 -221
- chronaxis/guide/fields/mapping/analysis-all-19.json +0 -237
- chronaxis/guide/fields/mapping/analysis-all-2.json +0 -342
- chronaxis/guide/fields/mapping/analysis-all-20.json +0 -233
- chronaxis/guide/fields/mapping/analysis-all-21.json +0 -244
- chronaxis/guide/fields/mapping/analysis-all-22.json +0 -228
- chronaxis/guide/fields/mapping/analysis-all-23.json +0 -226
- chronaxis/guide/fields/mapping/analysis-all-24.json +0 -230
- chronaxis/guide/fields/mapping/analysis-all-25.json +0 -227
- chronaxis/guide/fields/mapping/analysis-all-26.json +0 -230
- chronaxis/guide/fields/mapping/analysis-all-27.json +0 -221
- chronaxis/guide/fields/mapping/analysis-all-28.json +0 -237
- chronaxis/guide/fields/mapping/analysis-all-29.json +0 -233
- chronaxis/guide/fields/mapping/analysis-all-3.json +0 -322
- chronaxis/guide/fields/mapping/analysis-all-30.json +0 -244
- chronaxis/guide/fields/mapping/analysis-all-31.json +0 -228
- chronaxis/guide/fields/mapping/analysis-all-32.json +0 -226
- chronaxis/guide/fields/mapping/analysis-all-33.json +0 -230
- chronaxis/guide/fields/mapping/analysis-all-34.json +0 -227
- chronaxis/guide/fields/mapping/analysis-all-35.json +0 -230
- chronaxis/guide/fields/mapping/analysis-all-36.json +0 -221
- chronaxis/guide/fields/mapping/analysis-all-37.json +0 -291
- chronaxis/guide/fields/mapping/analysis-all-38.json +0 -315
- chronaxis/guide/fields/mapping/analysis-all-39.json +0 -304
- chronaxis/guide/fields/mapping/analysis-all-4.json +0 -312
- chronaxis/guide/fields/mapping/analysis-all-40.json +0 -307
- chronaxis/guide/fields/mapping/analysis-all-41.json +0 -292
- chronaxis/guide/fields/mapping/analysis-all-42.json +0 -333
- chronaxis/guide/fields/mapping/analysis-all-43.json +0 -296
- chronaxis/guide/fields/mapping/analysis-all-44.json +0 -322
- chronaxis/guide/fields/mapping/analysis-all-45.json +0 -298
- chronaxis/guide/fields/mapping/analysis-all-46.json +0 -347
- chronaxis/guide/fields/mapping/analysis-all-47.json +0 -302
- chronaxis/guide/fields/mapping/analysis-all-48.json +0 -263
- chronaxis/guide/fields/mapping/analysis-all-49.json +0 -301
- chronaxis/guide/fields/mapping/analysis-all-5.json +0 -313
- chronaxis/guide/fields/mapping/analysis-all-50.json +0 -336
- chronaxis/guide/fields/mapping/analysis-all-51.json +0 -335
- chronaxis/guide/fields/mapping/analysis-all-52.json +0 -335
- chronaxis/guide/fields/mapping/analysis-all-53.json +0 -334
- chronaxis/guide/fields/mapping/analysis-all-54.json +0 -339
- chronaxis/guide/fields/mapping/analysis-all-55.json +0 -335
- chronaxis/guide/fields/mapping/analysis-all-56.json +0 -336
- chronaxis/guide/fields/mapping/analysis-all-57.json +0 -335
- chronaxis/guide/fields/mapping/analysis-all-58.json +0 -333
- chronaxis/guide/fields/mapping/analysis-all-59.json +0 -325
- chronaxis/guide/fields/mapping/analysis-all-6.json +0 -323
- chronaxis/guide/fields/mapping/analysis-all-60.json +0 -334
- chronaxis/guide/fields/mapping/analysis-all-61.json +0 -311
- chronaxis/guide/fields/mapping/analysis-all-63.json +0 -383
- chronaxis/guide/fields/mapping/analysis-all-64.json +0 -357
- chronaxis/guide/fields/mapping/analysis-all-65.json +0 -375
- chronaxis/guide/fields/mapping/analysis-all-66.json +0 -351
- chronaxis/guide/fields/mapping/analysis-all-67.json +0 -346
- chronaxis/guide/fields/mapping/analysis-all-68.json +0 -317
- chronaxis/guide/fields/mapping/analysis-all-69.json +0 -313
- chronaxis/guide/fields/mapping/analysis-all-7.json +0 -306
- chronaxis/guide/fields/mapping/analysis-all-70.json +0 -326
- chronaxis/guide/fields/mapping/analysis-all-71.json +0 -316
- chronaxis/guide/fields/mapping/analysis-all-72.json +0 -264
- chronaxis/guide/fields/mapping/analysis-all-73.json +0 -228
- chronaxis/guide/fields/mapping/analysis-all-74.json +0 -241
- chronaxis/guide/fields/mapping/analysis-all-75.json +0 -273
- chronaxis/guide/fields/mapping/analysis-all-76.json +0 -382
- chronaxis/guide/fields/mapping/analysis-all-77.json +0 -328
- chronaxis/guide/fields/mapping/analysis-all-78.json +0 -338
- chronaxis/guide/fields/mapping/analysis-all-8.json +0 -309
- chronaxis/guide/fields/mapping/analysis-all-80.json +0 -140
- chronaxis/guide/fields/mapping/analysis-all-9.json +0 -329
- chronaxis/guide/fields/mapping/io_policy-all-1.json +0 -19
- chronaxis/guide/fields/mapping/mapping_version-all-1.json +0 -19
- chronaxis/guide/fields/mapping/path_base-all-1.json +0 -19
- chronaxis/guide/fields/mapping/sections-all-1.json +0 -402
- chronaxis/guide/fields/mapping/sections-all-2.json +0 -232
- chronaxis/guide/schemas/legacy-import-v1.schema.json +0 -143
- chronaxis/migration/__init__.py +0 -11
- chronaxis/migration/api.py +0 -270
- chronaxis/migration/ini.py +0 -110
- chronaxis/migration/legacy_sum.py +0 -304
- chronaxis/migration/models.py +0 -83
- chronaxis/migration/publication.py +0 -230
- chronaxis/migration/sclicer.py +0 -241
- chronaxis/migration/slope.py +0 -78
- chronaxis/migration/start_end.py +0 -48
- {chronaxis-0.1.0.dev0.dist-info → chronaxis-0.1.0.dev2.dist-info}/WHEEL +0 -0
- {chronaxis-0.1.0.dev0.dist-info → chronaxis-0.1.0.dev2.dist-info}/entry_points.txt +0 -0
- {chronaxis-0.1.0.dev0.dist-info → chronaxis-0.1.0.dev2.dist-info}/licenses/LICENSE +0 -0
- {chronaxis-0.1.0.dev0.dist-info → chronaxis-0.1.0.dev2.dist-info}/top_level.txt +0 -0
chronaxis/__init__.py
CHANGED
|
@@ -24,7 +24,7 @@ __all__ = [
|
|
|
24
24
|
"parse_datetime_ns", "parse_duration", "shift_times_reference", "time_deltas_reference",
|
|
25
25
|
"AnalysisPlan", "PlanValidationError", "load_analysis_plan", "validate_analysis_plan", "explain_analysis_plan",
|
|
26
26
|
"PresetSpec", "PlanFragment", "build_preset", "resolve_preset_roles",
|
|
27
|
-
"AnalysisResult", "OutputManifest", "RunMetadata", "AnalysisError", "ResultClosedError",
|
|
27
|
+
"AnalysisResult", "OutputManifest", "ExportManifest", "RunMetadata", "AnalysisError", "ResultClosedError",
|
|
28
28
|
"AnalysisRunner",
|
|
29
29
|
"BatchJob", "BatchJobResult", "BatchReport", "BatchRunner", "BatchPlan", "load_batch_plan", "run_batch",
|
|
30
30
|
]
|
|
@@ -52,10 +52,10 @@ def __getattr__(name: str):
|
|
|
52
52
|
# 取得类本身不创建session或输入;validate/explain仍只消费冻结计划声明。
|
|
53
53
|
from .runner import AnalysisRunner
|
|
54
54
|
return AnalysisRunner
|
|
55
|
-
if name in ('AnalysisResult', 'OutputManifest', 'RunMetadata', 'AnalysisError', 'ResultClosedError'):
|
|
55
|
+
if name in ('AnalysisResult', 'OutputManifest', 'ExportManifest', 'RunMetadata', 'AnalysisError', 'ResultClosedError'):
|
|
56
56
|
# 结果层只依赖标准库和核心模型,访问入口也不启动任何读取或发布。
|
|
57
|
-
from .results import AnalysisResult, OutputManifest, RunMetadata, AnalysisError, ResultClosedError
|
|
58
|
-
return {'AnalysisResult': AnalysisResult, 'OutputManifest': OutputManifest, 'RunMetadata': RunMetadata,
|
|
57
|
+
from .results import AnalysisResult, OutputManifest, ExportManifest, RunMetadata, AnalysisError, ResultClosedError
|
|
58
|
+
return {'AnalysisResult': AnalysisResult, 'OutputManifest': OutputManifest, 'ExportManifest': ExportManifest, 'RunMetadata': RunMetadata,
|
|
59
59
|
'AnalysisError': AnalysisError, 'ResultClosedError': ResultClosedError}[name]
|
|
60
60
|
if name in ('PresetSpec', 'PlanFragment', 'build_preset', 'resolve_preset_roles'):
|
|
61
61
|
# 预设是外部领域构建层,根包普通导入仍仅加载标准库时间协议。
|
chronaxis/__main__.py
CHANGED
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
"""批量工作进程控制消息;只在收到对应任务时加载计算实现。"""
|
|
2
|
+
from dataclasses import replace
|
|
3
|
+
import os
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
import signal
|
|
6
|
+
from threading import Event
|
|
7
|
+
|
|
8
|
+
def _diagnostics(error) -> tuple[str, ...]:
|
|
9
|
+
"""保留有限异常链而不持有栈。参数: error原异常。返回: 文本tuple。异常: 原异常格式错误。"""
|
|
10
|
+
seen, details = set(), []
|
|
11
|
+
while error is not None and id(error) not in seen:
|
|
12
|
+
seen.add(id(error))
|
|
13
|
+
details.append(type(error).__name__ + ': ' + str(error))
|
|
14
|
+
details.extend(getattr(error, '__notes__', ()))
|
|
15
|
+
error = error.__cause__
|
|
16
|
+
return tuple(details)
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def worker_main(connection, fast_cancel=None):
|
|
20
|
+
"""处理run/cleanup/close命令。参数: 私有管道。返回: None。异常: 断管退出,父进程负责树清理。"""
|
|
21
|
+
if fast_cancel is None:
|
|
22
|
+
from .batch.runner import _run_job
|
|
23
|
+
from .batch.process_transport import restore_job
|
|
24
|
+
from .batch.models import BatchJobResult
|
|
25
|
+
# Ctrl+C只由调度主进程接收,正在写结果的工作进程继续完整收尾。
|
|
26
|
+
signal.signal(signal.SIGINT, signal.SIG_IGN)
|
|
27
|
+
recovery = None
|
|
28
|
+
try:
|
|
29
|
+
while True:
|
|
30
|
+
command = connection.recv()
|
|
31
|
+
if command[0] == 'close':
|
|
32
|
+
if recovery is not None:
|
|
33
|
+
recovery.close()
|
|
34
|
+
return
|
|
35
|
+
if command[0] == 'cleanup':
|
|
36
|
+
try:
|
|
37
|
+
if recovery is not None:
|
|
38
|
+
recovery.close()
|
|
39
|
+
recovery = None
|
|
40
|
+
connection.send(('cleaned', None))
|
|
41
|
+
except BaseException as error:
|
|
42
|
+
connection.send(('cleanup_failed', _diagnostics(error)))
|
|
43
|
+
continue
|
|
44
|
+
if recovery is not None:
|
|
45
|
+
raise RuntimeError('存在未关闭资源的工作进程不能接收新任务')
|
|
46
|
+
_, packet, directory, backend = command
|
|
47
|
+
if fast_cancel is not None:
|
|
48
|
+
if command[0] != 'fast':
|
|
49
|
+
raise RuntimeError('fast 工作进程收到未知命令')
|
|
50
|
+
from .fast.worker import file_response
|
|
51
|
+
connection.send(file_response(packet, Path(directory), fast_cancel))
|
|
52
|
+
continue
|
|
53
|
+
try:
|
|
54
|
+
job = restore_job(packet)
|
|
55
|
+
outcome = _run_job(job, Path(directory), backend, Event(), Event())
|
|
56
|
+
except BaseException as error:
|
|
57
|
+
outcome = BatchJobResult(packet['job_id'], 'failed',
|
|
58
|
+
plan_path=packet.get('path'), error_type=type(error).__name__,
|
|
59
|
+
error_message=str(error), error_chain=_diagnostics(error))
|
|
60
|
+
recovery = outcome.recovery
|
|
61
|
+
pending = recovery is not None and not recovery.closed
|
|
62
|
+
# 只发送冻结的纯结果字段;CleanupHandle的原资源必须留在拥有它的进程。
|
|
63
|
+
response = ('result', replace(outcome, recovery=None, worker_pid=os.getpid()), pending)
|
|
64
|
+
connection.send(response)
|
|
65
|
+
except EOFError:
|
|
66
|
+
if recovery is not None:
|
|
67
|
+
recovery.close()
|
|
68
|
+
finally:
|
|
69
|
+
connection.close()
|
chronaxis/analysis_cli.py
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
validate和explain直接调用同一AnalysisRunner接口,不读取CSV、DBC或旧日志,不建立
|
|
5
5
|
运行目录。序列化逐项选择公开报告字段,不遍历任意对象或另做一套配置校验。
|
|
6
6
|
报告中的预算和有效期是声明,execution_ready不证明实际输入已核实。
|
|
7
|
-
run
|
|
7
|
+
run先检查输出根与独占工作路径,再交给AnalysisRunner运行,在同一结果上write并关闭。
|
|
8
8
|
只有真实关闭成功才打印成功摘要;失败保留原原因链和现存产物,不假报回滚。
|
|
9
9
|
|
|
10
10
|
本模块仅在实际计划命令时加载;CLI帮助与参数错误仍只需要标准库。
|
|
@@ -95,7 +95,7 @@ def run_static(arguments) -> int:
|
|
|
95
95
|
|
|
96
96
|
|
|
97
97
|
def _new_output(arguments) -> Path:
|
|
98
|
-
"""
|
|
98
|
+
"""在扫描来源前检查可复用输出根、历史目录和冲突工作路径。
|
|
99
99
|
|
|
100
100
|
参数: arguments包含output和可选work_dir,由argparse限定为路径文本。
|
|
101
101
|
返回: 绝对输出Path;不创建目录,工作目录实际分配仍归AnalysisRunner。
|
|
@@ -115,12 +115,14 @@ def _new_output(arguments) -> Path:
|
|
|
115
115
|
raise PlanValidationError('$.cli.work_dir', '工作目录不能等于输出目录或位于输出目录内部')
|
|
116
116
|
for path in candidates:
|
|
117
117
|
try:
|
|
118
|
-
#
|
|
118
|
+
# 工作路径必须新建;输出允许已有目录,悬空链接不允许。
|
|
119
119
|
# 最终输出安装的竞态仍由原write检查,运行会话也保留原工作目录检查。
|
|
120
120
|
path.lstat()
|
|
121
121
|
except FileNotFoundError:
|
|
122
122
|
continue
|
|
123
|
-
|
|
123
|
+
if path == output and path.is_dir() and not path.is_symlink():
|
|
124
|
+
continue
|
|
125
|
+
raise FileExistsError('工作目录须新建,输出目标须为目录:' + str(path))
|
|
124
126
|
return output
|
|
125
127
|
|
|
126
128
|
|
|
@@ -143,15 +145,16 @@ def _cause_chain(error) -> tuple:
|
|
|
143
145
|
def run_analysis(arguments) -> int:
|
|
144
146
|
"""复用真实分析和发布,成功关闭所有结果资源后才报告completed。
|
|
145
147
|
|
|
146
|
-
参数: arguments含plan、output、可选backend及work_dir
|
|
148
|
+
参数: arguments含plan、output、可选backend及work_dir;输出目录可复用。
|
|
147
149
|
返回: 成功0;已知分析/发布/关闭失败1,并说明输出可能已经发布。
|
|
148
150
|
异常: 配置/路径错误交主CLI分类;取消原样传播;MemoryError及已知编程错误
|
|
149
151
|
的直接或AnalysisError原因链继续抛出,不伪装成普通业务错误。
|
|
150
|
-
副作用: AnalysisRunner建立专用暂存并读来源,write
|
|
152
|
+
副作用: AnalysisRunner建立专用暂存并读来源,write在输出根新增本次文件;不删除已有产物。
|
|
151
153
|
"""
|
|
152
154
|
plan = load_analysis_plan(arguments.plan)
|
|
153
155
|
output = _new_output(arguments)
|
|
154
156
|
runner = AnalysisRunner(backend=arguments.backend)
|
|
157
|
+
manifest = None
|
|
155
158
|
try:
|
|
156
159
|
# 结果拥有其真实仓和资源;write不重新运行来源,退出上下文完成实际close。
|
|
157
160
|
with runner.run(plan, work_dir=arguments.work_dir) as result:
|
|
@@ -172,6 +175,11 @@ def run_analysis(arguments) -> int:
|
|
|
172
175
|
if any(isinstance(item, (MemoryError, TypeError, AttributeError, AssertionError, NameError)) for item in chain):
|
|
173
176
|
raise
|
|
174
177
|
causes = ' <- '.join(type(item).__name__ + ': ' + str(item).replace('\n', ' ') for item in chain)
|
|
178
|
+
completed = manifest or next((getattr(item, 'completed_manifest') for item in chain
|
|
179
|
+
if getattr(item, 'completed_manifest', None) is not None), None)
|
|
180
|
+
if completed is not None:
|
|
181
|
+
print('结果已发布,清单:' + str(completed.manifest_path) + ';收尾失败:' + causes, file=sys.stderr)
|
|
182
|
+
return 1
|
|
175
183
|
# 目录可能已安装后才发生close错误,非零退出不证明输出被撤销。
|
|
176
184
|
print('分析或发布失败(输出可能已发布,请核对目标目录 ' + str(output) + '):' + causes,
|
|
177
185
|
file=sys.stderr)
|
chronaxis/batch/api.py
CHANGED
|
@@ -13,7 +13,7 @@ from .publication import write_batch_report
|
|
|
13
13
|
def run_batch(plan: BatchPlan, output_dir, *, process_count: int | None = None, cancel_event=None, device_label='pending'):
|
|
14
14
|
"""运行并发布批量报告。
|
|
15
15
|
|
|
16
|
-
参数: plan为load_batch_plan取得的BatchPlan,规定本次独立任务及调度设置;process_count为可选正整数覆盖,None沿用计划;output_dir
|
|
16
|
+
参数: plan为load_batch_plan取得的BatchPlan,规定本次独立任务及调度设置;process_count为可选正整数覆盖,None沿用计划;output_dir可复用目录;cancel_event为可选Event;device_label为实际标签。
|
|
17
17
|
返回: BatchReport;failed/cancelled保留在报告,不伪装全部成功。
|
|
18
18
|
异常: TypeError为非BatchPlan或取消对象类型不符;ValueError为无效并发、后端或
|
|
19
19
|
任务声明;目录创建、调度、报告发布错误按原类型传播。单任务失败记录在jobs中,
|
|
@@ -33,14 +33,21 @@ def run_batch(plan: BatchPlan, output_dir, *, process_count: int | None = None,
|
|
|
33
33
|
report = replace(report, infrastructure_error=type(error).__name__ + ': ' + str(error))
|
|
34
34
|
error.batch_report = report
|
|
35
35
|
try:
|
|
36
|
-
write_batch_report(report)
|
|
36
|
+
report = replace(report, report_path=write_batch_report(report))
|
|
37
|
+
error.batch_report = report
|
|
37
38
|
except BaseException as diagnostic:
|
|
38
|
-
|
|
39
|
+
if hasattr(diagnostic, 'published_report_path'):
|
|
40
|
+
report = replace(report, report_path=diagnostic.published_report_path)
|
|
41
|
+
error.batch_report = report
|
|
42
|
+
error.add_note(('批量报告已发布但收尾失败: ' if hasattr(diagnostic, 'published_report_path')
|
|
43
|
+
else '批量失败报告未发布: ') + type(diagnostic).__name__)
|
|
39
44
|
raise
|
|
40
45
|
try:
|
|
41
|
-
write_batch_report(report)
|
|
46
|
+
report = replace(report, report_path=write_batch_report(report))
|
|
42
47
|
except BaseException as error:
|
|
43
48
|
# 已完成任务绝不因汇总文件失败而丢失,未清理资源仍可从原异常恢复。
|
|
49
|
+
if hasattr(error, 'published_report_path'):
|
|
50
|
+
report = replace(report, report_path=error.published_report_path)
|
|
44
51
|
error.batch_report = report
|
|
45
52
|
raise
|
|
46
53
|
return report
|
chronaxis/batch/cli.py
CHANGED
|
@@ -41,7 +41,7 @@ def run_batch_cli(arguments) -> int:
|
|
|
41
41
|
print('批量结果清理失败:' + type(error).__name__ + ': ' + str(error), file=sys.stderr)
|
|
42
42
|
document = report.to_dict()
|
|
43
43
|
print(json.dumps(dict(status=report.status, counts=document['counts'],
|
|
44
|
-
output=str(report.output_dir), report=str(report.
|
|
44
|
+
output=str(report.output_dir), report=str(report.report_path),
|
|
45
45
|
cleanup_pending=cleanup_failed, device_label=label), ensure_ascii=False, allow_nan=False))
|
|
46
46
|
if cleanup_failed or report.status == 'failed':
|
|
47
47
|
return 1
|
chronaxis/batch/models.py
CHANGED
|
@@ -84,6 +84,23 @@ class BatchPlan:
|
|
|
84
84
|
返回: 冻结BatchPlan。异常: PlanValidationError定位字段;不读取业务报文,模板可读取DBC元数据。
|
|
85
85
|
"""
|
|
86
86
|
from ..planning.checks import object_at, array_at, text_at, model_at, PlanValidationError, number_at, choice_at
|
|
87
|
+
from ..planning.diagnostic_collection import DiagnosticCollection
|
|
88
|
+
from ..planning.checks import require_json_value
|
|
89
|
+
require_json_value(config, '$')
|
|
90
|
+
diagnostics = DiagnosticCollection(config, 'batch-v1.schema.json')
|
|
91
|
+
names = set()
|
|
92
|
+
if type(config) is dict and type(config.get('jobs')) is list:
|
|
93
|
+
for index, job in enumerate(config['jobs']):
|
|
94
|
+
path = f'$.jobs[{index}]'
|
|
95
|
+
if type(job) is not dict or not diagnostics.usable(path):
|
|
96
|
+
continue
|
|
97
|
+
name = job.get('name', f'job_{index+1}').casefold()
|
|
98
|
+
if name in names:
|
|
99
|
+
diagnostics.add(PlanValidationError(path+'.name', '任务名称不能重复或仅大小写不同',
|
|
100
|
+
'duplicate_id', hint='为每个独立任务指定唯一name'))
|
|
101
|
+
names.add(name)
|
|
102
|
+
# 批量自身结构一次报告;jobs子计划仍在工作进程启动时才加载。
|
|
103
|
+
diagnostics.finish()
|
|
87
104
|
if type(config) is dict and ('workers' in config or 'max_workers' in config):
|
|
88
105
|
raise PlanValidationError('$.process_count', '请使用process_count设置分析工作进程数;workers/max_workers不再接受')
|
|
89
106
|
raw = object_at(config, '$', ('batch_version',), ('jobs', 'template', 'inputs', 'source', 'process_count', 'fail_fast', 'backend'))
|
|
@@ -129,6 +146,7 @@ class BatchJobResult:
|
|
|
129
146
|
input_identity: str | None = None # 只有实际分析返回后才记录。
|
|
130
147
|
run_id: str | None = None # 同计划重跑仍有独立运行身份。
|
|
131
148
|
output_dir: Path | None = None # 实际发布成功的目录,不预填未生成输出。
|
|
149
|
+
manifest_path: Path | None = None # 本次任务实际完成清单,不猜固定文件名。
|
|
132
150
|
error_type: str | None = None # 原异常类型或未启动取消原因。
|
|
133
151
|
error_message: str | None = None # 原错误说明,不用成功值覆盖失败。
|
|
134
152
|
error_chain: tuple[str, ...] = () # cause链和清理notes,保留错误来源。
|
|
@@ -151,6 +169,7 @@ class BatchJobResult:
|
|
|
151
169
|
'elapsed_seconds', 'memory_budget_bytes', 'worker_pid')
|
|
152
170
|
result = {name: getattr(self, name) for name in names}
|
|
153
171
|
result.update(output_dir=None if self.output_dir is None else str(self.output_dir),
|
|
172
|
+
manifest_path=None if self.manifest_path is None else str(self.manifest_path),
|
|
154
173
|
error_chain=list(self.error_chain),
|
|
155
174
|
cleanup_pending=self.recovery is not None and not self.recovery.closed)
|
|
156
175
|
return result
|
|
@@ -160,7 +179,7 @@ class BatchJobResult:
|
|
|
160
179
|
class BatchReport:
|
|
161
180
|
"""按原任务顺序汇总结果;每个任务数值表仍在各自目录,不跨口径归约。"""
|
|
162
181
|
|
|
163
|
-
output_dir: Path #
|
|
182
|
+
output_dir: Path # 可复用的输出根,任务子目录和报告按本批时间区分。
|
|
164
183
|
jobs: tuple[BatchJobResult, ...] # 最终结果顺序不受工作进程完成先后影响。
|
|
165
184
|
process_count: int # 本次配置的工作进程上限。
|
|
166
185
|
fail_fast: bool # 是否在观察到失败后停止派发。
|
|
@@ -168,6 +187,8 @@ class BatchReport:
|
|
|
168
187
|
device_label: str # 调用方明确提供;缺省pending,不猜测机器。
|
|
169
188
|
worker_process_count: int = 0 # 本次实际启动的工作进程数,空批次为0。
|
|
170
189
|
infrastructure_error: str | None = None # 调度或汇总基础设施错误,与任务业务失败分开。
|
|
190
|
+
report_path: Path | None = None # 成功发布后返回的实际报告路径。
|
|
191
|
+
_publication: object = field(default=None, repr=False, compare=False) # 持有本批名称,关闭释放。
|
|
171
192
|
|
|
172
193
|
@property
|
|
173
194
|
def status(self) -> str:
|
|
@@ -190,6 +211,11 @@ class BatchReport:
|
|
|
190
211
|
def close(self) -> None:
|
|
191
212
|
"""重试所有未完清理。参数: 无。返回: None。异常: 首个关闭错误,其他项仍尝试。"""
|
|
192
213
|
first = None
|
|
214
|
+
if self._publication is not None:
|
|
215
|
+
try:
|
|
216
|
+
self._publication.close()
|
|
217
|
+
except BaseException as error:
|
|
218
|
+
first = error
|
|
193
219
|
for job in self.jobs:
|
|
194
220
|
if job.recovery is not None:
|
|
195
221
|
try:
|
|
@@ -198,5 +224,7 @@ class BatchReport:
|
|
|
198
224
|
# 一项失败不能阻止其他独立任务释放资源;原句柄仍由本报告保留。
|
|
199
225
|
if first is None:
|
|
200
226
|
first = error
|
|
227
|
+
else:
|
|
228
|
+
first.add_note('其他批量任务清理失败:' + str(error))
|
|
201
229
|
if first is not None:
|
|
202
230
|
raise first
|
|
@@ -6,15 +6,14 @@
|
|
|
6
6
|
|
|
7
7
|
from dataclasses import replace
|
|
8
8
|
import multiprocessing
|
|
9
|
-
import os
|
|
10
9
|
from pathlib import Path
|
|
11
|
-
import
|
|
12
|
-
from threading import Event
|
|
10
|
+
from time import monotonic
|
|
13
11
|
|
|
14
12
|
from ..planning.plan import AnalysisPlan
|
|
15
13
|
from ..planning.checks import json_value
|
|
16
14
|
from .models import BatchJob, BatchJobResult, CleanupHandle
|
|
17
15
|
from .process_tree import ProcessTree
|
|
16
|
+
from .._batch_worker import worker_main
|
|
18
17
|
|
|
19
18
|
|
|
20
19
|
def job_packet(job):
|
|
@@ -35,60 +34,23 @@ def restore_job(packet):
|
|
|
35
34
|
return BatchJob(packet['job_id'], plan)
|
|
36
35
|
|
|
37
36
|
|
|
38
|
-
def worker_main(connection):
|
|
39
|
-
"""处理run/cleanup/close命令。参数: 私有管道。返回: None。异常: 断管退出,父进程负责树清理。"""
|
|
40
|
-
from .runner import _run_job, _diagnostics
|
|
41
|
-
# Ctrl+C只由调度主进程接收,正在写结果的工作进程继续完整收尾。
|
|
42
|
-
signal.signal(signal.SIGINT, signal.SIG_IGN)
|
|
43
|
-
recovery = None
|
|
44
|
-
try:
|
|
45
|
-
while True:
|
|
46
|
-
command = connection.recv()
|
|
47
|
-
if command[0] == 'close':
|
|
48
|
-
if recovery is not None:
|
|
49
|
-
recovery.close()
|
|
50
|
-
return
|
|
51
|
-
if command[0] == 'cleanup':
|
|
52
|
-
try:
|
|
53
|
-
if recovery is not None:
|
|
54
|
-
recovery.close()
|
|
55
|
-
recovery = None
|
|
56
|
-
connection.send(('cleaned', None))
|
|
57
|
-
except BaseException as error:
|
|
58
|
-
connection.send(('cleanup_failed', _diagnostics(error)))
|
|
59
|
-
continue
|
|
60
|
-
if recovery is not None:
|
|
61
|
-
raise RuntimeError('存在未关闭资源的工作进程不能接收新任务')
|
|
62
|
-
_, packet, directory, backend = command
|
|
63
|
-
try:
|
|
64
|
-
job = restore_job(packet)
|
|
65
|
-
outcome = _run_job(job, Path(directory), backend, Event(), Event())
|
|
66
|
-
except BaseException as error:
|
|
67
|
-
outcome = BatchJobResult(packet['job_id'], 'failed',
|
|
68
|
-
plan_path=packet.get('path'), error_type=type(error).__name__,
|
|
69
|
-
error_message=str(error), error_chain=_diagnostics(error))
|
|
70
|
-
recovery = outcome.recovery
|
|
71
|
-
pending = recovery is not None and not recovery.closed
|
|
72
|
-
# 只发送冻结的纯结果字段;CleanupHandle的原资源必须留在拥有它的进程。
|
|
73
|
-
connection.send(('result', replace(outcome, recovery=None, worker_pid=os.getpid()), pending))
|
|
74
|
-
except EOFError:
|
|
75
|
-
if recovery is not None:
|
|
76
|
-
recovery.close()
|
|
77
|
-
finally:
|
|
78
|
-
connection.close()
|
|
79
|
-
|
|
80
|
-
|
|
81
37
|
class ProcessWorker:
|
|
82
38
|
"""保存一个可复用工作进程、私有管道及其后代的清理责任。"""
|
|
83
39
|
|
|
84
|
-
def __init__(self):
|
|
40
|
+
def __init__(self, *, fast=False):
|
|
85
41
|
"""启动等待命令的spawn进程。参数: 无。返回: 新对象。异常: 启动/树登记失败立即回收。"""
|
|
86
42
|
context = multiprocessing.get_context('spawn')
|
|
87
43
|
self.connection, child = context.Pipe()
|
|
88
|
-
self.
|
|
44
|
+
self.fast_cancel = context.Event() if fast else None
|
|
45
|
+
self.cancel_deadline = None
|
|
46
|
+
self.fast_rows = None
|
|
47
|
+
args = (child, self.fast_cancel) if fast else (child,)
|
|
48
|
+
self.process = context.Process(target=worker_main, args=args, daemon=False)
|
|
89
49
|
self.tree = None
|
|
90
50
|
self.closed = False
|
|
91
51
|
self.pending = False
|
|
52
|
+
self._pipe_lost = False
|
|
53
|
+
self._stop_requested = False
|
|
92
54
|
try:
|
|
93
55
|
self.process.start()
|
|
94
56
|
self.tree = ProcessTree(self.process.pid)
|
|
@@ -107,21 +69,49 @@ class ProcessWorker:
|
|
|
107
69
|
"""发送独立任务。参数: job/目录/后端。返回: None。异常: 转换或通信错误。"""
|
|
108
70
|
self.connection.send(('run', job_packet(job), str(directory), backend))
|
|
109
71
|
|
|
72
|
+
def submit_fast(self, job, directory, spec, total):
|
|
73
|
+
"""只传准备完成的文件参数,工作进程不重新解析模板或建立下层进程。"""
|
|
74
|
+
packet = dict(job_id=job.job_id, spec=spec, total=total,
|
|
75
|
+
path=str(job.plan) if isinstance(job.plan, Path) else None)
|
|
76
|
+
self.connection.send(('fast', packet, str(directory), None))
|
|
77
|
+
|
|
78
|
+
def cancel_fast(self):
|
|
79
|
+
"""通知在途文件,协作退出超过五秒才结束本次拥有的进程树。返回是否已强制回收。"""
|
|
80
|
+
if self.cancel_deadline is None:
|
|
81
|
+
self.fast_cancel.set()
|
|
82
|
+
self.cancel_deadline = monotonic() + 5
|
|
83
|
+
elif monotonic() >= self.cancel_deadline:
|
|
84
|
+
self._pipe_lost = True
|
|
85
|
+
self.close()
|
|
86
|
+
return True
|
|
87
|
+
return False
|
|
88
|
+
|
|
110
89
|
def ready(self):
|
|
111
90
|
"""查询结果或异常退出。参数: 无。返回: bool。异常: 原管道错误。"""
|
|
112
91
|
try:
|
|
113
92
|
return self.connection.poll() or not self.process.is_alive()
|
|
114
93
|
except (EOFError, OSError) as error:
|
|
94
|
+
if isinstance(error, (EOFError, BrokenPipeError)):
|
|
95
|
+
self._pipe_lost = True
|
|
115
96
|
raise ChildProcessError('分析工作进程管道已断开,PID=' + str(self.pid)) from error
|
|
116
97
|
|
|
117
98
|
def receive(self):
|
|
118
99
|
"""取结果并挂远程恢复句柄。参数: 无。返回: BatchJobResult。异常: 进程死亡或协议错误。"""
|
|
119
|
-
if not self.connection.poll():
|
|
120
|
-
raise ChildProcessError('分析工作进程异常退出,PID=' + str(self.pid))
|
|
121
100
|
try:
|
|
122
|
-
|
|
123
|
-
|
|
101
|
+
# Windows 同伴退出时,探测或随后的读取都可能先报告断管。
|
|
102
|
+
if not self.connection.poll():
|
|
103
|
+
raise ChildProcessError('分析工作进程异常退出,PID=' + str(self.pid))
|
|
104
|
+
message = self.connection.recv()
|
|
105
|
+
except (EOFError, BrokenPipeError) as error:
|
|
106
|
+
self._pipe_lost = True
|
|
124
107
|
raise ChildProcessError('分析工作进程断开,PID=' + str(self.pid)) from error
|
|
108
|
+
if len(message) == 4 and getattr(self,'fast_cancel',None) is not None:
|
|
109
|
+
kind, result, self.pending, self.fast_rows = message
|
|
110
|
+
else:
|
|
111
|
+
kind, result, self.pending = message
|
|
112
|
+
if kind == 'fast_result' and getattr(self,'fast_cancel',None) is not None and isinstance(result,dict):
|
|
113
|
+
result = BatchJobResult(**result)
|
|
114
|
+
kind = 'result'
|
|
125
115
|
if kind != 'result' or not isinstance(result, BatchJobResult):
|
|
126
116
|
raise RuntimeError('批量工作进程返回无效结果')
|
|
127
117
|
return replace(result, recovery=CleanupHandle(self) if self.pending else None)
|
|
@@ -130,20 +120,36 @@ class ProcessWorker:
|
|
|
130
120
|
"""重试远程清理后关闭进程树。参数: 无。返回: None。异常: 清理失败保留全部所有者。"""
|
|
131
121
|
if self.closed:
|
|
132
122
|
return
|
|
133
|
-
if self.process.is_alive():
|
|
134
|
-
|
|
135
|
-
self.
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
123
|
+
if not self._pipe_lost and not self._stop_requested and self.process.is_alive():
|
|
124
|
+
try:
|
|
125
|
+
if self.pending:
|
|
126
|
+
self.connection.send(('cleanup',))
|
|
127
|
+
kind, detail = self.connection.recv()
|
|
128
|
+
if kind != 'cleaned':
|
|
129
|
+
raise RuntimeError('工作进程资源尚未关闭:' + '; '.join(detail))
|
|
130
|
+
self.pending = False
|
|
131
|
+
self.connection.send(('close',))
|
|
132
|
+
self._stop_requested = True
|
|
133
|
+
except (EOFError, BrokenPipeError):
|
|
134
|
+
self._pipe_lost = True
|
|
135
|
+
if self._pipe_lost:
|
|
136
|
+
# 管道可先于进程退出;无法再握手时由父进程先结束本次拥有的树。
|
|
137
|
+
if not self._stop_requested:
|
|
138
|
+
if self.tree is not None:
|
|
139
|
+
self.tree.close()
|
|
140
|
+
self.tree = None
|
|
141
|
+
elif self.process.is_alive():
|
|
142
|
+
self.process.kill()
|
|
143
|
+
self._stop_requested = True
|
|
144
|
+
self.process.join(timeout=5)
|
|
145
|
+
if self.process.is_alive():
|
|
146
|
+
raise TimeoutError('等待断管工作进程退出超时,PID=' + str(self.pid))
|
|
142
147
|
else:
|
|
143
148
|
self.process.join()
|
|
144
|
-
#
|
|
149
|
+
# 正常停止也需关闭作业,清除残余后代;前一步失败时保留所有者供重试。
|
|
145
150
|
if self.tree is not None:
|
|
146
151
|
self.tree.close()
|
|
152
|
+
self.tree = None
|
|
147
153
|
self.connection.close()
|
|
148
154
|
self.process.close()
|
|
149
|
-
self.closed = True
|
|
155
|
+
self.pending, self.closed = False, True
|
chronaxis/batch/publication.py
CHANGED
|
@@ -7,16 +7,21 @@
|
|
|
7
7
|
import json
|
|
8
8
|
from pathlib import Path
|
|
9
9
|
import platform
|
|
10
|
-
|
|
10
|
+
import tempfile
|
|
11
11
|
from .models import BatchReport
|
|
12
12
|
|
|
13
13
|
|
|
14
14
|
def write_batch_report(report: BatchReport) -> Path:
|
|
15
|
-
"""
|
|
15
|
+
"""发布带本批时间的汇总文件。参数: report实际结果。返回: 实际报告路径。
|
|
16
16
|
|
|
17
17
|
异常: 类型、只读路径、未支持平台、已有目标或原文件错误;关闭失败不安装正式文件。
|
|
18
18
|
副作用: 只写本批目录的唯一暂存文件,失败不删除任务结果或覆盖已有报告。
|
|
19
19
|
"""
|
|
20
|
+
return _write_batch_report(report, {})
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def _write_batch_report(report, extra):
|
|
24
|
+
"""先完成可选的 fast 总量计算,再一次发布同一批报告。"""
|
|
20
25
|
if not isinstance(report, BatchReport):
|
|
21
26
|
raise TypeError('需要BatchReport')
|
|
22
27
|
root = report.output_dir.absolute()
|
|
@@ -25,17 +30,32 @@ def write_batch_report(report: BatchReport) -> Path:
|
|
|
25
30
|
raise ValueError('批量报告不能位于RefProjects')
|
|
26
31
|
if platform.system() != 'Windows':
|
|
27
32
|
raise NotImplementedError('批量报告原子发布目前只验收Windows')
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
else:
|
|
34
|
-
raise FileExistsError('批量报告已存在,拒绝覆盖:' + str(destination))
|
|
35
|
-
temporary = root / ('.batch-report-' + uuid4().hex + '.incomplete.json')
|
|
33
|
+
from ..results.publication import PublicationNames
|
|
34
|
+
publication = report._publication or PublicationNames(root, ('batch_report.json',))
|
|
35
|
+
destination = root / publication.names['batch_report.json']
|
|
36
|
+
from ..results.owned_directory import directory_identity, remove_owned_directory
|
|
37
|
+
temporary = identity = None
|
|
36
38
|
# 编码、写入和真实close全部成功才安装;Windows rename在目标已存在时拒绝。
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
39
|
+
try:
|
|
40
|
+
temporary = Path(tempfile.mkdtemp(prefix='.batch-report-', suffix='.incomplete', dir=root))
|
|
41
|
+
identity = directory_identity(temporary)
|
|
42
|
+
with (temporary/'batch_report.json').open('x', encoding='utf-8', newline='') as stream:
|
|
43
|
+
json.dump(dict(report.to_dict(), **extra), stream, ensure_ascii=False, allow_nan=False, indent=2)
|
|
44
|
+
stream.write('\n')
|
|
45
|
+
publication.publish(temporary, 'batch_report.json', only=('batch_report.json',))
|
|
46
|
+
temporary.rmdir()
|
|
47
|
+
return destination
|
|
48
|
+
except BaseException as error:
|
|
49
|
+
if publication.completed:
|
|
50
|
+
error.published_report_path = destination
|
|
51
|
+
# 不撤销已完成任务;只清理本次报告临时目录,保留最初的写出错误。
|
|
52
|
+
try:
|
|
53
|
+
if temporary is not None and identity is not None:
|
|
54
|
+
remove_owned_directory(temporary, identity)
|
|
55
|
+
except BaseException as cleanup:
|
|
56
|
+
error.add_note('批量报告暂存清理失败:' + str(cleanup))
|
|
57
|
+
try:
|
|
58
|
+
publication.close()
|
|
59
|
+
except BaseException as cleanup:
|
|
60
|
+
error.add_note('批量命名锁释放失败:' + str(cleanup))
|
|
61
|
+
raise
|