Files
generalFunctions/wiki/Home.md
2026-05-19 09:30:26 +02:00

27 KiB
Raw Blame History

generalFunctions

Reflects code as of f21e2aa · regenerated 2026-05-11 (hand-written) No npm run wiki:all script exists for this library. The API surface block (section 5) is hand-maintained between the AUTOGEN markers. If the banner is stale, treat this page as informative, not authoritative.


1. What this library is

generalFunctions is the shared infrastructure every EVOLV node depends on. It provides the base classes all nodes extend (BaseDomain, BaseNodeAdapter), the command dispatch engine, the measurement store, unit-policy system, child-registration machinery, InfluxDB output formatting, and a set of domain utilities (PID, curve interpolation, prediction, statistics, coolprop). Nodes hold zero duplicated scaffolding — they only write the logic that differs.


2. Position in the platform

flowchart LR
    gf["generalFunctions\n(shared library)"]:::lib

    rm["rotatingMachine\nEquipment"]:::equip
    mgc["machineGroupControl\nUnit"]:::unit
    ps["pumpingStation\nProcess Cell"]:::proc
    meas["measurement\nControl Module"]:::ctrl
    valve["valve\nEquipment"]:::equip
    vgc["valveGroupControl\nUnit"]:::unit
    reactor["reactor\nUnit"]:::unit
    settler["settler\nUnit"]:::unit
    monster["monster\nUnit"]:::unit
    diffuser["diffuser\nEquipment"]:::equip
    dashAPI["dashboardAPI\nutility"]:::util

    gf --> rm
    gf --> mgc
    gf --> ps
    gf --> meas
    gf --> valve
    gf --> vgc
    gf --> reactor
    gf --> settler
    gf --> monster
    gf --> diffuser
    gf --> dashAPI

    classDef lib fill:#222,color:#fff,stroke:#444
    classDef proc fill:#0c99d9,color:#fff
    classDef unit fill:#50a8d9,color:#000
    classDef equip fill:#86bbdd,color:#000
    classDef ctrl fill:#a9daee,color:#000
    classDef util fill:#dddddd,color:#000

Every EVOLV node declares generalFunctions as a dependencies entry and imports from the package root only (require('generalFunctions')). Cross-node coupling happens exclusively through this library's API surface and Node-RED messages — never through direct imports between node packages.


3. Capability matrix

Capability Status Notes
Base domain scaffolding (BaseDomain) Constructor, emitter, logger, measurements, child registry wired automatically
Base Node-RED adapter (BaseNodeAdapter) Tick/event loop, status badge, input dispatch, Port 0/1/2 output
Declarative command dispatch (CommandRegistry) Alias deprecation warnings, unit normalisation, query.units auto-topic
Declarative child-registration routing (ChildRouter) Replaces per-node registerChild switch blocks
Unit policy + conversion (UnitPolicy, convert) Canonical ↔ output ↔ curve unit sets; dual method/property access
Measurement store (MeasurementContainer) Chainable, windowed, auto-convert, 4-segment key output
InfluxDB + process output formatting (outputUtils) Delta-compressed; consumers must cache and merge
Status badge helpers (statusBadge, StatusUpdater) Converged look-and-feel across all nodes
Latest-wins async gate (LatestWinsGate) Extracted from MGC; shared by PS, VGC, MGC
Prediction quality / drift tracking (HealthStatus) Frozen plain-object shape; composable
Config schema registry (configManager) One JSON schema per node in src/configs/
PID control (PIDController, CascadePIDController) Full-featured discrete PID with bumpless transfer
Curve interpolation (interpolation, predict) Multidimensional characteristic-curve predictor
Statistical helpers (stats, nrmse, outliers) Mean, stddev, median, NRMSE, dynamic-cluster outlier detection
Thermodynamic properties (coolprop) CoolProp bindings for fluid/gas property lookup
FSM for valve/machine states (state) StateManager + MovementManager
Gravity calculations (gravity) WGS-84 model
Physical constants (Fysics) Air density, viscosity, etc.
Browser-side editor dropdowns (MenuManager, menuUtils) Node-RED editor form population

4. Module map

