Files
RollCalcPython/PROJECT_KNOWLEDGE.md
T

15 KiB
Raw Blame History

PROJECT_KNOWLEDGE.md

Project-specific technical and domain knowledge for RollCalcPython.

Purpose

RollCalcPython is an internal roll calculator for Naue roll products. The backend provides authentication, routing, static file delivery, access logs, and the authoritative direct roll-diameter calculation service. Other modes and the load optimizer still contain client-side behavior in templates/roll_calculator.html.

The tool is not intended as an unchecked source of truth. Inputs and outputs must be checked for plausibility before operational use.

Current Architecture

Backend:

  • app.py creates the Flask app.
  • HTTP Basic Auth is implemented with Flask-HTTPAuth.
  • Users are currently configured in BETA_USERS with Werkzeug password hashes.
  • / renders the active calculator template.
  • /static/<path:filename> is intended to serve static files behind Basic Auth.
  • /api/health returns health/version information.
  • /api/user returns the authenticated user.
  • /api/calculations/roll executes a CalculationRequest through the deterministic core.
  • /api/calculations/roll/modify applies controlled changes to CalculationState and recalculates.
  • /api/calculations/transport executes the shared transport-capacity service.
  • /api/reports/roll-calculation.pdf recalculates a request and renders its authoritative result.
  • /api/conversations creates a process-local conversational session.
  • /api/conversations/<id>/messages runs constrained NLU followed by the existing deterministic calculation functions.
  • /api/conversations/reports/<id>.pdf recalculates a stored input-only request through the existing PDF path.
  • Access events are appended to access_log.json.
  • build_info.json is loaded at startup and exposed to all templates as build_info.

Frontend:

  • templates/roll_calculator.html is the active application page.
  • The page contains most CSS and JavaScript inline.
  • window.ARTICLE_DATA is loaded from /static/article-data.json.
  • window.APP_CONFIG is currently defined inline for forklift rules.
  • A disclaimer modal blocks use until the user checks the confirmation checkbox.
  • The footer displays build metadata: version, branch, commit, and timestamp.
  • A valid direct diameter request/result is retained as window.CURRENT_ROLL_REQUEST / window.CURRENT_ROLL_RESULT until an input changes.

Domain service:

  • roll_calculation.py owns CalculationRequest, CalculationState, ArticleRepository, get_article(), calculate_roll(), and modify_calculation().
  • transport_calculation.py owns analyze_transport() and the canonical active transport presets. The template calls the protected transport API and retains only UI rendering and SVG drawing.
  • Direct diameter ranges, optional weight, and direct-flow forklift warnings originate there.
  • Request parsing rejects calculated diameter, weight, warning, and calculation fields.
  • Natural-language clients may describe or modify a calculation request, but engineering results are generated exclusively by the deterministic Roll Calculator core.

Conversational adapter:

  • ollama_nlu.py calls local Ollama directly through /api/chat, using qwen3.5:35B-A3B, temperature 0, think=false, non-streaming output, and a strict JSON schema by default. ROLLCALC_OLLAMA_MODEL overrides the model name.
  • The NLU schema allows only new_calculation, modify_calculation, and unsupported. Modifications contain one or more explicitly allowed request-field changes, validated and applied atomically.
  • The model receives no formulas, product dataset, engineering results, warnings, or PDF content. After success it receives only a minimal current-input summary for referential follow-ups.
  • conversation_service.py owns CalculationState, the last deterministic result, typed pending clarification context, and temporary input-only report references. Ambiguous core context retains the canonical family and matching CorePreset candidates; invalid replies do not replace the previous valid state.
  • An identifier-free incomplete new_calculation from the model is converted to a follow-up only when a successful current state exists and at least one explicit modification-like value is present. Null fields inherit current inputs; a different article or explicit new-calculation phrase starts fresh.
  • User-facing answers and clarification messages are deterministic templates. Numeric output is formatted only from CalculationResult.
  • The adapter does not apply the browser's visible 150 mm core default. Current master data has no usable core value, so absent core information requires clarification.
  • Ollama/schema/timeout failures do not mutate calculation state.
  • The store is in-memory, process-local, non-persistent, and intended only for the initial demo.

