diff --git a/app/lib/scorecard/models.dart b/app/lib/scorecard/models.dart
new file mode 100644
index 0000000000..13c56448cd
--- /dev/null
+++ b/app/lib/scorecard/models.dart
@@ -0,0 +1,120 @@
+// Copyright (c) 2018, the Dart project authors.  Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import 'dart:convert';
+import 'dart:io';
+
+import 'package:gcloud/db.dart' as db;
+import 'package:meta/meta.dart';
+
+import 'package:pub_dartlang_org/search/scoring.dart'
+    show calculateOverallScore;
+
+import '../shared/model_properties.dart';
+
+String _id(String packageName, String packageVersion) =>
+    '$packageName/$packageVersion';
+
+final _gzipCodec = new GZipCodec();
+
+/// Summary of various reports for a given PackageVersion.
+@db.Kind(name: 'ScoreCard', idType: db.IdType.String)
+class ScoreCard extends db.ExpandoModel {
+  @db.StringProperty(required: true)
+  String packageName;
+
+  @db.StringProperty(required: true)
+  String packageVersion;
+
+  @db.DateTimeProperty(required: true)
+  DateTime packageCreated;
+
+  @db.DateTimeProperty(required: true)
+  DateTime packageVersionCreated;
+
+  /// Whether the package has its discontinued flag set.
+  @db.BoolProperty()
+  bool isDiscontinued;
+
+  /// The platform tags (flutter, web, other) set by `pana` analysis.
+  @CompatibleStringListProperty()
+  List<String> panaPlatformTags;
+
+  /// Score for documentation coverage (0.0 - 1.0).
+  @db.DoubleProperty()
+  double documentationScore;
+
+  /// Score for code health (0.0 - 1.0).
+  @db.DoubleProperty()
+  double healthScore;
+
+  /// Score for package maintenance (0.0 - 1.0).
+  @db.DoubleProperty()
+  double maintenanceScore;
+
+  /// Score for package popularity (0.0 - 1.0).
+  @db.DoubleProperty()
+  double popularityScore;
+
+  ScoreCard();
+
+  ScoreCard.init({
+    @required this.packageName,
+    @required this.packageVersion,
+    @required this.packageCreated,
+    @required this.packageVersionCreated,
+  }) {
+    id = _id(packageName, packageVersion);
+  }
+
+  double get overallScore =>
+      // TODO: use documentationScore too
+      calculateOverallScore(
+        health: healthScore ?? 0.0,
+        maintenance: maintenanceScore ?? 0.0,
+        popularity: popularityScore ?? 0.0,
+      );
+}
+
+/// Detail of a specific report for a given PackageVersion.
+@db.Kind(name: 'ScoreCardReport', idType: db.IdType.String)
+class ScoreCardReport extends db.ExpandoModel {
+  @db.StringProperty(required: true)
+  String packageName;
+
+  @db.StringProperty(required: true)
+  String packageVersion;
+
+  @db.StringProperty(required: true)
+  String reportType;
+
+  @db.BlobProperty()
+  List<int> reportJsonGz;
+
+  ScoreCardReport();
+
+  ScoreCardReport.init({
+    @required this.packageName,
+    @required this.packageVersion,
+    @required this.reportType,
+  }) {
+    parentKey = db.dbService.emptyKey
+        .append(ScoreCard, id: _id(packageName, packageVersion));
+    id = reportType;
+  }
+
+  Map<String, dynamic> get reportJson {
+    if (reportJsonGz == null) return null;
+    return json.decode(utf8.decode(_gzipCodec.decode(reportJsonGz)))
+        as Map<String, dynamic>;
+  }
+
+  set reportJson(Map<String, dynamic> map) {
+    if (map == null) {
+      reportJsonGz = null;
+    } else {
+      reportJsonGz = _gzipCodec.encode(utf8.encode(json.encode(map)));
+    }
+  }
+}