flowchart TB
    subgraph domain["src/domain/ — base classes"]
        BD["BaseDomain.js"]
        CR["ChildRouter.js"]
        UP["UnitPolicy.js"]
        LWG["LatestWinsGate.js"]
        HS["HealthStatus.js"]
    end

    subgraph nodered["src/nodered/ — Node-RED adapter layer"]
        BNA["BaseNodeAdapter.js"]
        CMR["commandRegistry.js"]
        SB["statusBadge.js"]
        SU["statusUpdater.js"]
    end

    subgraph measurements["src/measurements/ — measurement store"]
        MC["MeasurementContainer.js"]
        MB["MeasurementBuilder.js"]
        Meas["Measurement.js"]
    end

    subgraph helper["src/helper/ — shared utilities"]
        LOG["logger.js"]
        OU["outputUtils.js"]
        CRU["childRegistrationUtils.js"]
        CFG["configUtils.js"]
        VAL["validationUtils.js"]
        MU["menuUtils.js"]
        GR["gravity.js"]
    end

    subgraph predict_grp["src/predict/ — curve prediction"]
        PRED["predict_class.js"]
        INTERP["interpolation.js"]
    end

    subgraph configs["src/configs/ — schema registry"]
        CFGM["index.js (ConfigManager)"]
        JSON["*.json — per-node schemas"]
    end

    subgraph math["numeric & domain utilities"]
        PID["src/pid/ — PIDController"]
        NRMSE["src/nrmse/ — ErrorMetrics"]
        STATS["src/stats/ — mean/stddev/median"]
        OUT["src/outliers/ — DynamicClusterDeviation"]
        STATE["src/state/ — state FSM"]
        CONV["src/convert/ — unit conversion"]
        COOL["src/coolprop-node/ — thermodynamics"]
        FYS["src/convert/fysics.js — physical constants"]
    end

    subgraph menu_grp["src/menu/ — editor menus"]
        MM["MenuManager"]
    end

    subgraph constants["src/constants/"]
        POS["positions.js"]
    end

    BD --> CR
    BD --> UP
    BD --> MC
    BD --> CRU
    BD --> LOG
    BNA --> BD
    BNA --> CMR
    BNA --> OU
    BNA --> SU
Directory Primary export Read first if you're changing…
src/domain/ BaseDomain, ChildRouter, UnitPolicy, LatestWinsGate, HealthStatus Base class contracts, child routing, unit system
src/nodered/ BaseNodeAdapter, CommandRegistry, statusBadge, StatusUpdater Input dispatch, output loops, editor status
src/measurements/ MeasurementContainer Measurement storage, statistics, 4-segment key output
src/helper/ logger, outputUtils, childRegistrationUtils, configUtils, validationUtils, menuUtils, gravity Logging, InfluxDB formatting, child registration
src/configs/ ConfigManager + per-node JSON schemas Schema loading, config validation, default values
src/predict/ predict, interpolation Characteristic curve fitting and flow/power prediction
src/pid/ PIDController, CascadePIDController Closed-loop control
src/nrmse/ ErrorMetrics (NRMSE) Prediction quality scoring
src/stats/ stats (mean, stddev, median) Statistical reducers
src/outliers/ DynamicClusterDeviation Online outlier detection
src/state/ state, StateManager, MovementManager FSM for valve/machine state machines
src/convert/ convert, Fysics Unit conversion, physical constants
src/coolprop-node/ coolprop Thermodynamic property lookup
src/menu/ MenuManager Editor-form dropdown population
src/constants/ POSITIONS, POSITION_VALUES, isValidPosition Canonical spatial position constants

5. API surface

All imports use the package root: const { X } = require('generalFunctions');

