diff --git a/src/main/java/no/nibio/vips/logic/entity/Observation.java b/src/main/java/no/nibio/vips/logic/entity/Observation.java
index 241e0c14ad16f334804c251a2f47dc698315c2f1..7d971682885caa3fe572f0df23933e2762e477ef 100755
--- a/src/main/java/no/nibio/vips/logic/entity/Observation.java
+++ b/src/main/java/no/nibio/vips/logic/entity/Observation.java
@@ -691,6 +691,8 @@ public class Observation implements Serializable, no.nibio.vips.observation.Obse
         }
         return new ObservationListItem(
                 this.getObservationId(),
+                this.userId,
+                this.user != null ? this.user.getFullName() : null,
                 this.getObservationTimeSeriesId(),
                 this.getTimeOfObservation(),
                 this.getOrganismId(),
@@ -701,8 +703,11 @@ public class Observation implements Serializable, no.nibio.vips.observation.Obse
                 // Specific geoInfo trumps location. This is to be interpreted 
                 // as that the observation has been geographically masked by
                 // choice of the observer
+                this.location != null ? this.location.getPointOfInterestId() : null,
+                this.location != null ? this.location.getName() : null,
                 this.location != null && this.geoinfo == null ? this.location.getGeoJSON() : this.getGeoinfo(),
                 this.getObservationHeading(),
+                this.getObservationText(),
                 this.getBroadcastMessage(),
                 this.getLocationIsPrivate(),
                 this.getIsPositive(),
diff --git a/src/main/java/no/nibio/vips/logic/entity/VipsLogicUser.java b/src/main/java/no/nibio/vips/logic/entity/VipsLogicUser.java
index c8d96e9477c78f3cac0cb482c7168e210f084eb1..0d9c0f09189b5ed1e42c857df8748a611b141114 100755
--- a/src/main/java/no/nibio/vips/logic/entity/VipsLogicUser.java
+++ b/src/main/java/no/nibio/vips/logic/entity/VipsLogicUser.java
@@ -143,6 +143,12 @@ public class VipsLogicUser implements Serializable, Comparable{
         this.lastName = lastName;
     }
 
+    @JsonIgnore
+    @Transient
+    public String getFullName() {
+        return firstName + " " + lastName;
+    }
+
     @OneToMany(cascade = CascadeType.ALL, mappedBy = "vipsLogicUser", fetch=FetchType.EAGER)
     @XmlTransient
     @JsonIgnore
diff --git a/src/main/java/no/nibio/vips/logic/entity/rest/ObservationListItem.java b/src/main/java/no/nibio/vips/logic/entity/rest/ObservationListItem.java
index a4dd12f901526bfda95fb6c5edadc1c793218772..5082c7089796662fdd85982ab4ff6529e0e41b90 100644
--- a/src/main/java/no/nibio/vips/logic/entity/rest/ObservationListItem.java
+++ b/src/main/java/no/nibio/vips/logic/entity/rest/ObservationListItem.java
@@ -28,11 +28,16 @@ import no.nibio.vips.observationdata.ObservationDataSchema;
  */
 public class ObservationListItem implements Comparable{
     private Integer observationId, observationTimeSeriesId, organismId, cropOrganismId;
+    private Integer observerId;
+    private String observerName;
     private Date timeOfObservation;
     private String organismName, cropOrganismName;
     private String observationTimeSeriesLabel;
+    private Integer locationPointOfInterestId;
+    private String locationPointOfInterestName;
     private String geoInfo;
     private String observationHeading;
+    private String observationText;
     private String observationData;
     private ObservationDataSchema observationDataSchema;
     private Boolean broadcastMessage;
@@ -41,6 +46,8 @@ public class ObservationListItem implements Comparable{
 
     public ObservationListItem(
             Integer observationId,
+            Integer observerId,
+            String observerName,
             Integer observationTimeSeriesId,
             Date timeOfObservation,
             Integer organismId,
@@ -48,15 +55,19 @@ public class ObservationListItem implements Comparable{
             Integer cropOrganismId,
             String cropOrganismName,
             String observationTimeSeriesLabel,
+            Integer poiId,
+            String poiName,
             String geoinfo,
             String observationHeading,
+            String observationText,
             Boolean broadcastMessage,
             Boolean locationIsPrivate,
             Boolean isPositive,
             String observationData,
-            ObservationDataSchema observationDataSchema
-    ){
+            ObservationDataSchema observationDataSchema){
         this.observationId = observationId;
+        this.observerId = observerId;
+        this.observerName = observerName;
         this.observationTimeSeriesId = observationTimeSeriesId;
         this.timeOfObservation = timeOfObservation;
         this.organismId = organismId;
@@ -64,8 +75,11 @@ public class ObservationListItem implements Comparable{
         this.cropOrganismId = cropOrganismId;
         this.cropOrganismName = cropOrganismName;
         this.observationTimeSeriesLabel = observationTimeSeriesLabel;
+        this.locationPointOfInterestId = poiId;
+        this.locationPointOfInterestName = poiName;
         this.geoInfo = geoinfo;
         this.observationHeading = observationHeading;
+        this.observationText = observationText;
         this.broadcastMessage = broadcastMessage;
         this.locationIsPrivate = locationIsPrivate;
         this.isPositive = isPositive;
@@ -98,6 +112,34 @@ public class ObservationListItem implements Comparable{
         this.observationId = observationId;
     }
 
+    /**
+     * @return The ID of the observer
+     */
+    public Integer getObserverId() {
+        return observerId;
+    }
+
+    /**
+     * @param observerId The ID to set
+     */
+    public void setObserverId(Integer observerId) {
+        this.observerId = observerId;
+    }
+
+    /**
+     * @return The full name of the observer
+     */
+    public String getObserverName() {
+        return observerName;
+    }
+
+    /**
+     * @param observerName The observer name to set
+     */
+    public void setObserverName(String observerName) {
+        this.observerName = observerName;
+    }
+
     /**
      * @return the observationTimeSeriesId
      */
@@ -162,6 +204,22 @@ public class ObservationListItem implements Comparable{
         this.observationTimeSeriesLabel = observationTimeSeriesLabel;
     }
 
+    public Integer getLocationPointOfInterestId() {
+        return locationPointOfInterestId;
+    }
+
+    public void setLocationPointOfInterestId(Integer locationPointOfInterestId) {
+        this.locationPointOfInterestId = locationPointOfInterestId;
+    }
+
+    public String getLocationPointOfInterestName() {
+        return locationPointOfInterestName;
+    }
+
+    public void setLocationPointOfInterestName(String locationPointOfInterestName) {
+        this.locationPointOfInterestName = locationPointOfInterestName;
+    }
+
     /**
      * @return the geoInfo
      */
@@ -190,6 +248,20 @@ public class ObservationListItem implements Comparable{
         this.observationHeading = observationHeading;
     }
 
+    /**
+     * @return the observation text
+     */
+    public String getObservationText() {
+        return observationText;
+    }
+
+    /**
+     * @param observationText The observation text to set
+     */
+    public void setObservationText(String observationText) {
+        this.observationText = observationText;
+    }
+
     /**
      * @return the organismId
      */
diff --git a/src/main/java/no/nibio/vips/logic/service/ObservationService.java b/src/main/java/no/nibio/vips/logic/service/ObservationService.java
index 128a5bf9c241cb2103a9bc04d34486a1d313def7..12aa050d36c928713615101745cb370c492db080 100755
--- a/src/main/java/no/nibio/vips/logic/service/ObservationService.java
+++ b/src/main/java/no/nibio/vips/logic/service/ObservationService.java
@@ -192,7 +192,7 @@ public class ObservationService {
         ULocale locale = new ULocale(localeStr != null ? localeStr :
                 user != null ? user.getOrganizationId().getDefaultLocale() :
                         userBean.getOrganization(organizationId).getDefaultLocale());
-        LOGGER.info("Generate xlsx file for observations for user {}", user != null ? user.getUserId() : "unregistered");
+        LOGGER.info("Generate xlsx file for observations for user {} from {} to {}", user != null ? user.getUserId() : "unregistered", fromStr, toStr);
 
         LocalDateTime now = LocalDateTime.now();
         DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyyMMddHHmmss");
@@ -200,7 +200,7 @@ public class ObservationService {
 
         try {
             List<ObservationListItem> observations = getFilteredObservationListItems(organizationId, observationTimeSeriesId, pestId, cropId, cropCategoryId, fromStr, toStr, userUUID, localeStr, isPositive);
-            byte[] excelFile = ExcelFileGenerator.generateExcel(observations, locale);
+            byte[] excelFile = ExcelFileGenerator.generateExcel(user, locale, now, fromStr, toStr, observations);
 
             return Response
                     .ok(excelFile)
diff --git a/src/main/java/no/nibio/vips/logic/util/ExcelFileGenerator.java b/src/main/java/no/nibio/vips/logic/util/ExcelFileGenerator.java
index 0e927d28cfd17357b6d7d3ad2633cd6ff51cc045..0a2ad0d909e4cf108ff1a1d8de324e8d8fe5beaa 100644
--- a/src/main/java/no/nibio/vips/logic/util/ExcelFileGenerator.java
+++ b/src/main/java/no/nibio/vips/logic/util/ExcelFileGenerator.java
@@ -4,46 +4,119 @@ import com.fasterxml.jackson.core.JsonProcessingException;
 import com.fasterxml.jackson.databind.JsonNode;
 import com.fasterxml.jackson.databind.ObjectMapper;
 import com.ibm.icu.util.ULocale;
+import no.nibio.vips.logic.entity.VipsLogicUser;
 import no.nibio.vips.logic.entity.rest.ObservationListItem;
 import no.nibio.vips.observationdata.ObservationDataSchema;
+import org.apache.poi.common.usermodel.HyperlinkType;
 import org.apache.poi.xssf.usermodel.XSSFWorkbook;
 import org.apache.poi.ss.usermodel.*;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
 
 import java.io.ByteArrayOutputStream;
 import java.io.IOException;
 import java.time.LocalDate;
+import java.time.LocalDateTime;
 import java.time.ZoneId;
 import java.time.format.DateTimeFormatter;
 import java.util.*;
 
 public final class ExcelFileGenerator {
 
+    private static final Logger LOGGER = LoggerFactory.getLogger(ExcelFileGenerator.class);
     private static final DateTimeFormatter DATE_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd");
+    private static final DateTimeFormatter DATE_TIME_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
     private static final ObjectMapper objectMapper = new ObjectMapper();
 
-    private static final int COL_INDEX_DATE = 0;
-    private static final int COL_INDEX_LOCATION = 1;
-    private static final int COL_INDEX_ORGANISM = 2;
-    private static final int COL_INDEX_CROP_ORGANISM = 3;
-    private static final int COL_INDEX_HEADING = 4;
-    private static final int COL_START_INDEX_DATA = 5;
+    private enum ColumnIndex {
+        ID(false, 0, 0, "observationId"),
+        DATE(false, 1, 1, "timeOfObservation"),
+        POI_ID(false, 2, 2, "locationPointOfInterestId"),
+        POI_NAME(false, 3, 3, "location"),
+        OBSERVER_ID(true, null, 4, "observerId"),
+        OBSERVER_NAME(true, null, 5, "observer"),
+        OBSERVATION_TIME_SERIES_ID(false, 4, 6, "observationTimeSeriesId"),
+        OBSERVATION_TIME_SERIES_LABEL(false, 5, 7, "observationTimeSeriesLabel"),
+        ORGANISM(false, 6, 8, "organism"),
+        CROP_ORGANISM(false, 7, 9, "cropOrganismId"),
+        HEADING(false, 8, 10, "observationHeading"),
+        DESCRIPTION(false, 9, 11, "observationText"),
+        BROADCAST(false, 10, 12, "isBroadcast"),
+        POSITIVE(false, 11, 13, "isPositiveRegistration"),
+        INDEX_DATA(false, 12, 14, null);
 
-    public static byte[] generateExcel(List<ObservationListItem> observations, ULocale locale) throws IOException {
-        ResourceBundle rb = ResourceBundle.getBundle("no.nibio.vips.logic.i18n.vipslogictexts", locale.toLocale());
+        private final boolean isSensitive;
+        private final Integer openIndex;
+        private final Integer adminIndex;
+        private final String rbKey;
+
+        ColumnIndex(boolean isSensitive, Integer openIndex, Integer adminIndex, String rbKey) {
+            this.isSensitive = isSensitive;
+            this.openIndex = openIndex;
+            this.adminIndex = adminIndex;
+            this.rbKey = rbKey;
+        }
+
+        public static List<ColumnIndex> forUser(boolean isAdmin) {
+            if (!isAdmin) {
+                return Arrays.stream(ColumnIndex.values()).filter(columnIndex -> !columnIndex.isSensitive).toList();
+            }
+            return Arrays.stream(ColumnIndex.values()).toList();
+        }
+
+        public String getColumnHeading(ResourceBundle rb) {
+            return rbKey != null && !rbKey.isBlank() ? rb.getString(rbKey) : "";
+        }
 
+        public Integer getIndex(boolean admin) {
+            if (admin) {
+                return adminIndex;
+            }
+            return openIndex;
+        }
+    }
+
+    public static byte[] generateExcel(VipsLogicUser user, ULocale locale, LocalDateTime now, String fromStr, String toStr, List<ObservationListItem> observations) throws IOException {
+        ResourceBundle rb = ResourceBundle.getBundle("no.nibio.vips.logic.i18n.vipslogictexts", locale.toLocale());
+        boolean isAdmin = user != null && (user.isSuperUser() || user.isOrganizationAdmin());
+        LOGGER.info("Create Excel file containing {} observations for {} user", observations.size(), isAdmin ? "admin" : "regular");
         try (XSSFWorkbook workbook = new XSSFWorkbook();
              ByteArrayOutputStream out = new ByteArrayOutputStream()) {
 
-            // Create main mainSheet for all observations, with header row
+            Font font = workbook.createFont();
+            font.setBold(true);
+            CellStyle headerStyle = workbook.createCellStyle();
+            headerStyle.setFont(font);
+
+            // Create main sheet for all observations, with header row
             Sheet mainSheet = workbook.createSheet(rb.getString("allObservations"));
-            createHeaderRow(mainSheet, rb);
+            createHeaderRow(isAdmin, mainSheet, headerStyle, rb);
 
             int mainSheetRowIndex = 1;
             // Add one row for each observation in list of all observations
             for (ObservationListItem item : observations) {
-                createItemRow(mainSheet, mainSheetRowIndex++, item);
+                createItemRow(isAdmin, mainSheet, mainSheetRowIndex++, item, rb);
             }
-            autoSizeColumns(mainSheet, 0, COL_INDEX_HEADING);
+            autoSizeColumns(mainSheet, 0, ColumnIndex.INDEX_DATA.getIndex(isAdmin) - 1);
+
+            // Create meta sheet for information about the download
+            Sheet metaSheet = workbook.createSheet(rb.getString("downloadInfo"));
+            Row userRow = metaSheet.createRow(0);
+            userRow.createCell(0).setCellValue(rb.getString("downloadedBy"));
+            userRow.createCell(1).setCellValue(user != null ? user.getFullName() : rb.getString("unregisteredUser"));
+            Row timeRow = metaSheet.createRow(1);
+            timeRow.createCell(0).setCellValue(rb.getString("downloadedTime"));
+            timeRow.createCell(1).setCellValue(DATE_TIME_FORMATTER.format(now));
+            Row fromRow = metaSheet.createRow(2);
+            fromRow.createCell(0).setCellValue(rb.getString("dateStart"));
+            fromRow.createCell(1).setCellValue(fromStr);
+            Row toRow = metaSheet.createRow(3);
+            toRow.createCell(0).setCellValue(rb.getString("dateEnd"));
+            toRow.createCell(1).setCellValue(toStr);
+            Row countRow = metaSheet.createRow(4);
+            countRow.createCell(0).setCellValue(rb.getString("observationCount"));
+            countRow.createCell(1).setCellValue(observations.size());
+            autoSizeColumns(metaSheet, 0, 1);
 
             // Prepare list of observations for each type of pest
             Map<Integer, List<ObservationListItem>> pestObservations = getObservationsForEachPest(observations);
@@ -53,36 +126,33 @@ public final class ExcelFileGenerator {
                 List<ObservationListItem> observationsForPest = pestObservations.get(pestId);
                 ObservationListItem firstObservationForPest = observationsForPest.get(0);
                 String pestName = firstObservationForPest.getOrganismName();
-                Sheet pestSheet = workbook.createSheet(pestName);
-                Row headerRow = createHeaderRow(pestSheet, rb);
+                Sheet pestSheet = workbook.createSheet(sanitizeSheetName(pestId, pestName));
+                Row headerRow = createHeaderRow(isAdmin, pestSheet, headerStyle, rb);
 
                 // Add column titles for observation data
                 Map<String, String> dataColumnTitles = getObservationDataColumnTitles(firstObservationForPest.getObservationDataSchema());
-                int pestSheetColIndex = COL_START_INDEX_DATA;
+                int pestSheetColIndex = ColumnIndex.INDEX_DATA.getIndex(isAdmin);
                 for (String key : dataColumnTitles.keySet()) {
-                    headerRow.createCell(pestSheetColIndex++).setCellValue(dataColumnTitles.get(key));
+                    Cell cell = headerRow.createCell(pestSheetColIndex++);
+                    cell.setCellStyle(headerStyle);
+                    cell.setCellValue(dataColumnTitles.get(key));
                 }
 
                 int pestSheetRowIndex = 1;
                 for (ObservationListItem item : observationsForPest) {
-                    Row row = createItemRow(pestSheet, pestSheetRowIndex++, item);
+                    Row row = createItemRow(isAdmin, pestSheet, pestSheetRowIndex++, item, rb);
 
                     if (item.getObservationData() != null) {
                         Map<String, Object> observationDataMap = objectMapper.readValue(item.getObservationData(), HashMap.class);
                         if (observationDataMap != null) {
-                            pestSheetColIndex = COL_START_INDEX_DATA;
+                            pestSheetColIndex = ColumnIndex.INDEX_DATA.getIndex(isAdmin);
                             for (String key : dataColumnTitles.keySet()) {
-                                Object value = observationDataMap.get(key);
-                                if (value instanceof Number) {
-                                    row.createCell(pestSheetColIndex++).setCellValue(((Number) value).intValue());
-                                } else {
-                                    row.createCell(pestSheetColIndex++).setCellValue(value != null ? value.toString() : "");
-                                }
+                                pestSheetColIndex = addValueToCell(row, pestSheetColIndex, observationDataMap.get(key));
                             }
                         }
                     }
                 }
-                autoSizeColumns(pestSheet, COL_INDEX_DATE, COL_START_INDEX_DATA + dataColumnTitles.size());
+                autoSizeColumns(pestSheet, ColumnIndex.ID.getIndex(isAdmin), ColumnIndex.INDEX_DATA.getIndex(isAdmin) + dataColumnTitles.size());
             }
 
             workbook.write(out);
@@ -90,6 +160,21 @@ public final class ExcelFileGenerator {
         }
     }
 
+    /**
+     * Create sheet name without invalid characters and within size limits
+     *
+     * @param pestId   The id of the pest
+     * @param pestName The name of the pest
+     * @return a sanitized string to be used as sheet name
+     */
+    private static String sanitizeSheetName(Integer pestId, String pestName) {
+        if (pestName == null || pestName.isBlank()) {
+            return "Id=" + pestId;
+        }
+        return pestName.replaceAll("[\\[\\]\\*/:?\\\\]", "_").substring(0, Math.min(pestName.length(), 31));
+    }
+
+
     /**
      * Auto-size columns of given sheet, from startIndex to endIndex, to ensure that content fits
      *
@@ -123,23 +208,6 @@ public final class ExcelFileGenerator {
         return resultMap;
     }
 
-    /**
-     * Find the name of the point of interest in given geoInfo string
-     *
-     * @param geoInfo The geoInfo which might contain the name of the point of interest
-     * @return the point of interest name, or empty string
-     */
-    private static String getPointOfInterestName(String geoInfo) throws JsonProcessingException {
-        JsonNode rootNode = objectMapper.readTree(geoInfo);
-        JsonNode featuresNode = rootNode.path("features");
-        if (featuresNode.isArray() && !featuresNode.isEmpty()) {
-            JsonNode firstFeature = featuresNode.get(0);
-            JsonNode propertiesNode = firstFeature.path("properties");
-            return propertiesNode.path("pointOfInterestName").asText();
-        }
-        return "";
-    }
-
     /**
      * Create map with pestId as key, and list of corresponding observations as value
      *
@@ -161,41 +229,129 @@ public final class ExcelFileGenerator {
         return pestObservations;
     }
 
+    /**
+     * Create first row of given sheet, with column titles dependent on user privileges
+     *
+     * @param isAdmin     Whether the user is a logged in admin
+     * @param sheet       The sheet to which a row will be added
+     * @param headerStyle The style to be applied to each cell in the header row
+     * @param rb          A resource bundle enabling localized messages
+     * @return the newly created header row
+     */
+    public static Row createHeaderRow(boolean isAdmin, Sheet sheet, CellStyle headerStyle, ResourceBundle rb) {
+        Row headerRow = sheet.createRow(0);
+        for (ColumnIndex columnIndex : ColumnIndex.forUser(isAdmin)) {
+            Cell cell = headerRow.createCell(columnIndex.getIndex(isAdmin));
+            cell.setCellStyle(headerStyle);
+            cell.setCellValue(columnIndex.getColumnHeading(rb));
+        }
+        return headerRow;
+    }
+
     /**
      * Create row with given index, for given observation list item
      *
+     * @param isAdmin  Whether the user is a logged in admin
      * @param sheet    The sheet to which a row will be added
      * @param rowIndex The index of the row
      * @param item     The item of which to add data
      * @return the newly created row
      */
-    private static Row createItemRow(Sheet sheet, int rowIndex, ObservationListItem item) throws JsonProcessingException {
+    private static Row createItemRow(boolean isAdmin, Sheet sheet, int rowIndex, ObservationListItem item, ResourceBundle rb) throws JsonProcessingException {
         LocalDate localDateOfObservation = item.getTimeOfObservation().toInstant().atZone(ZoneId.systemDefault()).toLocalDate();
-        String pointOfInterestName = getPointOfInterestName(item.getGeoInfo());
-
         Row row = sheet.createRow(rowIndex);
-        row.createCell(COL_INDEX_DATE).setCellValue(localDateOfObservation.format(DATE_FORMATTER));
-        row.createCell(COL_INDEX_LOCATION).setCellValue(pointOfInterestName);
-        row.createCell(COL_INDEX_ORGANISM).setCellValue(item.getOrganismName());
-        row.createCell(COL_INDEX_CROP_ORGANISM).setCellValue(item.getCropOrganismName());
-        row.createCell(COL_INDEX_HEADING).setCellValue(item.getObservationHeading());
+        addObservationLinkToIdCol(isAdmin, row, item.getObservationId());
+        addValueToCell(row, ColumnIndex.DATE.getIndex(isAdmin), localDateOfObservation.format(DATE_FORMATTER));
+
+        if (item.getLocationPointOfInterestId() != null) {
+            addValueToCell(row, ColumnIndex.POI_ID.getIndex(isAdmin), item.getLocationPointOfInterestId());
+            addValueToCell(row, ColumnIndex.POI_NAME.getIndex(isAdmin), item.getLocationPointOfInterestName());
+        }
+
+        if (isAdmin) {
+            addValueToCell(row, ColumnIndex.OBSERVER_ID.getIndex(isAdmin), item.getObserverId());
+            addValueToCell(row, ColumnIndex.OBSERVER_NAME.getIndex(isAdmin), item.getObserverName());
+        }
+        if (item.getObservationTimeSeriesId() != null) {
+            addValueToCell(row, ColumnIndex.OBSERVATION_TIME_SERIES_ID.getIndex(isAdmin), item.getObservationTimeSeriesId());
+            addValueToCell(row, ColumnIndex.OBSERVATION_TIME_SERIES_LABEL.getIndex(isAdmin), item.getObservationTimeSeriesLabel());
+        }
+        addValueToCell(row, ColumnIndex.ORGANISM.getIndex(isAdmin), item.getOrganismName());
+        addValueToCell(row, ColumnIndex.CROP_ORGANISM.getIndex(isAdmin), item.getCropOrganismName());
+        addValueToCell(row, ColumnIndex.HEADING.getIndex(isAdmin), item.getObservationHeading());
+        addValueToCell(row, ColumnIndex.DESCRIPTION.getIndex(isAdmin), item.getObservationText());
+
+        addValueToCell(row, ColumnIndex.BROADCAST.getIndex(isAdmin), getBooleanStringValue(rb, item.getBroadcastMessage()));
+        addValueToCell(row, ColumnIndex.POSITIVE.getIndex(isAdmin), getBooleanStringValue(rb, item.getIsPositive()));
         return row;
     }
 
     /**
-     * Create first row of given sheet, with standard set of column titles
+     * Add value to cell
      *
-     * @param sheet The sheet to which a row will be added
-     * @param rb    A resource bundle enabling localized messages
-     * @return the newly created header row
+     * @param row      The row to which the value should be added
+     * @param colIndex The index of the column to add the value to
+     * @param value    The value
+     * @return The next index value
      */
-    public static Row createHeaderRow(Sheet sheet, ResourceBundle rb) {
-        Row headerRow = sheet.createRow(0);
-        headerRow.createCell(COL_INDEX_DATE).setCellValue(rb.getString("timeOfObservation"));
-        headerRow.createCell(COL_INDEX_LOCATION).setCellValue(rb.getString("location"));
-        headerRow.createCell(COL_INDEX_ORGANISM).setCellValue(rb.getString("organism"));
-        headerRow.createCell(COL_INDEX_CROP_ORGANISM).setCellValue(rb.getString("cropOrganismId"));
-        headerRow.createCell(COL_INDEX_HEADING).setCellValue(rb.getString("observationHeading"));
-        return headerRow;
+    private static Integer addValueToCell(Row row, Integer colIndex, Object value) {
+        Integer index = colIndex;
+        if (value == null) {
+            row.createCell(index++).setCellValue("");
+        } else if (value instanceof Number) {
+            row.createCell(index++).setCellValue(((Number) value).intValue());
+        } else {
+            try {
+                int intValue = Integer.parseInt(value.toString());
+                row.createCell(index++).setCellValue(intValue);
+            } catch (NumberFormatException e) {
+                row.createCell(index++).setCellValue(value.toString());
+            }
+        }
+        return index;
+    }
+
+    /**
+     * Get a localized String representing either true or false
+     *
+     * @param rb    The resource bundle to get the localized string from
+     * @param value Either true or false
+     * @return A localized String
+     */
+    private static String getBooleanStringValue(ResourceBundle rb, Boolean value) {
+        if (value == null) {
+            return null;
+        } else if (value) {
+            return rb.getString("yes");
+        }
+        return rb.getString("no");
     }
+
+    /**
+     * Add link to observation details in column containing the observation Id
+     *
+     * @param isAdmin       Whether or not the user is admin
+     * @param row           A reference to the current row
+     * @param observationId The id of the observation
+     */
+    private static void addObservationLinkToIdCol(boolean isAdmin, Row row, Integer observationId) {
+        Cell cell = row.createCell(ColumnIndex.ID.getIndex(isAdmin));
+        cell.setCellValue(observationId);
+
+        Workbook workbook = row.getSheet().getWorkbook();
+        CreationHelper creationHelper = workbook.getCreationHelper();
+        Hyperlink hyperlink = creationHelper.createHyperlink(HyperlinkType.URL);
+        //hyperlink.setAddress("https://www.vips-landbruk.no/observations/" + observationId);
+        // TODO Dette må fikses før deploy til prod
+        hyperlink.setAddress("https://testvips.nibio.no/observations/" + observationId);
+        cell.setHyperlink(hyperlink);
+
+        CellStyle hlinkStyle = workbook.createCellStyle();
+        Font hlinkFont = workbook.createFont();
+        hlinkFont.setUnderline(Font.U_SINGLE);
+        hlinkFont.setColor(IndexedColors.BLUE.getIndex());
+        hlinkStyle.setFont(hlinkFont);
+        cell.setCellStyle(hlinkStyle);
+    }
+
 }
diff --git a/src/main/resources/no/nibio/vips/logic/i18n/vipslogictexts.properties b/src/main/resources/no/nibio/vips/logic/i18n/vipslogictexts.properties
index 03e0245fe58ee6f99153ddd76e9281ba5d3d982a..bde7a3eda0709993ca8d40955b378b0adbc3d5f1 100755
--- a/src/main/resources/no/nibio/vips/logic/i18n/vipslogictexts.properties
+++ b/src/main/resources/no/nibio/vips/logic/i18n/vipslogictexts.properties
@@ -596,7 +596,7 @@ observationSiteStored = Observation site was successfully updated
 
 observationStored = Observation was stored
 
-observationText = Observation text
+observationText = Description
 
 observations = Observations
 
@@ -604,6 +604,8 @@ observedDateOfFirstCatch = Observed date of first catch
 
 observedValue = Observed value
 
+observerId = Observer ID
+
 observer = Observer
 
 older = Older
@@ -1067,3 +1069,15 @@ infoUriExpression=Template for request for station information
 isGridWeatherDataSource=This is a grid based weather data source
 weatherStationDataSourceStored=Weather (station) data source was successfully stored
 weatherStationDataSourceDeleted=The weather (station) data source was successfully deleted
+observationTimeSeriesId=Timeseries
+observationTimeSeriesLabel=Timeseries label
+observationId=Observation
+isBroadcast=Is broadcast
+yes=Yes
+no=No
+downloadInfo=About download
+downloadedBy=Downloaded by
+unregisteredUser=Unregistered user
+downloadedTime=Time of download
+observationCount=Observation count
+
diff --git a/src/main/resources/no/nibio/vips/logic/i18n/vipslogictexts_nb.properties b/src/main/resources/no/nibio/vips/logic/i18n/vipslogictexts_nb.properties
index bc7b59b08ae5e993f8616d0b36d54d476f6b67e5..c50e7a8c6066d6b3d030196e525b4e3fc360b70e 100755
--- a/src/main/resources/no/nibio/vips/logic/i18n/vipslogictexts_nb.properties
+++ b/src/main/resources/no/nibio/vips/logic/i18n/vipslogictexts_nb.properties
@@ -420,13 +420,13 @@ listSelectedCropCategoryOnTop = List kulturer fra valgt gruppe \u00f8verst
 
 localName = Lokalt navn
 
-location = Plassering
+location=Sted
 
-locationIsPrivate = Lokalitet skal ikke offentliggj\u00f8res
+locationIsPrivate = Sted skal ikke offentliggj\u00f8res
 
-locationIsPublic = Lokaliteten kan vises offentlig
+locationIsPublic = Sted kan vises offentlig
 
-locationPointOfInterestId = Lokalitet
+locationPointOfInterestId=Sted-Id
 
 logInterval = M\u00e5leintervall
 
@@ -560,7 +560,7 @@ allObservations = Alle observasjoner
 
 observationDeleted = Observasjonen ble slettet
 
-observationHeading = Observasjonstittel
+observationHeading = Tittel
 
 observationMap = Observasjonskart
 
@@ -594,7 +594,7 @@ observationSiteStored = Rogneb\u00e6rm\u00f8llstasjonen ble oppdatert
 
 observationStored = Observasjonen ble lagret
 
-observationText = Observasjonstekst
+observationText = Beskrivelse
 
 observations = Observasjoner/f\u00f8rstefunn
 
@@ -602,6 +602,8 @@ observedDateOfFirstCatch = Observert dato for f\u00f8rste fellefangst
 
 observedValue = Observert verdi
 
+observerId = Observat\u00f8r-Id
+
 observer = Observat\u00f8r
 
 older = Eldre
@@ -1065,3 +1067,15 @@ infoUriExpression=Mal for henvendelse om stasjonsinformasjon
 isGridWeatherDataSource=Er en grid-basert v\u00e6rdatakilde
 weatherStationDataSourceStored=V\u00e6r(stasjons)datakilden ble lagret
 weatherStationDataSourceDeleted=V\u00e6r(stasjons)datakilden ble slettet
+observationTimeSeriesId=Tidsserie-Id
+observationTimeSeriesLabel=Tidsserie
+observationId=Observasjon-Id
+isBroadcast=Er kringkastet
+yes=Ja
+no=Nei
+downloadInfo=Om nedlastingen
+downloadedBy=Lastet ned av
+unregisteredUser=Uregistrert bruker
+downloadedTime=Tidspunkt for nedlasting
+observationCount=Antall observasjoner
+
diff --git a/src/main/resources/no/nibio/vips/logic/i18n/vipslogictexts_zh_CN.properties b/src/main/resources/no/nibio/vips/logic/i18n/vipslogictexts_zh_CN.properties
index 731d0896dc4fb0df8f637c2d89571c44a58c00a9..a914181f176776529e06f8cd633d7bdfced87c07 100755
--- a/src/main/resources/no/nibio/vips/logic/i18n/vipslogictexts_zh_CN.properties
+++ b/src/main/resources/no/nibio/vips/logic/i18n/vipslogictexts_zh_CN.properties
@@ -1055,6 +1055,9 @@ privacyStatement=Privacy statement
 privacyStatementFileName=Privacy_statement_NIBIO-VIPS.pdf
 thresholdDSVMax=DSV threshold for high infection risk
 thresholdDSVTempMin=Minimum temperature for DSV calculation
+isBroadcast=Is broadcast
+yes=Yes
+no=No
 useGridWeatherData=Use grid weather data
 doNotUse=Do not use
 defaultGridWeatherStationDataSource=Gridded weather data source