# === core/site_report_schema.py === # Single source of truth for the on-site progress metric set used by # SiteReport.metrics (a JSONField). Edit this file to add/remove/rename # metrics — NO database migration required, the JSONField stores # whatever keys you save. # # Why this file exists: # The SiteReport model has structured fields (weather, temperature, # notes, supervisor, timestamps) AND a JSON `metrics` blob. The blob # stores per-day operational counts and checkboxes (e.g. how many # plinths were cast today, was steel tied, did the team go to town # for materials). These metrics will EVOLVE over time as different # construction phases come up — and adding a new column for every # change would mean a migration, a deploy, and a risk of breaking # historic data. # # How to evolve: # - Add a new metric: append a dict to COUNT_METRICS or CHECK_METRICS # with a unique snake_case `key` and a human `label`. Done. The # form renders it on next page load. Old reports just don't have # that key in their JSON — which is fine, they render as 0 / unchecked. # - Rename a label: change the `label` value only — the `key` stays # so old data still maps to the same metric. # - Retire a metric: delete it from this list. Old reports keep their # data in the JSON (it's just no longer rendered in the form). If # you want to fully purge it from the DB, write a one-off management # command — the JSON is just a Python dict at the storage layer. # # How NOT to use this file: # - Do NOT change a `key` of a metric that has data — historic reports # will silently lose their value for that metric. If a key really # must change, write a data migration that walks every SiteReport # and renames the JSON key. # === COUNT METRICS === # Numeric counts. Form renders each as a `` # with a numeric keyboard on mobile. Default value is 0 (or blank) — # blank is treated the same as 0 when summing for reports. COUNT_METRICS = [ {'key': 'plinth_holes_dug', 'label': 'Plinth holes dug'}, {'key': 'plinths_cast', 'label': 'Plinths cast'}, {'key': 'plinths_deshuttered', 'label': 'Plinths de-shuttered'}, {'key': 'tables_set_out', 'label': 'Tables set out'}, {'key': 'boxes_placed', 'label': 'Boxes placed'}, {'key': 'shutter_boxes_leveled', 'label': 'Shutter boxes leveled'}, {'key': 'steel_placed', 'label': 'Steel placed'}, ] # === CHECK METRICS === # Boolean flags. Form renders each as a large-tap-target checkbox. # Default value is False (unchecked) — absence in the JSON means False. CHECK_METRICS = [ {'key': 'steel_tied', 'label': 'Steel tied / built'}, {'key': 'boxes_cleaned', 'label': 'Boxes cleaned'}, {'key': 'town_run', 'label': 'Town for materials / food'}, {'key': 'rain_delay', 'label': 'Rain delay'}, ] def get_count_keys(): """Return the list of count-metric keys in display order. Useful for: rendering the form, validating posted data, summing counts across many reports for a project dashboard. """ return [m['key'] for m in COUNT_METRICS] def get_check_keys(): """Return the list of check-metric keys in display order.""" return [m['key'] for m in CHECK_METRICS] def label_for(key): """Look up the human label for a metric key. Returns the key itself (snake_case) if no match — useful as a fallback when displaying historic data whose key has since been retired from the schema. """ for m in COUNT_METRICS + CHECK_METRICS: if m['key'] == key: return m['label'] return key def empty_metrics(): """Return a fresh metrics dict with all current keys at default values. Counts default to 0, checks default to False. Use as a default for new SiteReport instances OR as a template when comparing reports. """ return { 'counts': {m['key']: 0 for m in COUNT_METRICS}, 'checks': {m['key']: False for m in CHECK_METRICS}, }