Export Import name Source file Contract
BaseDomain BaseDomain src/domain/BaseDomain.js Abstract base class for every specificClass.js. Provides emitter, config, logger, measurements, childRegistrationUtils, router. Subclass must declare static name (maps to schema JSON) and implement configure(). See CONTRACTS.md §3.
BaseNodeAdapter BaseNodeAdapter src/nodered/BaseNodeAdapter.js Abstract base for every nodeClass.js. Wires config build → domain instantiation → registration delay → output strategy → status loop → input dispatch → close handler. Subclass declares static DomainClass, static commands, static tickInterval, static statusInterval, and overrides buildDomainConfig(uiConfig, nodeId). See CONTRACTS.md §2.
ChildRouter ChildRouter src/domain/ChildRouter.js Declarative parent-side child registration. Replaces per-node registerChild switch. Chain .onRegister(softwareType, cb), .onMeasurement(softwareType, filter, cb), .onPrediction(softwareType, filter, cb). See CONTRACTS.md §5.
CommandRegistry CommandRegistry src/nodered/commandRegistry.js Class form of the command registry. Accepts array of descriptors (topic, aliases, payloadSchema, units, description, handler). Dispatches by O(1) lookup, normalises units before handler runs, warns on alias use.
createRegistry createRegistry src/nodered/commandRegistry.js Factory: createRegistry(descriptors, options)CommandRegistry. Used by BaseNodeAdapter; rarely needed directly.
UnitPolicy UnitPolicy src/domain/UnitPolicy.js Declare unit sets: UnitPolicy.declare({ canonical, output, curve?, requireUnitForTypes? }). Returns policy with dual method/property access (policy.canonical('flow') and policy.canonical.flow). Methods: canonical, output, curve, resolve, convert, containerOptions, setLogger. See CONTRACTS.md §6.
LatestWinsGate LatestWinsGate src/domain/LatestWinsGate.js Serialises async dispatches so only the latest value wins. fire(value) — non-blocking. fireAndWait(value)Promise that resolves with dispatch result or LatestWinsGate.SUPERSEDED. drain() — await idle. See CONTRACTS.md §8.
HealthStatus HealthStatus src/domain/HealthStatus.js Factory functions for frozen health objects: HealthStatus.ok(msg, src), HealthStatus.degraded(level, flags, msg, src), HealthStatus.compose(statuses). Shape: { level: 03, flags: string[], message, source }. See CONTRACTS.md §9.
MeasurementContainer MeasurementContainer src/measurements/MeasurementContainer.js Chainable measurement store: .type().variant().position().value(v, ts, srcUnit). Query: getCurrentValue(unit), getAverage(unit), difference({ from, to, unit }). Introspect: getFlattenedOutput() returns 4-segment keyed object (type.variant.position.childId).
outputUtils outputUtils src/helper/outputUtils.js Singleton-per-node delta-compression engine. formatMsg(output, config, format) returns msg only when fields changed, or undefined. format is 'process' or 'influxdb'. Consumers must cache and merge.
logger logger src/helper/logger.js new logger(enabled, logLevel, moduleName). Methods: debug, info, warn, error, setLogLevel, toggleLogging. Never use console.log directly.
configManager configManager src/configs/index.js new configManager(). Methods: getConfig(name), buildConfig(name, uiConfig, nodeId, domainSlice?), getAvailableConfigs(), hasConfig(name). Config files live in src/configs/*.json.
configUtils configUtils src/helper/configUtils.js new configUtils(defaultConfig). initConfig(userConfig) validates and merges user values over defaults via validationUtils.
validation validation src/helper/validationUtils.js new validation(logEnabled, logLevel). validateSchema(config, schema, name) walks schema, clamps numbers, coerces types, strips unknown keys.
childRegistrationUtils childRegistrationUtils src/helper/childRegistrationUtils.js new childRegistrationUtils(parentDomain). registerChild(child, positionVsParent, distance?) stores child by softwareType/category with alias normalisation. getChildrenOfType(softwareType, category?), getChildById(id), getAllChildren(). Normally used via ChildRouter — direct use is for advanced cases.
statusBadge statusBadge src/nodered/statusBadge.js Pure-function badge builder. statusBadge.compose(parts, opts?){ fill, shape, text }. statusBadge.error(msg), statusBadge.idle(label). Text clipped to 60 chars. See CONTRACTS.md §7.
StatusUpdater StatusUpdater src/nodered/statusUpdater.js new StatusUpdater({ node, source, intervalMs, logger }). start(), stop(). Calls source.getStatusBadge() on interval; catches errors and shows a red badge. Owned by BaseNodeAdapter — rarely needed directly.
convert convert src/convert/index.js unit-converter factory. convert(value).from(unit).to(unit). convert.possibilities(measure) lists accepted units. Measures: volumeFlowRate, pressure, power, temperature, volume, length, mass, energy, reactivePower, apparentPower, reactiveEnergy, and more.
Fysics Fysics src/convert/fysics.js new Fysics(). Physical constants: air_density, g0; methods for gravity and viscosity calculations.
gravity gravity src/helper/gravity.js Singleton-style Gravity instance. getStandardGravity() → 9.80665 m/s². WGS-84 latitude/altitude corrections available.
predict predict src/predict/predict_class.js new predict(config, logger). Multidimensional characteristic-curve predictor; emits results via internal EventEmitter.
interpolation interpolation src/predict/interpolation.js Class for 1-D and 2-D curve interpolation (linear, cubic-spline). Used internally by predict.
PIDController PIDController src/pid/PIDController.js Discrete PID with bumpless auto/manual transfer, anti-windup, derivative filtering, rate limiting, gain scheduling, feedforward.
CascadePIDController CascadePIDController src/pid/PIDController.js Outer-inner PID cascade built on PIDController.
createPidController createPidController src/pid/index.js Factory shorthand: createPidController(options)PIDController.
createCascadePidController createCascadePidController src/pid/index.js Factory shorthand for cascade PID.
nrmse nrmse src/nrmse/index.js ErrorMetrics class for normalised-root-mean-squared-error tracking. Multi-metric via registerMetric(id), update(id, predicted, measured).
stats stats src/stats/index.js Pure functions: mean(arr), stdDev(arr), median(arr). No state; safe to call on any numeric array.
state state src/state/index.js new state(config, logger). FSM for valve/machine: StateManager (transitions) + MovementManager (timed moves). Emits state-change events.
MenuManager MenuManager src/menu/index.js new MenuManager(). Manages editor dropdown menus (asset, logger, position, aquon). registerMenu(type, factory). Used in node entry files to power Node-RED editor forms.
menuUtils / MenuUtils via menuUtils in helper src/helper/menuUtils.js Browser-side editor helper. Toggles, data fetching, URL construction, dropdown population, HTML generation. Served to browser via endpointUtils.
POSITIONS POSITIONS src/constants/positions.js Frozen enum: { UPSTREAM, DOWNSTREAM, AT_EQUIPMENT, DELTA }.
POSITION_VALUES POSITION_VALUES src/constants/positions.js string[] of all four position strings.
isValidPosition isValidPosition src/constants/positions.js (pos: string) => boolean.
coolprop coolprop src/coolprop-node/src/index.js CoolProp fluid/gas thermodynamic property lookup. Used by nodes that model heat transfer or gas compression.
loadModel loadModel datasets/assetData/modelData/index.js Load a JSON model-data asset by dataset type and asset ID (with LRU cache). Preferred over deprecated loadCurve.
loadCurve loadCurve datasets/assetData/curves/index.js Deprecated — load a pump-curve JSON. Replaced by loadModel.

6. Config schema registry

One JSON file per node in src/configs/. ConfigManager.buildConfig merges the schema defaults with the Node-RED editor values before the domain sees them.

File Node What it defines
baseConfig.json all nodes Shared general, asset, functionality, logging sections
rotatingMachine.json rotatingMachine Curve selection, startup/shutdown ramps, safety thresholds, unit config
machineGroupControl.json machineGroupControl Demand targets, strategy selection, dispatcher settings
pumpingStation.json pumpingStation Basin geometry, hydraulics, control strategies, safety levels
measurement.json measurement Scaling, smoothing, stability threshold, digital/MQTT mode
valve.json valve Actuator travel time, position limits, FSM config
valveGroupControl.json valveGroupControl Group strategy, demand distribution
reactor.json reactor ASM kinetics, reactor type (CSTR/PFR), volume, influent
settler.json settler Sludge settling parameters, effluent quality
monster.json monster Multi-parameter monitoring, flow bounds, sample intervals
diffuser.json diffuser Aeration model, oxygen transfer parameters

To add a new node: create src/configs/<nodeName>.json extending baseConfig.json, declare static name = '<nodeName>' in the domain class. configManager.buildConfig finds it automatically.


7. Lifecycle — how a node tick or event reaches the output port

The sequence below uses rotatingMachine as the example. Every stateful EVOLV node follows the same path. See the rotatingMachine wiki for node-specific detail.

sequenceDiagram
    participant RED as Node-RED runtime
    participant BNA as BaseNodeAdapter
    participant CMD as CommandRegistry
    participant DOM as Domain (specificClass)
    participant CR as ChildRouter
    participant MC as MeasurementContainer
    participant OU as outputUtils
    participant PORT as Port 0 / 1 / 2

    RED->>BNA: constructor(uiConfig, RED, node, name)
    BNA->>BNA: configManager.buildConfig()
    BNA->>DOM: new DomainClass(config)
    DOM->>MC: new MeasurementContainer(unitPolicy.containerOptions())
    DOM->>DOM: configure() — wire ChildRouter, concern modules
    BNA-->>PORT: Port 2 registration msg (after 100 ms delay)
    BNA->>BNA: start status loop (1000 ms)

    Note over RED,PORT: Event-driven path (default)

    RED->>BNA: input msg {topic: 'data.pressure', payload: 3.4}
    BNA->>CMD: dispatch(msg)
    CMD->>CMD: unit normalisation (Pa → mbar)
    CMD->>DOM: handler(source, msg, ctx)
    DOM->>MC: .type('pressure').variant('measured').position('upstream').value(3.4)
    DOM->>DOM: emitter.emit('output-changed')
    BNA->>DOM: getOutput()
    DOM-->>BNA: flat snapshot object
    BNA->>OU: formatMsg(snapshot, config, 'process')
    OU-->>BNA: delta msg (only changed fields)
    BNA-->>PORT: Port 0 process msg, Port 1 influx msg

    Note over RED,PORT: Tick-driven path (opt-in — tickInterval set)

    RED->>BNA: timer fires every tickInterval ms
    BNA->>DOM: tick()
    DOM->>DOM: time-based math; emitter.emit('output-changed')
    BNA->>DOM: getOutput()
    BNA->>OU: formatMsg(...)
    BNA-->>PORT: Port 0 / 1 msgs (delta only)

8. Stability + versioning

Source of truth: .claude/rules/general-functions.md.

Category Rule
Safe to add New named exports. New optional methods on existing classes. New config keys with defaults in the schema.
Requires decision-gate interview Removing or renaming any export. Changing a method signature. Changing the output key format of MeasurementContainer.getFlattenedOutput(). Changing the formatMsg delta-compression behaviour.
Forbidden without migration Breaking the 4-segment key shape (type.variant.position.childId). Changing Port 0/1/2 payload envelope. Changing the CONTRACTS.md §1§9 shapes.

generalFunctions is a git submodule shared by all 12 node repos. A breaking change here requires updating every consumer in a single coordinated commit. Before modifying any module, run grep -r "require('generalFunctions')" nodes/*/ to identify all call sites.