OpenWebUI adapter:

  • integrations/openwebui/rollcalc_pipe.py is a presentation/integration Pipe, not another NLU or calculation service. It calls only the existing conversation endpoints.
  • One process-local mapping from (OpenWebUI user ID, chat ID) to a RollCalc conversation_id preserves follow-up state without reconstructing it from the OpenWebUI transcript. Per-chat asynchronous locks preserve message order.
  • The Pipe renders only the deterministic API message. It validates the report path and creates Markdown links from ROLLCALC_PUBLIC_BASE_URL; it never exposes raw interpretation, result, state, or diagnostic objects.
  • ROLLCALC_API_BASE_URL is the server/container route, whereas ROLLCALC_PUBLIC_BASE_URL is the browser route. They are deliberately separate because container-local hostnames must not leak into PDF links.
  • Server-side API calls use configured Basic Auth. Credentials are never placed in PDF URLs, so users authenticate separately to the existing RollCalc PDF endpoint in their browser.
  • The Pipe's chat mapping is lost on OpenWebUI Function reload/restart. It is not shared by multiple OpenWebUI processes. Deployment and demo details live in integrations/openwebui/README.md.

Build Info

Build/deployment metadata is read from:

build_info.json

Expected fields:

  • version
  • branch
  • commit
  • timestamp

If build_info.json is missing, malformed, or does not contain a usable value, RollCalc falls back to unknown for that value and continues serving the site.

The data is loaded centrally in app.py and made available to every template as build_info.

Authentication

RollCalc uses HTTP Basic Auth. User entries in BETA_USERS have this shape:

"username": {
    "password_hash": "..."
}

Password verification uses werkzeug.security.check_password_hash. New hashes or config snippets can be generated with:

python scripts/manage_users.py username

Passwords and hashes must not be logged.

Roll Geometry

Calculations assume an ideal cylindrical winding.

Assumptions:

  • Constant material thickness.
  • No compression.
  • No air gaps.
  • Roll geometry is derived mathematically, not physically measured in the app.

Nominal diameter formula used by roll_calculation.calculate_roll() and returned to the Direct Calculation tab:

D = sqrt(d^2 + (4 * L * 1000 * t) / pi)

Variables:

  • D: roll diameter in mm.
  • d: core diameter in mm.
  • L: product length in m.
  • t: material thickness in mm.

When a tolerance/stddev value is present, the UI also displays a -2σ and +2σ diameter range.

Product Length and Extrapolation

Product length from a known diameter is calculated by rearranging the diameter formula:

L = (pi / (4 * t)) * (D^2 - d^2) / 1000

The extrapolation tab estimates a new diameter from a known roll diameter/length pair:

D1 = sqrt(d^2 + (L1 / L0) * (D0^2 - d^2))

Variables:

  • D0: known/measured roll diameter.
  • L0: known/measured roll length.
  • L1: target length.
  • D1: extrapolated roll diameter.

Roll Weight

For the authoritative direct roll-diameter flow, roll weight is calculated server-side when requested and width, area weight, and length are available:

weight_kg = area_weight_g_m2 * length_m * width_m / 1000

Material thickness and area weight values are loaded from article data and are generated automatically. They are not guaranteed to be complete or reviewed.

PDF Reports

The browser and PDF endpoints both submit a CalculationRequest. The PDF endpoint recalculates through calculate_roll() and passes its CalculationResult through report_from_calculation_result(); it never accepts client-supplied result fields. Minimum, average, maximum, effective roll length, optional weight, warnings, notes, effective inputs, and provenance therefore come from the same core.

POST /api/reports/roll-calculation.pdf is protected by the existing HTTP Basic Auth. Incomplete, conflicting, or invalid calculation requests return HTTP 400 rather than a document. Generated filenames include date and time without colon characters.

pdf_report.py uses no third-party package. It creates one A4 page with a compact header, result cards, two-column input area, warnings/notes, and footer. Repeated diagonal INTERNAL USE ONLY text is drawn into the actual page content stream with reduced opacity; it is a marking/deterrence mechanism and is not tamper-proof or DRM.

Direct-flow warnings are returned by the domain core and rendered by both consumers. checkForklift() remains only for the existing non-headless length/target UI modes. Build metadata and generation time are supplied server-side.