9. No editor form — consumers' config forms map to config slices

generalFunctions has no Node-RED editor form of its own. The library is never placed directly in a flow.

Consumer nodes expose their own editor forms. Each form field writes into a config key that configManager.buildConfig validates against the node's schema (in src/configs/<nodeName>.json). The resulting merged config is passed to the domain constructor.

For the form-to-config mapping of a specific node, see section 9 of that node's wiki page.


10. Examples — usage snippets from a real node

10.1 Extending BaseDomain (from pumpingStation/specificClass.js pattern)

const { BaseDomain, UnitPolicy, ChildRouter } = require('generalFunctions');

class PumpingStation extends BaseDomain {
  static name = 'pumpingStation';

  static unitPolicy = UnitPolicy.declare({
    canonical: { flow: 'm3/s', pressure: 'Pa', power: 'W', temperature: 'K' },
    output:    { flow: 'm3/h', pressure: 'mbar', power: 'kW', temperature: 'C' },
  });

  configure() {
    // Declare named child getters — readable in code, registry is source of truth
    this.declareChildGetter('machines',      'machine');
    this.declareChildGetter('machineGroups', 'machinegroup');

    // Declarative child routing — no per-node registerChild switch
    this.router
      .onRegister('machinegroup', (child) => this._onMachineGroupRegistered(child))
      .onMeasurement('measurement', { type: 'level' }, (data, child) => {
        this._onLevel(data.value, data);
      });
  }

  getOutput() {
    return {
      ...this.measurements.getFlattenedOutput(),
      ...this.basin.snapshot(),
    };
  }

  getStatusBadge() {
    const { statusBadge } = require('generalFunctions');
    return statusBadge.compose(['filling', 'V=12.4/50.0 m³']);
  }
}
module.exports = PumpingStation;

10.2 Extending BaseNodeAdapter (from pumpingStation/nodeClass.js pattern)

const { BaseNodeAdapter } = require('generalFunctions');
const Domain  = require('./specificClass');
const commands = require('./commands');

class nodeClass extends BaseNodeAdapter {
  static DomainClass   = Domain;
  static commands      = commands;
  static tickInterval  = 1000;   // ms — only for time-driven math
  static statusInterval = 1000;

  buildDomainConfig(uiConfig, nodeId) {
    return {
      basin: {
        volume:         Number(uiConfig.basinVolume),
        height:         Number(uiConfig.basinHeight),
        surfaceArea:    Number(uiConfig.basinSurface),
      },
      hydraulics: {
        inflowPipeArea: Number(uiConfig.inflowArea),
      },
    };
  }
}
module.exports = nodeClass;

10.3 Command descriptor with unit normalisation

// src/commands/index.js
module.exports = [
  {
    topic:         'set.demand',
    aliases:       ['Qd'],          // legacy name — logs one-time deprecation
    units:         { measure: 'volumeFlowRate', default: 'm3/h' },
    payloadSchema: { type: 'number' },
    description:   'Operator demand setpoint. Unit-normalised before handler runs.',
    handler:       (source, msg) => { source.setDemand(msg.payload); },
  },
  {
    topic:         'cmd.startup',
    payloadSchema: { type: 'none' },
    description:   'Trigger startup sequence.',
    handler:       (source, msg) => { source.startup(msg.payload?.source); },
  },
];