Request Resolution and Clarification

  • Article numbers are strings and exact matches take precedence.
  • Duplicate records for one exact article number require clarification; file order never decides the result.
  • A supplied name hint is validated conservatively; a clear mismatch produces article_conflict.
  • Article-name normalization in roll_calculation.py maps only the product-family tokens Stex/Secutex, Bfix/Bentofix, and Sgrid/Secugrid to their abbreviated canonical forms before deterministic matching. The mappings are token-bounded and do not alter article-data.json.
  • Unknown numbers/names produce article_not_found; duplicate exact names require clarification.
  • The service may extract one unambiguous width from standardized article-name dimensions and marks its source as article_master_data.
  • Required absent values produce status = needs_clarification with missing fields.
  • Canonical core presets live in core_presets.py and are rendered into the web UI as well as consumed by the headless resolver. The ordered list remains 133 mm steel, 150 mm PVC, 168 mm PVC, and 194 mm steel.
  • Complete preset labels and finite explicit German/English aliases resolve canonically. Generic steel/Stahlkern and PVC/plastic descriptions remain ambiguous because both families contain two presets; unknown descriptions never select a diameter.
  • CalculationState stores structured request fields. Mutations change only validated fields and always recalculate.
  • Unknown state fields, including caller-supplied calculation results, are rejected rather than ignored.

Article Data

Active data source:

static/article-data.json

The UI expects article records with fields such as:

  • nr
  • name
  • thickness
  • thickness_stddev
  • area_weight
  • area_weight_stddev
  • core_type

Observed additional fields include min/max/count values for material thickness and area weight.

Important data rule:

  • Article data is generated from ERP-derived sources.
  • These values should not be manually changed unless the task is explicitly data maintenance.

Known domain caveat from existing project notes:

  • For articles in the groups Secugrid, Combigrid, Carbofol MF/MF, Carbofol F/F, Carbofol s/F, and Carbofol BF/s, the thickness from article-data.json must not be used without domain validation.

Forklift and Heavy-Roll Rules

The direct roll-diameter service evaluates the following active behavior. Other UI modes still evaluate the equivalent inline window.APP_CONFIG rules through checkForklift().

Current inline rule behavior:

  • Applies to category bentofix.
  • Warning threshold: 1700 kg.
  • Hard limit: 2750 kg.
  • Minimum core outer diameter: 170 mm.

There is also a richer JSON configuration in:

static/config.json

That file includes localized messages and additional equipment requirements. Before changing forklift behavior, verify whether the active inline config or the static JSON should be authoritative.

Load Optimizer

transport_calculation.py is authoritative for transport presets and capacity analysis through analyze_transport(). The active template calls the protected transport endpoint and only collects inputs, renders returned values, and draws the SVG front view.

It uses displayed roll data from the Direct Calculation tab:

  • Roll diameter.
  • Core diameter.
  • Roll width.
  • Roll weight.
  • Product length.
  • Square meters per roll.

It supports transport presets:

  • 20ft Container.
  • 40ft Container.
  • 40ft High Cube.
  • LKW Sattelzug.
  • LKW Tandem.

It calculates limiting scenarios by:

  • Weight.
  • Volume.
  • Geometry.

It also draws a side-view SVG visualization.

Disclaimer Gate

The active calculator page includes a disclaimer modal shown after Basic Auth login and before calculator use.

The modal:

  • Contains German and English disclaimer text.
  • Requires checkbox confirmation.
  • Enables the confirmation button only after the checkbox is selected.
  • Hides the overlay after confirmation.

This is client-side only. It is not persisted, logged, or enforced server-side.

Admin Area State

RollCalc no longer contains an admin UI or admin API. The previous /admin/logs route and templates/admin.html have been removed.

Login/access logging remains part of RollCalc, but log viewing and article-data administration should not be implemented inside this app. Maintenance of article-data.json is planned for a separate application.

Known Technical Risks

  • Password hashes are currently configured in app.py; this should eventually move to a protected external configuration or secrets mechanism.
  • Basic Auth only; no sessions or role framework beyond the user dictionary.
  • access_log.json is rewritten on every logged request and is not concurrency-safe.
  • No log rotation or retention policy is implemented.
  • Flask's default static route may conflict with the intended authenticated /static/<path:filename> behavior; verify effective routing before relying on protected static files.
  • Most active logic is concentrated in one large HTML template.
  • Multiple similar static JavaScript files exist; not all are necessarily active.
  • The deterministic service, state mutation, template integration, PDF adapter/renderer, and Flask endpoints have standard-library unittest coverage under tests/.
  • Some docs and filenames in docs/ may reflect earlier package/archive versions.
  • Conversation sessions and report references are lost on restart and are not shared across WSGI workers.
  • Ollama availability/model installation is external runtime state; normal tests use mocks and do not require it.