11. Debug recipes

Symptom First check Where to look
Child never registers (no registerChild log) Is the child's softwareType in the SOFTWARE_TYPE_ALIASES map? src/helper/childRegistrationUtils.js line 112 and src/domain/ChildRouter.js
Port 0 sends nothing after an input outputUtils only emits on changes. Is the field actually different from the last call? Add a debug tap after formatMsg; check outputUtils._output[format] state
Unit mismatch — handler receives wrong value Did the command descriptor declare units: { measure, default }? Is msg.unit set by the sender? commandRegistry.js_normaliseUnit(); check the warn log
query.units returns empty object The commands array has no descriptors with a units field. BaseNodeAdapter._buildImplicitUnitsCommand()
MeasurementContainer.getFlattenedOutput() returns unexpected key shape Key is type.variant.position.childId — position is always lowercase. Check setChildId() was called. src/measurements/MeasurementContainer.jsgetFlattenedOutput()
LatestWinsGate promise never resolves A superseded fire resolves with { superseded: true }, not undefined. Branch on r && r.superseded. src/domain/LatestWinsGate.js
Status badge stuck at grey getStatusBadge() threw and StatusUpdater caught it. Look for statusBadge.error(...) in the container log. src/nodered/statusUpdater.js

Never ship enableLog: 'debug' in a demo or production config — it fills the container log within seconds and obscures real errors.


12. When NOT to depend on this library

  • Passive HTTP gateway nodes (e.g. dashboardAPI) may skip BaseDomain and BaseNodeAdapter entirely if they hold no domain state. A plain Node-RED node with HTTP endpoints needs only logger, outputUtils, and configManager. See the dashboardAPI wiki for the rationale.
  • External scripts or standalone tools that need only unit conversion can import just const { convert } = require('generalFunctions') without pulling in the full domain stack.
  • Nodes at a different S88 level that inherit from a third-party base class must not import from src/domain/ or src/nodered/ internal paths — they may only use root-level exports.

13. Known limitations

# Issue Tracked in
1 loadCurve is deprecated; replacement loadModel exists but not all nodes have migrated OPEN_QUESTIONS.md — Phase 8.5 cleanup
2 outlierDetection (DynamicClusterDeviation) prints to console.log internally — not routed through logger Code review backlog
3 configUtils.initConfig strips unknown keys silently; schema must include every key the domain reads or defaults are lost OPEN_QUESTIONS.md — e.g. monster schema fix 2026-05-11
4 state (FSM) and predict are not yet integrated with BaseDomain lifecycle — nodes wire them manually in configure() Architecture backlog
5 menuUtils / MenuManager are served as browser JavaScript and bypass the normal Node.js import path — deep changes require testing in both environments endpointUtils.js
6 CascadePIDController has no dedicated test suite Test backlog
7 Wiki autogen script (wiki:all) not yet wired for this library; API surface block is hand-maintained Phase 9 follow-up